From fbd7553bc68864d5776d32498ad326c58ca667ce Mon Sep 17 00:00:00 2001 From: Madeleine Sydney Date: Thu, 23 Jan 2025 14:08:56 -0700 Subject: [PATCH] feat: add Git-annex module --- README.org | 5 +- hosts/deertopia/configuration.nix | 20 +++- modules/nixos/deertopia/git-annex.nix | 46 -------- modules/nixos/deertopia/nginx.nix | 42 +++++--- modules/nixos/deertopia/webdav.nix | 63 +++++++++++ modules/nixos/git-annex.nix | 149 ++++++++++++++++++++++++++ users/lain/default.nix | 2 +- users/{escort => public}/default.nix | 5 +- 8 files changed, 262 insertions(+), 70 deletions(-) delete mode 100755 modules/nixos/deertopia/git-annex.nix create mode 100644 modules/nixos/deertopia/webdav.nix create mode 100644 modules/nixos/git-annex.nix rename users/{escort => public}/default.nix (79%) diff --git a/README.org b/README.org index 9700c62..23a4fff 100755 --- a/README.org +++ b/README.org @@ -169,9 +169,9 @@ A bit on the nose for a transfemme into computers, but my chosen name is also Ma Used as a server admin account with little configuration. -** escort +** public -Another low-config user for "escorting" people to system services that require a user, e.g. logging in for file-sharing. +Another low-config, low-permission user for access to public resources. * ~sydnix-cli~ @@ -535,3 +535,4 @@ Following is a subset of the many places I've learnt from. - [[https://prelude.emacsredux.com/en/stable/][Emacs Prelude]] - [[https://github.com/doomemacs/doomemacs][Doom Emacs]] - [[https://cce.whatthefuck.computer/cce][Ryan Rix's Complete Computing Environment]] +- [cite:@wünsch2024setting] diff --git a/hosts/deertopia/configuration.nix b/hosts/deertopia/configuration.nix index 773d865..5cfff58 100755 --- a/hosts/deertopia/configuration.nix +++ b/hosts/deertopia/configuration.nix @@ -10,7 +10,7 @@ users.users = [ "lain" - "escort" + "public" ]; impermanence = { @@ -37,15 +37,29 @@ keyFile = "/persist/vault/root/deertopia-key"; }; + git-annex = { + enable = true; + user.name = "annex"; + user.email = "annex@deertopia.net"; + keyFiles = [ ../../public-keys/crumb-at-guix-rebound.pub ]; + repos = { + "/persist/deertopia.net/dav/org" = { + managed = true; + remotes = { + "guix-rebound" = "crumb@guix-rebound:/tmp/org"; + }; + }; + }; + }; + deertopia = { nginx.enable = true; - git-annex.enable = true; + webdav.enable = true; # A simple default webpage. This should probably live somewhere else. nginx.vhosts."www" = { vhostName = "deertopia.net"; vhost = { - # addSSL = true; forceSSL = true; enableACME = true; diff --git a/modules/nixos/deertopia/git-annex.nix b/modules/nixos/deertopia/git-annex.nix deleted file mode 100755 index dc15f80..0000000 --- a/modules/nixos/deertopia/git-annex.nix +++ /dev/null @@ -1,46 +0,0 @@ -{ config, lib, pkgs, ... }: - -let - cfg = config.sydnix.deertopia.git-annex; -in -{ - options.sydnix.deertopia.git-annex = { - enable = lib.mkEnableOption "Git-annex"; - }; - - config = { - environment.systemPackages = with pkgs; [ - git-annex - # git - # rsync - ]; - - # # Our files managed by git-annex actually live on a WebDAV server that is - # # declared by the following section. This is mainly because it's the most - # # convenient way to share files with my iPhone. Apple hates developers! - # services.nginx = { - # # Nginx's WebDAV support is in a separate module we must import. - # additionalModules = [ pkgs.nginxModules.dav ]; - - # virtualHosts."dav.deertopia.net" = { - # addSSL = true; - # enableACME = true; - # locations."/".extraConfig = '' - # alias /persist/web/webdav; - # client_body_temp_path /tmp/nginx/webdav; - # dav_methods PUT DELETE MKCOL COPY MOVE; - # dav_ext_methods PROPFIND OPTIONS; - # create_full_put_path on - - # auth_basic "Restricted Access"; - # auth_basic_user_file /etc/nginx/webdav.passwd; - - # # Deny all access unless authenticated - # satisfy all; - # allow all; # This allows all authenticated users - # deny all; # This denies all other users - # ''; - # }; - # }; - }; -} diff --git a/modules/nixos/deertopia/nginx.nix b/modules/nixos/deertopia/nginx.nix index f3f5a6c..95042fc 100644 --- a/modules/nixos/deertopia/nginx.nix +++ b/modules/nixos/deertopia/nginx.nix @@ -41,6 +41,16 @@ in type = lib.types.str; default = "${cfg.root}/${name}"; }; + user = lib.mkOption { + type = lib.types.nullOr lib.types.str; + description = "The owning user of the host's root directory."; + default = cfg.user; + }; + group = lib.mkOption { + type = lib.types.nullOr lib.types.str; + description = "The owning group of the host's root directory."; + default = cfg.group; + }; vhostName = lib.mkOption { type = lib.types.str; default = "${name}.deertopia.net"; @@ -98,22 +108,22 @@ in # }; # }; - system.activationScripts.initialiseDeertopiaRoot.text = - let - directories = - builtins.catAttrs "directory" (builtins.attrValues cfg.vhosts); - inherit (cfg) root group user; - in '' - mkdir -p "${root}" - chown -R "${user}:${group}" "${root}" - chmod -R 775 "${root}" + # system.activationScripts.initialiseDeertopiaRoot.text = + # let + # # FIXME: Use `lib.strings.toShellVar`. + # inherit (cfg) root group user; + # in '' + # mkdir -p "${root}" + # chown -R "${user}:${group}" "${root}" + # chmod -R 775 "${root}" - dirs=(${builtins.concatStringsSep " " (map (x: "'${x}'") directories)}) - for i in "''${dirs[@]}"; do - mkdir -p "$i" - chown -R "${user}:${group}" "$i" - chmod -R 775 "$i" - done - ''; + # ${lib.toShellVar "dirs" + # (builtins.catAttrs "directory" (builtins.attrValues cfg.vhosts))} + # for i in "''${dirs[@]}"; do + # mkdir -p "$i" + # chown -R "${user}:${group}" "$i" + # chmod -R 775 "$i" + # done + # ''; }; } diff --git a/modules/nixos/deertopia/webdav.nix b/modules/nixos/deertopia/webdav.nix new file mode 100644 index 0000000..6be71cb --- /dev/null +++ b/modules/nixos/deertopia/webdav.nix @@ -0,0 +1,63 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let cfg = config.sydnix.deertopia.webdav; +in { + options = { + sydnix.deertopia.webdav = { + enable = mkEnableOption "Deertopia's WebDAV server"; + + port = lib.mkOption { + default = 22016; + type = lib.types.port; + description = '' + The internal WebDAV port. The actual server will be hosted at + https://dav.deertopia.net:80/. + ''; + }; + }; + }; + + config = mkIf cfg.enable { + systemd.services.deertopia-webdav-server = + let htpasswdFile = "/persist/deertopia.net/htpasswd"; + directory = "/persist/deertopia.net/dav"; + in { + description = "Deertopia's WebDAV server"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + script = '' + ${pkgs.rclone}/bin/rclone serve webdav \ + --addr ":${builtins.toString cfg.port}" \ + --htpasswd "${htpasswdFile}" "${directory}" + ''; + serviceConfig.Restart = "always"; + }; + + # Without this, Nginx will attempt redirections to https://localhost, which + # is not okay, as localhost does not have any associated certs! + # See: https://forum.seafile.com/t/seafdav-move-command-causing-502/11582/26 + services.nginx.appendHttpConfig = '' + map $http_destination $http_destination_webdav { + ~*https://(.+) http://$1; + default $http_destination; + } + ''; + + sydnix.deertopia.nginx.vhosts."dav".vhost = { + forceSSL = true; + enableACME = true; + locations."/" = { + extraConfig = '' + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $remote_addr; + # See previous note regarding the HTTPS -> HTTP redirection. + proxy_set_header Destination $http_destination_webdav; + + proxy_pass "http://localhost:${builtins.toString cfg.port}"; + ''; + }; + }; + }; +} diff --git a/modules/nixos/git-annex.nix b/modules/nixos/git-annex.nix new file mode 100644 index 0000000..98ddfa4 --- /dev/null +++ b/modules/nixos/git-annex.nix @@ -0,0 +1,149 @@ +{ config, lib, pkgs, ... }: + +let cfg = config.sydnix.git-annex; +in { + options = { + sydnix.git-annex = { + enable = lib.mkEnableOption "git-annex"; + + user = { + name = lib.mkOption { + type = lib.types.str; + }; + email = lib.mkOption { + type = lib.types.str; + }; + }; + + keyFiles = lib.mkOption { + type = lib.types.listOf lib.types.path; + default = []; + }; + + repos = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule ({ name, ... }: { + options = { + enable = lib.mkOption { + description = '' + Git-annex repo ${name}; + ''; + type = lib.types.bool; + default = true; + }; + + path = lib.mkOption { + description = '' + Path to the repo. + ''; + type = lib.types.str; + default = name; + }; + + remotes = lib.mkOption { + type = lib.types.attrsOf lib.types.str; + default = {}; + }; + + user = lib.mkOption { + type = lib.types.str; + default = "annex"; + }; + + group = lib.mkOption { + type = lib.types.str; + default = "annex"; + }; + + managed = lib.mkOption { + description = '' + If true, the repo will be automatically managed by Nix. + 'Management' includes creating and initialising the repo and + setting permissions. + ''; + type = lib.types.bool; + default = false; + }; + }; + })); + }; + }; + }; + + config = lib.mkIf cfg.enable { + # Git-annex assistant requires remotes to have git-annex on the PATH. + environment.systemPackages = [ + pkgs.git-annex + ]; + + # The user that users log in as to access managed repos. + users.groups.annex = {}; + users.users.annex = { + isSystemUser = true; + group = "annex"; + home = "/var/sydnix/annex"; + createHome = true; + openssh.authorizedKeys.keyFiles = cfg.keyFiles; + }; + + system.activationScripts.initialiseUserAnnex = + let gitconfig-file = pkgs.writeText "gitconfig" '' + [user] + email = ${cfg.user.email} + name = ${cfg.user.name} + [init] + defaultBranch = main + ''; + in '' + ln -sf "${gitconfig-file}" ~/.gitconfig + ''; + + systemd.services = + let + init-if-necessary = repo: + pkgs.writeScript "git-annex-init-if-necessary" '' + #!/usr/bin/env bash + set -e + [ -e .git ] || git init + [ -e .git/annex ] || git annex init + + ${lib.strings.toShellVar "remotes" repo.remotes} + for remoteName in ''${!remotes[@]}; do + git remote get-url "$remoteName" + if [ $? -eq 0 ]; then + git remote set-url "$remoteName" "''${remotes["$remoteName"]}" + else + git remote add "$remoteName" "''${remotes["$remoteName"]}" + fi + done + ''; + + mkAssistantService = repo: { + description = "git-annex assistant (${repo.path})"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig.User = repo.user; + serviceConfig.WorkingDirectory = repo.path; + path = [ pkgs.git pkgs.git-annex pkgs.openssh ]; + # Set the default file mode for new files created by this process to + # 775. Oddly, `UMask = x` will set the mode to `0777 & x`, so 002 + # gets us the desired figure of 775. We want 775 because Nginx, + # running under user `nginx` needs to be able to read/write these + # files to server them over WebDAV. + serviceConfig.UMask = "002"; + serviceConfig.ExecStartPre = + lib.optionalString + repo.managed + "${pkgs.bash}/bin/bash ${init-if-necessary repo}"; + serviceConfig.ExecStart = '' + ${pkgs.git-annex}/bin/git-annex assistant --foreground + ''; + }; + in builtins.listToAttrs + (builtins.map + (repo: { + name = "annex-${lib.strings.sanitizeDerivationName repo.path}"; + value = mkAssistantService repo; + }) + (builtins.attrValues cfg.repos)); + }; +} diff --git a/users/lain/default.nix b/users/lain/default.nix index b40ee7c..ebca2bd 100755 --- a/users/lain/default.nix +++ b/users/lain/default.nix @@ -3,7 +3,7 @@ isNormalUser = true; # TODO: Don't hard-code `persist`. Use # config.sydnix.impermanence.persistGroupName. - extraGroups = [ "wheel" "persist" "nginx" ]; + extraGroups = [ "wheel" "persist" "nginx" "annex" ]; initialHashedPassword = "$y$j9T$aEFDDwdTZbAc6VQRXrkBJ0$K8wxTGTWDihyX1wxJ.ZMH//wmQFfrGGUkLkxIU0Lyq8"; diff --git a/users/escort/default.nix b/users/public/default.nix similarity index 79% rename from users/escort/default.nix rename to users/public/default.nix index 54b575e..472d66f 100755 --- a/users/escort/default.nix +++ b/users/public/default.nix @@ -1,11 +1,12 @@ { systemConfiguration = { config, ... }: { isNormalUser = true; - # TODO: Don't hard-code `persist`. Use - # config.sydnix.impermanence.persistGroupName. extraGroups = [ ]; initialHashedPassword = "$y$j9T$uU64mjI.5Y1JICkKAaIgl0$kkO089hyDp3akSj7ReIKqFthA4T/d1w/nF40a5Tujt1"; + openssh.authorizedKeys.keyFiles = [ + ../../public-keys/crumb-at-guix-rebound.pub + ]; }; homeConfiguration = { config, lib, pkgs, ... }: {