diff --git a/hosts/alto/default.nix b/hosts/alto/default.nix index d210fcb..64537f1 100644 --- a/hosts/alto/default.nix +++ b/hosts/alto/default.nix @@ -1,3 +1,7 @@ +{ config, ... }: +let + myCfg = config.myConfig; + in { system.stateVersion = "24.11"; @@ -8,6 +12,17 @@ enable = true; ssh.enable = true; exitNode.enable = true; + + caddyServe.nextcloud = { + subdomain = "cloud"; + inherit (myCfg.nextcloud) port; + }; + }; + + nextcloud = { + enable = true; + backups.enable = true; + inherit (myCfg.tailscale.caddyServe.nextcloud) subdomain; }; }; } diff --git a/hosts/alto/secrets.yaml b/hosts/alto/secrets.yaml index 44cb920..03b28b5 100644 --- a/hosts/alto/secrets.yaml +++ b/hosts/alto/secrets.yaml @@ -1,5 +1,13 @@ seb-password: ENC[AES256_GCM,data:oGrXukkbK9qYYo0ci+F4RwiwlRyme/+ypJozgiqH2DFd33SyjYnzX6u2f6a0+rIfwxO45dUrXCJyidWE2Fw26xE/uH9nPmDzuw==,iv:GpBQNm1jspU8PCN+SzfAUKSps3YySg6JJVYOLOFetOI=,tag://NpB2SnxWlJPHNp92hdVA==,type:str] tailscale-auth-key: ENC[AES256_GCM,data:lGXbnNHnlKSv2Po4J7yTVOdCxwgxENBglp/MLZnIpdqVxEkO3D2Risi4iPkVPnPyKBuI4hog4xtGyiUH5L4=,iv:Cvc8+VPRpPrNYTcWjBYBPzYAwy80hJv1VCR8hrMh4AM=,tag:+qt5Caaxfig6TqoJm/uCwg==,type:str] +service-tailscale-auth-key: ENC[AES256_GCM,data:9tcgzoRvxKaop1wztCIQmyej7mhou/wGlLlX7JY9r9RibxAmDX61mO28hFrsFQQfdmQTulbcSGPgZQN0xl8=,iv:8kA0iTunR6E9GRy7lrRVv2R6oYA4IijlbTuT9w6NqOI=,tag:pdkr9HeNGD38zZe6zSQFzQ==,type:str] +nextcloud: + admin-password: ENC[AES256_GCM,data:TepYe5rZox6aoa3jeIhmBxaZIQGpjjf+SAG8E39y,iv:cAanc3a5e3PF+BkiFjcME+PiTSRaNj/e78kT/RTbaxY=,tag:wbn4vWeDnSJH7jWu7hutVw==,type:str] + gmail-password: ENC[AES256_GCM,data:4NVrKWXcAA8OCxMx31ZnVYOnLbw=,iv:OlAtnSor7GDaqJSBHK+b0uFzweVnbc/9EdKxWarGt2c=,tag:UoOTTN8pM0tyYd59k1A+BA==,type:str] +restic: + environment: ENC[AES256_GCM,data:v1Ui5mG7Q98CFEpq7sSpzEf86cJAcRi+sqFdvy6ZPuY9dukJD2wAGt5fuNQkMzBCKAUTHb46ga1WYf9fZ5AUOPdA1MNrJWKrXlrsYh8ZJYKOgfEVBBYPUKKGcajILNQ5SzU=,iv:Asg4CWJbGqSZh8YaxcWA0Yxau1dE4ZV9JBJSiDHufGI=,tag:46pNMWoCbciEv4cIHo7KFQ==,type:str] + password: ENC[AES256_GCM,data:NVeqrWqtdgbhu3U7dAgwFeNLS9oPtnAPSrkGtvYD,iv:3l+9+bZfOpZdSCBKzXn5PqJvqo7mz/rj1tkihJqMHIs=,tag:JXigRR1adGlm8ehRv5wzIA==,type:str] +healthchecks-ping-key: ENC[AES256_GCM,data:Cbk04CrYd9WcHnVRUed9aIImHbULhA==,iv:70cOOk5LfYciBx5baftFiBuquXY2welnjhoYmIB1iAQ=,tag:I5hqoai/HLdqUqonK77ubA==,type:str] sops: kms: [] gcp_kms: [] @@ -24,8 +32,8 @@ sops: dEhnSkQ5SDlnbmhGSVdYaDNuc3ZkM00K7WPEZRYWAd7uGY0IcDwGgQVPrpkF/tnz ncj03JXM4BXwvEQOmD/i6wS4U4WCwkh9EauGJljVFTeu6TciomDULQ== -----END AGE ENCRYPTED FILE----- - lastmodified: "2025-03-27T20:51:15Z" - mac: ENC[AES256_GCM,data:e0DDr/JHEdceS1ZZBRwdiG783MN5UulCz5GIEhvy3psqMirVBSsnXYGavEwg6E550Dby6wGdaqpFPjorBhj2Qb441gFf6IVGDPGSQg1JVzKpkMVhYBiW9vlshG2dSONcKe2J92O0uIA05Cp7uiv48bUBj13MovvCqvS0O17QCns=,iv:tNC4gk4ardfK01t/LKY73Uzdvn/R5BPdtIaPXR6g1x4=,tag:vygO6ZeQiIySEXREYPprbw==,type:str] + lastmodified: "2025-04-07T22:05:28Z" + mac: ENC[AES256_GCM,data:HuoqNVicXopTQPJcaXhlCpZBhSqAIhVmhL3Pwxe+L0XE6SB08wJZkdI44Tc9QpfyzU/tAFQQqR85Y8cPODCena/RCDHPPNsYmi38DTf+zQkZEfTgC7PBpeyPeLy4StMPC2MCAyEEnwqx6Lfel0qGrwMJYP3K9C+wZhAfEnlLObY=,iv:mrRXbC3ce2JVswIZOBsevPc894yPWd0hJ783f+JGASA=,tag:m8kpzfGq063NVShMG/67bw==,type:str] pgp: [] unencrypted_suffix: _unencrypted version: 3.9.4 diff --git a/justfile b/justfile index 2d68f98..b041769 100644 --- a/justfile +++ b/justfile @@ -20,9 +20,9 @@ dev shell='default': nix develop .#{{ shell }} --command zsh install host: - ssh -o StrictHostKeyChecking=no root@installer 'disko --mode destroy,format,mount --yes-wipe-all-disks --flake github:SebastianStork/nixos-config#{{ host }}' + ssh -o StrictHostKeyChecking=no root@installer 'disko --mode destroy,format,mount --yes-wipe-all-disks --flake github:SebastianStork/nixos-config/61083d4359d379981d4bd838998820199c5d8bb9#{{ host }}' ssh -o StrictHostKeyChecking=no root@installer 'mkdir -p /mnt/etc/ssh' scp -o StrictHostKeyChecking=no ~/.ssh/{{ host }} root@installer:/mnt/etc/ssh/ssh_host_ed25519_key scp -o StrictHostKeyChecking=no ~/.ssh/{{ host }}.pub root@installer:/mnt/etc/ssh/ssh_host_ed25519_key.pub - ssh -o StrictHostKeyChecking=no root@installer 'nixos-install --flake github:SebastianStork/nixos-config#{{ host }}' + ssh -o StrictHostKeyChecking=no root@installer 'nixos-install --flake github:SebastianStork/nixos-config/61083d4359d379981d4bd838998820199c5d8bb9#{{ host }}' ssh -o StrictHostKeyChecking=no root@installer 'reboot' diff --git a/modules/system/nextcloud/backups.nix b/modules/system/nextcloud/backups.nix new file mode 100644 index 0000000..3f833e0 --- /dev/null +++ b/modules/system/nextcloud/backups.nix @@ -0,0 +1,51 @@ +{ + config, + pkgs, + lib, + ... +}: +let + cfg = config.myConfig.nextcloud; + + user = config.users.users.nextcloud.name; + inherit (config.users.users.nextcloud) group; +in +{ + options.myConfig.nextcloud.backups.enable = lib.mkEnableOption ""; + + config = lib.mkIf cfg.backups.enable { + systemd.tmpfiles.rules = [ "d ${cfg.dataDir}/backup 700 ${user} ${group} -" ]; + + myConfig.resticBackup.nextcloud = { + inherit user; + healthchecks.enable = true; + + extraConfig = { + backupPrepareCommand = '' + ${lib.getExe' config.services.nextcloud.occ "nextcloud-occ"} maintenance:mode --on + ${lib.getExe' config.services.postgresql.package "pg_dump"} nextcloud --format=custom --file=${cfg.dataDir}/backup/db.dump + ''; + backupCleanupCommand = '' + ${lib.getExe' config.services.nextcloud.occ "nextcloud-occ"} maintenance:mode --off + ''; + paths = [ + "${cfg.dataDir}/home/data" + "${cfg.dataDir}/home/config/config.php" + "${cfg.dataDir}/backup" + ]; + }; + }; + + environment.systemPackages = [ + (pkgs.writeShellApplication { + name = "nextcloud-restore"; + text = '' + sudo --user=${user} ${lib.getExe' config.services.nextcloud.occ "nextcloud-occ"} maintenance:mode --on + sudo --user=${user} restic-nextcloud restore --target / latest + sudo --user=${user} pg_restore --clean --if-exists --dbname nextcloud ${cfg.dataDir}/backup/db.dump + sudo --user=${user} ${lib.getExe' config.services.nextcloud.occ "nextcloud-occ"} maintenance:mode --off + ''; + }) + ]; + }; +} diff --git a/modules/system/nextcloud/default.nix b/modules/system/nextcloud/default.nix new file mode 100644 index 0000000..8f02b2d --- /dev/null +++ b/modules/system/nextcloud/default.nix @@ -0,0 +1,94 @@ +{ + config, + inputs, + pkgs, + lib, + ... +}: +let + cfg = config.myConfig.nextcloud; + + user = config.users.users.nextcloud.name; + inherit (config.users.users.nextcloud) group; +in +{ + options.myConfig.nextcloud = { + enable = lib.mkEnableOption ""; + subdomain = lib.mkOption { + type = lib.types.nonEmptyStr; + default = ""; + }; + dataDir = lib.mkOption { + type = lib.types.path; + default = config.services.nextcloud.home; + readOnly = true; + }; + port = lib.mkOption { + type = lib.types.port; + default = 80; + readOnly = true; + }; + }; + + config = lib.mkIf cfg.enable { + sops.secrets."nextcloud/admin-password" = { + owner = user; + inherit group; + }; + + systemd.tmpfiles.rules = [ "d ${cfg.dataDir}/home 750 ${user} ${group} -" ]; + + services.nextcloud = { + enable = true; + package = pkgs.nextcloud30; + hostName = "${cfg.subdomain}.${config.networking.domain}"; + + database.createLocally = true; + config = { + dbtype = "pgsql"; + adminuser = "admin"; + adminpassFile = config.sops.secrets."nextcloud/admin-password".path; + }; + + https = true; + settings = { + overwriteProtocol = "https"; + trusted_proxies = [ "127.0.0.1" ]; + log_type = "file"; + default_phone_region = "DE"; + maintenance_window_start = "2"; # UTC + defaultapp = "side_menu"; + }; + + configureRedis = true; + maxUploadSize = "16G"; + phpOptions."opcache.interned_strings_buffer" = "16"; + + autoUpdateApps = { + enable = true; + startAt = "04:00:00"; + }; + extraApps = { + inherit (config.services.nextcloud.package.packages.apps) + calendar + contacts + deck + onlyoffice + ; + + twofactor_totp = pkgs.fetchNextcloudApp { + url = inputs.nextcloud-twofactor-totp.outPath; + sha256 = inputs.nextcloud-twofactor-totp.narHash; + license = "agpl3Plus"; + unpack = true; + }; + side_menu = pkgs.fetchNextcloudApp { + url = inputs.nextcloud-side-menu.outPath; + sha256 = inputs.nextcloud-side-menu.narHash; + license = "agpl3Plus"; + unpack = true; + }; + }; + }; + }; +} diff --git a/modules/system/tailscale/caddy-serve.nix b/modules/system/tailscale/caddy-serve.nix new file mode 100644 index 0000000..af70ef7 --- /dev/null +++ b/modules/system/tailscale/caddy-serve.nix @@ -0,0 +1,69 @@ +{ + config, + pkgs-unstable, + lib, + ... +}: +let + nodes = config.myConfig.tailscale.caddyServe |> lib.filterAttrs (_: value: value.enable); + + caddy-tailscale = pkgs-unstable.caddy.withPlugins { + plugins = [ "github.com/tailscale/caddy-tailscale@v0.0.0-20250207163903-69a970c84556" ]; + hash = "sha256-UR9CG/zIslkXHDj1fDWmhx8hJZ8VLvZzOTGvGqqx1Ls="; + }; +in +{ + options.myConfig.tailscale.caddyServe = lib.mkOption { + type = lib.types.attrsOf ( + lib.types.submodule ( + { name, ... }: + { + options = { + enable = lib.mkEnableOption "" // { + default = true; + }; + subdomain = lib.mkOption { + type = lib.types.nonEmptyStr; + default = name; + }; + port = lib.mkOption { + type = lib.types.nullOr lib.types.port; + default = null; + }; + }; + } + ) + ); + default = { }; + }; + + config = lib.mkIf (nodes != { }) { + sops.secrets."service-tailscale-auth-key" = { + owner = config.services.caddy.user; + inherit (config.services.caddy) group; + }; + + services.caddy = { + enable = true; + package = caddy-tailscale; + enableReload = false; + + globalConfig = '' + tailscale { + auth_key {file.${config.sops.secrets."service-tailscale-auth-key".path}} + } + ''; + + virtualHosts = lib.mapAttrs' ( + _: value: + lib.nameValuePair "https://${value.subdomain}.${config.networking.domain}" { + extraConfig = '' + bind tailscale/${value.subdomain} + tailscale_auth + reverse_proxy localhost:${toString value.port} + ''; + } + ) nodes; + }; + }; +} diff --git a/modules/system/tailscale.nix b/modules/system/tailscale/default.nix similarity index 100% rename from modules/system/tailscale.nix rename to modules/system/tailscale/default.nix