From de1ee54b8b3120e75143ea824197e56598ba5c2c Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Tue, 17 Mar 2026 21:46:44 +0100 Subject: [PATCH] feat(attic): extract attic to service module, add cache host, configure reverse proxy/DNS --- flake.nix | 1 + hosts/BinaryCache/default.nix | 49 ++++++++++++ hosts/Binnenpost/default.nix | 10 ++- modules/apps/bind9/db.depeuter.dev | 5 +- modules/common/default.nix | 2 + modules/common/substituters.nix | 28 +++++++ modules/services/attic/default.nix | 119 +++++++++++++++++++++++++++++ modules/services/default.nix | 1 + 8 files changed, 213 insertions(+), 2 deletions(-) create mode 100644 hosts/BinaryCache/default.nix create mode 100644 modules/common/substituters.nix create mode 100644 modules/services/attic/default.nix diff --git a/flake.nix b/flake.nix index 446f4ce..aa18c00 100644 --- a/flake.nix +++ b/flake.nix @@ -47,6 +47,7 @@ Ingress.modules = [ ./hosts/Ingress ]; Gitea.modules = [ ./hosts/Gitea ]; Vaultwarden.modules = [ ./hosts/Vaultwarden ]; + BinaryCache.modules = [ ./hosts/BinaryCache ]; # Production multi-service Binnenpost.modules = [ ./hosts/Binnenpost ]; diff --git a/hosts/BinaryCache/default.nix b/hosts/BinaryCache/default.nix new file mode 100644 index 0000000..e651599 --- /dev/null +++ b/hosts/BinaryCache/default.nix @@ -0,0 +1,49 @@ +{ config, pkgs, lib, system, ... }: + +let + hostIp = "192.168.0.25"; +in { + config = { + homelab = { + services.attic = { + enable = true; + enableRemoteBuilder = true; + openFirewall = true; + }; + virtualisation.guest.enable = true; + }; + + networking = { + hostName = "BinaryCache"; + hostId = "100002500"; + domain = "depeuter.dev"; + + useDHCP = false; + + enableIPv6 = true; + + defaultGateway = { + address = "192.168.0.1"; + interface = "ens18"; + }; + + interfaces.ens18 = { + ipv4.addresses = [ + { + address = hostIp; + prefixLength = 24; + } + ]; + }; + + nameservers = [ + "1.1.1.1" # Cloudflare + "1.0.0.1" # Cloudflare + ]; + }; + + # Sops configuration for this host is now handled by the common module + + system.stateVersion = "24.05"; + }; +} diff --git a/hosts/Binnenpost/default.nix b/hosts/Binnenpost/default.nix index 561fbe1..e1325ba 100644 --- a/hosts/Binnenpost/default.nix +++ b/hosts/Binnenpost/default.nix @@ -1,4 +1,4 @@ -{ pkgs, ... }: +{ config, inputs, pkgs, ... }: { config = { @@ -83,6 +83,14 @@ "traefik.http.routers.hugo.rule" = "Host(`hugo.depeuter.dev`)"; "traefik.http.services.hugo.loadbalancer.server.url" = "https://192.168.0.11:444"; + + "traefik.http.routers.attic.rule" = "Host(`${inputs.self.nixosConfigurations.BinaryCache.config.homelab.services.attic.domain}`)"; + "traefik.http.services.attic.loadbalancer.server.url" = + let + bcConfig = inputs.self.nixosConfigurations.BinaryCache.config; + bcIp = (pkgs.lib.head bcConfig.networking.interfaces.ens18.ipv4.addresses).address; + bcPort = bcConfig.homelab.services.attic.port; + in "http://${bcIp}:${toString bcPort}"; }; system.stateVersion = "24.05"; diff --git a/modules/apps/bind9/db.depeuter.dev b/modules/apps/bind9/db.depeuter.dev index 72f3825..413adfe 100644 --- a/modules/apps/bind9/db.depeuter.dev +++ b/modules/apps/bind9/db.depeuter.dev @@ -1,6 +1,6 @@ $TTL 604800 @ IN SOA ns1 admin ( - 15 ; Serial + 16 ; Serial 604800 ; Refresh 86400 ; Retry 2419200 ; Expire @@ -40,6 +40,9 @@ sonarr IN A 192.168.0.33 ; Development VM plex IN A 192.168.0.91 +; Binary Cache (via Binnenpost proxy) +nix-cache IN A 192.168.0.89 + ; Catchalls *.production IN A 192.168.0.31 *.development IN A 192.168.0.91 diff --git a/modules/common/default.nix b/modules/common/default.nix index 746d1c1..dc6cb5f 100644 --- a/modules/common/default.nix +++ b/modules/common/default.nix @@ -1,12 +1,14 @@ { imports = [ ./secrets.nix + ./substituters.nix ]; config = { homelab = { services.openssh.enable = true; users.admin.enable = true; + common.substituters.enable = true; }; nix.settings.experimental-features = [ diff --git a/modules/common/substituters.nix b/modules/common/substituters.nix new file mode 100644 index 0000000..bb8e564 --- /dev/null +++ b/modules/common/substituters.nix @@ -0,0 +1,28 @@ +{ config, lib, pkgs, inputs, ... }: + +let + cfg = config.homelab.common.substituters; +in { + options.homelab.common.substituters = { + enable = lib.mkEnableOption "Binary cache substituters"; + domain = lib.mkOption { + type = lib.types.str; + default = inputs.self.nixosConfigurations.BinaryCache.config.homelab.services.attic.domain; + description = "The domain name of the binary cache."; + }; + publicKey = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = "The public key of the Attic cache (e.g., 'homelab:...')"; + }; + }; + + config = lib.mkIf cfg.enable { + nix.settings = { + substituters = [ + "https://${cfg.domain}" + ]; + trusted-public-keys = lib.optional (cfg.publicKey != null) cfg.publicKey; + }; + }; +} diff --git a/modules/services/attic/default.nix b/modules/services/attic/default.nix new file mode 100644 index 0000000..6241748 --- /dev/null +++ b/modules/services/attic/default.nix @@ -0,0 +1,119 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.homelab.services.attic; +in { + options.homelab.services.attic = { + enable = lib.mkEnableOption "Attic binary cache server"; + domain = lib.mkOption { + type = lib.types.str; + default = "nix-cache.depeuter.dev"; + description = "The domain name for the Attic server."; + }; + port = lib.mkOption { + type = lib.types.port; + default = 8080; + description = "The port Attic server listens on."; + }; + databaseName = lib.mkOption { + type = lib.types.str; + default = "attic"; + description = "The name of the PostgreSQL database."; + }; + dbContainerName = lib.mkOption { + type = lib.types.str; + default = "attic-db"; + description = "The name of the PostgreSQL container."; + }; + storagePath = lib.mkOption { + type = lib.types.str; + default = "/var/lib/atticd/storage"; + description = "The path where Attic store's its blobs."; + }; + openFirewall = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Whether to open the firewall port for Attic."; + }; + enableRemoteBuilder = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Whether to enable remote build capabilities on this host."; + }; + }; + + config = lib.mkIf cfg.enable { + sops.secrets = { + "attic/db-password" = { }; + "attic/server-token-secret" = { }; + }; + + services.atticd = { + enable = true; + environmentFile = config.sops.secrets."attic/server-token-secret".path; + + settings = { + listen = "[::]:${toString cfg.port}"; + allowed-hosts = [ cfg.domain ]; + api-endpoint = "https://${cfg.domain}/"; + + database.url = "postgresql://${cfg.databaseName}@${cfg.dbContainerName}:5432/${cfg.databaseName}"; + + storage = { + type = "local"; + path = cfg.storagePath; + }; + + chunking = { + min-size = 16384; # 16 KiB + avg-size = 65536; # 64 KiB + max-size = 262144; # 256 KiB + }; + }; + }; + + homelab.virtualisation.containers.enable = true; + + virtualisation.oci-containers.containers."${cfg.dbContainerName}" = { + image = "postgres:15-alpine"; + autoStart = true; + # We still map it to host for Attic (running on host) to connect to it via bridge IP or name + # if we set up networking/DNS correctly. + ports = [ + "5432:5432/tcp" + ]; + environment = { + POSTGRES_USER = cfg.databaseName; + POSTGRES_PASSWORD_FILE = config.sops.secrets."attic/db-password".path; + POSTGRES_DB = cfg.databaseName; + }; + volumes = [ + "attic-db:/var/lib/postgresql/data" + ]; + }; + + # Map the container name to localhost if Attic is on the host + networking.extraHosts = '' + 127.0.0.1 ${cfg.dbContainerName} + ''; + + networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall [ cfg.port ]; + + # Remote build host configuration + nix.settings.trusted-users = lib.mkIf cfg.enableRemoteBuilder [ "root" "@wheel" "builder" ]; + + users.users.builder = lib.mkIf cfg.enableRemoteBuilder { + isNormalUser = true; + group = "builder"; + openssh.authorizedKeys.keys = [ + # Placeholders - user should provide actual keys + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFrp6aM62Bf7bj1YM5AlAWuNrANU3N5e8+LtbbpmZPKS" + ]; + }; + users.groups.builder = lib.mkIf cfg.enableRemoteBuilder {}; + + # Only open SSH if remote builder is enabled + services.openssh.ports = lib.mkIf cfg.enableRemoteBuilder [ 22 ]; + networking.firewall.allowedTCPPorts = lib.mkIf cfg.enableRemoteBuilder [ 22 ]; + }; +} diff --git a/modules/services/default.nix b/modules/services/default.nix index f70bc54..81e0042 100644 --- a/modules/services/default.nix +++ b/modules/services/default.nix @@ -1,6 +1,7 @@ { imports = [ ./actions + ./attic ./openssh ]; }