diff --git a/README.org b/README.org index 784d5a8..e0df3dc 100644 --- a/README.org +++ b/README.org @@ -4,7 +4,7 @@ #+OPTIONS: toc:t num:nil *Abstract* -This guide documents the process for a minimal installation of NixOS on a Proxmox virtual machine. It leverages the =nixos-anywhere= tool for remote deployment and =disko= for declarative disk partitioning. It also covers the essential post-installation steps for integrating the new host with =sops-nix= for secrets management. +This guide documents the process for a minimal installation of NixOS on a Proxmox virtual machine. It leverages the =nixos-anywhere= tool for remote deployment and =disko= for declarative disk partitioning. It also covers the essential post-installation steps for integrating the new host with =sops-nix= for secrets management and lists available custom modules. * TL;DR: Quick Install Guide 1. *Prepare VM:* Boot the target Proxmox VM from a NixOS ISO and set a root password: @@ -38,6 +38,8 @@ This guide documents the process for a minimal installation of NixOS on a Proxmo - [[#post-installation-secrets-management][Post-Installation: Secrets Management]] - [[#step-1-generating-the-host-age-key][Step 1: Generating the Host AGE Key]] - [[#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]] - [[#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]] @@ -102,12 +104,50 @@ The new AGE key must be added to your =.sops.yaml= configuration file. This allo - *susano #+end_src -3. After saving the updated =.sops.yaml= file, run the =updatekeys= command. This re-encrypts the specified secrets file with the new set of keys defined in =.sops.yaml=. For more information, see the [[https://github.com/getsops/sops?tab=readme-ov-file#281updatekeys-command][official documentation]]. +3. After saving the updated =.sops.yaml= file, run the =updatekeys= command. This re-encrypts the specified secrets file with the new set of keys defined in =.sots.yaml=. For more information, see the [[https://github.com/getsops/sops?tab=readme-ov-file#281updatekeys-command][official documentation]]. #+begin_src sh sops updatekeys secrets/secrets.yaml #+end_src Your secrets are now encrypted for both the primary key and the new host's key. +* Optional NixOS Modules +** 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. + +*** NGINX +The initial switch to this configuration may be slow as it waits for ACME to issue SSL certificates. +#+begin_src nix +dov = { + # Reverse Proxy + reverse-proxy = { + nginx.enable = true; + }; +}; +#+end_src + +*** Traefik +This module is currently considered to be in a broken state. +#+begin_src nix +dov = { + # Reverse Proxy + reverse-proxy = { + traefik.enable = true; + }; +}; +#+end_src + +*** Caddy +#+begin_src nix +dov = { + # Reverse Proxy + reverse-proxy = { + caddy.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. @@ -154,5 +194,9 @@ nix run github:nix-community/nixos-anywhere -- \ #+end_src * TODOs -- [ ] 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 across different hardware setups. +- [ ] Investigate and fix the issue preventing any of the reverse proxy modules (NGINX, Traefik, Caddy) from working correctly. +- [ ] 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. +- [ ] Create a custom ISO image to streamline the installation process, potentially pre-configuring items like the root user to avoid manual console steps. +- [ ] Develop an automated installation script to handle the post-install process, such as fetching the AGE key and updating sops, based on [[https://unmovedcentre.com/posts/remote-install-nixos-config/#update-sops-file][this guide]]. diff --git a/flake.nix b/flake.nix index 46f42dd..ab01ab4 100644 --- a/flake.nix +++ b/flake.nix @@ -51,6 +51,7 @@ sops-nix.nixosModules.sops ./main + ./modules ]; }; }; diff --git a/main/.sops.yaml b/main/.sops.yaml index fb5a43b..3630b0d 100644 --- a/main/.sops.yaml +++ b/main/.sops.yaml @@ -1,6 +1,6 @@ keys: - &primary age19wvqtn4ju6k4vs8fxr34unl6xx4cv04jw0lx9ps20xlde927zfssgl4qke - - &susano age1puzhjqxkxxfygm00taqql9vsv26cn2drqr3fk097mnu6t90fn9rqx7vtvs + - &susano age1z2982ful2wun2ec3vjn2q22yzmrrk72t7qhlhtkwh8vfnrrfrdpqhc8xs0 creation_rules: - path_regex: secrets/secrets.yaml$ key_groups: diff --git a/main/default.nix b/main/default.nix index c685fc4..65eebe9 100644 --- a/main/default.nix +++ b/main/default.nix @@ -129,6 +129,24 @@ in { }; }; + ### + # My Services + ### + + dov = { + # Reverse Proxy + reverse-proxy = { + nginx.enable = false; # TODO does not work for some reason + traefik.enable = false; # TODO has issues retrieving certificate from duckdns + caddy.enable = false; # TODO has issues retrieving certificate from duckdns + }; + + virtualisation = { + podman.enable = false; + docker.enable = true; + }; + }; + # DO NOT CHANGE AT ANY POINT! system.stateVersion = "25.05"; } diff --git a/main/secrets/secrets.yaml b/main/secrets/secrets.yaml index 363cddb..1c4a2d9 100644 --- a/main/secrets/secrets.yaml +++ b/main/secrets/secrets.yaml @@ -9,27 +9,28 @@ example_booleans: - ENC[AES256_GCM,data:4rh2xA==,iv:2wQtaVPzLjQzPezrxd1w4/IZu4bT0rvU8G/edcsQ7VQ=,tag:re5rdTqPNSTZ+CuZjvs86A==,type:bool] - ENC[AES256_GCM,data:5VhbnIk=,iv:sRnE8roVMQVs1Dk9tOtALWiDtfM4aJiSX5gb/MDHak8=,tag:egUULcUP5vCsy5uUM+j6dA==,type:bool] 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] sops: age: - recipient: age19wvqtn4ju6k4vs8fxr34unl6xx4cv04jw0lx9ps20xlde927zfssgl4qke enc: | -----BEGIN AGE ENCRYPTED FILE----- - YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBHbE12b2ZsU2VNSjVwR29M - WU1ZT2gwUHo3eXE3a1EzRDVrb2g1V2sybTFvCmp6dmlKZjdxM1ZiZUdrZ2ZZaXNz - ZWRHNmVwVUhUcWJoYVluOXN2aWpSVEUKLS0tIDVHaVhob0J6RlFhb1pvOG5OZy9W - UjRFMDhvOElxc3U2OGZjOFp2aFdodWsKIJFb3ZUuLDAgCel09B8fdpowa+A8R/HT - vq4aS7TFAo4GsTfm6oF7AejnRj8teqqBTD99coQZeRJc8C6J+hp9FQ== + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBQVlphbjR3YXMxSUM4bElk + bmtoWGhCc0FEQkZJSHhvVW5yNHEybzBPSVFrCjU4TG4yUTNKaTBmVVdxc1dYT2tQ + YUpWT0RvZFhXZWJCNDNVVFZndFVsblkKLS0tIER5YkorbnBXMDJJbWgyUGVaSUhE + SDNXdkIwWnFSWTVoMjBqdnhVZmVhTU0KSx0FqiVZX0B5gapQS/SlxqRk1sTMAR1e + SiKifKlQ0vJIw8X3sLmlomHL+7C7SUmnI1gxFTxYQeHH7IRiFJhnOQ== -----END AGE ENCRYPTED FILE----- - - recipient: age1puzhjqxkxxfygm00taqql9vsv26cn2drqr3fk097mnu6t90fn9rqx7vtvs + - recipient: age1z2982ful2wun2ec3vjn2q22yzmrrk72t7qhlhtkwh8vfnrrfrdpqhc8xs0 enc: | -----BEGIN AGE ENCRYPTED FILE----- - YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBXaDJoMEphNjBjcFBVdVJ1 - VGJjU1VtRmhtL00zUGJaRDMxRHMyS2x1ekJBCjA1Z3V6YzhHaTNZd2FxZmoybjgr - S3dWUDM0Ty9ZZkV3RFhjRWRnVURJeHMKLS0tIFM5VTVuSFdOQnFBby9OQWJWZ0pt - N0Q0a2J2WURqZlRadUdacGdHZUUrQWMK2Q1nwOWsGSzlcuZfcnq/P/v4i3nriUGY - l9izT0xS6M8cHoh10YK3Qe1LcxfT/v0pXD8ppARdEDbEcJahb5ZHiA== + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBCRlQvTktUUHZyTThtMi9w + OTB5UUZFMkYweWxaNS9sNjdNWmtvbUFrMGc4ClhqWUdVRFRkRWNacGJnQTdnQ3I1 + dGxkRVVzTG1PaDlETC8vcDNGOFgxaDgKLS0tIGJSY0VZbzZnZVRhVWgvZ25DRjg3 + NHdWQnlGbk43WS80VDkxV0o4TE5uSUUK0WSdFzR3u0pLUYHXaTMrtBm0sKKe9ZPG + nF90b/jv66WGIH1n2oFaaohCkd7DZGzSpr0+KsqX6pkszYnp39YC5A== -----END AGE ENCRYPTED FILE----- - lastmodified: "2025-07-26T12:25:08Z" - mac: ENC[AES256_GCM,data:eE/qjURtZPxwGpzvb0C64nZHaLSWL26kTU+fhx7dv+T84pCLtDwSiLrUfR33aSPtu9xTFJIiiFPQCiew99UXMsGKt+CVVGtR7frS5DktvABIfHuPznP3q6ykYMrNplK+xNQx0n6cZ/BNRVgc/kMPB9J3QudglAuxP3rMHrcjubA=,iv:okgPMpyGw0bUCEc/XrKonK5EUYrJjNRkAF/0t7TyoZw=,tag:fBQhqIASwsrl6//mXYcBzQ==,type:str] + lastmodified: "2025-07-26T13:53:03Z" + mac: ENC[AES256_GCM,data:WJJxd7d/Ld3z54JMgB7RhiBzy1P/hW14kRjfpX4pRIKzNzvUEivh1FQ1NUbonAGXrZZhE0WNPQaLcv185KeqXLF3NxWTawH+he+/uZr+cqcLU8Ylnyt4sbDDUCJgfo8HU0d+7xWrXblNqWQDHcEvm+KoSgwFYfBVGGvpCOv/mIs=,iv:jRMxA37VB21CQ1DqtKGYAMBHkf1O6bi65fvB0yh7roU=,tag:k29jd2jP137EkemkE4p2fw==,type:str] unencrypted_suffix: _unencrypted version: 3.10.2 diff --git a/main/sops.nix b/main/sops.nix index 754ea6e..b2e3fae 100644 --- a/main/sops.nix +++ b/main/sops.nix @@ -5,9 +5,7 @@ defaultSopsFile = ./secrets/secrets.yaml; age = { # This will automatically import SSH keys as age keys - sshKeyPaths = [ - "/etc/ssh/ssh_host_ed25519_key" - ]; + sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ]; # This is using an age key that is expected to already be in the filesystem keyFile = "/var/lib/sops-nix/key.txt"; # This will generate a new key if the key specified above does not exist @@ -15,8 +13,6 @@ # This is the actual specification of the secrets. }; - secrets."user_password" = { - neededForUsers = true; - }; + secrets = { "user_password" = { neededForUsers = true; }; }; }; } diff --git a/modules/default.nix b/modules/default.nix new file mode 100644 index 0000000..7b52442 --- /dev/null +++ b/modules/default.nix @@ -0,0 +1,8 @@ +{ config, lib, pkgs, ... }: + +{ + imports = [ + ./reverse-proxy + ./virtualisation + ]; +} diff --git a/modules/reverse-proxy/caddy/default.nix b/modules/reverse-proxy/caddy/default.nix new file mode 100644 index 0000000..9fc1933 --- /dev/null +++ b/modules/reverse-proxy/caddy/default.nix @@ -0,0 +1,62 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.dov.reverse-proxy.caddy; + caddyWithDuckDNS = pkgs.caddy.withPlugins { + plugins = [ + "github.com/caddy-dns/duckdns@v0.5.0" + ]; + # Replace with the hash NixOS provides on the first build attempt. + hash = "sha256-83ETc9K4T13Ws8gVOYwLarhuCA48Drs/i3rVLBMHyrc="; + }; + email = "susano@local.com"; +in { + options.dov.reverse-proxy.caddy = { enable = mkEnableOption "caddy config"; }; + + config = mkIf cfg.enable { + sops.secrets.duckdns-token = { + owner = config.services.caddy.user; + group = config.services.caddy.group; + }; + + services.caddy = { + enable = cfg.enable; + package = caddyWithDuckDNS; + + environmentFile = config.sops.secrets.duckdns-token.path; + + # Add a global options block. + # Let's Encrypt will use this email to send you important notices. + globalConfig = '' + email ${email} + ''; + + virtualHosts."test.susano-lab.duckdns.org" = { + extraConfig = '' + # Reverse proxy to your Immich instance. + reverse_proxy http://192.168.1.57:2283 { + # Send correct headers to the backend service. + header_up Host {host} + header_up X-Real-IP {remote_ip} + header_up X-Forwarded-For {remote_ip} + header_up X-Forwarded-Proto {scheme} + + # Recommended for large file uploads in Immich. + transport http { + read_buffer 1m + } + } + + # Configure automatic HTTPS with the DuckDNS provider. + tls { + dns duckdns {env.DUCKDNS_TOKEN} + + propagation_timeout -1 + } + ''; + }; + }; + }; +} diff --git a/modules/reverse-proxy/default.nix b/modules/reverse-proxy/default.nix new file mode 100644 index 0000000..76c151f --- /dev/null +++ b/modules/reverse-proxy/default.nix @@ -0,0 +1,9 @@ +{ config, lib, pkgs, ... }: + +{ + imports = [ + ./nginx + ./traefik + ./caddy + ]; +} diff --git a/modules/reverse-proxy/nginx/default.nix b/modules/reverse-proxy/nginx/default.nix new file mode 100644 index 0000000..7026600 --- /dev/null +++ b/modules/reverse-proxy/nginx/default.nix @@ -0,0 +1,150 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.dov.reverse-proxy.nginx; + email = "susano@local.com"; + # configFile = pkgs.writeText "duckdns_config" + # '' + # example config file bla bla + # ''; +in { + options.dov.reverse-proxy.nginx = { enable = mkEnableOption "nginx config"; }; + + config = mkIf cfg.enable { + sops.secrets.duckdns-token = { + owner = config.services.nginx.user; + group = config.services.nginx.group; + }; + + # 1. Enable Nginx + + # 2. Enable Automatic Certificate Management (ACME) + # NixOS uses acme.sh to handle Let's Encrypt certificates. + security.acme = { + acceptTerms = true; + defaults.email = email; + + # Define the certificates you want to obtain. + # We use the DNS-01 challenge for wildcard domains. + certs = { + # Certificate for *.susano-lab.duckdns.org + "susano-lab.duckdns.org" = { + domain = "*.susano-lab.duckdns.org"; + extraDomainNames = [ "susano-lab.duckdns.org" ]; + dnsProvider = "duckdns"; + # The credentialsFile points to the secret file we created. + credentialsFile = config.sops.secrets.duckdns-token.path; + group = config.services.nginx.group; + }; + + # Certificate for *.susano-tailscale.duckdns.org + "susano-tailscale.duckdns.org" = { + domain = "*.susano-tailscale.duckdns.org"; + extraDomainNames = [ "susano-tailscale.duckdns.org" ]; + dnsProvider = "duckdns"; + credentialsFile = config.sops.secrets.duckdns-token.path; + group = config.services.nginx.group; + }; + }; + }; + + # 3. Define Nginx Reverse Proxy Configuration + services.nginx = { + enable = cfg.enable; + + # Use httpConfig to define 'map' blocks at the correct level. + httpConfig = '' + # Map for susano-lab.duckdns.org domains + map $host $lab_proxy_pass { + "immich.susano-lab.duckdns.org" "http://192.168.1.57:2283"; + "jellyfin.susano-lab.duckdns.org" "http://192.168.1.64:8096"; + "jellyseer.susano-lab.duckdns.org" "http://192.168.1.68:5055"; + "nginx.susano-lab.duckdns.org" "http://192.168.1.57:81"; + "portainer.susano-lab.duckdns.org" "https://192.168.1.57:9443"; + "qbittorrent.susano-lab.duckdns.org" "http://192.168.1.57:8080"; + "radarr.susano-lab.duckdns.org" "http://192.168.1.57:7878"; + "searxng.susano-lab.duckdns.org" "http://192.168.1.82:8080"; + "sonarr.susano-lab.duckdns.org" "http://192.168.1.57:8989"; + "susano-lab.duckdns.org" "http://192.168.1.53:8006"; + default "http://127.0.0.1:8000"; # Optional: a default to avoid errors if no host matches + } + + # Map for susano-tailscale.duckdns.org domains + map $host $tailscale_proxy_pass { + "immich.susano-tailscale.duckdns.org" "http://192.168.1.57:2283"; + "searxng.susano-tailscale.duckdns.org" "http://192.168.1.82:8080"; + default "http://127.0.0.1:8000"; # Optional: a default + } + + # Map for susano-traefik.duckdns.org domains + map $host $traefik_proxy_pass { + "immich.susano-traefik.duckdns.org" "http://192.168.1.57:2283"; + default "http://127.0.0.1:8000"; # Optional: a default + } + ''; + + virtualHosts = { + + # === Group for susano-lab.duckdns.org subdomains === + "susano-lab" = { + serverName = "susano-lab.duckdns.org"; + serverAliases = [ + "test.susano-lab.duckdns.org" + "immich.susano-lab.duckdns.org" + "jellyfin.susano-lab.duckdns.org" + "jellyseer.susano-lab.duckdns.org" + "nginx.susano-lab.duckdns.org" + "portainer.susano-lab.duckdns.org" + "qbittorrent.susano-lab.duckdns.org" + "radarr.susano-lab.duckdns.org" + "searxng.susano-lab.duckdns.org" + "sonarr.susano-lab.duckdns.org" + ]; + useACMEHost = "susano-lab.duckdns.org"; + forceSSL = true; + + locations."/".extraConfig = '' + # The map block is removed from here. + # We now use the variable defined in httpConfig. + proxy_pass $lab_proxy_pass; + + # Standard proxy headers for websockets and correct IP forwarding. + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + ''; + }; + + # === Group for susano-tailscale.duckdns.org subdomains === + "susano-tailscale" = { + serverName = "susano-tailscale.duckdns.org"; + serverAliases = [ + "immich.susano-tailscale.duckdns.org" + "searxng.susano-tailscale.duckdns.org" + ]; + useACMEHost = "susano-tailscale.duckdns.org"; + forceSSL = true; + + locations."/".extraConfig = '' + # Use the second variable defined in httpConfig. + proxy_pass $tailscale_proxy_pass; + + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + ''; + }; + }; + }; + }; +} diff --git a/modules/reverse-proxy/traefik/default.nix b/modules/reverse-proxy/traefik/default.nix new file mode 100644 index 0000000..c65d05c --- /dev/null +++ b/modules/reverse-proxy/traefik/default.nix @@ -0,0 +1,114 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.dov.reverse-proxy.traefik; +in { + options.dov.reverse-proxy.traefik = { enable = mkEnableOption "traefik config"; }; + + config = mkIf cfg.enable { + # 1. SOPS Configuration for the DuckDNS Token + # This decrypts the secret and provides it to the Traefik service. + sops.secrets.duckdns-token = { + # The Traefik service needs permission to read this file. + owner = "traefik"; + group = config.services.traefik.group; + }; + + # 3. Traefik Service Configuration + services.traefik = { + enable = true; + + # Load the DuckDNS token as an environment variable for Traefik. + environmentFiles = [ config.sops.secrets.duckdns-token.path ]; + + # Static configuration (traefik.yml) - defines entrypoints and certificate resolvers. + staticConfigOptions = { + # -- EntryPoints: Where Traefik listens for traffic -- + entryPoints = { + # Unsecured HTTP on port 80, mainly for redirection + web = { + address = ":80"; + # Redirect all traffic from this entrypoint to the 'websecure' (HTTPS) entrypoint + http.redirections.entryPoint = { + to = "websecure"; + scheme = "https"; + permanent = true; + }; + }; + # Secured HTTPS on port 443 + websecure = { + address = ":443"; + }; + }; + + # -- Certificate Resolver: How Traefik gets SSL certs -- + certificatesResolvers = { + # We'll name our resolver 'duckdns' + duckdns = { + acme = { + email = "susano@local.com"; + storage = "/var/lib/traefik/acme.json"; # Where Traefik stores certs + # Use the DNS-01 challenge with the DuckDNS provider + dnsChallenge = { + provider = "duckdns"; + # Traefik will get the DUCKDNS_TOKEN from the environment file. + resolvers = [ + "1.1.1.1:53" + "8.8.8.8:53" + ]; + }; + }; + }; + }; + + # Optional but recommended: Enable the Traefik Dashboard + api.dashboard = true; + }; + + # Dynamic configuration - defines the actual routers and services. + dynamicConfigOptions = { + http = { + routers = { + # --- Router for the Traefik dashboard (optional) --- + dashboard-router = { + rule = "Host(`traefik.local.susano-traefik.duckdns.org`)"; # Example: A local-only subdomain + entryPoints = [ "websecure" ]; + service = "api@internal"; # Special service for the dashboard + tls.certResolver = "duckdns"; + }; + + immich-router = { + rule = "Host(`immich.susano-traefik.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 + }; + }; + + services = { + immich-service = { + loadBalancer.servers = [ + # The backend URL for Immich + { url = "http://192.168.1.57:2283"; } + ]; + }; + }; + + middlewares = { + # --- Middleware for dashboard authentication (optional) --- + auth = { + # Run `nix-shell -p apacheHttpdTools --run "htpasswd -nb your-user your-password"` + # to generate the user:password hash. + basicAuth.users = [ + "your-user:$apr1$....some-hash-here...." + ]; + }; + }; + }; + }; + }; + }; + +} diff --git a/modules/virtualisation/default.nix b/modules/virtualisation/default.nix new file mode 100644 index 0000000..1e011dd --- /dev/null +++ b/modules/virtualisation/default.nix @@ -0,0 +1,8 @@ +{ config, lib, pkgs, ... }: + +{ + imports = [ + ./podman + ./docker + ]; +} diff --git a/modules/virtualisation/docker/default.nix b/modules/virtualisation/docker/default.nix new file mode 100644 index 0000000..88a23ab --- /dev/null +++ b/modules/virtualisation/docker/default.nix @@ -0,0 +1,26 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.dov.virtualisation.docker; + username = "susano"; +in { + options.dov.virtualisation.docker = { enable = mkEnableOption "docker config"; }; + + config = mkIf cfg.enable { + users.extraGroups.docker.members = [ username ]; + + virtualisation.docker = { + enable = true; + rootless = { + enable = true; + setSocketVariable = true; + }; + + # TODO use if disko is btrfs + storageDriver = "btrfs"; + }; + }; + +} diff --git a/modules/virtualisation/podman/default.nix b/modules/virtualisation/podman/default.nix new file mode 100644 index 0000000..402ef18 --- /dev/null +++ b/modules/virtualisation/podman/default.nix @@ -0,0 +1,34 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.dov.virtualisation.podman; +in { + options.dov.virtualisation.podman = { enable = mkEnableOption "podman config"; }; + + config = mkIf cfg.enable { + # Enable common container config files in /etc/containers + virtualisation.containers.enable = true; + virtualisation = { + podman = { + enable = true; + + # Create a `docker` alias for podman, to use it as a drop-in replacement + dockerCompat = true; + + # Required for containers under podman-compose to be able to talk to each other. + defaultNetwork.settings.dns_enabled = true; + }; + }; + + # Useful other development tools + environment.systemPackages = with pkgs; [ + dive # look into docker image layers + podman-tui # status of containers in the terminal + #docker-compose # start group of containers for dev + podman-compose # start group of containers for dev + ]; + }; + +}