From c90169f242f68bbe495c8ae74cf8bd6ab0be5b2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Numbus?= Date: Fri, 20 Feb 2026 10:23:24 +0100 Subject: [PATCH] Created a helper to create the container configurations. Updated all containers files to use it. --- modules/global.nix | 4 +- modules/services/frigate.nix | 176 ++++------------ modules/services/gitea.nix | 172 ++++++---------- modules/services/home-assistant.nix | 140 ++++++------- modules/services/immich.nix | 228 ++++++++++----------- modules/services/it-tools.nix | 98 +++------ modules/services/lib.nix | 134 ++++++++++++ modules/services/passbolt.nix | 209 +++++++++---------- modules/services/pi-hole.nix | 157 +++++--------- modules/services/traefik.nix | 304 +++++++++++----------------- 10 files changed, 706 insertions(+), 916 deletions(-) create mode 100644 modules/services/lib.nix diff --git a/modules/global.nix b/modules/global.nix index 9909389..5222703 100644 --- a/modules/global.nix +++ b/modules/global.nix @@ -9,7 +9,7 @@ with lib; domain = mkOption { type = types.str; example = "numbus.eu"; - description = "The root domain name (e.g., example.com) that your services will use"; + description = "The root domain name (i.e. example.com) that your services will use"; }; dns = mkOption { type = types.enum [ "pi-hole" "adguard" ]; @@ -22,7 +22,7 @@ with lib; type = types.str; default = "/etc/traefik/rules"; example = "/etc/traefik/rules"; - description = "! Choose a directory outside of /etc/ will break things ! The directory where Traefik's dynamic configuration files will be stored"; + description = "! Choosing a directory outside of /etc/ will break things ! The directory where Traefik's dynamic configuration files will be stored"; }; email = { diff --git a/modules/services/frigate.nix b/modules/services/frigate.nix index 3cfc9b4..1af58df 100644 --- a/modules/services/frigate.nix +++ b/modules/services/frigate.nix @@ -3,154 +3,60 @@ with lib; let + helper = import ./lib.nix { inherit config pkgs lib; }; cfg = config.numbus.services.frigate; - containerName = "frigate"; - pod = "home-assistant"; - composeFile = "podman/frigate/compose.yaml"; in -{ - options.numbus.services.frigate = { - enable = mkEnableOption "Frigate fully-local NVR (Network Video Recorder)"; - - configDir = mkOption { - type = types.str; - default = "/mnt/config/frigate"; - example = "/mnt/config/frigate"; - description = "The directory where Frigate's configuration files will be stored"; - }; - - dataDir = mkOption { - type = types.str; - default = "/mnt/data/frigate"; - example = "/mnt/data/frigate"; - description = "The directory where Frigate's data (i.e. clips, recordings, exports) will be stored"; - }; - - subdomain = mkOption { - type = types.str; - default = "frigate"; - example = "frigate"; - description = "The subdomain that Frigate will use (i.e. your-subdomain.your-domain.com)"; - }; +helper.mkPodmanService { + name = "frigate"; + description = "Frigate, your fully-local NVR (Network Video Recorder)"; + defaultPort = "8971"; + pod = "home-assistant"; + scheme = "https"; + dependencies = [ "traefik.service" "${config.numbus.services.dns}.service" "home-assistant.service" ]; + extraOptions = { devices = mkOption { type = types.listOf types.str; default = []; example = [ "/dev/dri:/dev/dri" "/dev/bus/usb:/dev/bus/usb" "/dev/apex_0:/dev/apex_0" ]; description = "List of devices to map into the container. /dev/dri is used for graphics acceleration, /dev/bus/usb for USB Coral TPUs, and /dev/apex_0 for PCI coral TPUs"; }; - - port = mkOption { - type = types.str; - default = "8971"; - example = "8971"; - description = "The port that Frigate will use. Be careful, do not use a port already in use such as 80 or 443"; - }; }; - config = mkIf cfg.enable { - environment.etc."${composeFile}".text = - /* - yaml - */ - '' - services: - ${containerName}: - image: ghcr.io/blakeblackshear/frigate:stable - container_name: ${containerName} - hostname: ${containerName} - shm_size: "256mb" - networks: - home-assistant_frontend: - home-assistant_backend: - ports: - - "${cfg.port}:8971/tcp" - volumes: - - ${cfg.configDir}:/config - - ${cfg.dataDir}:/media/frigate - - /etc/localtime:/etc/localtime:ro - - type: tmpfs - target: /tmp/cache - tmpfs: - size: 1000000000 - environment: - - FRIGATE_MQTT_USER=$FRIGATE_MQTT_USER - - FRIGATE_MQTT_PASSWORD=$FRIGATE_MQTT_PASSWORD -${lib.optionalString (cfg.devices != []) '' - devices: -${lib.concatStringsSep "\n" (map (d: " - \"${d}\"") cfg.devices)} -''} - restart: unless-stopped - + composeText = '' + services: + frigate: + image: ghcr.io/blakeblackshear/frigate:stable + container_name: frigate + hostname: frigate + shm_size: "256mb" networks: - home-assistant_backend: - external: true home-assistant_frontend: - external: true - ''; + home-assistant_backend: + ports: + - "${cfg.port}:8971/tcp" + volumes: + - ${cfg.configDir}:/config + - ${cfg.dataDir}:/media/frigate + - /etc/localtime:/etc/localtime:ro + - type: tmpfs + target: /tmp/cache + tmpfs: + size: 1000000000 + environment: + - FRIGATE_MQTT_USER=$FRIGATE_MQTT_USER + - FRIGATE_MQTT_PASSWORD=$FRIGATE_MQTT_PASSWORD +${lib.optionalString (cfg.devices != []) '' + devices: +${lib.concatStringsSep "\n" (map (d: " - \"${d}\"") cfg.devices)} +''} + restart: unless-stopped - environment.etc."${config.numbus.services.traefikDynamicConfigDir}/frigate.yaml".text = - /* - yaml - */ - '' - http: - routers: - ${containerName}: - rule: "Host(`${cfg.subdomain}.${config.numbus.services.domain}`)" - entrypoints: - - "websecure" - service: ${containerName} - middlewares: - - secureHeaders - tls: - certresolver: "cloudflare" - options: "secureTLS" - services: - ${containerName}: - loadBalancer: - servers: - - url: "https://host.containers.internal:${cfg.port}" - ''; - - systemd.services."${containerName}" = { - description = "Podman container : ${containerName}"; - requires = [ "traefik.service" "home-assistant.service" "${config.numbus.services.dns}.service" ]; - after = [ "traefik.service" "home-assistant.service" "${config.numbus.services.dns}.service" ]; - wantedBy = [ "multi-user.target" ]; - path = [ pkgs.podman pkgs.podman-compose pkgs.coreutils pkgs.sudo ]; - serviceConfig = { - Type = "exec"; - ExecStartPre = "bash -c 'sleep $((RANDOM % 180))'"; - ExecStart = "sudo -u numbus-admin podman-compose --in-pod ${pod} -f /etc/${composeFile} up --remove-orphans"; - ExecStop = "sudo -u numbus-admin podman-compose --in-pod ${pod} -f /etc/${composeFile} down"; - Restart = "on-failure"; - RestartSec = "1m"; - StartLimitBurst = "5"; - }; - }; - - systemd.services."update-${containerName}" = { - description = "Update ${containerName} container"; - path = [ pkgs.podman pkgs.podman-compose pkgs.sudo ]; - serviceConfig = { - Type = "oneshot"; - ExecStart = [ - "sudo -u numbus-admin podman-compose --in-pod ${pod} -f /etc/${composeFile} pull" - "sudo -u numbus-admin podman-compose --in-pod ${pod} -f /etc/${composeFile} down" - "sudo -u numbus-admin podman-compose --in-pod ${pod} -f /etc/${composeFile} up -d" - ]; - }; - }; - - systemd.timers."update-${containerName}" = { - timerConfig = { - OnCalendar = "02:00"; - RandomizedDelaySec = "60m"; - Unit = "update-${containerName}.service"; - }; - wantedBy = [ "timers.target" ]; - }; - }; + networks: + home-assistant_backend: + external: true + home-assistant_frontend: + external: true + ''; } \ No newline at end of file diff --git a/modules/services/gitea.nix b/modules/services/gitea.nix index 92790b8..807fb49 100644 --- a/modules/services/gitea.nix +++ b/modules/services/gitea.nix @@ -1,119 +1,77 @@ -{ config, pkgs, ... }: +{ config, pkgs, lib, ... }: + +with lib; let - containerName = "gitea"; - composeFile = "podman/gitea/compose.yaml"; - configDir = "/mnt/config/gitea"; - dataDir = "gitea_data"; + helper = import ./lib.nix { inherit config pkgs lib; }; + cfg = config.numbus.services.gitea; in -{ - config = { - environment.etc."${compose_file}".text = - /* - yaml - */ - '' - services: - gitea-server: - image: docker.gitea.com/gitea:latest-rootless - container_name: gitea-server - hostname: gitea-server - networks: - gitea_frontend: - gitea_backend: - ports: - - "3000:3000/tcp" #http - volumes: - - gitea_data:/var/lib/gitea - - ${configDir}:/etc/gitea - - /etc/localtime:/etc/localtime:ro - environment: - - GITEA__database__DB_TYPE=postgres - - GITEA__database__HOST=$POSTGRES_HOST:$POSTGRES_PORT - - GITEA__database__NAME=$DB_NAME - - GITEA__database__USER=$DB_USERNAME - - GITEA__database__PASSWD=$DB_PASSWORD - - GITEA__server__SSH_PORT=2424 - - GITEA__server__ROOT_URL=gitea.$DOMAIN_NAME - depends_on: - - gitea-database - restart: unless-stopped - - gitea-database: - image: docker.io/library/postgres:14 - container_name: gitea-database - hostname: gitea-database - networks: - gitea_backend: - volumes: - - gitea_database:/var/lib/postgresql/data - environment: - - POSTGRES_USER=$DB_USERNAME - - POSTGRES_PASSWORD=$DB_PASSWORD - - POSTGRES_DB=$DB_NAME - restart: unless-stopped - - volumes: - gitea_data: - gitea_database: +helper.mkPodmanService { + name = "gitea"; + description = "Gitea, your own self-hosted git platform"; + defaultPort = "3000"; + pod = "gitea"; + dependencies = [ "traefik.service" "${config.numbus.services.dns}.service" ]; + composeText = '' + services: + gitea-server: + image: docker.gitea.com/gitea:latest-rootless + container_name: gitea-server + hostname: gitea-server networks: gitea_frontend: - name: gitea_frontend - driver: bridge - ipam: - config: - - subnet: "10.89.3.0/24" - gateway: "10.89.3.254" gitea_backend: - name: gitea_backend - driver: bridge - ipam: - config: - - subnet: "10.89.4.0/24" - gateway: "10.89.4.254" - ''; + ports: + - "${cfg.port}:3000/tcp" + volumes: + - ${cfg.dataDir}:/var/lib/gitea + - ${cfg.configDir}:/etc/gitea + - /etc/localtime:/etc/localtime:ro + environment: + - GITEA__database__DB_TYPE=postgres + - GITEA__database__HOST=$POSTGRES_HOST:$POSTGRES_PORT + - GITEA__database__NAME=$DB_NAME + - GITEA__database__USER=$DB_USERNAME + - GITEA__database__PASSWD=$DB_PASSWORD + - GITEA__server__SSH_PORT=2424 + - GITEA__server__ROOT_URL=${cfg.subdomain}.${config.numbus.services.domain} + depends_on: + - gitea-database + restart: unless-stopped - systemd.services."${container_name}" = { - description = "Podman container : ${container_name}"; - after = [ "network.target" "traefik.service" "pi-hole.service" ]; - requires = [ "traefik.service" ]; - wantedBy = [ "multi-user.target" ]; - path = [ pkgs.podman pkgs.coreutils ]; + gitea-database: + image: docker.io/library/postgres:14 + container_name: gitea-database + hostname: gitea-database + networks: + gitea_backend: + volumes: + - gitea_database:/var/lib/postgresql/data + environment: + - POSTGRES_USER=$DB_USERNAME + - POSTGRES_PASSWORD=$DB_PASSWORD + - POSTGRES_DB=$DB_NAME + restart: unless-stopped - serviceConfig = { - User = "numbus-admin"; - Environment = [ "XDG_RUNTIME_DIR=/run/user/1000" ]; - Type = "exec"; - TimeoutStartSec = "900"; - ExecStartPre = [ - "${pkgs.bash}/bin/bash -c 'sleep $((RANDOM % 400))'" - "-${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} pull" - ]; - ExecStart = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} up --remove-orphans"; - ExecStop = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} down"; - Restart = "on-failure"; - RestartSec = "5m"; - StartLimitBurst = "3"; - }; - }; + volumes: + gitea_database: - systemd.services."update-${container_name}" = { - description = "Update ${container_name} container"; - serviceConfig = { - Type = "oneshot"; - ExecStart = "${pkgs.systemd}/bin/systemctl restart ${container_name}.service"; - }; - }; - - systemd.timers."update-${container_name}" = { - timerConfig = { - OnCalendar = "02:00"; - RandomizedDelaySec = "60m"; - Unit = "update-${container_name}.service"; - }; - wantedBy = [ "timers.target" ]; - }; - }; + networks: + gitea_frontend: + name: gitea_frontend + driver: bridge + ipam: + config: + - subnet: "10.89.3.0/24" + gateway: "10.89.3.254" + gitea_backend: + name: gitea_backend + driver: bridge + ipam: + config: + - subnet: "10.89.4.0/24" + gateway: "10.89.4.254" + ''; } \ No newline at end of file diff --git a/modules/services/home-assistant.nix b/modules/services/home-assistant.nix index af89e46..c742e3a 100644 --- a/modules/services/home-assistant.nix +++ b/modules/services/home-assistant.nix @@ -1,83 +1,73 @@ -{ config, pkgs, ... }: +{ config, pkgs, lib, ... }: + +with lib; let - container_name = "home-assistant"; - compose_file = "podman/home-assistant/compose.yaml"; - config_dir_1 = "/mnt/config/home-assistant"; - config_dir_2 = "/mnt/config/mqtt"; + helper = import ./lib.nix { inherit config pkgs lib; }; + cfg = config.numbus.services.home-assistant; in -{ - config = { - environment.etc."${compose_file}".text = - /* - yaml - */ - '' - services: - home-assistant: - image: ghcr.io/home-assistant/home-assistant:latest - container_name: home-assistant - networks: - home-assistant_frontend: - home-assistant_backend: - volumes: - - ${config_dir_1}:/config - - /etc/localtime:/etc/localtime:ro - - /run/dbus:/run/dbus:ro - # --- home-assistant devices --- # - labels: - - traefik.enable=true - - traefik.docker.network=home-assistant_frontend - - traefik.http.services.home-assistant.loadbalancer.server.port=8123 - - traefik.http.services.home-assistant.loadbalancer.server.scheme=http - - traefik.http.routers.home-assistant-https.entrypoints=websecure - - traefik.http.routers.home-assistant-https.rule=Host(`home-assistant.$DOMAIN_NAME`) - - traefik.http.routers.home-assistant-https.tls=true - - traefik.http.routers.home-assistant-https.tls.certresolver=cloudflare - restart: unless-stopped - - frigate-mqtt: - image: eclipse-mosquitto - container_name: mqtt - user: 1000:1000 - networks: - home-assistant_backend: - volumes: - - ${config_dir_2}:/mosquitto - restart: unless-stopped - - networks: - home-assistant_backend: - external: true - home-assistant_frontend: - external: true - ''; - systemd.services.${container_name} = { - description = "Podman container : ${container_name}"; - after = [ "network.target" "traefik.service" "pi-hole.service" ]; - requires = [ "traefik.service" ]; - wantedBy = [ "multi-user.target" ]; - path = [ pkgs.podman pkgs.coreutils ]; +helper.mkPodmanService { + name = "home-assistant"; + description = "Home Assistant, libre house control and much more"; + defaultPort = "8123"; + pod = "home-assistant"; + dependencies = [ "traefik.service" "${config.numbus.services.dns}.service" ]; - serviceConfig = { - User = "numbus-admin"; - Environment = [ "XDG_RUNTIME_DIR=/run/user/1000" ]; - Type = "exec"; - TimeoutStartSec = "600"; - # Pull the latest image before running - ExecStartPre = [ - "${pkgs.coreutils}/bin/sleep 180" - "-${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} pull" - ]; - # Bring the service up - ExecStart = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} up --remove-orphans"; - # Take it down gracefully - ExecStop = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} down"; - Restart = "on-failure"; - RestartSec = "5m"; - StartLimitBurst = "3"; - }; + extraOptions = { + devices = mkOption { + type = types.listOf types.str; + default = []; + example = [ "/dev/serial/by-id/Sonoff_Zigbee_3.0-id-port0:/dev/ttyUSB0" ]; + description = "List of devices to map into the container. /dev/ttyUSB0 is used for Zigbee dongles"; }; }; + + composeText = '' + services: + home-assistant: + image: ghcr.io/home-assistant/home-assistant:latest + container_name: home-assistant + hostname: home-assistant + networks: + home-assistant_frontend: + home-assistant_backend: + ports: + - "${cfg.port}:8123/tcp" + volumes: + - ${cfg.configDir}:/config + - /etc/localtime:/etc/localtime:ro + - /run/dbus:/run/dbus:ro +${lib.optionalString (cfg.devices != []) '' + devices: +${lib.concatStringsSep "\n" (map (d: " - \"${d}\"") cfg.devices)} +''} + restart: unless-stopped + + home-assistant-mqtt: + image: docker.io/library/eclipse-mosquitto:latest + container_name: home-assistant-mqtt + hostname: home-assistant-mqtt + networks: + home-assistant_backend: + volumes: + - /mnt/config/mosquitto:/mosquitto + restart: unless-stopped + + networks: + home-assistant_frontend: + name: home-assistant_frontend + driver: bridge + ipam: + config: + - subnet: "10.89.5.0/24" + gateway: "10.89.5.254" + home-assistant_backend: + name: home-assistant_backend + driver: bridge + ipam: + config: + - subnet: "10.89.6.0/24" + gateway: "10.89.6.254" + ''; } \ No newline at end of file diff --git a/modules/services/immich.nix b/modules/services/immich.nix index 746c6c2..3d1fa93 100644 --- a/modules/services/immich.nix +++ b/modules/services/immich.nix @@ -1,134 +1,118 @@ -{ config, pkgs, ... }: +{ config, pkgs, lib, ... }: + +with lib; let - container_name = "immich"; - compose_file = "podman/immich/compose.yaml"; - config_dir = "/mnt/config/immich"; - data_dir = "/mnt/data/immich"; + helper = import ./lib.nix { inherit config pkgs lib; }; + cfg = config.numbus.services.immich; in -{ - config = { - environment.etc."${compose_file}".text = - /* - yaml - */ - '' - services: - immich-server: - image: ghcr.io/immich-app/immich-server:$IMMICH_VERSION - container_name: immich-server - networks: - immich_frontend: - immich_backend: - volumes: - - $UPLOAD_LOCATION:/data - - /etc/localtime:/etc/localtime:ro - # --- immich devices --- # - labels: - - traefik.enable=true - - traefik.docker.network=immich_frontend - - traefik.http.services.immich.loadbalancer.server.port=2283 - - traefik.http.services.immich.loadbalancer.server.scheme=http - - traefik.http.routers.immich-https.entrypoints=websecure - - traefik.http.routers.immich-https.rule=Host(`immich.$DOMAIN_NAME`) - - traefik.http.routers.immich-https.tls=true - - traefik.http.routers.immich-https.tls.certresolver=cloudflare - env_file: - - .env - depends_on: - - immich-redis - - immich-database - restart: always - healthcheck: - disable: false +helper.mkPodmanService { + name = "immich"; + description = "Immich, Google Photos but better"; + defaultPort = "2283"; + pod = "immich"; + dependencies = [ "traefik.service" "${config.numbus.services.dns}.service" ]; - immich-machine-learning: - container_name: immich-machine-learning - image: ghcr.io/immich-app/immich-machine-learning:$IMMICH_VERSION - networks: - immich_backend: - volumes: - - ${config_dir}/models:/cache - env_file: - - .env - restart: always - healthcheck: - disable: false - - immich-redis: - container_name: immich-redis - image: docker.io/valkey/valkey:8-bookworm@sha256:a137a2b60aca1a75130022d6bb96af423fefae4eb55faf395732db3544803280 - networks: - immich_backend: - healthcheck: - test: redis-cli ping || exit 1 - restart: always - - immich-database: - container_name: immich-database - image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:32324a2f41df5de9efe1af166b7008c3f55646f8d0e00d9550c16c9822366b4a - networks: - immich_backend: - shm_size: 128mb - volumes: - # Do not edit the next line. If you want to change the database storage location on your system, edit the value of DB_DATA_LOCATION in the .env file - - $DB_DATA_LOCATION:/var/lib/postgresql/data - environment: - POSTGRES_PASSWORD: $DB_PASSWORD - POSTGRES_USER: $DB_USERNAME - POSTGRES_DB: $DB_DATABASE_NAME - POSTGRES_INITDB_ARGS: '--data-checksums' - restart: always - healthcheck: - disable: false + composeText = '' + services: + immich-server: + image: ghcr.io/immich-app/immich-server:latest + container_name: immich-server + hostname: immich-server + user: '1000:1000' + networks: + immich_frontend: + immich_backend: + ports: + - "${cfg.port}:2283/tcp" #http + volumes: + - ${cfg.dataDir}:/data + - /etc/localtime:/etc/localtime:ro + env_file: + - .env + depends_on: + - immich-redis + - immich-database + healthcheck: + disable: false + security_opt: + - no-new-privileges:true + cap_drop: + - NET_RAW + restart: unless-stopped + immich-machine-learning: + image: ghcr.io/immich-app/immich-machine-learning:latest + container_name: immich-machine-learning + hostname: immich-machine-learning + user: '1000:1000' networks: immich_backend: - external: true - immich_frontend: - external: true - ''; + volumes: + - ${cfg.configDir}/machine-learning:/cache + env_file: + - .env + healthcheck: + disable: false + security_opt: + - no-new-privileges:true + cap_drop: + - NET_RAW + restart: unless-stopped - systemd.services."${container_name}" = { - description = "Podman container : ${container_name}"; - after = [ "network.target" "traefik.service" "pi-hole.service" ]; - requires = [ "traefik.service" ]; - wantedBy = [ "multi-user.target" ]; - path = [ pkgs.podman pkgs.coreutils ]; + immich-redis: + image: docker.io/valkey/valkey:8-bookworm + container_name: immich-redis + hostname: immich-redis + user: '1000:1000' + networks: + immich_backend: + healthcheck: + test: redis-cli ping || exit 1 + security_opt: + - no-new-privileges:true + cap_drop: + - NET_RAW + restart: unless-stopped - serviceConfig = { - User = "numbus-admin"; - Environment = [ "XDG_RUNTIME_DIR=/run/user/1000" ]; - Type = "exec"; - TimeoutStartSec = "900"; - ExecStartPre = [ - "${pkgs.bash}/bin/bash -c 'sleep $((RANDOM % 400))'" - "-${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} pull" - ]; - ExecStart = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} up --remove-orphans"; - ExecStop = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} down"; - Restart = "on-failure"; - RestartSec = "5m"; - StartLimitBurst = "3"; - }; - }; + immich-database: + image: ghcr.io/immich-app/postgres:14 + container_name: immich-database + hostname: immich-database + user: '1000:1000' + networks: + immich_backend: + shm_size: 128mb + volumes: + - ${cfg.configDir}/database:/var/lib/postgresql/data + environment: + POSTGRES_PASSWORD: $DB_PASSWORD + POSTGRES_USER: $DB_USERNAME + POSTGRES_DB: $DB_DATABASE_NAME + POSTGRES_INITDB_ARGS: '--data-checksums' + healthcheck: + disable: false + security_opt: + - no-new-privileges:true + cap_drop: + - NET_RAW + restart: unless-stopped - systemd.services."update-${container_name}" = { - description = "Update ${container_name} container"; - serviceConfig = { - Type = "oneshot"; - ExecStart = "${pkgs.systemd}/bin/systemctl restart ${container_name}.service"; - }; - }; - - systemd.timers."update-${container_name}" = { - timerConfig = { - OnCalendar = "02:00"; - RandomizedDelaySec = "60m"; - Unit = "update-${container_name}.service"; - }; - wantedBy = [ "timers.target" ]; - }; - }; + networks: + immich_frontend: + name: immich_frontend + driver: bridge + ipam: + config: + - subnet: "10.89.7.0/24" + gateway: "10.89.7.254" + immich_backend: + name: immich_backend + driver: bridge + ipam: + config: + - subnet: "10.89.8.0/24" + gateway: "10.89.8.254" + ''; } \ No newline at end of file diff --git a/modules/services/it-tools.nix b/modules/services/it-tools.nix index ee29889..7c19a4b 100644 --- a/modules/services/it-tools.nix +++ b/modules/services/it-tools.nix @@ -1,77 +1,37 @@ -{ config, pkgs, ... }: +{ config, pkgs, lib, ... }: + +with lib; let - container_name = "it-tools"; - compose_file = "podman/it-tools/compose.yaml"; + helper = import ./lib.nix { inherit config pkgs lib; }; + cfg = config.numbus.services.it-tools; in -{ - config = { - environment.etc."${compose_file}".text = - /* - yaml - */ - '' - services: - it-tools: - container_name: it-tools - image: corentinth/it-tools - networks: - it-tools_frontend: - labels: - - traefik.enable=true - - traefik.docker.network=it-tools_frontend - - traefik.http.services.it-tools.loadbalancer.server.port=80 - - traefik.http.services.it-tools.loadbalancer.server.scheme=http - - traefik.http.routers.it-tools-https.entrypoints=websecure - - traefik.http.routers.it-tools-https.rule=Host(`it-tools.$DOMAIN_NAME`) - - traefik.http.routers.it-tools-https.tls=true - - traefik.http.routers.it-tools-https.tls.certresolver=cloudflare - restart: unless-stopped +helper.mkPodmanService { + name = "it-tools"; + description = "Immich, Google Photos but better"; + defaultPort = "8880"; + pod = "false"; + + composeText = '' + services: + it-tools: + image: docker.io/corentinth/it-tools:latest + container_name: it-tools + hostname: it-tools networks: it-tools_frontend: - external: true - ''; + ports: + - "${cfg.port}:80/tcp" + restart: unless-stopped - systemd.services."${container_name}" = { - description = "Podman container : ${container_name}"; - after = [ "network.target" "traefik.service" "pi-hole.service" ]; - requires = [ "traefik.service" ]; - wantedBy = [ "multi-user.target" ]; - path = [ pkgs.podman pkgs.coreutils ]; - - serviceConfig = { - User = "numbus-admin"; - Environment = [ "XDG_RUNTIME_DIR=/run/user/1000" ]; - Type = "exec"; - TimeoutStartSec = "600"; - ExecStartPre = [ - "${pkgs.bash}/bin/bash -c 'sleep $((RANDOM % 180))'" - "-${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} pull" - ]; - ExecStart = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} up --remove-orphans"; - ExecStop = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} down"; - Restart = "on-failure"; - RestartSec = "5m"; - StartLimitBurst = "3"; - }; - }; - - systemd.services."update-${container_name}" = { - description = "Update ${container_name} container"; - serviceConfig = { - Type = "oneshot"; - ExecStart = "${pkgs.systemd}/bin/systemctl restart ${container_name}.service"; - }; - }; - - systemd.timers."update-${container_name}" = { - timerConfig = { - OnCalendar = "02:00"; - RandomizedDelaySec = "60m"; - Unit = "update-${container_name}.service"; - }; - wantedBy = [ "timers.target" ]; - }; - }; + networks: + it-tools_frontend: + name: it-tools_frontend + driver: bridge + ipam: + config: + - subnet: "10.89.9.0/24" + gateway: "10.89.9.254" + ''; } \ No newline at end of file diff --git a/modules/services/lib.nix b/modules/services/lib.nix new file mode 100644 index 0000000..222a90c --- /dev/null +++ b/modules/services/lib.nix @@ -0,0 +1,134 @@ +{ lib, config, pkgs }: + +with lib; + +{ + mkPodmanService = { + name, + description, + defaultPort ? "0", + defaultSubdomain ? name, + pod ? name, + reverseProxied ? true, + composeFile ? "podman/${name}/compose.yaml", + composeText, + scheme ? "http", + middlewares ? [ "secureHeaders" ], + dependencies ? [], + extraOptions ? {}, + extraConfig ? {}, + configDir ? true, + dataDir ? true, + delaySec ? 180 + }: + let + cfg = config.numbus.services.${name}; + Deps = dependencies; + in + { + options.numbus.services.${name} = recursiveUpdate ({ + enable = mkEnableOption description; + + subdomain = mkOption { + type = types.str; + default = defaultSubdomain; + example = defaultSubdomain; + description = "The subdomain that ${name} will use"; + }; + + port = mkOption { + type = types.str; + default = defaultPort; + example = defaultPort; + description = "The port that ${name} will use."; + }; + + reverseProxied = mkOption { + type = types.bool; + default = reverseProxied; + description = "Whether to create a Traefik reverse proxy configuration for this service."; + }; + } // (optionalAttrs configDir { + configDir = mkOption { + type = types.str; + default = "/mnt/config/${name}"; + example = "/mnt/config/${name}"; + description = "The directory where ${name}'s configuration files will be stored"; + }; + }) // (optionalAttrs dataDir { + dataDir = mkOption { + type = types.str; + default = "/mnt/data/${name}"; + example = "/mnt/data/${name}"; + description = "The directory where ${name}'s data will be stored"; + }; + })) extraOptions; + + config = mkIf cfg.enable (mkMerge [ + { + environment.etc."${composeFile}".text = composeText; + + environment.etc."${config.numbus.traefikDynamicConfigDir}/${name}.yaml" = mkIf cfg.reverseProxied { + text = '' + http: + routers: + ${name}: + rule: "Host(`${cfg.subdomain}.${config.numbus.services.domain}`)" + entrypoints: + - "websecure" + service: ${name} + middlewares: +${concatStringsSep "\n" (map (m: " - ${m}") middlewares)} + tls: + certresolver: "cloudflare" + options: "secureTLS" + services: + ${name}: + loadBalancer: + servers: + - url: "${scheme}://host.containers.internal:${cfg.port}" + ''; + }; + + systemd.services."${name}" = { + description = "Podman container : ${name}"; + requires = Deps; + after = Deps; + wantedBy = [ "multi-user.target" ]; + path = [ pkgs.podman pkgs.podman-compose pkgs.coreutils pkgs.sudo ]; + serviceConfig = { + Type = "exec"; + ExecStartPre = "bash -c 'sleep $((RANDOM % ${delaySec}))'"; + ExecStart = "sudo -u numbus-admin podman-compose --in-pod ${pod} -f /etc/${composeFile} up --remove-orphans"; + ExecStop = "sudo -u numbus-admin podman-compose --in-pod ${pod} -f /etc/${composeFile} down"; + Restart = "on-failure"; + RestartSec = "1m"; + StartLimitBurst = "5"; + }; + }; + + systemd.services."update-${name}" = { + description = "Update ${name} container"; + path = [ pkgs.podman pkgs.podman-compose pkgs.sudo pkgs.systemd ]; + serviceConfig = { + Type = "oneshot"; + ExecStart = [ + "sudo -u numbus-admin podman-compose --in-pod ${pod} -f /etc/${composeFile} pull" + "${pkgs.systemd}/bin/systemctl restart ${name}.service" + ]; + }; + }; + + systemd.timers."update-${name}" = { + timerConfig = { + OnCalendar = "02:00"; + RandomizedDelaySec = "60m"; + Unit = "update-${name}.service"; + }; + wantedBy = [ "timers.target" ]; + }; + } + extraConfig + ]); + }; +} \ No newline at end of file diff --git a/modules/services/passbolt.nix b/modules/services/passbolt.nix index 5901f11..0d362cd 100644 --- a/modules/services/passbolt.nix +++ b/modules/services/passbolt.nix @@ -1,129 +1,104 @@ -{ config, pkgs, ... }: +{ config, pkgs, lib, ... }: + +with lib; let - container_name = "passbolt"; - compose_file = "podman/passbolt/compose.yaml"; + helper = import ./lib.nix { inherit config pkgs lib; }; + cfg = config.numbus.services.passbolt; in -{ - config = { - environment.etc."${compose_file}".text = - /* - yaml - */ - '' - services: - passbolt: - image: passbolt/passbolt:latest-ce-non-root - container_name: passbolt - networks: - passbolt_frontend: - passbolt_backend: - volumes: - - passbolt-gpg:/etc/passbolt/gpg - - passbolt-jwt:/etc/passbolt/jwt - environment: - APP_DEFAULT_TIMEZONE: $TZ - APP_FULL_BASE_URL: https://passbolt.$DOMAIN_NAME - DATASOURCES_DEFAULT_HOST: "passbolt-database" - DATASOURCES_DEFAULT_USERNAME: $PASSBOLT_MYSQL_USER - DATASOURCES_DEFAULT_PASSWORD: $PASSBOLT_MYSQL_PASSWORD - DATASOURCES_DEFAULT_DATABASE: $PASSBOLT_MYSQL_DATABASE - EMAIL_DEFAULT_FROM_NAME: "Passbolt" - EMAIL_TRANSPORT_DEFAULT_HOST: $EMAIL_TRANSPORT_DEFAULT_HOST - EMAIL_TRANSPORT_DEFAULT_PORT: $EMAIL_TRANSPORT_DEFAULT_PORT - EMAIL_TRANSPORT_DEFAULT_USERNAME: $EMAIL_TRANSPORT_DEFAULT_USERNAME - EMAIL_TRANSPORT_DEFAULT_PASSWORD: $EMAIL_TRANSPORT_DEFAULT_PASSWORD - EMAIL_TRANSPORT_DEFAULT_TLS: true - EMAIL_DEFAULT_FROM: $EMAIL_ADDRESS - PASSBOLT_SSL_FORCE: true - labels: - - traefik.enable=true - - traefik.docker.network=passbolt_frontend - - traefik.http.services.passbolt.loadbalancer.server.port=4433 - - traefik.http.services.passbolt.loadbalancer.server.scheme=https - - traefik.http.routers.passbolt-https.entrypoints=websecure - - traefik.http.routers.passbolt-https.rule=Host(`passbolt.$DOMAIN_NAME`) - - traefik.http.routers.passbolt-https.tls=true - - traefik.http.routers.passbolt-https.tls.certresolver=cloudflare - command: - [ - "/usr/bin/wait-for.sh", - "-t", - "0", - "passbolt-database:3306", - "--", - "/docker-entrypoint.sh", - ] - depends_on: - - passbolt-database - restart: unless-stopped +helper.mkPodmanService { + name = "passbolt"; + description = "Passbolt, your password manager"; + defaultPort = "4433"; + pod = "passbolt"; + scheme = "https"; + dependencies = [ "traefik.service" "${config.numbus.services.dns}.service" ]; - passbolt-database: - image: mariadb:11.3 - container_name: passbolt-database - networks: - passbolt_backend: - volumes: - - passbolt-database:/var/lib/mysql - environment: - MYSQL_RANDOM_ROOT_PASSWORD: "true" - MYSQL_DATABASE: $PASSBOLT_MYSQL_DATABASE - MYSQL_USER: $PASSBOLT_MYSQL_USER - MYSQL_PASSWORD: $PASSBOLT_MYSQL_PASSWORD - restart: unless-stopped + composeText = '' + services: + passbolt-server: + image: docker.io/passbolt/passbolt:latest-ce-non-root + container_name: passbolt-server + hostname: passbolt-server + networks: + passbolt_frontend: + passbolt_backend: + ports: + - "${cfg.port}:4433/tcp" + volumes: + - passbolt-gpg:/etc/passbolt/gpg + - passbolt-jwt:/etc/passbolt/jwt + environment: + APP_DEFAULT_TIMEZONE: $TZ + APP_FULL_BASE_URL: https://${cfg.subdomain}.${config.numbus.services.domain} + DATASOURCES_DEFAULT_HOST: "passbolt-database" + DATASOURCES_DEFAULT_USERNAME: $PASSBOLT_MYSQL_USER + DATASOURCES_DEFAULT_PASSWORD: $PASSBOLT_MYSQL_PASSWORD + DATASOURCES_DEFAULT_DATABASE: $PASSBOLT_MYSQL_DATABASE + EMAIL_DEFAULT_FROM_NAME: "Passbolt" + EMAIL_TRANSPORT_DEFAULT_HOST: $EMAIL_TRANSPORT_DEFAULT_HOST + EMAIL_TRANSPORT_DEFAULT_PORT: $EMAIL_TRANSPORT_DEFAULT_PORT + EMAIL_TRANSPORT_DEFAULT_USERNAME: $EMAIL_TRANSPORT_DEFAULT_USERNAME + EMAIL_TRANSPORT_DEFAULT_PASSWORD: $EMAIL_TRANSPORT_DEFAULT_PASSWORD + EMAIL_TRANSPORT_DEFAULT_TLS: true + EMAIL_DEFAULT_FROM: $EMAIL_ADDRESS + PASSBOLT_SSL_FORCE: true + command: + [ + "/usr/bin/wait-for.sh", + "-t", + "0", + "passbolt-database:3306", + "--", + "/docker-entrypoint.sh", + ] + depends_on: + - passbolt-database + security_opt: + - no-new-privileges:true + cap_drop: + - NET_RAW + restart: unless-stopped + passbolt-database: + image: docker.io/library/mariadb:12.2 + container_name: passbolt-database + hostname: passbolt-database networks: passbolt_backend: - external: true - passbolt_frontend: - external: true - volumes: - passbolt-database: - passbolt-gpg: - passbolt-jwt: - ''; + - passbolt-database:/var/lib/mysql + environment: + MYSQL_RANDOM_ROOT_PASSWORD: "true" + MYSQL_DATABASE: $PASSBOLT_MYSQL_DATABASE + MYSQL_USER: $PASSBOLT_MYSQL_USER + MYSQL_PASSWORD: $PASSBOLT_MYSQL_PASSWORD + security_opt: + - no-new-privileges:true + cap_drop: + - NET_RAW + restart: unless-stopped - systemd.services."${container_name}" = { - description = "Podman container : ${container_name}"; - after = [ "network.target" "traefik.service" "pi-hole.service" ]; - requires = [ "traefik.service" ]; - wantedBy = [ "multi-user.target" ]; - path = [ pkgs.podman pkgs.coreutils ]; + volumes: + passbolt-database: + passbolt-gpg: + passbolt-jwt: - serviceConfig = { - User = "numbus-admin"; - Environment = [ "XDG_RUNTIME_DIR=/run/user/1000" ]; - Type = "exec"; - TimeoutStartSec = "600"; - ExecStartPre = [ - "${pkgs.bash}/bin/bash -c 'sleep $((RANDOM % 180))'" - "-${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} pull" - ]; - ExecStart = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} up --remove-orphans"; - ExecStop = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} down"; - Restart = "on-failure"; - RestartSec = "5m"; - StartLimitBurst = "3"; - }; - }; - - systemd.services."update-${container_name}" = { - description = "Update ${container_name} container"; - serviceConfig = { - Type = "oneshot"; - ExecStart = "${pkgs.systemd}/bin/systemctl restart ${container_name}.service"; - }; - }; - - systemd.timers."update-${container_name}" = { - timerConfig = { - OnCalendar = "02:00"; - RandomizedDelaySec = "60m"; - Unit = "update-${container_name}.service"; - }; - wantedBy = [ "timers.target" ]; - }; - }; + networks: + passbolt_frontend: + name: passbolt_frontend + driver: bridge + ipam: + config: + - subnet: "10.89.12.0/24" + gateway: "10.89.12.254" + passbolt_backend: + name: passbolt_backend + driver: bridge + ipam: + config: + - subnet: "10.89.13.0/24" + gateway: "10.89.13.254" + ''; } \ No newline at end of file diff --git a/modules/services/pi-hole.nix b/modules/services/pi-hole.nix index 3fd8833..2ceabdc 100644 --- a/modules/services/pi-hole.nix +++ b/modules/services/pi-hole.nix @@ -1,106 +1,61 @@ -{ config, pkgs, ... }: +{ config, pkgs, lib, ... }: + +with lib; let - container_name = "pi-hole"; - compose_file = "podman/pi-hole/compose.yaml"; - config_dir = "/mnt/config/pi-hole"; + helper = import ./lib.nix { inherit config pkgs lib; }; + cfg = config.numbus.services.pi-hole; in -{ - config = { - environment.etc."${compose_file}".text = - /* - yaml - */ - '' - services: - pihole: - image: docker.io/pihole/pihole:latest - container_name: pi-hole - networks: - pi-hole_frontend: - ports: - # DNS Ports - - "53:53/tcp" - - "53:53/udp" - environment: - TZ: $TZ - FTLCONF_webserver_api_password: $FTLCONF_webserver_api_password - FTLCONF_dns_hosts: | - $HOME_SERVER_IP frigate.$DOMAIN_NAME - $HOME_SERVER_IP gitea.$DOMAIN_NAME - $HOME_SERVER_IP home-assistant.$DOMAIN_NAME - $HOME_SERVER_IP immich.$DOMAIN_NAME - $HOME_SERVER_IP it-tools.$DOMAIN_NAME - $HOME_SERVER_IP nextcloud.$DOMAIN_NAME - $HOME_SERVER_IP nextcloud-aio.$DOMAIN_NAME - $HOME_SERVER_IP passbolt.$DOMAIN_NAME - $HOME_SERVER_IP pi-hole.$DOMAIN_NAME - $HOME_SERVER_IP traefik.$DOMAIN_NAME - FTLCONF_dhcp_active: "false" - FTLCONF_dns_upstreams: 9.9.9.9;149.112.112.112 - FTLCONF_ntp_ipv4_active: "false" - FTLCONF_ntp_ipv6_active: "false" - FTLCONF_ntp_sync_active: "false" - volumes: - - ${config_dir}:/etc/pihole - cap_add: - - SYS_NICE - labels: - - traefik.enable=true - - traefik.docker.network=pi-hole_frontend - - traefik.http.services.pihole.loadbalancer.server.port=80 - - traefik.http.services.pihole.loadbalancer.server.scheme=http - - traefik.http.routers.pihole-https.entrypoints=websecure - - traefik.http.routers.pihole-https.rule=Host(`pi-hole.$DOMAIN_NAME`) - - traefik.http.routers.pihole-https.tls=true - - traefik.http.routers.pihole-https.tls.certresolver=cloudflare - restart: unless-stopped +helper.mkPodmanService { + name = "pi-hole"; + description = "Pi-Hole, the ads black hole"; + defaultPort = "4443"; + pod = "false"; + scheme = "https"; + dependencies = [ "traefik.service" "${config.numbus.services.dns}.service" ]; + delaySec = 10; - networks: - pi-hole_frontend: - external: true - ''; - - systemd.services."${container_name}" = { - description = "Podman container : ${container_name}"; - after = [ "network.target" "traefik.service" ]; - requires = [ "traefik.service" ]; - wantedBy = [ "multi-user.target" ]; - path = [ pkgs.podman pkgs.coreutils ]; - - serviceConfig = { - User = "numbus-admin"; - Environment = [ "XDG_RUNTIME_DIR=/run/user/1000" ]; - Type = "exec"; - TimeoutStartSec = "600"; - ExecStartPre = [ - "${pkgs.bash}/bin/bash -c 'sleep 20'" - "-${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} pull" - ]; - ExecStart = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} up --remove-orphans"; - ExecStop = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} down"; - Restart = "on-failure"; - RestartSec = "5m"; - StartLimitBurst = "3"; - }; - }; - - systemd.services."update-${container_name}" = { - description = "Update ${container_name} container"; - serviceConfig = { - Type = "oneshot"; - ExecStart = "${pkgs.systemd}/bin/systemctl restart ${container_name}.service"; - }; - }; - - systemd.timers."update-${container_name}" = { - timerConfig = { - OnCalendar = "02:00"; - RandomizedDelaySec = "60m"; - Unit = "update-${container_name}.service"; - }; - wantedBy = [ "timers.target" ]; - }; - }; -} + composeText = '' + services: + pi-hole: + image: docker.io/pihole/pihole:latest + container_name: pi-hole + hostname: pi-hole + network_mode: pasta + ports: + - "${cfg.port}:443/tcp" + - "53:53/tcp" + - "53:53/udp" + environment: + PIHOLE_UID: '1000' + PIHOLE_GID: '1000' + TZ: $TZ + FTLCONF_webserver_api_password: $FTLCONF_webserver_api_password + FTLCONF_webserver_domain: ${cfg.subdomain}.${config.numbus.services.domain} + FTLCONF_dns_upstreams: 9.9.9.9;149.112.112.112 + FTLCONF_dns_hosts: | +${lib.concatStringsSep "" (lib.mapAttrsToList (name: service: + if builtins.isAttrs service && service ? enable && service.enable && service ? subdomain then + " $HOME_SERVER_IP ${service.subdomain}.${config.numbus.services.domain}\n" + else + "" +) config.numbus.services)} + # TODO : get revServers to work + # FTLCONF_dns_revServers: | + # true,$HOME_ROUTER_SUBNET,$HOME_ROUTER_IP,${config.numbus.services.domain} + # true,$HOME_VPN_SUBNET,$HOME_VPN_IP,${config.numbus.services.domain} + FTLCONF_dns_listeningMode: "BIND" + FTLCONF_dns_domain_name: "${config.numbus.services.domain}" + FTLCONF_dns_domain_local: "true" + FTLCONF_dhcp_active: "false" + FTLCONF_ntp_ipv4_active: "false" + FTLCONF_ntp_ipv6_active: "false" + FTLCONF_ntp_sync_active: "false" + volumes: + - ${cfg.configDir}:/etc/pihole + cap_add: + - SYS_NICE + restart: unless-stopped + ''; +} \ No newline at end of file diff --git a/modules/services/traefik.nix b/modules/services/traefik.nix index 57fe286..fc5e961 100644 --- a/modules/services/traefik.nix +++ b/modules/services/traefik.nix @@ -1,211 +1,139 @@ -{ config, pkgs, ... }: +{ config, pkgs, lib, ... }: with lib; let + helper = import ./lib.nix { inherit config pkgs lib; }; cfg = config.numbus.services.traefik; - containerName = "traefik"; - pod = "false"; - composeFile = "podman/traefik/compose.yaml"; in -{ - options.numbus.services.traefik = { - enable = mkOption { - type = lib.types.bool; - default = true; - example = true; - description = "Traefik reverse-proxy"; - }; +helper.mkPodmanService { + name = "traefik"; + description = "Traefik reverse proxy, one to rule them all"; + pod = "false"; + reverseProxied = false; + dependencies = [ "${config.numbus.services.dns}.service" ]; + configDir = false; + delaySec = 10; + extraOptions = { + enable.default = true; staticConfigFile = mkOption { type = types.str; default = "traefik/config.yaml"; - example = "traefik/config.yaml"; - description = "The directory path where Traefik's static configuration file will be stored, prefixed by /etc/"; + description = "The path for Traefik's static configuration file, relative to /etc/"; }; - - dataDir = mkOption { - type = types.str; - default = "/mnt/config/traefik"; - example = "/mnt/config/traefik"; - description = "The directory where traefik's data (i.e. SSL certificates) will be stored"; - }; - - subdomain = mkOption { - type = types.str; - default = "traefik"; - example = "traefik"; - description = "The subdomain that traefik will use (i.e. your-subdomain.your-domain.com)"; - }; - logLevel = mkOption { type = types.enum [ "TRACE" "DEBUG" "INFO" "WARN" "ERROR" "FATAL" ]; default = "ERROR"; - example = "ERROR"; - description = "The level of detail Traefik should print in the logs : TRACE, DEBUG, INFO, WARN, ERROR, FATAL (from most to least verbose)"; + description = "The level of detail Traefik should print in the logs."; }; }; - config = mkIf cfg.enable { - environment.etc."${composeFile}".text = - /* - yaml - */ - '' - services: - traefik: - image: docker.io/library/traefik:latest - container_name: traefik - hostname: traefik - network_mode: pasta - ports: - - "80:80/tcp" - - "443:443/tcp" - volumes: - - /run/user/1000/podman/podman.sock:/run/docker.sock:ro - - ${cfg.staticConfigFile}:/etc/traefik/traefik.yaml:ro - - ${config.numbus.services.traefikDynamicConfigDir}:/etc/traefik/conf:ro - - ${cfg.dataDir}:/var/traefik/certs:rw - environment: - - CF_DNS_API_TOKEN=$CF_DNS_API_TOKEN - security_opt: - - no-new-privileges:true - restart: unless-stopped - ''; + composeText = '' + services: + traefik: + image: docker.io/library/traefik:latest + container_name: traefik + hostname: traefik + network_mode: pasta + ports: + - "80:80/tcp" + - "443:443/tcp" + volumes: + - /run/user/1000/podman/podman.sock:/run/docker.sock:ro + - /etc/${cfg.staticConfigFile}:/etc/traefik/traefik.yaml:ro + - ${config.numbus.traefikDynamicConfigDir}:/etc/traefik/conf:ro + - ${cfg.dataDir}:/var/traefik/certs:rw + environment: + - CF_DNS_API_TOKEN=$CF_DNS_API_TOKEN + security_opt: + - no-new-privileges:true + restart: unless-stopped + ''; - environment.etc."${cfg.staticConfigFile}".text = - /* - yaml - */ - '' - global: - checkNewVersion: false - sendAnonymousUsage: false - log: - level: ${cfg.logLevel} - accesslog: {} - api: - dashboard: false - insecure: false - entryPoints: - web: - address: :80 - http: - redirections: - entryPoint: - to: websecure - scheme: https - websecure: - address: :443 - forwardedHeaders: - trustedIPs: - - "127.0.0.1/32" - - "10.0.0.0/8" - - "192.168.0.0/16" - - "172.16.0.0/12" - certificatesResolvers: - cloudflare: - acme: - email: ${config.numbus.email.administratorEmail} - storage: /var/traefik/certs/cloudflare-acme.json - caServer: "https://acme-v02.api.letsencrypt.org/directory" - dnsChallenge: - provider: cloudflare - resolvers: - - "1.1.1.1:53" - - "9.9.9.9:53" - serversTransport: - insecureSkipVerify: true - providers: - file: - directory: "/etc/traefik/conf/" - watch: true - ''; + extraConfig = { + environment.etc."${cfg.staticConfigFile}".text = '' + global: + checkNewVersion: false + sendAnonymousUsage: false + log: + level: ${cfg.logLevel} + accesslog: {} + api: + dashboard: false + insecure: false + entryPoints: + web: + address: :80 + http: + redirections: + entryPoint: + to: websecure + scheme: https + websecure: + address: :443 + forwardedHeaders: + trustedIPs: + - "127.0.0.1/32" + - "10.0.0.0/8" + - "192.168.0.0/16" + - "172.16.0.0/12" + certificatesResolvers: + cloudflare: + acme: + email: ${config.numbus.email.administratorEmail} + storage: /var/traefik/certs/cloudflare-acme.json + caServer: "https://acme-v02.api.letsencrypt.org/directory" + dnsChallenge: + provider: cloudflare + resolvers: + - "1.1.1.1:53" + - "9.9.9.9:53" + serversTransport: + insecureSkipVerify: true + providers: + file: + directory: "/etc/traefik/conf/" + watch: true + ''; - environment.etc."${config.numbus.services.traefikDynamicConfigDir}/secureHeaders.yaml".text = - /* - yaml - */ - '' - http: - middlewares: - secureHeaders: - headers: - FrameDeny: true - AccessControlAllowMethods: 'GET,OPTIONS,PUT' - AccessControlAllowOriginList: - - origin-list-or-null - AccessControlMaxAge: 100 - AddVaryHeader: true - BrowserXssFilter: true - ContentTypeNosniff: true - ForceSTSHeader: true - STSIncludeSubdomains: true - STSPreload: true - ContentSecurityPolicy: default-src 'self' 'unsafe-inline' - CustomFrameOptionsValue: SAMEORIGIN - ReferrerPolicy: same-origin - PermissionsPolicy: vibrate 'self' - STSSeconds: 315360000 - ''; + environment.etc."${config.numbus.traefikDynamicConfigDir}/secureHeaders.yaml".text = '' + http: + middlewares: + secureHeaders: + headers: + FrameDeny: true + AccessControlAllowMethods: 'GET,OPTIONS,PUT' + AccessControlAllowOriginList: + - origin-list-or-null + AccessControlMaxAge: 100 + AddVaryHeader: true + BrowserXssFilter: true + ContentTypeNosniff: true + ForceSTSHeader: true + STSIncludeSubdomains: true + STSPreload: true + ContentSecurityPolicy: default-src 'self' 'unsafe-inline' + CustomFrameOptionsValue: SAMEORIGIN + ReferrerPolicy: same-origin + PermissionsPolicy: vibrate 'self' + STSSeconds: 315360000 + ''; - environment.etc."${config.numbus.services.traefikDynamicConfigDir}/secureTLS.yaml".text = - /* - yaml - */ - '' - tls: - options: - secureTLS: - minVersion: VersionTLS12 - sniStrict: true - curvePreferences: - - CurveP521 - - CurveP384 - cipherSuites: - - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 - - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 - - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 - ''; - - systemd.services."${containerName}" = { - description = "Podman container : ${containerName}"; - requires = [ "network.target" ]; - after = [ "network.target" ]; - wantedBy = [ "multi-user.target" ]; - path = [ pkgs.podman pkgs.podman-compose pkgs.coreutils pkgs.sudo ]; - serviceConfig = { - Type = "exec"; - ExecStartPre = "bash -c 'sleep $((RANDOM % 180))'"; - ExecStart = "sudo -u numbus-admin podman-compose --in-pod ${pod} -f /etc/${composeFile} up --remove-orphans"; - ExecStop = "sudo -u numbus-admin podman-compose --in-pod ${pod} -f /etc/${composeFile} down"; - Restart = "on-failure"; - RestartSec = "1m"; - StartLimitBurst = "5"; - }; - }; - - systemd.services."update-${containerName}" = { - description = "Update ${containerName} container"; - path = [ pkgs.podman pkgs.podman-compose pkgs.sudo ]; - serviceConfig = { - Type = "oneshot"; - ExecStart = [ - "sudo -u numbus-admin podman-compose --in-pod ${pod} -f /etc/${composeFile} pull" - "sudo -u numbus-admin podman-compose --in-pod ${pod} -f /etc/${composeFile} down" - "sudo -u numbus-admin podman-compose --in-pod ${pod} -f /etc/${composeFile} up -d" - ]; - }; - }; - - systemd.timers."update-${containerName}" = { - timerConfig = { - OnCalendar = "02:00"; - RandomizedDelaySec = "60m"; - Unit = "update-${containerName}.service"; - }; - wantedBy = [ "timers.target" ]; - }; + environment.etc."${config.numbus.traefikDynamicConfigDir}/secureTLS.yaml".text = '' + tls: + options: + secureTLS: + minVersion: VersionTLS12 + sniStrict: true + curvePreferences: + - CurveP521 + - CurveP384 + cipherSuites: + - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 + - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 + - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 + ''; }; } \ No newline at end of file