From 30b6ce5f9cbc271251443709b83e7ff60e745f8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Numbus?= Date: Wed, 18 Feb 2026 21:42:28 +0100 Subject: [PATCH] Huge structural update. Move from template based architecture to modules based library architecture. --- .aiexclude | 4 + .gitignore | 4 +- deploy.conf | 2 +- flake.nix | 17 +++ frigate.nix | 97 +++++++++++++++ modules/default.nix | 7 ++ modules/services/adguard.nix | 0 modules/services/default.nix | 16 +++ modules/services/frigate.nix | 98 +++++++++++++++ modules/services/gitea.nix | 113 +++++++++++++++++ modules/services/home-assistant.nix | 83 +++++++++++++ modules/services/immich.nix | 134 +++++++++++++++++++++ modules/services/it-tools.nix | 77 ++++++++++++ modules/services/nextcloud.nix | 131 ++++++++++++++++++++ modules/services/passbolt.nix | 129 ++++++++++++++++++++ modules/services/pi-hole.nix | 106 ++++++++++++++++ modules/services/traefik.nix | 64 ++++++++++ templates/nix-config/podman/activation.nix | 0 18 files changed, 1079 insertions(+), 3 deletions(-) create mode 100644 .aiexclude create mode 100644 flake.nix create mode 100644 frigate.nix create mode 100644 modules/default.nix create mode 100644 modules/services/adguard.nix create mode 100644 modules/services/default.nix create mode 100644 modules/services/frigate.nix create mode 100644 modules/services/gitea.nix create mode 100644 modules/services/home-assistant.nix create mode 100644 modules/services/immich.nix create mode 100644 modules/services/it-tools.nix create mode 100644 modules/services/nextcloud.nix create mode 100644 modules/services/passbolt.nix create mode 100644 modules/services/pi-hole.nix create mode 100644 modules/services/traefik.nix create mode 100644 templates/nix-config/podman/activation.nix diff --git a/.aiexclude b/.aiexclude new file mode 100644 index 0000000..647c10e --- /dev/null +++ b/.aiexclude @@ -0,0 +1,4 @@ +final-nix-config/ +test* +.DS_Store +.env \ No newline at end of file diff --git a/.gitignore b/.gitignore index 280726f..5031784 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ agents/ -extra-files/ final-nix-config/ test* -.DS_Store \ No newline at end of file +.DS_Store +.env \ No newline at end of file diff --git a/deploy.conf b/deploy.conf index 0e16df7..44314ca 100644 --- a/deploy.conf +++ b/deploy.conf @@ -1,5 +1,5 @@ # SCRIPT SETTINGS -DEBUG="true" +export DEBUG="true" #TARGET SETTINGS export TARGET_HOST="192.168.1.10" diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..87081f4 --- /dev/null +++ b/flake.nix @@ -0,0 +1,17 @@ +{ + description = "Numbus Server - Your Personal Cloud, Simplified"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11"; + }; + + outputs = { self, nixpkgs, ... }: { + nixosModules = { + default = { config, pkgs, lib, ... }: { + imports = [ + ./modules/default.nix + ]; + }; + }; + }; +} \ No newline at end of file diff --git a/frigate.nix b/frigate.nix new file mode 100644 index 0000000..466d8ca --- /dev/null +++ b/frigate.nix @@ -0,0 +1,97 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.numbus.services.frigate; + container_name = "frigate"; + compose_file = "podman/frigate/compose.yaml"; + config_dir = "/mnt/config/frigate"; + data_dir = "/mnt/data/frigate"; +in +{ + options.numbus.services.frigate = { + enable = mkEnableOption "Frigate NVR"; + + domain = mkOption { + type = types.str; + description = "The root domain name (e.g., example.com). Frigate will use frigate.example.com"; + }; + + mqtt = { + user = mkOption { + type = types.str; + default = "frigate"; + description = "MQTT User for Frigate"; + }; + # In the future, we will handle passwords via sops-nix secrets + }; + + devices = mkOption { + type = types.listOf types.str; + default = []; + example = [ "/dev/dri:/dev/dri" "/dev/bus/usb:/dev/bus/usb" ]; + description = "List of devices to map into the container"; + }; + }; + + config = mkIf cfg.enable { + environment.etc."${compose_file}".text = + '' + services: + frigate: + image: ghcr.io/blakeblackshear/frigate:stable + container_name: frigate + shm_size: "512MB" + networks: + home-assistant_frontend: + home-assistant_backend: + volumes: + - ${config_dir}:/config + - ${data_dir}/clips:/media/frigate/clips + - ${data_dir}/recordings:/media/frigate/recordings + - ${data_dir}/exports:/media/frigate/exports + - /etc/localtime:/etc/localtime:ro + - type: tmpfs + target: /tmp/cache + tmpfs: + size: 2000000000 + environment: + FRIGATE_MQTT_USER: ${cfg.mqtt.user} + # We will handle the password injection securely in the next phase + FRIGATE_MQTT_PASSWORD: $FRIGATE_MQTT_PASSWORD + devices: + ${concatStringsSep "\n " (map (d: "- ${d}") cfg.devices)} + labels: + - traefik.enable=true + - traefik.docker.network=home-assistant_frontend + - traefik.http.services.frigate.loadbalancer.server.port=8971 + - traefik.http.services.frigate.loadbalancer.server.scheme=http + - traefik.http.routers.frigate-https.entrypoints=websecure + - "traefik.http.routers.frigate-https.rule=Host(`frigate.${cfg.domain}`)" + - traefik.http.routers.frigate-https.tls=true + - traefik.http.routers.frigate-https.tls.certresolver=cloudflare + restart: unless-stopped + + networks: + home-assistant_backend: + external: true + home-assistant_frontend: + external: true + ''; + + systemd.services."${container_name}" = { + description = "Podman container : ${container_name}"; + after = [ "traefik.service" "home-assistant.service" "pi-hole.service" ]; + requires = [ "traefik.service" "home-assistant.service" ]; + wantedBy = [ "multi-user.target" ]; + path = [ pkgs.podman pkgs.podman-compose pkgs.coreutils ]; + serviceConfig = { + User = "numbus-admin"; + Type = "exec"; + 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"; + }; + }; + }; +} \ No newline at end of file diff --git a/modules/default.nix b/modules/default.nix new file mode 100644 index 0000000..e6a2234 --- /dev/null +++ b/modules/default.nix @@ -0,0 +1,7 @@ +{ ... }: + +{ + imports = [ + ./services/default.nix + ]; +} \ No newline at end of file diff --git a/modules/services/adguard.nix b/modules/services/adguard.nix new file mode 100644 index 0000000..e69de29 diff --git a/modules/services/default.nix b/modules/services/default.nix new file mode 100644 index 0000000..6ce174d --- /dev/null +++ b/modules/services/default.nix @@ -0,0 +1,16 @@ +{ ... }: + +{ + imports = [ + ./adguard.nix + ./frigate.nix + ./gitea.nix + ./home-assistant.nix + ./immich.nix + ./it-tools.nix + ./nextcloud.nix + ./passbolt.nix + ./pi-hole.nix + ./traefik.nix + ]; +} \ No newline at end of file diff --git a/modules/services/frigate.nix b/modules/services/frigate.nix new file mode 100644 index 0000000..ff82aa3 --- /dev/null +++ b/modules/services/frigate.nix @@ -0,0 +1,98 @@ +{ config, pkgs, ... }: + +let + container_name = "frigate"; + compose_file = "podman/frigate/compose.yaml"; + config_dir = "/mnt/config/frigate"; + data_dir = "/mnt/data/frigate"; +in + +{ + config = { + environment.etc."${compose_file}".text = + /* + yaml + */ + '' + services: + frigate: + image: ghcr.io/blakeblackshear/frigate:stable + container_name: frigate + shm_size: "512MB" + networks: + home-assistant_frontend: + home-assistant_backend: + volumes: + - ${config_dir}:/config + - ${data_dir}/clips:/media/frigate/clips + - ${data_dir}/recordings:/media/frigate/recordings + - ${data_dir}/exports:/media/frigate/exports + - /etc/localtime:/etc/localtime:ro + - type: tmpfs + target: /tmp/cache + tmpfs: + size: 2000000000 + environment: + FRIGATE_MQTT_USER: $FRIGATE_MQTT_USER + FRIGATE_MQTT_PASSWORD: $FRIGATE_MQTT_PASSWORD + # --- frigate devices --- # + labels: + - traefik.enable=true + - traefik.docker.network=home-assistant_frontend + - traefik.http.services.frigate.loadbalancer.server.port=8971 + - traefik.http.services.frigate.loadbalancer.server.scheme=http + - traefik.http.routers.frigate-https.entrypoints=websecure + - traefik.http.routers.frigate-https.rule=Host(`frigate.$DOMAIN_NAME`) + - traefik.http.routers.frigate-https.tls=true + - traefik.http.routers.frigate-https.tls.certresolver=cloudflare + restart: unless-stopped + + networks: + home-assistant_backend: + external: true + home-assistant_frontend: + external: true + ''; + + systemd.services."${container_name}" = { + description = "Podman container : ${container_name}"; + after = [ "traefik.service" "home-assistant.service" "pi-hole.service" ]; + requires = [ "traefik.service" "home-assistant.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" ]; + }; + }; +} \ No newline at end of file diff --git a/modules/services/gitea.nix b/modules/services/gitea.nix new file mode 100644 index 0000000..0b21a15 --- /dev/null +++ b/modules/services/gitea.nix @@ -0,0 +1,113 @@ +{ config, pkgs, ... }: + +let + container_name = "gitea"; + compose_file = "podman/gitea/compose.yaml"; +in + +{ + config = { + environment.etc."${compose_file}".text = + /* + yaml + */ + '' + services: + gitea: + image: docker.io/gitea/gitea:latest + container_name: gitea + networks: + gitea_frontend: + gitea_backend: + volumes: + - gitea_data:/data + - /etc/localtime:/etc/localtime:ro + environment: + - USER_UID=1000 + - USER_GID=1000 + - 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 + labels: + - traefik.enable=true + - traefik.docker.network=gitea_frontend + - traefik.http.services.gitea.loadbalancer.server.port=3000 + - traefik.http.services.gitea.loadbalancer.server.scheme=http + - traefik.http.routers.gitea-https.entrypoints=websecure + - traefik.http.routers.gitea-https.rule=Host(`gitea.$DOMAIN_NAME`) + - traefik.http.routers.gitea-https.tls=true + - traefik.http.routers.gitea-https.tls.certresolver=cloudflare + depends_on: + - gitea-database + restart: unless-stopped + + gitea-database: + image: docker.io/library/postgres:17.5 + container_name: gitea-database + environment: + - POSTGRES_USER=$DB_USERNAME + - POSTGRES_PASSWORD=$DB_PASSWORD + - POSTGRES_DB=$DB_NAME + networks: + gitea_backend: + volumes: + - gitea_database:/var/lib/postgresql/data + restart: unless-stopped + + volumes: + gitea_data: + gitea_database: + + networks: + gitea_frontend: + external: true + gitea_backend: + 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 ]; + + 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"; + }; + }; + + 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" ]; + }; + }; +} \ No newline at end of file diff --git a/modules/services/home-assistant.nix b/modules/services/home-assistant.nix new file mode 100644 index 0000000..af89e46 --- /dev/null +++ b/modules/services/home-assistant.nix @@ -0,0 +1,83 @@ +{ config, pkgs, ... }: + +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"; +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 ]; + + 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"; + }; + }; + }; +} \ No newline at end of file diff --git a/modules/services/immich.nix b/modules/services/immich.nix new file mode 100644 index 0000000..746c6c2 --- /dev/null +++ b/modules/services/immich.nix @@ -0,0 +1,134 @@ +{ config, pkgs, ... }: + +let + container_name = "immich"; + compose_file = "podman/immich/compose.yaml"; + config_dir = "/mnt/config/immich"; + data_dir = "/mnt/data/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 + + 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 + + networks: + immich_backend: + external: true + immich_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 ]; + + 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"; + }; + }; + + 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" ]; + }; + }; +} \ No newline at end of file diff --git a/modules/services/it-tools.nix b/modules/services/it-tools.nix new file mode 100644 index 0000000..ee29889 --- /dev/null +++ b/modules/services/it-tools.nix @@ -0,0 +1,77 @@ +{ config, pkgs, ... }: + +let + container_name = "it-tools"; + compose_file = "podman/it-tools/compose.yaml"; +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 + networks: + it-tools_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 ]; + + 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" ]; + }; + }; +} \ No newline at end of file diff --git a/modules/services/nextcloud.nix b/modules/services/nextcloud.nix new file mode 100644 index 0000000..9f02a75 --- /dev/null +++ b/modules/services/nextcloud.nix @@ -0,0 +1,131 @@ +{ config, pkgs, ... }: + +let + container_name = "nextcloud"; + compose_file = "podman/nextcloud/compose.yaml"; + data_dir = "/mnt/data/nextcloud"; +in + +{ + config = { + environment.etc."${compose_file}".text = + /* + yaml + */ + '' + services: + nextcloud-server: + image: docker.io/library/nextcloud:latest + container_name: nextcloud-server + restart: unless-stopped + networks: + nextcloud_frontend: + nextcloud_backend: + volumes: + - nextcloud_data:/var/www/html + - ${data_dir}:/var/www/html/data + environment: + MYSQL_HOST: nextcloud-database + MYSQL_DATABASE: $MYSQL_DATABASE + MYSQL_USER: $MYSQL_USER + MYSQL_PASSWORD: $MYSQL_PASSWORD + REDIS_HOST: nextcloud-redis + REDIS_HOST_PASSWORD: $REDIS_HOST_PASSWORD + NEXTCLOUD_TRUSTED_DOMAINS: $DOMAIN_NAME + SMTP_HOST: $SMTP_HOST + SMTP_SECURE: tls + SMTP_PORT: $SMTP_PORT + SMTP_NAME: $SMTP_NAME + SMTP_PASSWORD: $SMTP_PASSWORD + MAIL_FROM_ADDRESS: $MAIL_FROM_ADDRESS + MAIL_DOMAIN: $DOMAIN_NAME + APACHE_DISABLE_REWRITE_IP: 1 + TRUSTED_PROXIES: traefik + OVERWRITEPROTOCOL: https + labels: + - traefik.enable=true + - traefik.docker.network=nextcloud_frontend + - traefik.http.services.nextcloud.loadbalancer.server.port=80 + - traefik.http.services.nextcloud.loadbalancer.server.scheme=http + - traefik.http.routers.nextcloud-https.entrypoints=websecure + - traefik.http.routers.nextcloud-https.rule=Host(`nextcloud.$DOMAIN_NAME`) + - traefik.http.routers.nextcloud-https.tls=true + - traefik.http.routers.nextcloud-https.tls.certresolver=cloudflare + depends_on: + - nextcloud-database + + nextcloud-redis: + image: docker.io/library/redis:alpine + name: nextcloud-redis + restart: unless-stopped + networks: + nextcloud_backend: + command: redis-server --requirepass $REDIS_HOST_PASSWORD + + nextcloud-database: + image: docker.io/library/mariadb:latest + container_name: nextcloud-database + restart: unless-stopped + networks: + nextcloud_backend: + volumes: + - nextcloud_database:/var/lib/mysql + environment: + MARIADB_DATABASE: $MYSQL_DATABASE + MARIADB_USER: $MYSQL_USER + MARIADB_PASSWORD: $MYSQL_PASSWORD + MARIADB_RANDOM_ROOT_PASSWORD: true + + networks: + nextcloud_frontend: + external: true + nextcloud_backend: + external: true + + volumes: + nextcloud_data: + nextcloud_database: + ''; + + 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" ]; + }; + }; +} \ No newline at end of file diff --git a/modules/services/passbolt.nix b/modules/services/passbolt.nix new file mode 100644 index 0000000..5901f11 --- /dev/null +++ b/modules/services/passbolt.nix @@ -0,0 +1,129 @@ +{ config, pkgs, ... }: + +let + container_name = "passbolt"; + compose_file = "podman/passbolt/compose.yaml"; +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 + + 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 + + networks: + passbolt_backend: + external: true + passbolt_frontend: + external: true + + volumes: + passbolt-database: + passbolt-gpg: + passbolt-jwt: + ''; + + 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" ]; + }; + }; +} \ No newline at end of file diff --git a/modules/services/pi-hole.nix b/modules/services/pi-hole.nix new file mode 100644 index 0000000..3fd8833 --- /dev/null +++ b/modules/services/pi-hole.nix @@ -0,0 +1,106 @@ +{ config, pkgs, ... }: + +let + container_name = "pi-hole"; + compose_file = "podman/pi-hole/compose.yaml"; + config_dir = "/mnt/config/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 + + 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" ]; + }; + }; +} diff --git a/modules/services/traefik.nix b/modules/services/traefik.nix new file mode 100644 index 0000000..e8594f6 --- /dev/null +++ b/modules/services/traefik.nix @@ -0,0 +1,64 @@ +{ config, pkgs, ... }: + +let + container_name = "traefik"; + compose_file = "podman/traefik/compose.yaml"; + config_dir = "/mnt/config/traefik"; +in + +{ + config = { + environment.etc."${compose_file}".text = + /* + yaml + */ + '' + services: + traefik: + image: docker.io/library/traefik:latest + container_name: traefik + networks: +TRAEFIK_NETWORKS + ports: + - "80:80" + - "443:443" + volumes: + - /run/user/1000/podman/podman.sock:/run/docker.sock:ro + - ${config_dir}/rules/:/etc/traefik/conf/:ro + - ${config_dir}/traefik.yaml:/etc/traefik/traefik.yaml:ro + - ${config_dir}/certs/:/var/traefik/certs/:rw + environment: + - CF_DNS_API_TOKEN=$CF_DNS_API_TOKEN + labels: + - traefik.enable=true + - traefik.http.services.traefik.loadbalancer.server.port=8080 + - traefik.http.services.traefik.loadbalancer.server.scheme=http + - traefik.http.routers.traefik-https.entrypoints=websecure + - traefik.http.routers.traefik-https.rule=Host(`traefik.$DOMAIN_NAME`) + - traefik.http.routers.traefik-https.tls=true + - traefik.http.routers.traefik-https.tls.certresolver=cloudflare + restart: always + networks: +TRAEFIK_REF_NETWORKS + ''; + + systemd.services.traefik = { + description = "Podman container : ${container_name}"; + after = [ "numbus-activation.service" ]; + wantedBy = [ "multi-user.target" ]; + path = [ pkgs.podman pkgs.coreutils ]; + + serviceConfig = { + User = "numbus-admin"; + Environment = [ "XDG_RUNTIME_DIR=/run/user/1000" ]; + Type = "exec"; + ExecStartPre = "${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"; + }; + }; + }; +} \ No newline at end of file diff --git a/templates/nix-config/podman/activation.nix b/templates/nix-config/podman/activation.nix new file mode 100644 index 0000000..e69de29