diff --git a/README.org b/README.org index e0df3dc..de776f7 100644 --- a/README.org +++ b/README.org @@ -40,6 +40,7 @@ This guide documents the process for a minimal installation of NixOS on a Proxmo - [[#step-2-updating-sops-and-re-encrypting-secrets][Step 2: Updating SOPS and Re-encrypting Secrets]] - [[#optional-nixos-modules][Optional NixOS Modules]] - [[#reverse-proxies][Reverse Proxies]] + - [[#file-servers][File Servers]] - [[#notes-and-configuration-details][Notes and Configuration Details]] - [[#disko-configuration-for-proxmox-mbr-boot][Disko Configuration for Proxmox (MBR Boot)]] - [[#generating-hardware-configuration][Generating Hardware Configuration]] @@ -114,7 +115,7 @@ The new AGE key must be added to your =.sops.yaml= configuration file. This allo ** Reverse Proxies The following modules can be enabled to provide a reverse proxy. -*Note:* Currently, none of the available reverse proxy modules (NGINX, Traefik, Caddy) are functional. This is a known issue that needs to be investigated. +*Note:* Previously, all reverse proxy modules were considered non-functional. Recent troubleshooting has provided a fix for Traefik, but the other modules may still have issues. *** NGINX The initial switch to this configuration may be slow as it waits for ACME to issue SSL certificates. @@ -128,7 +129,6 @@ dov = { #+end_src *** Traefik -This module is currently considered to be in a broken state. #+begin_src nix dov = { # Reverse Proxy @@ -138,6 +138,14 @@ dov = { }; #+end_src +**** Troubleshooting Traefik ACME with DuckDNS +- *Context:* Issues getting an ACME certificate from DuckDNS with Traefik. +- *Roadblock:* The process was failing, but eventually succeeded. +- *Possible Solutions:* + - Setting =disablePropagationCheck = true;= for the DNS challenge. + - Extending the =delay= for the DNS challenge. +- *Notes:* It's unclear which specific option resolved the issue, but one of them, or a combination, allowed the certificate to be obtained. The first time Traefik tries to get a certificate it might fail, and a restart of the service is needed. After some time, the Let's Encrypt certificate will be received. + *** Caddy #+begin_src nix dov = { @@ -148,6 +156,22 @@ dov = { }; #+end_src +** File Servers +*** copyparty +Provides a web-based file manager. +#+begin_src nix +dov = { + file-server.copyparty.enable = true; +}; +#+end_src + +*Dependency:* For =copyparty= to function correctly in this configuration, it requires a Samba share to be mounted to the path =/MEDIA=. Therefore, the =samba= module must also be enabled: +#+begin_src nix +dov = { + samba.enable = true; +}; +#+end_src + * Notes and Configuration Details ** Disko Configuration for Proxmox (MBR Boot) A critical requirement for ensuring a NixOS VM can boot correctly in Proxmox is the disk partition scheme. Proxmox expects a Master Boot Record (MBR) compatible setup. @@ -194,7 +218,7 @@ nix run github:nix-community/nixos-anywhere -- \ #+end_src * TODOs -- [ ] Investigate and fix the issue preventing any of the reverse proxy modules (NGINX, Traefik, Caddy) from working correctly. +- [ ] Investigate and fix remaining issues with reverse proxy modules (NGINX, Caddy). - [ ] Troubleshoot and fix an issue that occurs when reloading the NixOS configuration remotely, which breaks the SSH pipe and requires entering the root password three times. - [ ] Investigate and resolve the issue where updating a user's password declaratively using a secret managed by =sops= failed after the initial installation. - [ ] Refactor the =disko= configuration to make the disk device name (e.g., =/dev/sda=) a variable. This will avoid hardcoding the value and make the configuration more portable. diff --git a/flake.nix b/flake.nix index 5c6a426..3773542 100644 --- a/flake.nix +++ b/flake.nix @@ -51,6 +51,7 @@ disko.nixosModules.disko home-manager.nixosModules.home-manager sops-nix.nixosModules.sops + inputs.copyparty.nixosModules.default ./main ./modules diff --git a/main/default.nix b/main/default.nix index f99d23c..1903544 100644 --- a/main/default.nix +++ b/main/default.nix @@ -137,7 +137,7 @@ in { # Reverse Proxy reverse-proxy = { nginx.enable = false; # TODO does not work for some reason - traefik.enable = true; # TODO has issues retrieving certificate from duckdns + traefik.enable = true; caddy.enable = false; # TODO has issues retrieving certificate from duckdns }; @@ -148,7 +148,9 @@ in { social.matrix.enable = false; # TODO does not work :) - file-server.copyparty.enable = false; + file-server.copyparty.enable = true; + + samba.enable = true; }; # DO NOT CHANGE AT ANY POINT! diff --git a/main/secrets/secrets.yaml b/main/secrets/secrets.yaml index f5760a7..0c7afc4 100644 --- a/main/secrets/secrets.yaml +++ b/main/secrets/secrets.yaml @@ -11,6 +11,10 @@ example_booleans: user_password: ENC[AES256_GCM,data:Q7rk67ylyjr5Sa+AYCxnQAPLbBP5Fy85wTGLZuqxBG3iJ+MmhEgfeatVA2tcsY7GSaU/vghny+TJtrvhDYYMqa10h/F0wPxUjId78qkhKbnRQs4mqAxA9heSi4ojp1kh/pXN7tj64wNyJA==,iv:FTUojVNz78tn/Uj1N8Oj5Iov9eEMRo5vz+mqHdewxjg=,tag:YF74hLXXUby0IjHrqdkBUQ==,type:str] duckdns-token: ENC[AES256_GCM,data:Gf3kIpOO/X+ZVXV4w71Fp5qMuNedBBoobazAFpp22RC70xKb6xsJVffWdtFq0blDe5Y=,iv:SNq6wnhG6CuDwB3NQ/PryTgY3U/J2g1XfGCW7gSEYbo=,tag:MWqhrJRreGZ/SaapAaCXQA==,type:str] matrix_secret: ENC[AES256_GCM,data:U1yPFsFeLA5tbFf/MMACrhmH/32zUMUg2HOHWdAtcm+ybg9KgjhQmbGDM/MTDoRaAa+Zqfs774gz3A6Rg4HLuvCr4cPotSCHH8qRPz+UDK4Bvf305EfLP22Rrhc=,iv:A9BSgw1hHg+y8x4GC4hWNBCaYZNlRfS1+jKKv38znXg=,tag:SkwEfez7TRhFuLEL4PkvZA==,type:str] +copyparty: + admin_password: ENC[AES256_GCM,data:VlHcQB1Z1/wSUi8yCEpcW+i8h3c=,iv:mystE6THTS50LzV/TPm+QtZ1C87Vxtx+W9jVzcGAnSM=,tag:8nxtbklHwJnI7VHjJA55dQ==,type:str] + alex_password: ENC[AES256_GCM,data:0X5AZH8tqJRd6er5w3oMaWI0jrE=,iv:/2aLquP4LVCKCozJsMGItqX9+L9pxSM4PRpn6QnDzbE=,tag:b1GRHEBwQNYBtERj1xqjoA==,type:str] +smb-secrets: ENC[AES256_GCM,data:RW8xaGU94jxE/iTocH3ylCP5uIpmnSg/MQDC+e5i9PhvlsNY+kfUiqQHoDXETgEPmNUbLr2qZSMLPhQ=,iv:5vkw0Qfa7UHYZ2ODOvFZgirehpY7muV6fvjWHAyHMu4=,tag:cuEzibaBZVf5HVlAF2xUIA==,type:str] sops: age: - recipient: age19wvqtn4ju6k4vs8fxr34unl6xx4cv04jw0lx9ps20xlde927zfssgl4qke @@ -31,7 +35,7 @@ sops: NHdWQnlGbk43WS80VDkxV0o4TE5uSUUK0WSdFzR3u0pLUYHXaTMrtBm0sKKe9ZPG nF90b/jv66WGIH1n2oFaaohCkd7DZGzSpr0+KsqX6pkszYnp39YC5A== -----END AGE ENCRYPTED FILE----- - lastmodified: "2025-07-26T21:14:39Z" - mac: ENC[AES256_GCM,data:76/u+mXqsYQA0Y5rcUskN2Uh8nCKyZxPk3yLd4F/zXnfOe6eqLBAfwvZ2XGu1Y+KQEMidSvrd+7WJ9bPHFxbftglIIeU8NxdXqgQZrH2Bx6kMgGzSR72IzYOJvl5rPsYa3mjRIcaBdyE7oo3ZSQctHlf40zEaTNesNgjVPgvWhs=,iv:yYq5knPV7JdvnkC18/MFg1/6W1cx2d7zAtRCe/C2Txg=,tag:jc7grjZajT2TH3TzLVQ82Q==,type:str] + lastmodified: "2025-07-27T18:42:07Z" + mac: ENC[AES256_GCM,data:EP1ziiWZ3hPCbjLtPdLccL4csnNmLqtQkhwn8x5bwXuqIU9Q8U6+nXIorAG2Ck28hyjnAjHyYMbJcSSrNOVHroA5Xnlps65975WuKe3akNF6n6Nz+gTDQgEzIpxwYD2rEFNutCNN4EhESzh13lvsvdkxTCsFLjZzwQ4DSuVId24=,iv:f2jM71VXiShEkTUOtfONhMIeR6JnC3xpUN2zXJvdDLo=,tag:s62lb8GtrHHBj6ferphGog==,type:str] unencrypted_suffix: _unencrypted version: 3.10.2 diff --git a/modules/default.nix b/modules/default.nix index 1e07eec..11c16fa 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -6,5 +6,6 @@ ./virtualisation ./social ./file-server + ./samba ]; } diff --git a/modules/file-server/copyparty/default.nix b/modules/file-server/copyparty/default.nix index eb3533d..1ed7965 100644 --- a/modules/file-server/copyparty/default.nix +++ b/modules/file-server/copyparty/default.nix @@ -1,19 +1,96 @@ -{ config, lib, pkgs, ... }: +{ config, lib, pkgs, inputs, ... }: with lib; -let - cfg = config.dov.file-server.copyparty; +let cfg = config.dov.file-server.copyparty; in { - options.dov.file-server.copyparty = { enable = mkEnableOption "copyparty config"; }; + options.dov.file-server.copyparty = { + enable = mkEnableOption "copyparty config"; + }; config = mkIf cfg.enable { - # # add the copyparty overlay to expose the package to the module - # nixpkgs.overlays = [ copyparty.overlays.default ]; - # # (optional) install the package globally - # environment.systemPackages = [ pkgs.copyparty ]; - # # configure the copyparty module - # services.copyparty.enable = cfg.enable; + networking.firewall.allowedTCPPorts = config.services.copyparty.settings.p; + + sops.secrets."copyparty/admin_password" = { + owner = "copyparty"; + group = "copyparty"; + }; + sops.secrets."copyparty/alex_password" = { + owner = "copyparty"; + group = "copyparty"; + }; + + # add the copyparty overlay to expose the package to the module + nixpkgs.overlays = [ inputs.copyparty.overlays.default ]; + # (optional) install the package globally + environment.systemPackages = [ pkgs.copyparty ]; + # configure the copyparty module + services.copyparty = { + enable = cfg.enable; + settings = { + p = [ 3923 ]; + e2dsa = true; # enable file indexing and filesystem scanning + e2ts = true; # and enable multimedia indexing + z = true; # and zeroconf + qr = true; # and qrcode (you can comma-separate arguments) + }; + accounts = { + admin.passwordFile = "/run/secrets/copyparty/admin_password"; + alex.passwordFile = "/run/secrets/copyparty/alex_password"; + }; + + # create a volume + volumes = { + "/" = { + # share the contents of "/MEDIA" + path = "/"; + # see `copyparty --help-accounts` for available options + access = { + # everyone gets read-access, but + r = [ "admin" "alex" ]; + # users "ed" and "k" get read-write + rw = [ "admin" ]; + }; + # see `copyparty --help-flags` for available options + flags = { + # "fk" enables filekeys (necessary for upget permission) (4 chars long) + fk = 4; + # scan for new files every 60sec + scan = 60; + # volflag "e2d" enables the uploads database + e2d = true; + # "d2t" disables multimedia parsers (in case the uploads are malicious) + d2t = true; + # skips hashing file contents if path matches *.iso + nohash = ".iso$"; + }; + }; + "/MEDIA" = { + # share the contents of "/MEDIA" + path = "/MEDIA"; + # see `copyparty --help-accounts` for available options + access = { + # everyone gets read-access, but + r = "alex"; + # users "ed" and "k" get read-write + rw = [ "admin" "alex" ]; + }; + # see `copyparty --help-flags` for available options + flags = { + # "fk" enables filekeys (necessary for upget permission) (4 chars long) + fk = 4; + # scan for new files every 60sec + scan = 60; + # volflag "e2d" enables the uploads database + e2d = true; + # "d2t" disables multimedia parsers (in case the uploads are malicious) + d2t = true; + # skips hashing file contents if path matches *.iso + nohash = ".iso$"; + }; + }; + }; + }; }; } diff --git a/modules/reverse-proxy/traefik/default.nix b/modules/reverse-proxy/traefik/default.nix index 8af18a9..5cd3f6f 100644 --- a/modules/reverse-proxy/traefik/default.nix +++ b/modules/reverse-proxy/traefik/default.nix @@ -4,6 +4,7 @@ with lib; let cfg = config.dov.reverse-proxy.traefik; + domain = "susano-nixos.duckdns.org"; configFile = pkgs.writeText "duckdns-options" '' DUCKDNS_PROPAGATION_TIMEOUT=120 @@ -71,17 +72,24 @@ in { routers = { # --- Router for the Traefik dashboard (optional) --- dashboard-router = { - rule = "Host(`traefik.susano-test.duckdns.org`)"; # Example: A local-only subdomain + rule = "Host(`traefik.${domain}`)"; # Example: A local-only subdomain entryPoints = [ "websecure" ]; service = "api@internal"; # Special service for the dashboard tls.certResolver = "duckdns"; }; immich-router = { - rule = "Host(`immich.susano-test.duckdns.org`)"; # 1. The new domain - entryPoints = [ "websecure" ]; # 2. Listen on HTTPS - service = "immich-service"; # 3. Link to the new Immich service - tls.certResolver = "duckdns"; # 4. Use the same SSL resolver + rule = "Host(`immich.${domain}`)"; + entryPoints = [ "websecure" ]; + service = "immich-service"; + tls.certResolver = "duckdns"; + }; + + copyparty = mkIf config.dov.file-server.copyparty.enable { + rule = "Host(`copyparty.${domain}`)"; + entryPoints = [ "websecure" ]; + service = "copyparty-service"; + tls.certResolver = "duckdns"; }; }; @@ -92,6 +100,13 @@ in { { url = "http://192.168.1.57:2283"; } ]; }; + + copyparty-service = mkIf config.dov.file-server.copyparty.enable { + loadBalancer.servers = [ + # The backend URL for Immich + { url = "http://192.168.1.85:3923"; } + ]; + }; }; middlewares = { diff --git a/modules/samba/default.nix b/modules/samba/default.nix new file mode 100644 index 0000000..ecfba1a --- /dev/null +++ b/modules/samba/default.nix @@ -0,0 +1,30 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.dov.samba; + ip = "192.168.1.88"; +in { + options.dov.samba = { + enable = mkEnableOption "samba share config"; + }; + + config = mkIf cfg.enable { + sops.secrets.smb-secrets = { + }; + + environment.systemPackages = [ pkgs.cifs-utils ]; + + fileSystems."/MEDIA" = { + device = "//${ip}/MEDIA"; + fsType = "cifs"; + options = let + # this line prevents hanging on network split + automount_opts = "x-systemd.automount,noauto,x-systemd.idle-timeout=60,x-systemd.device-timeout=5s,x-systemd.mount-timeout=5s"; + + in ["${automount_opts},credentials=/run/secrets/smb-secrets"]; + }; + }; + +}