Add nginx,traefik,caddy,docker,podman
This commit is contained in:
+47
-3
@@ -4,7 +4,7 @@
|
|||||||
#+OPTIONS: toc:t num:nil
|
#+OPTIONS: toc:t num:nil
|
||||||
|
|
||||||
*Abstract*
|
*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
|
* TL;DR: Quick Install Guide
|
||||||
1. *Prepare VM:* Boot the target Proxmox VM from a NixOS ISO and set a root password:
|
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]]
|
- [[#post-installation-secrets-management][Post-Installation: Secrets Management]]
|
||||||
- [[#step-1-generating-the-host-age-key][Step 1: Generating the Host AGE Key]]
|
- [[#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]]
|
- [[#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]]
|
- [[#notes-and-configuration-details][Notes and Configuration Details]]
|
||||||
- [[#disko-configuration-for-proxmox-mbr-boot][Disko Configuration for Proxmox (MBR Boot)]]
|
- [[#disko-configuration-for-proxmox-mbr-boot][Disko Configuration for Proxmox (MBR Boot)]]
|
||||||
- [[#generating-hardware-configuration][Generating Hardware Configuration]]
|
- [[#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
|
- *susano
|
||||||
#+end_src
|
#+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
|
#+begin_src sh
|
||||||
sops updatekeys secrets/secrets.yaml
|
sops updatekeys secrets/secrets.yaml
|
||||||
#+end_src
|
#+end_src
|
||||||
Your secrets are now encrypted for both the primary key and the new host's key.
|
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
|
* Notes and Configuration Details
|
||||||
** Disko Configuration for Proxmox (MBR Boot)
|
** 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.
|
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
|
#+end_src
|
||||||
|
|
||||||
* TODOs
|
* 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.
|
- [ ] 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]].
|
||||||
|
|||||||
@@ -51,6 +51,7 @@
|
|||||||
sops-nix.nixosModules.sops
|
sops-nix.nixosModules.sops
|
||||||
|
|
||||||
./main
|
./main
|
||||||
|
./modules
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
keys:
|
keys:
|
||||||
- &primary age19wvqtn4ju6k4vs8fxr34unl6xx4cv04jw0lx9ps20xlde927zfssgl4qke
|
- &primary age19wvqtn4ju6k4vs8fxr34unl6xx4cv04jw0lx9ps20xlde927zfssgl4qke
|
||||||
- &susano age1puzhjqxkxxfygm00taqql9vsv26cn2drqr3fk097mnu6t90fn9rqx7vtvs
|
- &susano age1z2982ful2wun2ec3vjn2q22yzmrrk72t7qhlhtkwh8vfnrrfrdpqhc8xs0
|
||||||
creation_rules:
|
creation_rules:
|
||||||
- path_regex: secrets/secrets.yaml$
|
- path_regex: secrets/secrets.yaml$
|
||||||
key_groups:
|
key_groups:
|
||||||
|
|||||||
@@ -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!
|
# DO NOT CHANGE AT ANY POINT!
|
||||||
system.stateVersion = "25.05";
|
system.stateVersion = "25.05";
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-13
@@ -9,27 +9,28 @@ example_booleans:
|
|||||||
- ENC[AES256_GCM,data:4rh2xA==,iv:2wQtaVPzLjQzPezrxd1w4/IZu4bT0rvU8G/edcsQ7VQ=,tag:re5rdTqPNSTZ+CuZjvs86A==,type:bool]
|
- 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]
|
- 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]
|
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:
|
sops:
|
||||||
age:
|
age:
|
||||||
- recipient: age19wvqtn4ju6k4vs8fxr34unl6xx4cv04jw0lx9ps20xlde927zfssgl4qke
|
- recipient: age19wvqtn4ju6k4vs8fxr34unl6xx4cv04jw0lx9ps20xlde927zfssgl4qke
|
||||||
enc: |
|
enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBHbE12b2ZsU2VNSjVwR29M
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBQVlphbjR3YXMxSUM4bElk
|
||||||
WU1ZT2gwUHo3eXE3a1EzRDVrb2g1V2sybTFvCmp6dmlKZjdxM1ZiZUdrZ2ZZaXNz
|
bmtoWGhCc0FEQkZJSHhvVW5yNHEybzBPSVFrCjU4TG4yUTNKaTBmVVdxc1dYT2tQ
|
||||||
ZWRHNmVwVUhUcWJoYVluOXN2aWpSVEUKLS0tIDVHaVhob0J6RlFhb1pvOG5OZy9W
|
YUpWT0RvZFhXZWJCNDNVVFZndFVsblkKLS0tIER5YkorbnBXMDJJbWgyUGVaSUhE
|
||||||
UjRFMDhvOElxc3U2OGZjOFp2aFdodWsKIJFb3ZUuLDAgCel09B8fdpowa+A8R/HT
|
SDNXdkIwWnFSWTVoMjBqdnhVZmVhTU0KSx0FqiVZX0B5gapQS/SlxqRk1sTMAR1e
|
||||||
vq4aS7TFAo4GsTfm6oF7AejnRj8teqqBTD99coQZeRJc8C6J+hp9FQ==
|
SiKifKlQ0vJIw8X3sLmlomHL+7C7SUmnI1gxFTxYQeHH7IRiFJhnOQ==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
- recipient: age1puzhjqxkxxfygm00taqql9vsv26cn2drqr3fk097mnu6t90fn9rqx7vtvs
|
- recipient: age1z2982ful2wun2ec3vjn2q22yzmrrk72t7qhlhtkwh8vfnrrfrdpqhc8xs0
|
||||||
enc: |
|
enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBXaDJoMEphNjBjcFBVdVJ1
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBCRlQvTktUUHZyTThtMi9w
|
||||||
VGJjU1VtRmhtL00zUGJaRDMxRHMyS2x1ekJBCjA1Z3V6YzhHaTNZd2FxZmoybjgr
|
OTB5UUZFMkYweWxaNS9sNjdNWmtvbUFrMGc4ClhqWUdVRFRkRWNacGJnQTdnQ3I1
|
||||||
S3dWUDM0Ty9ZZkV3RFhjRWRnVURJeHMKLS0tIFM5VTVuSFdOQnFBby9OQWJWZ0pt
|
dGxkRVVzTG1PaDlETC8vcDNGOFgxaDgKLS0tIGJSY0VZbzZnZVRhVWgvZ25DRjg3
|
||||||
N0Q0a2J2WURqZlRadUdacGdHZUUrQWMK2Q1nwOWsGSzlcuZfcnq/P/v4i3nriUGY
|
NHdWQnlGbk43WS80VDkxV0o4TE5uSUUK0WSdFzR3u0pLUYHXaTMrtBm0sKKe9ZPG
|
||||||
l9izT0xS6M8cHoh10YK3Qe1LcxfT/v0pXD8ppARdEDbEcJahb5ZHiA==
|
nF90b/jv66WGIH1n2oFaaohCkd7DZGzSpr0+KsqX6pkszYnp39YC5A==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
lastmodified: "2025-07-26T12:25:08Z"
|
lastmodified: "2025-07-26T13:53:03Z"
|
||||||
mac: ENC[AES256_GCM,data:eE/qjURtZPxwGpzvb0C64nZHaLSWL26kTU+fhx7dv+T84pCLtDwSiLrUfR33aSPtu9xTFJIiiFPQCiew99UXMsGKt+CVVGtR7frS5DktvABIfHuPznP3q6ykYMrNplK+xNQx0n6cZ/BNRVgc/kMPB9J3QudglAuxP3rMHrcjubA=,iv:okgPMpyGw0bUCEc/XrKonK5EUYrJjNRkAF/0t7TyoZw=,tag:fBQhqIASwsrl6//mXYcBzQ==,type:str]
|
mac: ENC[AES256_GCM,data:WJJxd7d/Ld3z54JMgB7RhiBzy1P/hW14kRjfpX4pRIKzNzvUEivh1FQ1NUbonAGXrZZhE0WNPQaLcv185KeqXLF3NxWTawH+he+/uZr+cqcLU8Ylnyt4sbDDUCJgfo8HU0d+7xWrXblNqWQDHcEvm+KoSgwFYfBVGGvpCOv/mIs=,iv:jRMxA37VB21CQ1DqtKGYAMBHkf1O6bi65fvB0yh7roU=,tag:k29jd2jP137EkemkE4p2fw==,type:str]
|
||||||
unencrypted_suffix: _unencrypted
|
unencrypted_suffix: _unencrypted
|
||||||
version: 3.10.2
|
version: 3.10.2
|
||||||
|
|||||||
+2
-6
@@ -5,9 +5,7 @@
|
|||||||
defaultSopsFile = ./secrets/secrets.yaml;
|
defaultSopsFile = ./secrets/secrets.yaml;
|
||||||
age = {
|
age = {
|
||||||
# This will automatically import SSH keys as age keys
|
# This will automatically import SSH keys as age keys
|
||||||
sshKeyPaths = [
|
sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
|
||||||
"/etc/ssh/ssh_host_ed25519_key"
|
|
||||||
];
|
|
||||||
# This is using an age key that is expected to already be in the filesystem
|
# This is using an age key that is expected to already be in the filesystem
|
||||||
keyFile = "/var/lib/sops-nix/key.txt";
|
keyFile = "/var/lib/sops-nix/key.txt";
|
||||||
# This will generate a new key if the key specified above does not exist
|
# 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.
|
# This is the actual specification of the secrets.
|
||||||
};
|
};
|
||||||
|
|
||||||
secrets."user_password" = {
|
secrets = { "user_password" = { neededForUsers = true; }; };
|
||||||
neededForUsers = true;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
./reverse-proxy
|
||||||
|
./virtualisation
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
./nginx
|
||||||
|
./traefik
|
||||||
|
./caddy
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -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...."
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
./podman
|
||||||
|
./docker
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -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";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user