diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index 74d6457..0000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,43 +0,0 @@ -name: "Build" -on: - pull_request: - push: - -jobs: - determine-hosts: - name: "Determining hosts to build" - runs-on: ubuntu-latest - container: catthehacker/ubuntu:act-24.04 - outputs: - hosts: ${{ steps.hosts.outputs.hostnames }} - steps: - - uses: actions/checkout@v5 - - uses: https://github.com/cachix/install-nix-action@v31 - with: - nix_path: nixpkgs=channel:nixos-unstable - - name: "Determine hosts" - id: hosts - run: | - hostnames="$(nix eval .#nixosConfigurations --apply builtins.attrNames --json)" - printf "hostnames=%s\n" "${hostnames}" >> "${GITHUB_OUTPUT}" - - build: - runs-on: ubuntu-latest - container: catthehacker/ubuntu:act-24.04 - needs: determine-hosts - strategy: - matrix: - hostname: [ - Development, - Testing - ] - - steps: - - uses: actions/checkout@v5 - - uses: https://github.com/cachix/install-nix-action@v31 - with: - nix_path: nixpkgs=channel:nixos-unstable - - name: "Build host" - run: | - nix build ".#nixosConfigurations.${{ matrix.hostname }}.config.system.build.toplevel" --verbose - diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index 8cb0f4b..0000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,17 +0,0 @@ -name: "Test" -on: - pull_request: - push: -jobs: - tests: - if: false - runs-on: ubuntu-latest - container: - image: catthehacker/ubuntu:act-latest - steps: - - uses: actions/checkout@v5 - - uses: https://github.com/cachix/install-nix-action@v31 - with: - nix_path: nixpkgs=channel:nixos-unstable - - name: "My custom step" - run: nix run nixpkgs#hello diff --git a/.gitignore b/.gitignore index 8daf605..485dee6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1 @@ .idea -result diff --git a/README.md b/README.md deleted file mode 100644 index 19f9892..0000000 --- a/README.md +++ /dev/null @@ -1,64 +0,0 @@ -# Bos55 NixOS Config - -Automated CI/CD deployment for NixOS homelab using `deploy-rs`. - -## Repository Structure - -- `hosts/`: Host-specific configurations. -- `modules/`: Shared NixOS modules. -- `users/`: User definitions (including the `deploy` user). -- `secrets/`: Encrypted secrets via `sops-nix`. - -## Deployment Workflow - -### Prerequisites -- SSH access to the `deploy` user on target hosts. -- `deploy-rs` installed locally (`nix profile install github:serokell/deploy-rs`). - -### Deployment Modes - -1. **Production Deployment (main branch):** - Triggered on push to `main`. Automatically builds and switches all hosts. bootloader is updated. - Manual: `deploy .` - -2. **Test Deployment (test- branch):** - Triggered on push to `test-`. Builds and activates the configuration on the specific host **without** updating the bootloader. Reboots will revert to the previous generation. - Manual: `deploy .#.test` - -3. **Kernel Upgrades / Maintenance:** - Use `deploy .#.system --boot` to update the bootloader without immediate activation, followed by a manual reboot. - -## Local Development - -### 1. Developer Shell -This repository includes a standardized development environment containing all necessary tools (`deploy-rs`, `sops`, `age`, etc.). -```bash -nix develop -# or if using direnv -direnv allow -``` - -### 2. Build a host VM -You can build a QEMU VM for any host configuration to test changes locally: -```bash -nix build .#nixosConfigurations..config.system.build.vm -./result/bin/run--vm -``` - -> [!WARNING] -> **Network Conflict**: Default VMs use user-mode networking (NAT) which is safe. However, if you configure the VM to use bridge networking, it will attempt to use the static IP defined in `hostIp`. Ensure you do not have a physical host with that IP active on the same bridge to avoid network interference. - -### 3. Run Integration Tests -Run the automated test suite: -```bash -nix-build test/vm-test.nix -``` - -### 3. Test CI Workflows Locally -Use `act` to test the GitHub Actions workflows: -```bash -act -W .github/workflows/check.yml -``` - -## Security -See [SECURITY.md](SECURITY.md) for details on the trust model and secret management. diff --git a/flake.lock b/flake.lock index da5c167..67df8c4 100644 --- a/flake.lock +++ b/flake.lock @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1772624091, - "narHash": "sha256-QKyJ0QGWBn6r0invrMAK8dmJoBYWoOWy7lN+UHzW1jc=", + "lastModified": 1760524057, + "narHash": "sha256-EVAqOteLBFmd7pKkb0+FIUyzTF61VKi7YmvP1tw4nEw=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "80bdc1e5ce51f56b19791b52b2901187931f5353", + "rev": "544961dfcce86422ba200ed9a0b00dd4b1486ec5", "type": "github" }, "original": { @@ -48,11 +48,11 @@ ] }, "locked": { - "lastModified": 1772495394, - "narHash": "sha256-hmIvE/slLKEFKNEJz27IZ8BKlAaZDcjIHmkZ7GCEjfw=", + "lastModified": 1760393368, + "narHash": "sha256-8mN3kqyqa2PKY0wwZ2UmMEYMcxvNTwLaOrrDsw6Qi4E=", "owner": "Mic92", "repo": "sops-nix", - "rev": "1d9b98a29a45abe9c4d3174bd36de9f28755e3ff", + "rev": "ab8d56e85b8be14cff9d93735951e30c3e86a437", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 7e7e68c..446f4ce 100644 --- a/flake.nix +++ b/flake.nix @@ -13,85 +13,52 @@ url = "github:gytis-ivaskevicius/flake-utils-plus"; inputs.flake-utils.follows = "flake-utils"; }; - deploy-rs = { - url = "github:serokell/deploy-rs"; - inputs.nixpkgs.follows = "nixpkgs"; - }; }; outputs = inputs@{ self, nixpkgs, - flake-utils, sops-nix, utils, deploy-rs, + flake-utils, sops-nix, utils, ... }: let system = utils.lib.system.x86_64-linux; - lib = nixpkgs.lib; in - utils.lib.mkFlake { - inherit self inputs; + utils.lib.mkFlake { + inherit self inputs; - hostDefaults.modules = [ + hostDefaults = { + inherit system; + + modules = [ ./modules ./users + sops-nix.nixosModules.sops - ({ self, ... }: { - sops.defaultSopsFile = "${self}/secrets/secrets.yaml"; - sops.age.keyFile = "/var/lib/sops-nix/key.txt"; - }) - ({ self, ... }: { - sops.defaultSopsFile = "${self}/secrets/secrets.yaml"; - sops.age.keyFile = "/var/lib/sops-nix/key.txt"; - }) ]; - - hosts = { - # Infrastructure - Niko.modules = [ ./hosts/Niko ]; - Ingress.modules = [ ./hosts/Ingress ]; - Gitea.modules = [ ./hosts/Gitea ]; - Vaultwarden.modules = [ ./hosts/Vaultwarden ]; - - # Production - Binnenpost.modules = [ ./hosts/Binnenpost ]; - Production.modules = [ ./hosts/Production ]; - ProductionGPU.modules = [ ./hosts/ProductionGPU ]; - ProductionArr.modules = [ ./hosts/ProductionArr ]; - ACE.modules = [ ./hosts/ACE ]; - - # Lab - Template.modules = [ ./hosts/Template ]; - Development.modules = [ ./hosts/Development ]; - Testing.modules = [ ./hosts/Testing ]; - }; - - deploy.nodes = let - pkg = deploy-rs.lib.${system}; - isDeployable = nixos: (nixos.config.homelab.users.deploy.enable or false) && (nixos.config.homelab.networking.hostIp != null); - in - builtins.mapAttrs (_: nixos: { - hostname = nixos.config.homelab.networking.hostIp; - sshUser = "deploy"; - user = "root"; - profiles.system.path = pkg.activate.nixos nixos; - profiles.test.path = pkg.activate.custom nixos.config.system.build.toplevel '' - $PROFILE/bin/switch-to-configuration test - ''; - }) (lib.filterAttrs (_: isDeployable) self.nixosConfigurations); - - checks = builtins.mapAttrs (_: lib: lib.deployChecks self.deploy) deploy-rs.lib; - - outputsBuilder = channels: { - formatter = channels.nixpkgs.alejandra; - devShells.default = channels.nixpkgs.mkShell { - name = "homelab-dev"; - buildInputs = [ - deploy-rs.packages.${system}.deploy-rs - channels.nixpkgs.sops - channels.nixpkgs.age - ]; - shellHook = "echo '🛡️ Homelab Development Shell Loaded'"; - }; - }; }; + + hosts = { + # Physical hosts + Niko.modules = [ ./hosts/Niko ]; + + # Virtual machines + + # Single-service + Ingress.modules = [ ./hosts/Ingress ]; + Gitea.modules = [ ./hosts/Gitea ]; + Vaultwarden.modules = [ ./hosts/Vaultwarden ]; + + # Production multi-service + Binnenpost.modules = [ ./hosts/Binnenpost ]; + Production.modules = [ ./hosts/Production ]; + ProductionGPU.modules = [ ./hosts/ProductionGPU ]; + ProductionArr.modules = [ ./hosts/ProductionArr ]; + ACE.modules = [ ./hosts/ACE ]; + + # Others + Template.modules = [ ./hosts/Template ]; + Development.modules = [ ./hosts/Development ]; + Testing.modules = [ ./hosts/Testing ]; + }; + }; } diff --git a/hosts/ACE/default.nix b/hosts/ACE/default.nix index 094b077..04aa284 100644 --- a/hosts/ACE/default.nix +++ b/hosts/ACE/default.nix @@ -1,12 +1,10 @@ -{ config, pkgs, ... }: +{ pkgs, ... }: { config = { homelab = { - networking.hostIp = "192.168.0.41"; services.actions.enable = true; virtualisation.guest.enable = true; - users.deploy.enable = true; }; networking = { @@ -26,7 +24,7 @@ interfaces.ens18 = { ipv4.addresses = [ { - address = config.homelab.networking.hostIp; + address = "192.168.0.41"; prefixLength = 24; } ]; diff --git a/hosts/Binnenpost/default.nix b/hosts/Binnenpost/default.nix index 624608b..561fbe1 100644 --- a/hosts/Binnenpost/default.nix +++ b/hosts/Binnenpost/default.nix @@ -1,4 +1,4 @@ -{ config, pkgs, ... }: +{ pkgs, ... }: { config = { @@ -13,14 +13,12 @@ }; homelab = { - networking.hostIp = "192.168.0.89"; apps = { speedtest.enable = true; technitiumDNS.enable = true; traefik.enable = true; }; virtualisation.guest.enable = true; - users.deploy.enable = true; }; networking = { @@ -45,7 +43,7 @@ interfaces.ens18 = { ipv4.addresses = [ { - address = config.homelab.networking.hostIp; + address = "192.168.0.89"; prefixLength = 24; } ]; diff --git a/hosts/Development/default.nix b/hosts/Development/default.nix index 68c5fea..77f6758 100644 --- a/hosts/Development/default.nix +++ b/hosts/Development/default.nix @@ -3,7 +3,6 @@ { config = { homelab = { - networking.hostIp = "192.168.0.91"; apps = { bind9.enable = true; homepage = { @@ -12,9 +11,9 @@ }; traefik.enable = true; plex.enable = true; + solidtime.enable = true; }; virtualisation.guest.enable = true; - users.deploy.enable = true; }; networking = { @@ -38,7 +37,7 @@ interfaces.ens18 = { ipv4.addresses = [ { - address = config.homelab.networking.hostIp; + address = "192.168.0.91"; prefixLength = 24; } ]; @@ -61,8 +60,7 @@ environment = { # NOTE Required # The email address used when setting up the initial administrator account to login to pgAdmin. - # TODO Hugo: Populate 'pgadmin_email' in sops. - PGADMIN_DEFAULT_EMAIL = config.sops.placeholder.pgadmin_email or "pgadmin-admin@example.com"; + PGADMIN_DEFAULT_EMAIL = "kmtl.hugo+pgadmin@gmail.com"; # NOTE Required # The password used when setting up the initial administrator account to login to pgAdmin. PGADMIN_DEFAULT_PASSWORD = "ChangeMe"; diff --git a/hosts/Gitea/default.nix b/hosts/Gitea/default.nix index d6996b2..c6c9b43 100644 --- a/hosts/Gitea/default.nix +++ b/hosts/Gitea/default.nix @@ -3,12 +3,9 @@ { config = { homelab = { - networking.hostIp = "192.168.0.24"; apps.gitea.enable = true; virtualisation.guest.enable = true; - users.deploy.enable = true; - users.admin = { enable = true; authorizedKeys = [ @@ -31,7 +28,7 @@ interfaces.ens18 = { ipv4.addresses = [ { - address = config.homelab.networking.hostIp; + address = "192.168.0.24"; prefixLength = 24; } ]; diff --git a/hosts/Ingress/default.nix b/hosts/Ingress/default.nix index c16f151..c0a3ac9 100644 --- a/hosts/Ingress/default.nix +++ b/hosts/Ingress/default.nix @@ -2,11 +2,7 @@ { config = { - homelab = { - networking.hostIp = "192.168.0.10"; - virtualisation.guest.enable = true; - users.deploy.enable = true; - }; + homelab.virtualisation.guest.enable = true; networking = { hostName = "Ingress"; @@ -23,8 +19,8 @@ interfaces.ens18 = { ipv4.addresses = [ { - address = config.homelab.networking.hostIp; - prefixLength = 24; + address = "192.168.0.10"; +prefixLength = 24; } ]; }; @@ -43,7 +39,6 @@ }; }; - security.acme = { acceptTerms = true; defaults = { @@ -51,7 +46,7 @@ dnsPropagationCheck = true; dnsProvider = "cloudflare"; dnsResolver = "1.1.1.1:53"; - email = config.sops.placeholder.acme_email or "acme-email@example.com"; + email = "tibo.depeuter@telenet.be"; credentialFiles = { CLOUDFLARE_DNS_API_TOKEN_FILE = "/var/lib/secrets/depeuter-dev-cloudflare-api-token"; }; diff --git a/hosts/Isabel/default.nix b/hosts/Isabel/default.nix index f275b0e..0a1f50f 100644 --- a/hosts/Isabel/default.nix +++ b/hosts/Isabel/default.nix @@ -165,7 +165,7 @@ providers: # Certificates "--certificatesresolvers.letsencrypt.acme.dnschallenge=true" "--certificatesresolvers.letsencrypt.acme.dnschallenge.provider=cloudflare" - "--certificatesresolvers.letsencrypt.acme.email=${config.sops.placeholder.acme_email or "acme-email@example.com"}" + "--certificatesresolvers.letsencrypt.acme.email=tibo.depeuter@telenet.be" "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json" # Additional routes @@ -176,8 +176,8 @@ providers: # "8080:8080/tcp" # The Web UI (enabled by --api.insecure=true) ]; environment = { - # TODO Hugo: Populate 'cloudflare_dns_token' in sops. - "CLOUDFLARE_DNS_API_TOKEN" = config.sops.placeholder.cloudflare_dns_token or "CLOUDFLARE_TOKEN_PLACEHOLDER"; + # TODO Hide this! + "CLOUDFLARE_DNS_API_TOKEN" = "6Vz64Op_a6Ls1ljGeBxFoOVfQ-yB-svRbf6OyPv2"; }; environmentFiles = [ ]; diff --git a/hosts/Niko/default.nix b/hosts/Niko/default.nix index c08ccdc..910f325 100644 --- a/hosts/Niko/default.nix +++ b/hosts/Niko/default.nix @@ -7,7 +7,6 @@ ]; homelab = { - networking.hostIp = "192.168.0.11"; apps = { technitiumDNS.enable = true; traefik.enable = true; diff --git a/hosts/Production/default.nix b/hosts/Production/default.nix index a4ebc75..9bb565d 100644 --- a/hosts/Production/default.nix +++ b/hosts/Production/default.nix @@ -3,13 +3,11 @@ { config = { homelab = { - networking.hostIp = "192.168.0.31"; apps = { calibre.enable = true; traefik.enable = true; }; virtualisation.guest.enable = true; - users.deploy.enable = true; }; networking = { @@ -33,7 +31,7 @@ interfaces.ens18 = { ipv4.addresses = [ { - address = config.homelab.networking.hostIp; + address = "192.168.0.31"; prefixLength = 24; } ]; diff --git a/hosts/ProductionArr/default.nix b/hosts/ProductionArr/default.nix index 1168bc8..ff4f4c2 100644 --- a/hosts/ProductionArr/default.nix +++ b/hosts/ProductionArr/default.nix @@ -3,13 +3,11 @@ { config = { homelab = { - networking.hostIp = "192.168.0.33"; apps = { arr.enable = true; traefik.enable = true; }; virtualisation.guest.enable = true; - users.deploy.enable = true; }; networking = { @@ -33,7 +31,7 @@ interfaces.ens18 = { ipv4.addresses = [ { - address = config.homelab.networking.hostIp; + address = "192.168.0.33"; prefixLength = 24; } ]; diff --git a/hosts/ProductionGPU/default.nix b/hosts/ProductionGPU/default.nix index 5f8ad82..fa9ca8c 100644 --- a/hosts/ProductionGPU/default.nix +++ b/hosts/ProductionGPU/default.nix @@ -3,10 +3,8 @@ { config = { homelab = { - networking.hostIp = "192.168.0.94"; apps.jellyfin.enable = true; virtualisation.guest.enable = true; - users.deploy.enable = true; }; networking = { @@ -30,7 +28,7 @@ interfaces.ens18 = { ipv4.addresses = [ { - address = config.homelab.networking.hostIp; + address = "192.168.0.94"; prefixLength = 24; } ]; diff --git a/hosts/Testing/default.nix b/hosts/Testing/default.nix index cc4efcf..cc353f6 100644 --- a/hosts/Testing/default.nix +++ b/hosts/Testing/default.nix @@ -3,13 +3,11 @@ { config = { homelab = { - networking.hostIp = "192.168.0.92"; apps = { freshrss.enable = true; traefik.enable = true; }; virtualisation.guest.enable = true; - users.deploy.enable = true; }; networking = { @@ -34,7 +32,7 @@ interfaces.ens18 = { ipv4.addresses = [ { - address = config.homelab.networking.hostIp; + address = "192.168.0.92"; prefixLength = 24; } ]; diff --git a/hosts/Vaultwarden/default.nix b/hosts/Vaultwarden/default.nix index b24ef6d..5ded575 100644 --- a/hosts/Vaultwarden/default.nix +++ b/hosts/Vaultwarden/default.nix @@ -3,7 +3,6 @@ { config = { homelab = { - networking.hostIp = "192.168.0.22"; apps.vaultwarden = { enable = true; domain = "https://vault.depeuter.dev"; @@ -11,15 +10,11 @@ }; virtualisation.guest.enable = true; - users = { - deploy.enable = true; - - admin = { - enable = true; - authorizedKeys = [ - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJnihoyozOCnm6T9OzL2xoMeMZckBYR2w43us68ABA93" - ]; - }; + users.admin = { + enable = true; + authorizedKeys = [ + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJnihoyozOCnm6T9OzL2xoMeMZckBYR2w43us68ABA93" + ]; }; }; @@ -37,7 +32,7 @@ interfaces.ens18 = { ipv4.addresses = [ { - address = config.homelab.networking.hostIp; + address = "192.168.0.22"; prefixLength = 24; } ]; diff --git a/modules/apps/default.nix b/modules/apps/default.nix index f62dca7..385f915 100644 --- a/modules/apps/default.nix +++ b/modules/apps/default.nix @@ -9,6 +9,7 @@ ./homepage ./jellyfin ./plex + ./solidtime ./speedtest ./technitium-dns ./traefik diff --git a/modules/apps/gitea/default.nix b/modules/apps/gitea/default.nix index 34d52e4..0361bd5 100644 --- a/modules/apps/gitea/default.nix +++ b/modules/apps/gitea/default.nix @@ -496,8 +496,7 @@ in { #FORGEJO__mailer__CLIENT_KEY_FILE = "custom/mailer/key.pem"; # Mail from address, RFC 5322. This can be just an email address, or the # `"Name" ` format. - # TODO Hugo: Populate 'gitea_mailer_from' in sops. - FORGEJO__mailer__FROM = config.sops.placeholder.gitea_mailer_from or "git@example.com"; + FORGEJO__mailer__FROM = ''"${title}" ''; # Sometimes it is helpful to use a different address on the envelope. Set this to use # ENVELOPE_FROM as the from on the envelope. Set to `<>` to send an empty address. #FORGEJO__mailer__ENVELOPE_FROM = ""; diff --git a/modules/apps/solidtime/default.nix b/modules/apps/solidtime/default.nix new file mode 100644 index 0000000..725d32d --- /dev/null +++ b/modules/apps/solidtime/default.nix @@ -0,0 +1,278 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.homelab.apps.solidtime; + + networkName = "solidtime"; + internalNetworkName = "solidtime-internal"; + proxyNet = config.homelab.apps.traefiik.sharedNetworkName; + + user = "1000:1000"; + + # dbExternalPort = ...; + dbInternalPort = 5432; + + gotenbergPort = 3000; + + inherit (config.virtualisation.oci-containers) containers; + + solidtimeImageName = "solidtime/solidtime"; + version = "0.10.0"; + solidtimeImage = "${solidtimeImageName}:${version}"; + solidtimeImageFile = pkgs.dockerTools.pullImage { + imageName = solidtimeImageName; + finalImageTag = version; + imageDigest = "sha256:817d3a366ecc39f0473d7154372afa82dd4e6e50c66d70be45804892c8421cbb"; + sha256 = "sha256-h5aCKaquUF/EVsOHaLOHrn1HAoXZYPhAbJ+e4cmjSA8="; + }; + + volumes = [ + "solidtime-storage:/var/www/html/storage" + "solidtime-logs:/var/www/html/storage/logs" + "solidtime-app:/var/www/html/storage/app" + ]; + + # laravel.env + laravelEnv = { + APP_NAME = "Solidtime"; + VITE_APP_NAME = laravelEnv.APP_NAME; + APP_ENV = "production"; + APP_DEBUG = "false"; + APP_URL = "http://localhost:${toString cfg.port}"; + APP_FORCE_HTTPS = "false"; + APP_ENABLE_REGISTRATION = "false"; + TRUSTED_PROXIES = "0.0.0.0/0,2000:0:0:0:0:0:0:0/3"; + + # Logging + LOG_CHANNEL = "stderr_daily"; + LOG_LEVEL = "debug"; + + # Database + DB_CONNECTION = "pgsql"; + DB_HOST = containers.solidtimeDb.hostname; + DB_PORT = toString dbInternalPort; + DB_SSL_MODE = "require"; + DB_DATABASE = "solidtime"; + DB_USERNAME = "solidtime"; + DB_PASSWORD = "ChangeMe"; + + # Mail + #MAIL_MAILER = "smtp"; + #MAIL_HOST = "smtp.gmail.com"; + #MAIL_PORT = "465"; + #MAIL_ENCRYPTION = "tls"; + #MAIL_FROM_ADDRESS = "no-reply@time.depeuter.dev"; + MAIL_FROM_NAME = laravelEnv.APP_NAME; + #MAIL_USERNAME = "kmtl.hugo@gmail.com"; + #MAIL_PASSWORD = "fhfxoequhhqidrhd"; + + # Queue + QUEUE_CONNECTION = "database"; + + # File storage + FILESYSTEM_DISK = "local"; + PUBLIC_FILESYSTEM_DISK = "public"; + + # Services + GOTENBERG_URL = "http://${containers.solidtimeGotenberg.hostname}:${toString gotenbergPort}"; + }; + +in { + options.homelab.apps.solidtime = { + enable = lib.mkEnableOption "Solidtime time tracker using Docker"; + port = lib.mkOption { + type = lib.types.int; + default = 8000; + description = "Solidtime WebUI port"; + }; + exposePort = lib.mkEnableOption "Expose Soldtime port"; + }; + + config = lib.mkIf cfg.enable { + homelab.virtualisation.containers.enable = true; + + # Make sure the Docker network exists. + systemd.services = { + "docker-${networkName}-create-network" = { + description = "Create Docker network for ${networkName}"; + requiredBy = [ + "${containers.solidtime.serviceName}.service" + ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + }; + script = '' + if ! ${pkgs.docker}/bin/docker network ls | grep -q ${networkName}; then + ${pkgs.docker}/bin/docker network create ${networkName} + fi + ''; + }; + "docker-${internalNetworkName}-create-network" = { + description = "Create Docker network for ${internalNetworkName}"; + requiredBy = [ + "${containers.solidtime.serviceName}.service" + "${containers.solidtimeScheduler.serviceName}.service" + "${containers.solidtimeQueue.serviceName}.service" + "${containers.solidtimeDb.serviceName}.service" + "${containers.solidtimeGotenberg.serviceName}.service" + ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + }; + script = '' + if ! ${pkgs.docker}/bin/docker network ls | grep -q ${internalNetworkName}; then + ${pkgs.docker}/bin/docker network create ${internalNetworkName} + fi + ''; + }; + }; + + virtualisation.oci-containers.containers = { + solidtime = { + hostname = "solidtime"; + image = solidtimeImage; + imageFile = solidtimeImageFile; + inherit user; + autoStart = true; + dependsOn = [ + "solidtimeDb" + ]; + ports = [ + # Open ports if you don't use Traefik + "${toString cfg.port}:8000" + ]; + networks = [ + networkName + internalNetworkName + ]; + extraOptions = [ + # Healthecks + # test: [ "CMD", "curl", "--fail", "http://localhost:8000/health-check/up" ] + ''--health-cmd=curl --fail http://localhost:8000/health-check/up'' + ]; + inherit volumes; + labels = { + "traefik.enable" = "true"; + "traefik.http.routers.solidtime.rule" = "Host(`time.${config.networking.hostName}.depeuter.dev`)"; + "traefik.http.services.solidtime.loadbalancer.server.port" = toString cfg.port; + }; + environmentFiles = [ + "/home/admin/.solidtime.env" + ]; + environment = laravelEnv // { + CONTAINER_MODE = "http"; + }; + }; + solidtimeScheduler = { + hostname = "scheduler"; + image = solidtimeImage; + imageFile = solidtimeImageFile; + inherit user; + autoStart = true; + dependsOn = [ + "solidtimeDb" + ]; + networks = [ + internalNetworkName + ]; + extraOptions = [ + # Healthchecks + # test: [ "CMD", "healthcheck" ] + ''--health-cmd="healthcheck"'' + ]; + inherit volumes; + environmentFiles = [ + "/home/admin/.solidtime.env" + ]; + environment = laravelEnv // { + CONTAINER_MODE = "scheduler"; + }; + }; + solidtimeQueue = { + hostname = "queue"; + image = solidtimeImage; + imageFile = solidtimeImageFile; + inherit user; + autoStart = true; + networks = [ + internalNetworkName + ]; + extraOptions = [ + # Healthchecks + # test: [ "CMD", "healthcheck" ] + ''--health-cmd="healthcheck"'' + ]; + inherit volumes; + dependsOn = [ + "solidtimeDb" + ]; + environmentFiles = [ + "/home/admin/.solidtime.env" + ]; + environment = laravelEnv // { + CONTAINER_MODE = "worker"; + WORKER_COMMAND = "php /var/www/html/artisan queue:work"; + }; + }; + solidtimeDb = let + imageName = "postgres"; + finalImageTag = "15"; + in { + hostname = "database"; + image = "${imageName}:${finalImageTag}"; + imageFile = pkgs.dockerTools.pullImage { + inherit imageName finalImageTag; + imageDigest = "sha256:98fe06b500b5eb29e45bf8c073eb0ca399790ce17b1d586448edc4203627d342"; + sha256 = "sha256-AZ4VkOlROX+nR/MjDjsA4xdHzmtKjiBAtsp2Q6IdOvg="; + }; + autoStart = true; + ports = [ + # "${toString dbExternalPort}:${toString dbInternalPort}" + ]; + networks = [ + internalNetworkName + ]; + extraOptions = [ + # Healthchecks + # test: - CMD - pg_isready - '-q' - '-d' - '${DB_DATABASE}' - '-U' - '${DB_USERNAME}' retries: 3 timeout: 5s + ''--health-cmd="pg_isready -q -d ${laravelEnv.DB_DATABASE} -U ${laravelEnv.DB_USERNAME}"'' + "--health-retries=3" + "--health-timeout=5s" + ]; + volumes = [ + "solidtime-db:/var/lib/postgresql/data" + ]; + environment = { + PGPASSWORD = laravelEnv.DB_PASSWORD; + POSTGRES_DB = laravelEnv.DB_DATABASE; + POSTGRES_USER = laravelEnv.DB_USERNAME; + POSTGRES_PASSWORD = laravelEnv.DB_PASSWORD; + }; + }; + solidtimeGotenberg = let + imageName = "gotenberg/gotenberg"; + finalImageTag = "8.26.0"; + in { + hostname = "gotenberg"; + image = "${imageName}:${finalImageTag}"; + imageFile = pkgs.dockerTools.pullImage { + inherit imageName finalImageTag; + imageDigest = "sha256:328551506b3dec3ff6381dd47e5cd72a44def97506908269e201a8fbfa1c12c0"; + sha256 = "sha256-1zz4xDAgXxHUnkCVIfjHTgXb82EFEx+5am6Cu9+eZj4="; + }; + autoStart = true; + networks = [ + internalNetworkName + ]; + extraOptions = [ + # Healthchecks + # test: [ "CMD", "curl", "--silent", "--fail", "http://localhost:3000/health" ] + ''--health-cmd="curl --silent --fail http://localhost:${toString gotenbergPort}/health"'' + ]; + }; + }; + }; +} + diff --git a/modules/apps/traefik/default.nix b/modules/apps/traefik/default.nix index 54588c1..7f6ce38 100644 --- a/modules/apps/traefik/default.nix +++ b/modules/apps/traefik/default.nix @@ -72,7 +72,7 @@ in { # Certificates "--certificatesresolvers.letsencrypt.acme.dnschallenge=true" "--certificatesresolvers.letsencrypt.acme.dnschallenge.provider=cloudflare" - "--certificatesresolvers.letsencrypt.acme.email=${config.sops.placeholder.acme_email or "acme-email@example.com"}" + "--certificatesresolvers.letsencrypt.acme.email=tibo.depeuter@telenet.be" "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json" ]; volumes = [ diff --git a/modules/apps/vaultwarden/default.nix b/modules/apps/vaultwarden/default.nix index 6c55d0a..4510299 100644 --- a/modules/apps/vaultwarden/default.nix +++ b/modules/apps/vaultwarden/default.nix @@ -13,12 +13,12 @@ in { description = "Vaultwarden WebUI port"; }; domain = lib.mkOption { - type = lib.types.str; + type = lib.types.string; example = "https://vault.depeuter.dev"; description = "Domain to configure Vaultwarden on"; }; name = lib.mkOption { - type = lib.types.str; + type = lib.types.string; example = "Hugo's Vault"; description = "Service name to use for invitations and mail"; }; @@ -77,7 +77,7 @@ in { dataDir = "/data"; in { hostname = "vaultwarden"; - image = "vaultwarden/server:1.35.4-alpine"; + image = "vaultwarden/server:1.34.3-alpine"; autoStart = true; ports = [ "${toString cfg.port}:80/tcp" @@ -344,7 +344,6 @@ in { # ORG_CREATION_USERS=none ## A comma-separated list means only those users can create orgs: # ORG_CREATION_USERS=admin1@example.com,admin2@example.com - # TODO Hugo: Redact org creation users if needed. ## Invitations org admins to invite users, even when signups are disabled # INVITATIONS_ALLOWED=true @@ -591,7 +590,7 @@ in { ## To make sure the email links are pointing to the correct host, set the DOMAIN variable. ## Note: if SMTP_USERNAME is specified, SMTP_PASSWORD is mandatory SMTP_HOST = "smtp.gmail.com"; - SMTP_FROM = config.sops.placeholder.vaultwarden_smtp_from or "vaultwarden@example.com"; + SMTP_FROM = "vault@depeuter.dev"; SMTP_FROM_NAME = cfg.name; # SMTP_USERNAME=username # SMTP_PASSWORD=password diff --git a/modules/common/default.nix b/modules/common/default.nix index e8c60a6..44309f5 100644 --- a/modules/common/default.nix +++ b/modules/common/default.nix @@ -1,9 +1,4 @@ { - imports = [ - ./networking.nix - ./secrets.nix - ]; - config = { homelab = { services.openssh.enable = true; diff --git a/modules/common/networking.nix b/modules/common/networking.nix deleted file mode 100644 index 837684e..0000000 --- a/modules/common/networking.nix +++ /dev/null @@ -1,19 +0,0 @@ -{ config, lib, ... }: - -{ - options.homelab.networking = { - hostIp = lib.mkOption { - type = lib.types.nullOr lib.types.str; - default = null; - description = '' - The primary IP address of the host. - Used for automated deployment and internal service discovery. - ''; - }; - }; - - config = lib.mkIf (config.homelab.networking.hostIp != null) { - # If a hostIp is provided, we can potentially use it to configure - # networking interfaces or firewall rules automatically here in the future. - }; -} diff --git a/modules/common/secrets.nix b/modules/common/secrets.nix deleted file mode 100644 index 10b6473..0000000 --- a/modules/common/secrets.nix +++ /dev/null @@ -1,18 +0,0 @@ -{ config, lib, ... }: - -{ - sops.secrets = { - # -- User Public Keys (Anti-Fingerprinting) -- - "user_keys_admin" = { neededForUsers = true; }; - "user_keys_deploy" = { neededForUsers = true; }; - "user_keys_backup" = { neededForUsers = true; }; - - # -- Infrastructure Metadata -- - # Hugo TODO: Populate these in your .sops.yaml / secrets file - "acme_email" = {}; - "cloudflare_dns_token" = {}; - "pgadmin_email" = {}; - "gitea_mailer_from" = {}; - "vaultwarden_smtp_from" = {}; - }; -} diff --git a/users/admin/default.nix b/users/admin/default.nix index 14766da..dc01c81 100644 --- a/users/admin/default.nix +++ b/users/admin/default.nix @@ -26,9 +26,7 @@ in { config.users.groups.wheel.name # Enable 'sudo' for the user. ]; initialPassword = "ChangeMe"; - openssh.authorizedKeys.keyFiles = [ - config.sops.secrets.user_keys_admin.path - ]; + openssh.authorizedKeys.keys = cfg.authorizedKeys; packages = with pkgs; [ curl git diff --git a/users/backup/default.nix b/users/backup/default.nix index 8c20374..acae033 100644 --- a/users/backup/default.nix +++ b/users/backup/default.nix @@ -12,8 +12,9 @@ in { extraGroups = [ "docker" # Allow access to the docker socket. ]; - openssh.authorizedKeys.keyFiles = [ - config.sops.secrets.user_keys_backup.path + openssh.authorizedKeys.keys = [ + # Hugo + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICms6vjhE9kOlqV5GBPGInwUHAfCSVHLI2Gtzee0VXPh" ]; }; }; diff --git a/users/deploy/default.nix b/users/deploy/default.nix index 5c28561..0509d1e 100644 --- a/users/deploy/default.nix +++ b/users/deploy/default.nix @@ -3,19 +3,7 @@ let cfg = config.homelab.users.deploy; in { - options.homelab.users.deploy = { - enable = lib.mkEnableOption "user Deploy"; - - authorizedKeys = lib.mkOption { - type = lib.types.listOf lib.types.str; - default = []; - description = '' - Additional SSH public keys authorized for the deploy user. - The CI runner key should be provided as a base key; personal - workstation keys can be appended here per host or globally. - ''; - }; - }; + options.homelab.users.deploy.enable = lib.mkEnableOption "user Deploy"; config = lib.mkIf cfg.enable { users = { @@ -27,15 +15,12 @@ in { isSystemUser = true; home = "/var/empty"; shell = pkgs.bashInteractive; - openssh.authorizedKeys.keyFiles = [ - config.sops.secrets.user_keys_deploy.path + openssh.authorizedKeys.keys = [ + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPrG+ldRBdCeHEXrsy/qHXIJYg8xQXVuiUR0DxhFjYNg" ]; }; }; - # Allow the deploy user to push closures to the nix store - nix.settings.trusted-users = [ "deploy" ]; - security.sudo.extraRules = [ { groups = [