diff --git a/flake.lock b/flake.lock index a5e3b36..d4e6d06 100644 --- a/flake.lock +++ b/flake.lock @@ -30,11 +30,11 @@ ] }, "locked": { - "lastModified": 1765042799, - "narHash": "sha256-G7UJDUNcuHm1n2EuA+2iKPNISSWoUgqk85ktncJoelo=", + "lastModified": 1769608722, + "narHash": "sha256-yWUG0Emd9EuqIZ8jQ6fxqf7USw7Gtcqb4+sBhn+S+Wg=", "owner": "AdnanHodzic", "repo": "auto-cpufreq", - "rev": "2e5c725be0a7da2c749a345e09f3df3b9ef8c209", + "rev": "a11a98c46bf6a77d0c2e0ea8d87acef78507cae5", "type": "github" }, "original": { @@ -50,11 +50,11 @@ ] }, "locked": { - "lastModified": 1766150702, - "narHash": "sha256-P0kM+5o+DKnB6raXgFEk3azw8Wqg5FL6wyl9jD+G5a4=", + "lastModified": 1769524058, + "narHash": "sha256-zygdD6X1PcVNR2PsyK4ptzrVEiAdbMqLos7utrMDEWE=", "owner": "nix-community", "repo": "disko", - "rev": "916506443ecd0d0b4a0f4cf9d40a3c22ce39b378", + "rev": "71a3fc97d80881e91710fe721f1158d3b96ae14d", "type": "github" }, "original": { @@ -144,11 +144,11 @@ ] }, "locked": { - "lastModified": 1766529401, - "narHash": "sha256-OJAjJcW6ZADEzTBrvOTZanbgC8ObEWveObujtpazEbg=", + "lastModified": 1769580047, + "narHash": "sha256-tNqCP/+2+peAXXQ2V8RwsBkenlfWMERb+Uy6xmevyhM=", "owner": "nix-community", "repo": "home-manager", - "rev": "aaf46506426cc8c53719dd20de660fc856a5561e", + "rev": "366d78c2856de6ab3411c15c1cb4fb4c2bf5c826", "type": "github" }, "original": { @@ -181,11 +181,11 @@ }, "nixos-hardware": { "locked": { - "lastModified": 1764440730, - "narHash": "sha256-ZlJTNLUKQRANlLDomuRWLBCH5792x+6XUJ4YdFRjtO4=", + "lastModified": 1769302137, + "narHash": "sha256-QEDtctEkOsbx8nlFh4yqPEOtr4tif6KTqWwJ37IM2ds=", "owner": "NixOS", "repo": "nixos-hardware", - "rev": "9154f4569b6cdfd3c595851a6ba51bfaa472d9f3", + "rev": "a351494b0e35fd7c0b7a1aae82f0afddf4907aa8", "type": "github" }, "original": { @@ -197,11 +197,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1766473571, - "narHash": "sha256-5G1NDO2PulBx1RoaA6U1YoUDX0qZslpPxv+n5GX6Qto=", + "lastModified": 1769598131, + "narHash": "sha256-e7VO/kGLgRMbWtpBqdWl0uFg8Y2XWFMdz0uUJvlML8o=", "owner": "nixos", "repo": "nixpkgs", - "rev": "76701a179d3a98b07653e2b0409847499b2a07d3", + "rev": "fa83fd837f3098e3e678e6cf017b2b36102c7211", "type": "github" }, "original": { @@ -221,9 +221,10 @@ "type": "github" }, "original": { - "id": "nixpkgs", + "owner": "NixOS", + "repo": "nixpkgs", "rev": "e6f23dc08d3624daab7094b701aa3954923c6bbb", - "type": "indirect" + "type": "github" } }, "nixpkgs-stable": { @@ -244,32 +245,34 @@ }, "nixpkgs-stable_2": { "locked": { - "lastModified": 1761016216, - "narHash": "sha256-G/iC4t/9j/52i/nm+0/4ybBmAF4hzR8CNHC75qEhjHo=", + "lastModified": 1769318308, + "narHash": "sha256-Mjx6p96Pkefks3+aA+72lu1xVehb6mv2yTUUqmSet6Q=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "481cf557888e05d3128a76f14c76397b7d7cc869", + "rev": "1cd347bf3355fce6c64ab37d3967b4a2cb4b878c", "type": "github" }, "original": { - "id": "nixpkgs", - "ref": "nixos-25.05", - "type": "indirect" + "owner": "NixOS", + "ref": "nixos-25.11", + "repo": "nixpkgs", + "type": "github" } }, "nixpkgs-unstable": { "locked": { - "lastModified": 1761114652, - "narHash": "sha256-f/QCJM/YhrV/lavyCVz8iU3rlZun6d+dAiC3H+CDle4=", - "owner": "NixOS", + "lastModified": 1769861584, + "narHash": "sha256-Tu85RXpHMAWmsltAEKsG1IB7JfNGbekeHh2CSR0/xG8=", + "owner": "nixos", "repo": "nixpkgs", - "rev": "01f116e4df6a15f4ccdffb1bcd41096869fb385c", + "rev": "015e5f32a6258dc210b8e02fb47d86983959e243", "type": "github" }, "original": { - "id": "nixpkgs", - "ref": "nixos-unstable", - "type": "indirect" + "owner": "nixos", + "ref": "pull/483348/merge", + "repo": "nixpkgs", + "type": "github" } }, "pre-commit-hooks": { @@ -301,15 +304,14 @@ "flake-compat": "flake-compat_2", "nixpkgs-libvncserver": "nixpkgs-libvncserver", "nixpkgs-stable": "nixpkgs-stable_2", - "nixpkgs-unstable": "nixpkgs-unstable", "utils": "utils" }, "locked": { - "lastModified": 1764188113, - "narHash": "sha256-Oq4aCjczgnFQqFNWZ6Ablg6x9579CO8tyBktYDYiZEs=", + "lastModified": 1769870714, + "narHash": "sha256-wjwCj70iiFXoAasQto+3jTaA4wCMOAs/rdX+nsmtBrQ=", "owner": "SaumonNet", "repo": "proxmox-nixos", - "rev": "3be878a84866b9ef9214b8ea6f53630f47f4b192", + "rev": "c1f79f104930347a0b84abbca0d42884063a8c09", "type": "github" }, "original": { @@ -327,6 +329,7 @@ "lazy-apps": "lazy-apps", "nixos-hardware": "nixos-hardware", "nixpkgs": "nixpkgs", + "nixpkgs-unstable": "nixpkgs-unstable", "proxmox-nixos": "proxmox-nixos", "secrets": "secrets", "systems": "systems_3" @@ -335,11 +338,11 @@ "secrets": { "flake": false, "locked": { - "lastModified": 1758149597, - "narHash": "sha256-qUkhfFBEuDJ7nP6jcdBZzGBBhLKnXYxumBQI75DGcFc=", + "lastModified": 1769426267, + "narHash": "sha256-OBHSfMHZ+sWEtigOxTfIGnkZLPOz2P7VR8+KA2KY89g=", "ref": "refs/heads/main", - "rev": "8404f6877e25b8cbf3f504ef1926034e8c401dbe", - "revCount": 6, + "rev": "ebefef468e16eb692df0a3d54352c94a56110a97", + "revCount": 20, "type": "git", "url": "ssh://git@git.jfreudenberger.de/JuliusFreudenberger/nix-private.git" }, diff --git a/flake.nix b/flake.nix index 1e2ce3f..8b183d5 100644 --- a/flake.nix +++ b/flake.nix @@ -11,7 +11,7 @@ }; inputs = { - #nixpkgs-unstable.url = "github:nixos/nixpkgs/nixos-unstable"; + nixpkgs-unstable.url = "github:nixos/nixpkgs/nixos-unstable"; nixpkgs.url = "github:nixos/nixpkgs/nixos-25.11"; nixos-hardware.url = "github:NixOS/nixos-hardware/master"; home-manager = { @@ -48,7 +48,7 @@ outputs = { self, nixpkgs, - #nixpkgs-unstable, + nixpkgs-unstable, nixos-hardware, home-manager, auto-cpufreq, @@ -81,10 +81,6 @@ system = "x86_64-linux"; specialArgs = { - #pkgs-unstable = import nixpkgs-unstable { - # inherit system; - # config.allowUnfree = true; - #}; inherit inputs outputs username; }; @@ -142,6 +138,10 @@ specialArgs = { inherit inputs outputs; + pkgs-unstable = import nixpkgs-unstable { + inherit system; + config.allowUnfree = true; + }; }; modules = [ @@ -151,45 +151,6 @@ ]; }; - kube01 = nixpkgs.lib.nixosSystem rec { - system = "x86_64-linux"; - - specialArgs = { - inherit inputs outputs; - }; - - modules = [ - disko.nixosModules.disko - ./hosts/kube01 - ]; - }; - - kube02 = nixpkgs.lib.nixosSystem rec { - system = "x86_64-linux"; - - specialArgs = { - inherit inputs outputs; - }; - - modules = [ - disko.nixosModules.disko - ./hosts/kube02 - ]; - }; - - kube03 = nixpkgs.lib.nixosSystem rec { - system = "x86_64-linux"; - - specialArgs = { - inherit inputs outputs; - }; - - modules = [ - disko.nixosModules.disko - ./hosts/kube03 - ]; - }; - }; homeConfigurations = { diff --git a/hosts/kube01/default.nix b/hosts/kube01/default.nix deleted file mode 100644 index a72dee8..0000000 --- a/hosts/kube01/default.nix +++ /dev/null @@ -1,41 +0,0 @@ -{ inputs, outputs, config, lib, pkgs, ... }: - -{ - imports = - [ - ../../modules/disko/efi-full-btrfs.nix - ../../modules/systemd-boot.nix - - ../../users/julius/nixos-server.nix - ../../modules/nix.nix - ../../modules/network-server.nix - ../../modules/locale.nix - ../../modules/server-cli.nix - ../../modules/sshd.nix - ../../modules/k3s.nix - ../../modules/qemu-guest.nix - # Include the results of the hardware scan. - ./hardware-configuration.nix - ]; - - - networking.hostName = "kube01"; # Define your hostname. - - services.k3s = { - clusterInit = true; - }; - - # This option defines the first version of NixOS you have installed on this particular machine, - # and is used to maintain compatibility with application data (e.g. databases) created on older NixOS versions. - # Most users should NEVER change this value after the initial install, for any reason, - # even if you've upgraded your system to a new NixOS release. - # This value does NOT affect the Nixpkgs version your packages and OS are pulled from, - # so changing it will NOT upgrade your system - see https://nixos.org/manual/nixos/stable/#sec-upgrading for how - # to actually do that. - # This value being lower than the current NixOS release does NOT mean your system is - # out of date, out of support, or vulnerable. - # Do NOT change this value unless you have manually inspected all the changes it would make to your configuration, - # and migrated your data accordingly. - # For more information, see `man configuration.nix` or https://nixos.org/manual/nixos/stable/options#opt-system.stateVersion . - system.stateVersion = "25.05"; # Did you read the comment? -} diff --git a/hosts/kube01/hardware-configuration.nix b/hosts/kube01/hardware-configuration.nix deleted file mode 100644 index f198f81..0000000 --- a/hosts/kube01/hardware-configuration.nix +++ /dev/null @@ -1,24 +0,0 @@ -# Do not modify this file! It was generated by ‘nixos-generate-config’ -# and may be overwritten by future invocations. Please make changes -# to /etc/nixos/configuration.nix instead. -{ config, lib, pkgs, modulesPath, ... }: - -{ - imports = - [ (modulesPath + "/profiles/qemu-guest.nix") - ]; - - boot.initrd.availableKernelModules = [ "ahci" "xhci_pci" "virtio_pci" "sr_mod" "virtio_blk" ]; - boot.initrd.kernelModules = [ ]; - boot.kernelModules = [ "kvm-intel" ]; - boot.extraModulePackages = [ ]; - - # Enables DHCP on each ethernet and wireless interface. In case of scripted networking - # (the default) this is the recommended approach. When using systemd-networkd it's - # still possible to use this option, but it's recommended to use it in conjunction - # with explicit per-interface declarations with `networking.interfaces..useDHCP`. - networking.useDHCP = lib.mkDefault true; - # networking.interfaces.enp1s0.useDHCP = lib.mkDefault true; - - nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; -} diff --git a/hosts/kube02/default.nix b/hosts/kube02/default.nix deleted file mode 100644 index 504afef..0000000 --- a/hosts/kube02/default.nix +++ /dev/null @@ -1,41 +0,0 @@ -{ inputs, outputs, config, lib, pkgs, ... }: - -{ - imports = - [ - ../../modules/disko/efi-full-btrfs.nix - ../../modules/systemd-boot.nix - - ../../users/julius/nixos-server.nix - ../../modules/nix.nix - ../../modules/network-server.nix - ../../modules/locale.nix - ../../modules/server-cli.nix - ../../modules/sshd.nix - ../../modules/k3s.nix - ../../modules/qemu-guest.nix - # Include the results of the hardware scan. - ./hardware-configuration.nix - ]; - - networking.hostName = "kube02"; # Define your hostname. - - services.k3s = { - serverAddr = "https://kube01:6443"; - clusterInit = false; - }; - - # This option defines the first version of NixOS you have installed on this particular machine, - # and is used to maintain compatibility with application data (e.g. databases) created on older NixOS versions. - # Most users should NEVER change this value after the initial install, for any reason, - # even if you've upgraded your system to a new NixOS release. - # This value does NOT affect the Nixpkgs version your packages and OS are pulled from, - # so changing it will NOT upgrade your system - see https://nixos.org/manual/nixos/stable/#sec-upgrading for how - # to actually do that. - # This value being lower than the current NixOS release does NOT mean your system is - # out of date, out of support, or vulnerable. - # Do NOT change this value unless you have manually inspected all the changes it would make to your configuration, - # and migrated your data accordingly. - # For more information, see `man configuration.nix` or https://nixos.org/manual/nixos/stable/options#opt-system.stateVersion . - system.stateVersion = "25.05"; # Did you read the comment? -} diff --git a/hosts/kube02/hardware-configuration.nix b/hosts/kube02/hardware-configuration.nix deleted file mode 100644 index f198f81..0000000 --- a/hosts/kube02/hardware-configuration.nix +++ /dev/null @@ -1,24 +0,0 @@ -# Do not modify this file! It was generated by ‘nixos-generate-config’ -# and may be overwritten by future invocations. Please make changes -# to /etc/nixos/configuration.nix instead. -{ config, lib, pkgs, modulesPath, ... }: - -{ - imports = - [ (modulesPath + "/profiles/qemu-guest.nix") - ]; - - boot.initrd.availableKernelModules = [ "ahci" "xhci_pci" "virtio_pci" "sr_mod" "virtio_blk" ]; - boot.initrd.kernelModules = [ ]; - boot.kernelModules = [ "kvm-intel" ]; - boot.extraModulePackages = [ ]; - - # Enables DHCP on each ethernet and wireless interface. In case of scripted networking - # (the default) this is the recommended approach. When using systemd-networkd it's - # still possible to use this option, but it's recommended to use it in conjunction - # with explicit per-interface declarations with `networking.interfaces..useDHCP`. - networking.useDHCP = lib.mkDefault true; - # networking.interfaces.enp1s0.useDHCP = lib.mkDefault true; - - nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; -} diff --git a/hosts/kube03/default.nix b/hosts/kube03/default.nix deleted file mode 100644 index 12884e5..0000000 --- a/hosts/kube03/default.nix +++ /dev/null @@ -1,42 +0,0 @@ -{ inputs, outputs, config, lib, pkgs, ... }: - -{ - imports = - [ - ../../modules/disko/efi-full-btrfs.nix - ../../modules/systemd-boot.nix - - ../../users/julius/nixos-server.nix - ../../modules/nix.nix - ../../modules/network-server.nix - ../../modules/locale.nix - ../../modules/server-cli.nix - ../../modules/sshd.nix - ../../modules/k3s.nix - ../../modules/qemu-guest.nix - # Include the results of the hardware scan. - ./hardware-configuration.nix - ]; - - - networking.hostName = "kube03"; # Define your hostname. - - services.k3s = { - serverAddr = "https://kube01:6443"; - clusterInit = false; - }; - - # This option defines the first version of NixOS you have installed on this particular machine, - # and is used to maintain compatibility with application data (e.g. databases) created on older NixOS versions. - # Most users should NEVER change this value after the initial install, for any reason, - # even if you've upgraded your system to a new NixOS release. - # This value does NOT affect the Nixpkgs version your packages and OS are pulled from, - # so changing it will NOT upgrade your system - see https://nixos.org/manual/nixos/stable/#sec-upgrading for how - # to actually do that. - # This value being lower than the current NixOS release does NOT mean your system is - # out of date, out of support, or vulnerable. - # Do NOT change this value unless you have manually inspected all the changes it would make to your configuration, - # and migrated your data accordingly. - # For more information, see `man configuration.nix` or https://nixos.org/manual/nixos/stable/options#opt-system.stateVersion . - system.stateVersion = "25.05"; # Did you read the comment? -} diff --git a/hosts/kube03/hardware-configuration.nix b/hosts/kube03/hardware-configuration.nix deleted file mode 100644 index f198f81..0000000 --- a/hosts/kube03/hardware-configuration.nix +++ /dev/null @@ -1,24 +0,0 @@ -# Do not modify this file! It was generated by ‘nixos-generate-config’ -# and may be overwritten by future invocations. Please make changes -# to /etc/nixos/configuration.nix instead. -{ config, lib, pkgs, modulesPath, ... }: - -{ - imports = - [ (modulesPath + "/profiles/qemu-guest.nix") - ]; - - boot.initrd.availableKernelModules = [ "ahci" "xhci_pci" "virtio_pci" "sr_mod" "virtio_blk" ]; - boot.initrd.kernelModules = [ ]; - boot.kernelModules = [ "kvm-intel" ]; - boot.extraModulePackages = [ ]; - - # Enables DHCP on each ethernet and wireless interface. In case of scripted networking - # (the default) this is the recommended approach. When using systemd-networkd it's - # still possible to use this option, but it's recommended to use it in conjunction - # with explicit per-interface declarations with `networking.interfaces..useDHCP`. - networking.useDHCP = lib.mkDefault true; - # networking.interfaces.enp1s0.useDHCP = lib.mkDefault true; - - nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; -} diff --git a/hosts/srv01.hf/default.nix b/hosts/srv01.hf/default.nix index feb2183..cbfa9e5 100644 --- a/hosts/srv01.hf/default.nix +++ b/hosts/srv01.hf/default.nix @@ -17,6 +17,9 @@ ../../modules/docker.nix ../../modules/teleport.nix ../../modules/portainer_agent.nix + ../../modules/pangolin.nix + ../../modules/newt.nix + ../../modules/dockhand.nix ../../modules/auto-upgrade.nix # Include the results of the hardware scan. ./hardware-configuration.nix @@ -33,6 +36,29 @@ virtualisation.oci-containers.containers.portainer_agent.environmentFiles = [ config.age.secrets."portainer-join_token".path ]; + services = { + pangolin = { + dnsProvider = "netcup"; + baseDomain = "juliusfr.eu"; + letsEncryptEmail = "contact@jfreudenberger.de"; + environmentFile = config.age.secrets."pangolin".path; + }; + traefik = { + environmentFiles = [ config.age.secrets."netcup-dns".path ]; + }; + }; + + services.newt-docker = { + enable = true; + pangolinEndpoint = "https://pangolin.juliusfr.eu"; + connectionSecret = config.age.secrets."newt"; + }; + + services.dockhand = { + enable = true; + appUrl = "dockhand.juliusfr.eu"; + }; + systemd.network = { enable = true; networks."10-wan" = { diff --git a/hosts/srv01.hf/secrets.nix b/hosts/srv01.hf/secrets.nix index 8697e77..a328f24 100644 --- a/hosts/srv01.hf/secrets.nix +++ b/hosts/srv01.hf/secrets.nix @@ -4,5 +4,8 @@ teleport-ca_pin.file = "${inputs.secrets}/secrets/teleport/ca_pin"; teleport-join_token.file = "${inputs.secrets}/secrets/srv01-hf/teleport_auth_token"; portainer-join_token.file = "${inputs.secrets}/secrets/srv01-hf/portainer_join_token"; + netcup-dns.file = "${inputs.secrets}/secrets/dns-management/netcup"; + pangolin.file = "${inputs.secrets}/secrets/srv01-hf/pangolin"; + newt.file = "${inputs.secrets}/secrets/srv01-hf/newt"; }; } diff --git a/modules/arcane.nix b/modules/arcane.nix new file mode 100644 index 0000000..160088f --- /dev/null +++ b/modules/arcane.nix @@ -0,0 +1,64 @@ +{ + config, + lib, + ... +}: +let + cfg = config.services.arcane; +in { + options.services.arcane = { + enable = lib.mkEnableOption "arcane, a modern Docker management UI"; + appUrl = lib.mkOption { + description = "External URL arcane will be reachable from, without protocol"; + type = lib.types.str; + }; + secretFile = lib.mkOption { + description = '' + Agenix secret containing the following needed environment variables in dotenv notation: + - ENCRYPTION_KEY + - JWT_SECRET + - OIDC_CLIENT_ID + - OIDC_CLIENT_SECRET + - OIDC_ISSUER_URL + - OIDC_ADMIN_CLAIM + - OIDC_ADMIN_VALUE + ''; + }; + }; + + config = lib.mkIf cfg.enable { + virtualisation.oci-containers.containers = { + arcane = { + image = "ghcr.io/getarcaneapp/arcane:v1.11.2"; + volumes = [ + "/var/run/docker.sock:/var/run/docker.sock" + ]; + environment = { + APP_URL = "https://${cfg.appUrl}"; + PUID = "1000"; + PGID = "1000"; + LOG_LEVEL = "info"; + LOG_JSON = "false"; + OIDC_ENABLED = "true"; + OIDC_SCOPES = "openid email profile groups"; + DATABASE_URL = "file:data/arcane.db?_pragma=journal_mode(WAL)&_pragma=busy_timeout(2500)&_txlock=immediate"; + }; + environmentFiles = [ + cfg.secretFile.path + ]; + networks = [ + "traefik" + ]; + labels = { + "traefik.enable" = "true"; + "traefik.http.routers.arcane.middlewares" = "arcane-oidc-auth@file"; + "traefik.http.routers.arcane.rule" = "Host(`${cfg.appUrl}`)"; + "traefik.http.services.arcane.loadbalancer.server.port" = "3552"; + }; + extraOptions = [ + ''--mount=type=volume,source=arcane-data,target=/app/data,volume-driver=local'' + ]; + }; + }; + }; +} diff --git a/modules/docker.nix b/modules/docker.nix index d216ec2..2c88d1a 100644 --- a/modules/docker.nix +++ b/modules/docker.nix @@ -7,6 +7,10 @@ virtualisation = { docker = { enable = true; + daemon.settings = { + ipv6 = true; + ip6tables = true; + }; }; oci-containers.backend = "docker"; }; diff --git a/modules/dockhand.nix b/modules/dockhand.nix new file mode 100644 index 0000000..7eeaf8e --- /dev/null +++ b/modules/dockhand.nix @@ -0,0 +1,46 @@ +{ + config, + lib, + ... +}: +let + cfg = config.services.dockhand; +in { + options.services.dockhand = { + enable = lib.mkEnableOption "dockhand, a powerful, intuitive Docker platform"; + appUrl = lib.mkOption { + description = "External URL dockhand will be reachable from, without protocol"; + type = lib.types.str; + }; + }; + + config = lib.mkIf cfg.enable { + virtualisation.oci-containers.containers = { + dockhand = { + image = "fnsys/dockhand:v1.0.12"; + volumes = [ + "/var/run/docker.sock:/var/run/docker.sock" + ]; + environment = { + PUID = "1000"; + PGID = "1000"; + }; + networks = [ + "pangolin" + ]; + labels = { + "pangolin.public-resources.dockhand.name" = "dockhand"; + "pangolin.public-resources.dockhand.full-domain" = cfg.appUrl; + "pangolin.public-resources.dockhand.protocol" = "http"; + "pangolin.public-resources.dockhand.auth.sso-enabled" = "true"; + "pangolin.public-resources.dockhand.auth.auto-login-idp" = "1"; + "pangolin.public-resources.dockhand.targets[0].method" = "http"; + }; + extraOptions = [ + ''--mount=type=volume,source=dockhand-data,target=/app/data,volume-driver=local'' + ''--group-add=131'' # docker group + ]; + }; + }; + }; +} diff --git a/modules/network-server.nix b/modules/network-server.nix index 99645e8..017b1b0 100644 --- a/modules/network-server.nix +++ b/modules/network-server.nix @@ -5,5 +5,6 @@ }: { networking = { useDHCP = true; + firewall.allowPing = false; }; } diff --git a/modules/newt.nix b/modules/newt.nix new file mode 100644 index 0000000..1f8dafd --- /dev/null +++ b/modules/newt.nix @@ -0,0 +1,72 @@ +{ + pkgs, + config, + lib, + ... +}: +let + + cfg = config.services.newt-docker; + +in { + + options.services.newt-docker = { + enable = lib.mkEnableOption "Newt, user space tunnel client for Pangolin"; + pangolinEndpoint = lib.mkOption { + description = "External URL of the Pangolin instance"; + type = lib.types.str; + }; + connectionSecret = lib.mkOption { + description = "Secrets for Pangolin authentication."; + type = lib.types.anything; + }; + }; + + config = lib.mkIf cfg.enable { + virtualisation.oci-containers.containers = { + newt = { + image = "fosrl/newt:1.9.0"; + autoStart = true; + networks = [ + "pangolin" + ]; + environment = { + PANGOLIN_ENDPOINT = cfg.pangolinEndpoint; + DOCKER_SOCKET = "/var/run/docker.sock"; + }; + environmentFiles = [ cfg.connectionSecret.path ]; + volumes = [ + "/var/run/docker.sock:/var/run/docker.sock:ro" + ]; + extraOptions = [ + "--add-host=host.docker.internal:host-gateway" + ]; + }; + }; + + systemd.services."docker-pangolin" = { + after = [ + "docker-network-pangolin.service" + ]; + requires = [ + "docker-network-pangolin.service" + ]; + }; + + systemd.services."docker-network-pangolin" = { + path = [ pkgs.docker ]; + serviceConfig = { + Type = "oneshot"; + }; + script = '' + docker network inspect pangolin || docker network create pangolin --ipv4 --ipv6 --subnet=172.18.0.0/16 --gateway=172.18.0.1 + ''; + }; + + networking.firewall.extraCommands = '' + iptables -A INPUT -p icmp --source 100.89.128.0/24 -j ACCEPT + iptables -A INPUT -p tcp --source 172.18.0.0/12 --dport 22 -j ACCEPT + ''; + + }; +} diff --git a/modules/pangolin.nix b/modules/pangolin.nix new file mode 100644 index 0000000..3da3c9e --- /dev/null +++ b/modules/pangolin.nix @@ -0,0 +1,43 @@ +{ + pkgs-unstable, + ... +}: { + + services = { + pangolin = { + enable = true; + package = pkgs-unstable.fosrl-pangolin; + openFirewall = true; + settings = { + app = { + save_logs = true; + log_failed_attempts = true; + }; + domains = { + domain1 = { + prefer_wildcard_cert = true; + }; + }; + flags = { + disable_signup_without_invite = true; + disable_user_create_org = true; + }; + }; + }; + }; + +} + +# Settings needed on the host +# +# services = { +# pangolin = { +# dnsProvider = ""; +# baseDomain = ""; +# letsEncryptEmail = ""; +# environmentFile = config.age.secrets."".path; +# }; +# traefik = { +# environmentFiles = [ config.age.secrets."".path ]; +# }; +# }; diff --git a/modules/traefik.nix b/modules/traefik.nix new file mode 100644 index 0000000..92b1204 --- /dev/null +++ b/modules/traefik.nix @@ -0,0 +1,221 @@ +{ + pkgs, + config, + lib, + ... +}: +let + + cfg = config.services.traefik-docker; + + mapOidcClientNameToEnv = stringToReplace: lib.replaceString "-" "_" (lib.toUpper stringToReplace); + + traefik-mtls-config = (pkgs.formats.yaml { }).generate "traefik-mtls-config" { + tls.options.default.clientAuth = { + caFiles = "caFiles/root_ca.crt"; + clientAuthType = "VerifyClientCertIfGiven"; + }; + }; + +in { + + options.services.traefik-docker = { + enable = lib.mkEnableOption "traefik web server hosted as OCI container"; + dashboardUrl = lib.mkOption { + description = "External URL the traefik dashboard will be reachable from, without protocol"; + type = lib.types.str; + }; + dnsSecrets = lib.mkOption { + description = "Secrets for DNS providers."; + type = lib.types.listOf lib.types.anything; + }; + mTLSCaCertSecret = lib.mkOption { + description = "Agenix secret containing the CA file to verify client certificates against."; + }; + oidcAuthProviderUrl = lib.mkOption { + description = "Provider URL of OIDC auth provider."; + type = lib.types.str; + }; + oidcClients = lib.mkOption { + example = '' + immich = { + scopes = [ + "openid" + "email" + "profile" + ]; + enableBypassUsingClientCertificate = true; + usePkce = true; + }; + ''; + description = "Attribute set of OIDC clients with their configurations."; + type = lib.types.attrsOf ( + lib.types.submodule { + options = { + secret = lib.mkOption { + description = ''Agenix secret containing the following needed environment variables in dotenv notation: + - _OIDC_AUTH_SECRET + - _OIDC_AUTH_PROVIDER_CLIENT_ID + - _OIDC_CLIENT_SECRET + ''; + }; + scopes = lib.mkOption { + default = [ "openid" ]; + example = [ "openid" "email" "profile" "groups" ]; + description = "OIDC scopes to request from auth provider."; + type = lib.types.listOf lib.types.str; + }; + usePkce = lib.mkOption { + default = true; + description = "Whether to enable PKCE for this provider."; + type = lib.types.bool; + }; + enableBypassUsingClientCertificate = lib.mkOption { + default = false; + description = "Whether to allow bypassing OIDC protection when a verified client certificate is presented."; + type = lib.types.bool; + }; + useClaimsFromUserInfo = lib.mkOption { + default = false; + description = "When enabled, an additional request to the provider's userinfo_endpoint is made to validate the token and to retrieve additional claims. The userinfo claims are merged directly into the token claims, with userinfo values overriding token values for non-security-critical claims."; + type = lib.types.bool; + }; + headers = lib.mkOption { + default = []; + description = "Headers to be added to the upstream request. Templating is possible. Documentation can be found here: https://traefik-oidc-auth.sevensolutions.cc/docs/getting-started/middleware-configuration"; + type = lib.types.listOf (lib.types.submodule { + options = { + Name = lib.mkOption { + description = "The name of the header which should be added to the upstream request."; + type = lib.types.str; + }; + Value = lib.mkOption { + description = "The value of the header, which can use Go-Templates."; + type = lib.types.str; + }; + }; + }); + }; + }; + } + ); + }; + }; + + config = lib.mkIf cfg.enable { + virtualisation.oci-containers.containers = { + traefik = { + image = "traefik:v3.6.6"; + cmd = [ + "--providers.docker=true" + "--providers.docker.exposedByDefault=false" + "--providers.docker.network=traefik" + "--providers.file.directory=/dynamic-config" + "--log.level=INFO" + "--api=true" + "--ping=true" + "--entrypoints.web.address=:80" + "--entrypoints.websecure.address=:443" + "--entrypoints.websecure.transport.respondingTimeouts.readTimeout=600s" + "--entrypoints.websecure.transport.respondingTimeouts.idleTimeout=600s" + "--entrypoints.websecure.transport.respondingTimeouts.writeTimeout=600s" + "--entrypoints.web.http.redirections.entrypoint.to=websecure" + "--entrypoints.websecure.asDefault=true" + "--entrypoints.websecure.http.middlewares=strip-mtls-headers@docker,pass-tls-client-cert@docker" + "--entrypoints.websecure.http.tls.certresolver=letsencrypt" + "--certificatesresolvers.letsencrypt.acme.storage=/certs/acme.json" + "--certificatesresolvers.letsencrypt.acme.dnschallenge=true" + "--certificatesresolvers.letsencrypt.acme.dnschallenge.provider=netcup" + "--experimental.plugins.traefik-oidc-auth.modulename=github.com/sevensolutions/traefik-oidc-auth" + "--experimental.plugins.traefik-oidc-auth.version=v0.17.0" + ]; + autoStart = true; + ports = [ + "80:80" + "443:443" + ]; + networks = [ + "traefik" + ]; + environment = { + OIDC_AUTH_PROVIDER_URL = cfg.oidcAuthProviderUrl; + }; + environmentFiles = lib.forEach cfg.dnsSecrets (secret: secret.path) ++ (lib.mapAttrsToList (oidcClientName: oidcClientConfig: oidcClientConfig.secret.path) cfg.oidcClients); + labels = { + "traefik.enable" = "true"; + "traefik.http.routers.dashboard.rule" = "Host(`${cfg.dashboardUrl}`)"; + "traefik.http.routers.dashboard.service" = "dashboard@internal"; + "traefik.http.routers.dashboard.middlewares" = "traefik-dashboard-oidc-auth@file"; + "traefik.http.routers.api.rule" = "Host(`${cfg.dashboardUrl}`) && (PathPrefix(`/api`) || PathPrefix(`/oidc/callback`))"; + "traefik.http.routers.api.service" = "api@internal"; + "traefik.http.routers.api.middlewares" = "traefik-dashboard-oidc-auth@file"; + "traefik.http.middlewares.strip-mtls-headers.headers.customrequestheaders.X-Forwarded-Tls-Client-Cert" = ""; + "traefik.http.middlewares.pass-tls-client-cert.passtlsclientcert.pem" = "true"; + }; + volumes = let + oidc-config = lib.mapAttrs' ( + oidcClientName: oidcClientConfig: + lib.nameValuePair "${oidcClientName}-oidc-auth" { + plugin.traefik-oidc-auth = { + LogLevel = "INFO"; + Secret = ''{{ env "${mapOidcClientNameToEnv oidcClientName}_OIDC_AUTH_SECRET" }}''; + Provider = { + Url = ''{{ env "OIDC_AUTH_PROVIDER_URL" }}''; + ClientId = ''{{ env "${mapOidcClientNameToEnv oidcClientName}_OIDC_AUTH_PROVIDER_CLIENT_ID" }}''; + ClientSecret = ''{{ env "${mapOidcClientNameToEnv oidcClientName}_OIDC_AUTH_PROVIDER_CLIENT_SECRET" }}''; + UsePkce = oidcClientConfig.usePkce; + UseClaimsFromUserInfo = oidcClientConfig.useClaimsFromUserInfo; + }; + Scopes = oidcClientConfig.scopes; + LoginUrl = ''{{ env "OIDC_AUTH_PROVIDER_URL" }}''; + } // (lib.attrsets.optionalAttrs oidcClientConfig.enableBypassUsingClientCertificate { + BypassAuthenticationRule = "HeaderRegexp(`X-Forwarded-Tls-Client-Cert`, `.+`)"; + }) // (lib.attrsets.optionalAttrs ((lib.length oidcClientConfig.headers) > 0) { + Headers = oidcClientConfig.headers; + }); + } + ) cfg.oidcClients; + traefik-oidc-authentication-config = (pkgs.formats.yaml {}).generate "traefik-oidc-auth" { + http.middlewares = oidc-config; + }; + in [ + "/var/run/docker.sock:/var/run/docker.sock" + "${traefik-oidc-authentication-config}:/dynamic-config/traefik-oidc-auth.yaml:ro" + "${traefik-mtls-config}:/dynamic-config/traefik-mtls.yaml:ro" + "${cfg.mTLSCaCertSecret.path}:/caFiles/root_ca.crt:ro" + ]; + extraOptions = [ + ''--mount=type=volume,source=certs,target=/certs,volume-driver=local'' + "--add-host=host.docker.internal:host-gateway" + "--health-cmd=wget --spider --quiet http://localhost:8080/ping" + "--health-interval=10s" + "--health-timeout=5s" + "--health-retries=3" + "--health-start-period=5s" + ]; + }; + }; + + systemd.services."docker-traefik" = { + after = [ + "docker-network-traefik.service" + ]; + requires = [ + "docker-network-traefik.service" + ]; + }; + + systemd.services."docker-network-traefik" = { + path = [ pkgs.docker ]; + serviceConfig = { + Type = "oneshot"; + }; + script = '' + docker network inspect traefik || docker network create traefik --ipv4 --ipv6 --subnet=172.18.0.0/16 --gateway=172.18.0.1 + ''; + }; + + networking.firewall.extraCommands = "iptables -t nat -I PREROUTING -s 172.18.0.0/16 -d 172.18.0.0/16 -j MASQUERADE"; + + }; +} diff --git a/modules/virtualization.nix b/modules/virtualization.nix index fb232cc..4eb21ce 100644 --- a/modules/virtualization.nix +++ b/modules/virtualization.nix @@ -6,7 +6,10 @@ virtualisation = { libvirtd = { enable = true; - qemu.swtpm.enable = true; + qemu = { + swtpm.enable = true; + vhostUserPackages = [ pkgs.virtiofsd ]; + }; }; spiceUSBRedirection.enable = true; };