From 78a20fbe003bed59cfed366321ac85bb9c883e23 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Wed, 22 Jul 2026 21:20:16 +0200 Subject: [PATCH] feat(gitops): implement native custom gitops solution with webhooks --- .gitignore | 1 + NixOS GitOps Migration Specification.md | 138 ++++++++++ flake.lock | 102 +++++++ flake.nix | 21 +- hosts/ACE/default.nix | 41 +++ hosts/Binnenpost/default.nix | 90 +++++++ hosts/Builder/default.nix | 50 ++++ hosts/Development/default.nix | 72 +++++ hosts/Gitea/default.nix | 45 ++++ hosts/Ingress/default.nix | 263 +++++++++++++++++++ hosts/Isabel/.keep | 0 hosts/Isabel/dashboard/config/bookmarks.yaml | 32 +++ hosts/Isabel/dashboard/config/services.yaml | 30 +++ hosts/Isabel/default.nix | 255 ++++++++++++++++++ hosts/Isabel/hardware-configuration.nix | 39 +++ hosts/Niko/default.nix | 119 +++++++++ hosts/Niko/hardware-configuration.nix | 53 ++++ hosts/Production/default.nix | 48 ++++ hosts/ProductionArr/default.nix | 48 ++++ hosts/ProductionGPU/default.nix | 98 +++++++ hosts/Template/default.nix | 36 +++ hosts/Testing/default.nix | 49 ++++ hosts/Vaultwarden/default.nix | 49 ++++ modules/common/default.nix | 2 + modules/common/gitops.nix | 141 ++++++++++ 25 files changed, 1804 insertions(+), 18 deletions(-) create mode 100644 .gitignore create mode 100644 NixOS GitOps Migration Specification.md create mode 100644 flake.lock create mode 100644 hosts/ACE/default.nix create mode 100644 hosts/Binnenpost/default.nix create mode 100644 hosts/Builder/default.nix create mode 100644 hosts/Development/default.nix create mode 100644 hosts/Gitea/default.nix create mode 100644 hosts/Ingress/default.nix create mode 100644 hosts/Isabel/.keep create mode 100644 hosts/Isabel/dashboard/config/bookmarks.yaml create mode 100644 hosts/Isabel/dashboard/config/services.yaml create mode 100644 hosts/Isabel/default.nix create mode 100644 hosts/Isabel/hardware-configuration.nix create mode 100644 hosts/Niko/default.nix create mode 100644 hosts/Niko/hardware-configuration.nix create mode 100644 hosts/Production/default.nix create mode 100644 hosts/ProductionArr/default.nix create mode 100644 hosts/ProductionGPU/default.nix create mode 100644 hosts/Template/default.nix create mode 100644 hosts/Testing/default.nix create mode 100644 hosts/Vaultwarden/default.nix create mode 100644 modules/common/gitops.nix diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..485dee6 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.idea diff --git a/NixOS GitOps Migration Specification.md b/NixOS GitOps Migration Specification.md new file mode 100644 index 0000000..878984d --- /dev/null +++ b/NixOS GitOps Migration Specification.md @@ -0,0 +1,138 @@ +# **Infrastructure Specification: Automated NixOS GitOps Environment** + +## **1\. Architecture Overview** + +This specification outlines the migration from a manually deployed NixOS environment to a fully automated, pull-based GitOps architecture. + +* **Source of Truth:** Forgejo repository (v2 branch). +* **Infrastructure Provisioning:** Terraform (via bpg/proxmox provider) executed by Forgejo Actions. +* **Configuration Management:** NixOS Flakes, pulled autonomously by individual nodes via comin. +* **Secret Management:** sops-nix using Age keys (SSH host keys for VM decryption). +* **Storage:** TrueNAS serving persistent data via NFS/iSCSI, utilizing ZFS snapshot cloning for the staging environment. +* **Dependency Management:** RenovateBot running via Forgejo Actions. + +## **2\. Phase 1: Foundation & State Preparation** + +### **2.1 Git Branching Strategy** + +* Initialize a new orphan branch named v2 (or a fresh repository) to ensure a clean slate for the flake lockfile and SOPS configuration. +* Cherry-pick existing .nix service modules from the legacy main branch as they are migrated. + +### **2.2 Secret Management (Bankruptcy & Reset)** + +* Delete all legacy .sops.yaml configurations. +* Generate a new master age key. This key will be stored securely offline (e.g., Bitwarden) and **never** placed on the Forgejo runner or target VMs. +* Initialize a new .sops.yaml. Target VMs will be added via their public SSH host keys as they are provisioned. + +### **2.3 Storage Strategy (TrueNAS & Staging)** + +To provide the Staging VM with production-accurate data without risking corruption: + +1. **Production State:** TrueNAS maintains the primary ZFS datasets. +2. **Staging State (On-Demand):** When the staging environment spins up, a script (via Proxmox/Forgejo Action or TrueNAS API) takes a temporary ZFS snapshot of the production dataset. +3. **Clone & Mount:** The snapshot is cloned and exported via NFS/SMB specifically for the Staging VM. +4. **Teardown:** When Staging is spun down, the ZFS clone is destroyed. + +## **3\. Phase 2: Proxmox Configuration & Least Privilege** + +Terraform requires API access to Proxmox. To adhere to the principle of least privilege, Proxmox RBAC (Role-Based Access Control) will be utilized. + +### **3.1 Proxmox Static Configuration (RBAC)** + +The root user (root@pam) must **never** be used for automation. Instead, a dedicated API user with a highly restricted role must be created. This can be done via the Proxmox Web UI (Datacenter \-\> Permissions) or via the Proxmox shell (pveum). +**1\. Create the Restricted Role (TerraformProv):** +This role grants only the permissions needed to clone templates, configure hardware, and manage power states. +pveum role add TerraformProv \-privs "VM.Allocate VM.Audit VM.Clone VM.Config.CDROM VM.Config.CPU VM.Config.Disk VM.Config.HWType VM.Config.Memory VM.Config.Network VM.Config.Options VM.Monitor VM.PowerMgmt Datastore.AllocateSpace Datastore.Audit SDN.Use" + +**2\. Create the Terraform User:** +Create a user in the Proxmox VE authentication realm (@pve). +pveum user add terraform@pve + +**3\. Assign Permissions to Paths:** +Bind the role to the user, strictly limiting access to the VMs path and the specific storage path where VMs will reside (e.g., local-zfs). +pveum acl modify /vms \-user terraform@pve \-role TerraformProv +pveum acl modify /storage/local-zfs \-user terraform@pve \-role TerraformProv + +**4\. Generate API Token:** +Generate a token for the user. Proxmox will output a Secret ID. This ID is saved into Forgejo Secrets and passed to the Terraform provider. +pveum user token add terraform@pve tf-automation \--privsep 0 + +*(Note: \--privsep 0 ensures the token inherits the user's permissions, rather than requiring separate ACL definitions).* + +## **4\. Phase 3: Infrastructure as Code (Terraform)** + +### **4.1 The Golden Image (.qcow2)** + +A minimal NixOS image is built locally using nixos-generators and uploaded to Proxmox as a template (e.g., ID 9000). This template contains: + +* QEMU Guest Agent. +* Cloud-Init support. +* comin installed. + +### **4.2 Terraform Provisioning** + +Terraform maintains the hardware state. Its responsibilities are strictly limited to: + +* Cloning Template 9000\. +* Allocating vCPU, RAM, and Virtual Disks. +* Using Cloud-Init to inject network configurations (IP, Gateway) and the Forgejo runner's public SSH key for health check access. + +## **5\. Phase 4: CI/CD & Automation** + +### **5.1 Forgejo Runner & Terraform Pipeline** + +* The newly provisioned Forgejo Runner VM registers with Forgejo. +* A workflow (terraform.yml) runs terraform plan on Pull Requests and terraform apply on merges to the main branch. + +### **5.2 GitOps Pull Agent (comin and Alternatives)** + +Once provisioned by Terraform, VMs boot and pull their OS configuration autonomously. + +* **Chosen Tool (comin):** Polling agent designed specifically for NixOS GitOps. Runs as a systemd service, supports multiple git remotes, branch tracking, and local cryptographic verification. +* **Alternative 1 (system.autoUpgrade):** The native NixOS approach utilizing systemd timers. *Rejected* because it lacks advanced Git authentication and webhooks for immediate triggering. +* **Alternative 2 (deploy-rs / colmena):** Excellent push-based deployment tools. *Rejected* because they require the CI runner to maintain SSH root access to the entire fleet, violating our zero-trust/pull-only goals. + +### **5.3 RenovateBot Integration** + +* Renovate is deployed via a Forgejo Action running on a cron schedule. +* Nix flake support is explicitly enabled in renovate.json. +* Renovate parses flake.lock and Docker tags, automatically opening PRs for updates. + +## **6\. Phase 5: Staging Lifecycle & Healthchecks** + +### **6.1 Lifecycle Automation** + +1. **Trigger:** A PR is opened by Renovate or a developer. +2. **Wake-up:** A Forgejo Action calls the Proxmox API to start the nixos-staging VM and triggers the TrueNAS ZFS snapshot clone. +3. **Pull:** The Staging VM boots, connects to the network, and comin pulls the PR's commit hash. + +### **6.2 Automated Testing** + +The Forgejo Action will verify the deployment was successful: + +* **Systemd Checks:** systemctl is-system-running \--wait and systemctl is-active \. +* **Docker Healthchecks:** docker inspect \--format='{{json .State.Health.Status}}' \. +* **HTTP Probes:** Execute curl commands against the Staging VM to verify HTTP 200 OK. + +### **6.3 Spin Down** + +Upon PR merge or closure, the Forgejo Action shuts down the VM via the Proxmox API and destroys the temporary TrueNAS ZFS clone. + +## **7\. Phase 6: Rollbacks & Disaster Recovery** + +Because infrastructure involves both *stateless* configurations (NixOS) and *stateful* data (TrueNAS), a unified rollback strategy is critical. + +### **7.1 Differentiating Failures (Nix vs. TrueNAS)** + +If a deployment fails, the root cause must be identified to determine the rollback path: + +* **Stateless Failure (NixOS/Code):** The service fails to start immediately. System logs (journalctl \-u my-service) show syntax errors, missing binaries, or bad systemd unit definitions. +* **Stateful Failure (TrueNAS/Data):** The service starts but crashes with a database schema version mismatch, "permission denied" on the NFS mount, or missing user data. This means a service (like a Docker container) attempted a one-way database migration on the persistent TrueNAS dataset and failed halfway. + +### **7.2 The Unified Rollback Procedure** + +When a production rollout fails, **do not** attempt to fix it live. + +1. **Revert the State (TrueNAS):** If the failure was stateful (e.g., a bad database migration), immediately log into TrueNAS and rollback the primary dataset to the automated ZFS snapshot taken right before the deployment. +2. **Revert the Code (Forgejo):** Use the Forgejo UI to click "Revert" on the problematic Pull Request. This creates a new commit restoring the previous flake.nix state. +3. **Autonomous Recovery:** Within 60 seconds, the production VM's comin agent will detect the new commit on main, pull the reverted code, apply the old configuration, and reconnect to the restored TrueNAS dataset. \ No newline at end of file diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..67df8c4 --- /dev/null +++ b/flake.lock @@ -0,0 +1,102 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1760524057, + "narHash": "sha256-EVAqOteLBFmd7pKkb0+FIUyzTF61VKi7YmvP1tw4nEw=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "544961dfcce86422ba200ed9a0b00dd4b1486ec5", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "ref": "nixos-unstable", + "type": "indirect" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs", + "sops-nix": "sops-nix", + "utils": "utils" + } + }, + "sops-nix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1760393368, + "narHash": "sha256-8mN3kqyqa2PKY0wwZ2UmMEYMcxvNTwLaOrrDsw6Qi4E=", + "owner": "Mic92", + "repo": "sops-nix", + "rev": "ab8d56e85b8be14cff9d93735951e30c3e86a437", + "type": "github" + }, + "original": { + "owner": "Mic92", + "repo": "sops-nix", + "type": "github" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "utils": { + "inputs": { + "flake-utils": [ + "flake-utils" + ] + }, + "locked": { + "lastModified": 1738591040, + "narHash": "sha256-4WNeriUToshQ/L5J+dTSWC5OJIwT39SEP7V7oylndi8=", + "owner": "gytis-ivaskevicius", + "repo": "flake-utils-plus", + "rev": "afcb15b845e74ac5e998358709b2b5fe42a948d1", + "type": "github" + }, + "original": { + "owner": "gytis-ivaskevicius", + "repo": "flake-utils-plus", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix index 031df52..5450b8d 100644 --- a/flake.nix +++ b/flake.nix @@ -16,11 +16,6 @@ inputs.flake-utils.follows = "flake-utils"; }; - comin = { - url = "github:nlewo/comin"; - inputs.nixpkgs.follows = "nixpkgs"; - }; - pre-commit-hooks = { url = "github:cachix/pre-commit-hooks.nix"; inputs.nixpkgs.follows = "nixpkgs"; @@ -29,7 +24,7 @@ outputs = inputs@{ self, nixpkgs, - flake-utils, sops-nix, utils, comin, pre-commit-hooks, + flake-utils, sops-nix, utils, pre-commit-hooks, ... }: let @@ -67,19 +62,9 @@ ./users sops-nix.nixosModules.sops - comin.nixosModules.comin - - # Base comin configuration for all nodes ({ config, pkgs, ... }: { - services.comin = { - enable = true; - remotes = [{ - name = "origin"; - # Replace with actual internal forgejo URL once available - url = "https://github.com/example/nix-config.git"; - branches.main.name = "v2"; - }]; - }; + # Inject git revision for our custom GitOps module to query + system.configurationRevision = self.rev or self.dirtyRev or "unknown"; }) ]; }; diff --git a/hosts/ACE/default.nix b/hosts/ACE/default.nix new file mode 100644 index 0000000..04aa284 --- /dev/null +++ b/hosts/ACE/default.nix @@ -0,0 +1,41 @@ +{ pkgs, ... }: + +{ + config = { + homelab = { + services.actions.enable = true; + virtualisation.guest.enable = true; + }; + + networking = { + hostName = "ACE"; + hostId = "aaaa4100"; + domain = "depeuter.dev"; + + useDHCP = false; + + enableIPv6 = true; + + defaultGateway = { + address = "192.168.0.1"; + interface = "ens18"; + }; + + interfaces.ens18 = { + ipv4.addresses = [ + { + address = "192.168.0.41"; + prefixLength = 24; + } + ]; + }; + + nameservers = [ + "1.1.1.1" # Cloudflare + "1.0.0.1" # Cloudflare + ]; + }; + + system.stateVersion = "24.05"; + }; +} diff --git a/hosts/Binnenpost/default.nix b/hosts/Binnenpost/default.nix new file mode 100644 index 0000000..561fbe1 --- /dev/null +++ b/hosts/Binnenpost/default.nix @@ -0,0 +1,90 @@ +{ pkgs, ... }: + +{ + config = { + environment = { + etc."nixos/tailscale-authkey".text = '' + tskey-auth-k1tfJLTnGB11CNTRL-HhnegtfNzQ3G8h71SC2DR38PFXwseQiu + ''; + + systemPackages = with pkgs; [ + ethtool + ]; + }; + + homelab = { + apps = { + speedtest.enable = true; + technitiumDNS.enable = true; + traefik.enable = true; + }; + virtualisation.guest.enable = true; + }; + + networking = { + hostName = "Binnenpost"; + hostId = "aaaa1001"; + domain = "depeuter.dev"; + + useDHCP = false; + + enableIPv6 = true; + + defaultGateway = { + address = "192.168.0.1"; + interface = "ens18"; + }; + + # Open ports in the firewall. + firewall = { + enable = true; + }; + + interfaces.ens18 = { + ipv4.addresses = [ + { + address = "192.168.0.89"; + prefixLength = 24; + } + ]; + }; + + nameservers = [ + "1.1.1.1" # Cloudflare + "1.0.0.1" # Cloudflare + ]; + }; + + services = { + networkd-dispatcher = { + enable = true; + rules."50-tailscale" = { + onState = ["routable"]; + script = '' + ${pkgs.ethtool}/bin/ethtool -K ens18 rx-udp-gro-forwarding on rx-gro-list off + ''; + }; + }; + + tailscale = { + enable = true; + useRoutingFeatures = "server"; + authKeyFile = "/etc/nixos/tailscale-authkey"; + extraUpFlags = [ + "--advertise-routes=192.168.0.0/24" + "--exit-node" + ]; + }; + }; + + virtualisation.oci-containers.containers.traefik.labels = { + "traefik.http.routers.roxanne.rule" = "Host(`roxanne.depeuter.dev`)"; + "traefik.http.services.roxanne.loadbalancer.server.url" = "https://192.168.0.13:8006"; + + "traefik.http.routers.hugo.rule" = "Host(`hugo.depeuter.dev`)"; + "traefik.http.services.hugo.loadbalancer.server.url" = "https://192.168.0.11:444"; + }; + + system.stateVersion = "24.05"; + }; +} diff --git a/hosts/Builder/default.nix b/hosts/Builder/default.nix new file mode 100644 index 0000000..515bcb9 --- /dev/null +++ b/hosts/Builder/default.nix @@ -0,0 +1,50 @@ +{ pkgs, ... }: + +{ + config = { + networking = { + hostName = "Builder"; + hostId = "aaaa4200"; + domain = "depeuter.dev"; + + useDHCP = false; + enableIPv6 = true; + + defaultGateway = { + address = "192.168.0.1"; + interface = "ens18"; + }; + + interfaces.ens18 = { + ipv4.addresses = [ + { + address = "192.168.0.42"; + prefixLength = 24; + } + ]; + }; + + nameservers = [ + "1.1.1.1" + "1.0.0.1" + ]; + }; + + # Dedicated builder user + users.users.builder = { + isNormalUser = true; + description = "Nix Remote Builder"; + # You must add the public key corresponding to `builder-ssh-key` here + openssh.authorizedKeys.keys = [ + "ssh-ed25519 AAAAC3NzaC1... TODO: replace with build-farm-key.pub" + ]; + }; + + # Optimize nix settings for a build farm + nix.settings.trusted-users = [ "builder" ]; + nix.settings.cores = 0; # Use all cores + nix.settings.max-jobs = "auto"; + + system.stateVersion = "24.05"; + }; +} diff --git a/hosts/Development/default.nix b/hosts/Development/default.nix new file mode 100644 index 0000000..77f6758 --- /dev/null +++ b/hosts/Development/default.nix @@ -0,0 +1,72 @@ +{ config, pkgs, lib, system, ... }: + +{ + config = { + homelab = { + apps = { + bind9.enable = true; + homepage = { + enable = true; + exposePort = true; + }; + traefik.enable = true; + plex.enable = true; + solidtime.enable = true; + }; + virtualisation.guest.enable = true; + }; + + networking = { + hostId = "aaaa9100"; + domain = "roxanne.depeuter.dev"; + + useDHCP = false; + + enableIPv6 = true; + + defaultGateway = { + address = "192.168.0.1"; + interface = "ens18"; + }; + + # Open ports in the firewall. + firewall = { + enable = true; + }; + + interfaces.ens18 = { + ipv4.addresses = [ + { + address = "192.168.0.91"; + prefixLength = 24; + } + ]; + }; + + nameservers = [ + "1.1.1.1" # Cloudflare + "1.0.0.1" # Cloudflare + ]; + }; + + system.stateVersion = "24.05"; + + virtualisation.oci-containers.containers = { + pgadmin = { + image = "dpage/pgadmin4:8.11.0"; + ports = [ + "30056:80/tcp" + ]; + environment = { + # NOTE Required + # The email address used when setting up the initial administrator account to login to pgAdmin. + 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"; + }; + autoStart = true; + }; + }; + }; +} diff --git a/hosts/Gitea/default.nix b/hosts/Gitea/default.nix new file mode 100644 index 0000000..c6c9b43 --- /dev/null +++ b/hosts/Gitea/default.nix @@ -0,0 +1,45 @@ +{ config, pkgs, lib, system, ... }: + +{ + config = { + homelab = { + apps.gitea.enable = true; + virtualisation.guest.enable = true; + + users.admin = { + enable = true; + authorizedKeys = [ + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFrp6aM62Bf7bj1YM5AlAWuNrANU3N5e8+LtbbpmZPKS" + ]; + }; + }; + + networking = { + hostId = "aaaa1500"; + domain = "depeuter.dev"; + + enableIPv6 = true; + + useDHCP = false; + defaultGateway = { + address = "192.168.0.1"; + interface = "ens18"; + }; + interfaces.ens18 = { + ipv4.addresses = [ + { + address = "192.168.0.24"; + prefixLength = 24; + } + ]; + }; + + nameservers = [ + "1.1.1.1" # Cloudflare + "1.0.0.1" # Cloudflare + ]; + }; + + system.stateVersion = "24.05"; + }; +} diff --git a/hosts/Ingress/default.nix b/hosts/Ingress/default.nix new file mode 100644 index 0000000..c0a3ac9 --- /dev/null +++ b/hosts/Ingress/default.nix @@ -0,0 +1,263 @@ +{ config, pkgs, modulesPath, lib, system, ... }: + +{ + config = { + homelab.virtualisation.guest.enable = true; + + networking = { + hostName = "Ingress"; + hostId = "aaaa1000"; + domain = "depeuter.dev"; + + enableIPv6 = true; + + useDHCP = false; + defaultGateway = { + address = "192.168.0.1"; + interface = "ens18"; + }; + interfaces.ens18 = { + ipv4.addresses = [ + { + address = "192.168.0.10"; +prefixLength = 24; + } + ]; + }; + + nameservers = [ + "1.1.1.1" # Cloudflare + "1.0.0.1" # Cloudflare + ]; + + firewall = { + enable = true; + allowedTCPPorts = [ + 80 # HTTP + 443 # HTTPS + ]; + }; + }; + + security.acme = { + acceptTerms = true; + defaults = { + inherit (config.services.nginx) group; + dnsPropagationCheck = true; + dnsProvider = "cloudflare"; + dnsResolver = "1.1.1.1:53"; + email = "tibo.depeuter@telenet.be"; + credentialFiles = { + CLOUDFLARE_DNS_API_TOKEN_FILE = "/var/lib/secrets/depeuter-dev-cloudflare-api-token"; + }; + reloadServices = [ "nginx" ]; + }; + certs = { + "depeuter.dev" = { + domain = "depeuter.dev"; + extraDomainNames = [ "*.depeuter.dev" ]; + }; + "cloud.depeuter.dev" = { }; + "git.depeuter.dev" = { }; + "home.depeuter.dev" = { }; + "jelly.depeuter.dev" = { }; + "vault.depeuter.dev" = { }; + }; + }; + + # List services that you want to enable. + services = { + # Enable Nginx as a reverse proxy + nginx = let + nextcloud = { + host = "192.168.0.23"; + officePort = 8080; + }; + in { + enable = true; + + # Use recommended settings + # recommendedGzipSettings = true; + # recommendedOptimisation = true; + # recommendedProxySettings = true; + # recommendedTlsSettings = true; + + # Only allow PFS-enabled ciphers with AES256 + sslCiphers = "AES256+EECDH:AES256+EDH:!aNULL"; + + upstreams.docservice.servers."${nextcloud.host}:${toString nextcloud.officePort}" = {}; + + appendHttpConfig = '' + map $http_x_forwarded_proto $the_scheme { + default $http_x_forwarded_proto; + "" $scheme; + } + + map $http_x_forwarded_host $the_host { + default $http_x_forwarded_host; + "" $host; + } + + map $http_upgrade $proxy_connection { + default upgrade; + "" close; + } + ''; + + # Define hosts + virtualHosts = { + # Disable automatic routing. + "default" = { + locations."/".return = "301 https://youtu.be/dQw4w9WgXcQ"; + default = true; + }; + + "cloud.depeuter.dev" = { + enableACME = true; + forceSSL = true; + locations = { + "/" = { + proxyPass = "http://${nextcloud.host}"; + extraConfig = '' + add_header Strict-Transport-Security "max-age=15552000; includeSubDomains" always; + fastcgi_request_buffering off; + ''; + }; + "/office/" = { + proxyPass = "http://${nextcloud.host}:${toString nextcloud.officePort}/"; + priority = 500; + recommendedProxySettings = false; + extraConfig = '' + proxy_http_version 1.1; + ''; + }; + }; + extraConfig = '' + client_max_body_size 10G; + + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $proxy_connection; + proxy_set_header X-Forwarded-Host $the_host/office; + proxy_set_header X-Forwarded-Proto $the_scheme; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + ''; + }; + + "home.depeuter.dev" = { + enableACME = true; + forceSSL = true; + locations."/" = { + proxyPass = "http://192.168.0.21:8123"; + extraConfig = '' + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + proxy_set_header Host $host; + ''; + }; + }; + + "jelly.depeuter.dev" = let + jellyfin = { + host = "192.168.0.94"; + port = 8096; + }; + in { + enableACME = true; + forceSSL = true; + locations = { + "/" = { + proxyPass = "http://${jellyfin.host}:${toString jellyfin.port}"; + extraConfig = '' + # Proxy main Jellyfin traffic + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Protocol $scheme; + proxy_set_header X-Forwarded-Host $http_host; + + # Disable buffering when the nginx proxy gets very resource heavy upon streaming + proxy_buffering off; + ''; + }; + "/socket" = { + proxyPass = "http://${jellyfin.host}:${toString jellyfin.port}"; + extraConfig = '' + # Proxy Jellyfin Websockets traffic + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Protocol $scheme; + proxy_set_header X-Forwarded-Host $http_host; + ''; + }; + }; + extraConfig = '' + client_max_body_size 512M; + + # Security / XSS Mitigation Headers + # NOTE: X-Frame-Options may cause issues with the webOS app + add_header X-Frame-Options "SAMEORIGIN"; + add_header X-Content-Type-Options "nosniff"; + + # Permissions policy. May cause issues with some clients + add_header Permissions-Policy "accelerometer=(), ambient-light-sensor=(), battery=(), bluetooth=(), camera=(), clipboard-read=(), display-capture=(), document-domain=(), encrypted-media=(), gamepad=(), geolocation=(), gyroscope=(), hid=(), idle-detection=(), interest-cohort=(), keyboard-map=(), local-fonts=(), magnetometer=(), microphone=(), payment=(), publickey-credentials-get=(), serial=(), sync-xhr=(), usb=(), xr-spatial-tracking=()" always; + + # Content Security Policy + # See: https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP + # Enforces https content and restricts JS/CSS to origin + # External Javascript (such as cast_sender.js for Chromecast) must be whitelisted. + # NOTE: The default CSP headers may cause issues with the webOS app + add_header Content-Security-Policy "default-src https: data: blob: ; img-src 'self' https://* ; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline' https://www.gstatic.com https://www.youtube.com blob:; worker-src 'self' blob:; connect-src 'self'; object-src 'none'; frame-ancestors 'self'"; + ''; + }; + "git.depeuter.dev" = { + enableACME = true; + forceSSL = true; + locations."/".proxyPass = "http://192.168.0.24:3000"; + extraConfig = '' + proxy_set_header Connection $http_connection; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + client_max_body_size 10G; + keepalive_timeout 600s; + proxy_buffers 4 256k; # Number and size of buffers for reading response + proxy_buffer_size 256k; # Buffer for the first part of the response + proxy_busy_buffers_size 256k; # Max size of busy buffers + proxy_http_version 1.1; + proxy_read_timeout 600s; + proxy_temp_file_write_size 256k; # Size of temp file for large responses + ''; + }; + "vault.depeuter.dev" = { + enableACME = true; + forceSSL = true; + locations = { + "/" = { + proxyPass = "http://192.168.0.22:10102"; + proxyWebsockets = true; + }; + "~ ^/admin".return = 403; + }; + }; + "rss.depeuter.dev" = { + enableACME = true; + forceSSL = true; + locations."/".proxyPass = "http://192.168.92:${toString config.homelab.apps.freshrss.port}"; + }; + }; + }; + }; + + system.stateVersion = "24.05"; + }; +} diff --git a/hosts/Isabel/.keep b/hosts/Isabel/.keep new file mode 100644 index 0000000..e69de29 diff --git a/hosts/Isabel/dashboard/config/bookmarks.yaml b/hosts/Isabel/dashboard/config/bookmarks.yaml new file mode 100644 index 0000000..ac0566a --- /dev/null +++ b/hosts/Isabel/dashboard/config/bookmarks.yaml @@ -0,0 +1,32 @@ +- Office: + - Zoho Mail: + - icon: zohomail + href: https://mail.zoho.eu +- Network: + - Cloudlfare: + - icon: cloudflare + href: https://dash.cloudflare.com + - Pulsetic: + - href: https://status.depeuter.dev + icon: https://pulsetic.com/favicon-196x196.png + - Telenet Internet usage: + - icon: https://static.telenet.be/assets/favicon/favicon.ico + href: https://www2.telenet.be/nl/klantenservice/raadpleeg-je-internetverbruik/ + - Telenet Modem: + - icon: https://static.telenet.be/assets/favicon/favicon.ico + # href: https://mijn.telenet.be/mijntelenet/rgw/settings.do?identifier=u381160&action=showAdvancedSettings + href: https://www2.telenet.be/residential/nl/mijn-telenet/je-thuisnetwerk#/mainnavitem=hgw/mainnavitemid=item-1/subnavitem=modem_general + - TransIP: + - icon: https://www.transip.eu/cache-60c9b25f/img/transip-new/favicons/favicon.png + href: https://www.transip.eu/cp/ +- Homemade: + - AI-Transparency: + - href: https://ai-transparency.depeuter.dev + icon: https://ai-transparency.depeuter.dev/img/transparency.png + - Down-message: + - href: https://down.depeuter.dev + icon: https://down.depeuter.dev/assets/icon.jpg + - Portfolio: + - href: https://tibo.depeuter.dev + icon: https://tibo.depeuter.dev/assets/owl_circuit.png + diff --git a/hosts/Isabel/dashboard/config/services.yaml b/hosts/Isabel/dashboard/config/services.yaml new file mode 100644 index 0000000..e944db1 --- /dev/null +++ b/hosts/Isabel/dashboard/config/services.yaml @@ -0,0 +1,30 @@ +- Networking: + - AXE5400 Tri-Band Wi-Fi 6E Router: + description: Router + href: https://tplinkwifi.net + ping: http://192.168.0.1 + icon: tp-link + - Traefik Isabel: + description: Reverse proxy manager + href: https://traefik.isabel.depeuter.dev/dashboard/# + ping: https://traefik.isabel.depeuter.dev/dashboard/# + icon: traefik + widget: + type: traefik + url: https://traefik.isabel.depeuter.dev + - Traefik Niko: + description: Reverse proxy manager + href: https://traefik.niko.depeuter.dev/dashboard/# + ping: https://traefik.niko.depeuter.dev/dashboard/# + - Technitium DNS Isabel: + description: DNS server + href: https://dns.Isabel.depeuter.dev + ping: http://192.168.0.13:53 + icon: technitium + - Technitium DNS Niko: + description: DNS server + href: https://dns.niko.depeuter.dev + ping: http://192.168.0.30:53 + icon: technitium + + diff --git a/hosts/Isabel/default.nix b/hosts/Isabel/default.nix new file mode 100644 index 0000000..0a1f50f --- /dev/null +++ b/hosts/Isabel/default.nix @@ -0,0 +1,255 @@ +{ config, pkgs, ... }: + +{ + imports = [ + # Include the results of the hardware scan. + ./hardware-configuration.nix + ]; + + # Use the systemd-boot EFI boot loader. + boot.loader = { + systemd-boot.enable = true; + efi = { + canTouchEfiVariables = true; + efiSysMountPoint = "/boot/efi"; + }; + }; + + console = { + font = "Lat2-Terminus16"; + keyMap = "us"; + }; + + # List packages installed in the system profile. To search, run: + # $ nix search wget + environment.systemPackages = with pkgs; [ + ]; + + environment.etc = { + "homepage/bookmarks.yaml".text = '' +- Office: + - Zoho Mail: + - icon: zohomail + href: https://mail.zoho.eu +- Network: + - Cloudlfare: + - icon: cloudflare + href: https://dash.cloudflare.com + - TransIP: + - icon: https://www.transip.eu/cache-60c9b25f/img/transip-new/favicons/favicon.png + href: https://www.transip.eu/cp/ + - Telenet Internet usage: + - icon: https://static.telenet.be/assets/favicon/favicon.ico + href: https://www2.telenet.be/nl/klantenservice/raadpleeg-je-internetverbruik/ + - Telenet Modem: + - icon: https://static.telenet.be/assets/favicon/favicon.ico + # href: https://mijn.telenet.be/mijntelenet/rgw/settings.do?identifier=u381160&action=showAdvancedSettings + href: https://www2.telenet.be/residential/nl/mijn-telenet/je-thuisnetwerk#/mainnavitem=hgw/mainnavitemid=item-1/subnavitem=modem_general + - Pulsetic: + - href: https://status.depeuter.dev + icon: https://pulsetic.com/favicon-196x196.png +- Homemade: + - AI-Transparency: + - href: https://ai-transparency.depeuter.dev + icon: https://ai-transparency.depeuter.dev/img/transparency.png + - Down-message: + - href: https://down.depeuter.dev + icon: https://down.depeuter.dev/assets/icon.jpg + - Portfolio: + - href: https://tibo.depeuter.dev + icon: https://tibo.depeuter.dev/assets/owl_circuit.png + ''; + + "homepage/services.yaml".text = '' +- Networking: + - Traefik Isabel: + description: Reverse proxy manager + href: https://traefik.isabel.depeuter.dev/dashboard/# + ping: https://traefik.isabel.depeuter.dev/dashboard/# + icon: traefik + widget: + type: traefik + url: https://traefik.isabel.depeuter.dev + - Traefik Niko: + description: Reverse proxy manager + href: https://traefik.niko.depeuter.dev/dashboard/# + ping: https://traefik.niko.depeuter.dev/dashboard/# + icon: traefik + widget: + type: traefik + url: https://traefik.niko.depeuter.dev + ''; + + "homepage/settings.yaml".text = '' +--- +# For configuration options and examples, please see: +# https://gethomepage.dev/en/configs/settings + +providers: + openweathermap: openweathermapapikey + weatherapi: weatherapiapikey + ''; + }; + + homelab.apps.technitiumDNS.enable = true; + + # Select internationalisation properties. + i18n.defaultLocale = "en_GB.utf8"; + + networking = { + hostName = "Hugo-Isabel"; + domain = "depeuter.dev"; + + enableIPv6 = true; + + # Open ports in the firewall. + firewall = { + enable = true; + }; + + networkmanager.enable = true; + }; + + # List services that you want to enable: + services = { + tailscale = { + enable = true; + useRoutingFeatures = "server"; + authKeyFile = "/etc/nixos/tailscale-authkey"; + extraUpFlags = [ + "--advertise-routes=192.168.0.0/24" + "--exit-node" + ]; + }; + + # Fix DNS issues. See: + # https://github.com/tailscale/tailscale/issues/4254 + # resolved.enable = true; + }; + + system.stateVersion = "24.05"; + + security.sudo = { + enable = true; + }; + + virtualisation = { + docker = { + enable = true; + autoPrune.enable = true; + }; + + oci-containers = { + backend = "docker"; + containers = { + reverse-proxy = { + hostname = "traefik"; + image = "traefik:v3.0"; + cmd = [ + "--api.insecure=true" + # Add Docker provider + "--providers.docker=true" + "--providers.docker.exposedByDefault=false" + # Add web entrypoint + "--entrypoints.web.address=:80/tcp" + "--entrypoints.web.http.redirections.entrypoint.to=websecure" + "--entrypoints.web.http.redirections.entrypoint.scheme=https" + # Add websecure entrypoint + "--entrypoints.websecure.address=:443/tcp" + "--entrypoints.websecure.http.tls=true" + "--entrypoints.websecure.http.tls.certResolver=letsencrypt" + "--entrypoints.websecure.http.tls.domains[0].main=depeuter.dev" + "--entrypoints.websecure.http.tls.domains[0].sans=*.depeuter.dev" + "--entrypoints.websecure.http.tls.domains[1].sans=*.isabel.depeuter.dev" + "--entrypoints.websecure.http.tls.domains[2].sans=*.jelly.depeuter.dev" + # Certificates + "--certificatesresolvers.letsencrypt.acme.dnschallenge=true" + "--certificatesresolvers.letsencrypt.acme.dnschallenge.provider=cloudflare" + "--certificatesresolvers.letsencrypt.acme.email=tibo.depeuter@telenet.be" + "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json" + + # Additional routes + ]; + ports = [ + "80:80/tcp" + "443:443/tcp" + # "8080:8080/tcp" # The Web UI (enabled by --api.insecure=true) + ]; + environment = { + # TODO Hide this! + "CLOUDFLARE_DNS_API_TOKEN" = "6Vz64Op_a6Ls1ljGeBxFoOVfQ-yB-svRbf6OyPv2"; + }; + environmentFiles = [ + ]; + volumes = [ + "/var/run/docker.sock:/var/run/docker.sock:ro" # So that Traefik can listen to the Docker events + "letsencrypt:/letsencrypt" + ]; + labels = { + "traefik.enable" = "true"; + "traefik.http.routers.traefik.rule" = "Host(`traefik.isabel.depeuter.dev`)"; + "traefik.http.services.traefik.loadbalancer.server.port" = "8080"; + }; + autoStart = true; + }; + feishin = { + hostname = "feishin"; + image = "ghcr.io/jeffvli/feishin:0.7.1"; + ports = [ + # "9180:9180/tcp" # Web player (HTTP) + ]; + environment = { + # pre defined server name + SERVER_NAME = "Hugo"; + # When true AND name/type/url are set, only username/password can be toggled + SERVER_LOCK = "true"; + # navidrome also works + SERVER_TYPE = "jellyfin"; + # http://address:port + SERVER_URL= "https://jelly.depeuter.dev"; + TZ = config.time.timeZone; + }; + labels = { + "traefik.enable" = "true"; + "traefik.http.routers.feishin.rule" = "Host(`music.depeuter.dev`)"; + "traefik.http.services.feishin.loadbalancer.server.port" = "9180"; + "traefik.tls.options.default.minVersion" = "VersionTLS13"; + }; + autoStart = true; + }; + dashboard = { + hostname = "dashboard"; + image = "ghcr.io/gethomepage/homepage:v0.9.3"; + ports = [ + # "3000:3000/tcp" + ]; + volumes = [ + "/etc/homepage:/app/config" # Make sure your local config directory exists + "/var/run/docker.sock:/var/run/docker.sock:ro" # optional, for docker integrations + ]; + labels = { + "traefik.enable" = "true"; + "traefik.http.routers.dashboard.rule" = "Host(`dash.depeuter.dev`)"; + "traefik.http.services.dashboard.loadbalancer.server.port" = "3000"; + "traefik.tls.options.default.minVersion" = "VersionTLS13"; + }; + autoStart = true; + }; + prometheus = { + hostname = "prometheus"; + image = "prom/prometheus:v2.45.6"; + ports = [ + # "127.0.0.1:9090:9090/tcp" + ]; + labels = { + "traefik.enable" = "true"; + "traefik.http.routers.prometheus.rule" = "Host(`prometheus.isabel.depeuter.dev`)"; + "traefik.http.services.prometheus.loadbalancer.server.port" = "9090"; + "traefik.tls.options.default.minVersion" = "VersionTLS13"; + }; + autoStart = true; + }; + }; + }; + }; +} diff --git a/hosts/Isabel/hardware-configuration.nix b/hosts/Isabel/hardware-configuration.nix new file mode 100644 index 0000000..ec7ffda --- /dev/null +++ b/hosts/Isabel/hardware-configuration.nix @@ -0,0 +1,39 @@ +# Do not modify this file! It was generated by ‘nixos-generate-config’ +# and may be overwritten by future invocations. Please make changes +# to /etc/nixos/configuration.nix instead. +{ config, lib, pkgs, modulesPath, ... }: + +{ + imports = + [ (modulesPath + "/profiles/qemu-guest.nix") + ]; + + boot.initrd.availableKernelModules = [ "ata_piix" "xhci_pci" "ahci" "sd_mod" "sr_mod" ]; + boot.initrd.kernelModules = [ ]; + boot.kernelModules = [ ]; + boot.extraModulePackages = [ ]; + + fileSystems."/" = + { device = "/dev/disk/by-label/NIX-ROOT"; + fsType = "ext4"; + }; + + fileSystems."/boot/efi" = + { device = "/dev/disk/by-label/NIX-BOOT"; + fsType = "vfat"; + }; + + swapDevices = + [ { device = "/dev/disk/by-label/SWAP"; } + ]; + + # Enables DHCP on each ethernet and wireless interface. In case of scripted networking + # (the default) this is the recommended approach. When using systemd-networkd it's + # still possible to use this option, but it's recommended to use it in conjunction + # with explicit per-interface declarations with `networking.interfaces..useDHCP`. + networking.useDHCP = lib.mkDefault true; + # networking.interfaces.ens3.useDHCP = lib.mkDefault true; + + nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; + hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; +} diff --git a/hosts/Niko/default.nix b/hosts/Niko/default.nix new file mode 100644 index 0000000..910f325 --- /dev/null +++ b/hosts/Niko/default.nix @@ -0,0 +1,119 @@ +{ config, pkgs, ... }: + +{ + imports = [ + # Include the results of the hardware scan. + ./hardware-configuration.nix + ]; + + homelab = { + apps = { + technitiumDNS.enable = true; + traefik.enable = true; + }; + users.deploy.enable = true; + }; + + # Use the systemd-boot EFI boot loader. + boot.loader = { + systemd-boot.enable = true; + efi = { + canTouchEfiVariables = true; + efiSysMountPoint = "/boot/efi"; + }; + }; + + console = { + font = "Lat2-Terminus16"; + keyMap = "us"; + }; + + # List packages installed in the system profile. To search, run: + # $ nix search wget + environment.systemPackages = with pkgs; [ + cifs-utils + ]; + + hardware = { + enableRedistributableFirmware = true; + enableAllFirmware = true; + graphics.enable = true; + }; + + # Select internationalisation properties. + i18n.defaultLocale = "en_GB.UTF-8"; + + networking = { + hostName = "Niko"; + domain = "depeuter.dev"; + + enableIPv6 = true; + + # Open ports in the firewall. + firewall = { + enable = true; + }; + + networkmanager.enable = true; + + extraHosts = '' + 192.168.0.11 jelly.depeuter.dev + ''; + }; + + nixpkgs.config.allowUnfree = true; + + # List services that you want to enable: + services = { + # Cage, a wayland kiosk service + cage = { + enable = true; + environment = { + # Do not fail when there are no input devices. + # WLR_LIBINPUT_NO_DEVICES = "1"; + }; + extraArguments = [ + "-d" # Don't draw client side decorations, when possible + # "-m" "last" # Use only the last connected output + "-s" # Allow VT switching + ]; + program = "/home/jellyfin-mpv-shim/start.sh"; + user = config.users.users.jellyfin-mpv-shim.name; + }; + + pulseaudio.enable = true; + + tailscale = { + enable = true; + useRoutingFeatures = "server"; + authKeyFile = "/etc/nixos/tailscale-authkey"; + extraUpFlags = [ + "--advertise-routes=192.168.0.0/24" + "--exit-node" + ]; + }; + + # Fix DNS issues. See: + # https://github.com/tailscale/tailscale/issues/4254 + # resolved.enable = true; + }; + + # Define a user account. Don't forget to set a password with 'passwd'. + users.users.jellyfin-mpv-shim = { + description = "Jellyfin MPV Shim User"; + isNormalUser = true; + extraGroups = [ + config.users.groups.audio.name + config.users.groups.video.name + ]; + packages = with pkgs; [ + jellyfin-mpv-shim + mpv + socat + ]; + }; + + systemd.services."cage-tty1".serviceConfig.Restart = "always"; + + system.stateVersion = "24.05"; +} diff --git a/hosts/Niko/hardware-configuration.nix b/hosts/Niko/hardware-configuration.nix new file mode 100644 index 0000000..34c1dc6 --- /dev/null +++ b/hosts/Niko/hardware-configuration.nix @@ -0,0 +1,53 @@ +{ config, lib, pkgs, modulesPath, ... }: + +{ + imports = [ + (modulesPath + "/installer/scan/not-detected.nix") + ]; + + boot = { + initrd = { + availableKernelModules = [ + "xhci_pci" + "ahci" + "usb_storage" + "sd_mod" + ]; + }; + kernelModules = [ ]; + extraModulePackages = [ ]; + }; + + fileSystems = { + "/" = { + device = "/dev/disk/by-uuid/20b7eff3-fca5-4b60-a5a9-13219f70ce23"; + fsType = "ext4"; + }; + + "/boot/efi" = { + device = "/dev/disk/by-uuid/0B6D-0DCD"; + fsType = "vfat"; + }; + + "/media/photos" = { + device = "//192.168.0.11/CANVAS"; + fsType = "cifs"; + options = let + # This line prevents hanging on network split + automount_opts = "x-systemd.automount,noauto,x-systemd.idle-timeout=60,x-systemd.device-timeout=5s,x-systemd.mount-timeout=5s,user,users"; + in ["${automount_opts},credentials=/etc/nixos/smb-secrets,uid=1002,gid=100"]; + }; + }; + + swapDevices = [ + { device = "/dev/disk/by-uuid/f3679da0-45b3-45c0-a1d0-af8d771a7dbf"; } + ]; + + networking = { + hostId = "7a139e16"; + useDHCP = lib.mkDefault true; + }; + + powerManagement.cpuFreqGovernor = lib.mkDefault "powersave"; + hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; +} diff --git a/hosts/Production/default.nix b/hosts/Production/default.nix new file mode 100644 index 0000000..9bb565d --- /dev/null +++ b/hosts/Production/default.nix @@ -0,0 +1,48 @@ +{ config, pkgs, lib, system, ... }: + +{ + config = { + homelab = { + apps = { + calibre.enable = true; + traefik.enable = true; + }; + virtualisation.guest.enable = true; + }; + + networking = { + hostId = "aaaa2100"; + domain = "roxanne.depeuter.dev"; + + useDHCP = false; + + enableIPv6 = true; + + defaultGateway = { + address = "192.168.0.1"; + interface = "ens18"; + }; + + # Open ports in the firewall. + firewall = { + enable = true; + }; + + interfaces.ens18 = { + ipv4.addresses = [ + { + address = "192.168.0.31"; + prefixLength = 24; + } + ]; + }; + + nameservers = [ + "1.1.1.1" # Cloudflare + "1.0.0.1" # Cloudflare + ]; + }; + + system.stateVersion = "24.05"; + }; +} diff --git a/hosts/ProductionArr/default.nix b/hosts/ProductionArr/default.nix new file mode 100644 index 0000000..ff4f4c2 --- /dev/null +++ b/hosts/ProductionArr/default.nix @@ -0,0 +1,48 @@ +{ config, pkgs, lib, system, ... }: + +{ + config = { + homelab = { + apps = { + arr.enable = true; + traefik.enable = true; + }; + virtualisation.guest.enable = true; + }; + + networking = { + hostId = "aaaa2300"; + domain = "roxanne.depeuter.dev"; + + useDHCP = false; + + enableIPv6 = true; + + defaultGateway = { + address = "192.168.0.1"; + interface = "ens18"; + }; + + # Open ports in the firewall. + firewall = { + enable = true; + }; + + interfaces.ens18 = { + ipv4.addresses = [ + { + address = "192.168.0.33"; + prefixLength = 24; + } + ]; + }; + + nameservers = [ + "1.1.1.1" # Cloudflare + "1.0.0.1" # Cloudflare + ]; + }; + + system.stateVersion = "24.05"; + }; +} diff --git a/hosts/ProductionGPU/default.nix b/hosts/ProductionGPU/default.nix new file mode 100644 index 0000000..fa9ca8c --- /dev/null +++ b/hosts/ProductionGPU/default.nix @@ -0,0 +1,98 @@ +{ config, pkgs, lib, system, ... }: + +{ + config = { + homelab = { + apps.jellyfin.enable = true; + virtualisation.guest.enable = true; + }; + + networking = { + hostId = "aaaa2200"; + domain = "roxanne.depeuter.dev"; + + useDHCP = false; + + enableIPv6 = true; + + defaultGateway = { + address = "192.168.0.1"; + interface = "ens18"; + }; + + # Open ports in the firewall. + firewall = { + enable = true; + }; + + interfaces.ens18 = { + ipv4.addresses = [ + { + address = "192.168.0.94"; + prefixLength = 24; + } + ]; + }; + + nameservers = [ + "1.1.1.1" # Cloudflare + "1.0.0.1" # Cloudflare + ]; + }; + + system.stateVersion = "24.11"; + + ### Nvidia GPU support ### + + services.xserver.videoDrivers = [ "nvidia" ]; + + # virtualisation.docker.package = pkgs.nvidia-docker; + + nixpkgs.config = { + allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [ + "nvidia-x11" + "nvidia-settings" + "nvidia-persistenced" + ]; + + # enable vaapi on OS-level + # packageOverrides = pkgs: { + # vaapiIntel = pkgs.vaapiIntel.override { + # enableHybridCodec = true; + # }; + # }; + }; + + hardware = { + graphics = { + enable = true; + # driSupport = true; + # driSupport32Bit = true; + extraPackages = with pkgs; [ + # intel-media-driver + # intel-vaapi-driver # previously vaapiIntel + # vaapiVdpau + # intel-compute-runtime # OpenCL filter support (hardware tonemapping and subtitle burn-in) + # unstable.vpl-gpu-rt # QSV on 11th gen or newer + # intel-media-sdk # QSV up to 11th gen + ]; + }; + + nvidia = { + package = config.boot.kernelPackages.nvidiaPackages.stable; + # Whether to enable kernel modesetting when using the NVIDIA proprietary driver. + modesetting.enable = true; +# powerManagement = { +# enable = false; +# finegrained = false; +# }; + open = false; + nvidiaSettings = false; + + # Whether to enable nvidia-persistenced a update for NVIDIA GPU headless mode, i.e. It ensures all GPUs stay awake even during headless mode . + # nvidiaPersistenced = true; + }; + nvidia-container-toolkit.enable = true; + }; + }; +} diff --git a/hosts/Template/default.nix b/hosts/Template/default.nix new file mode 100644 index 0000000..21e54b7 --- /dev/null +++ b/hosts/Template/default.nix @@ -0,0 +1,36 @@ +{ config, pkgs, lib, system, ... }: + +{ + config = { + homelab.virtualisation.guest.enable = true; + + networking = { + # TODO hostName = "nixos"; + # TODO hostId = "aaaa9000"; + domain = "depeuter.dev"; + + enableIPv6 = true; + + useDHCP = false; + defaultGateway = { + address = "192.168.0.1"; + interface = "ens18"; + }; + interfaces.ens18 = { + ipv4.addresses = [ + { + address = "192.168.0.90"; + prefixLength = 24; + } + ]; + }; + + nameservers = [ + "1.1.1.1" # Cloudflare + "1.0.0.1" # Cloudflare + ]; + }; + + system.stateVersion = "24.05"; + }; +} diff --git a/hosts/Testing/default.nix b/hosts/Testing/default.nix new file mode 100644 index 0000000..cc353f6 --- /dev/null +++ b/hosts/Testing/default.nix @@ -0,0 +1,49 @@ +{ config, pkgs, lib, system, ... }: + +{ + config = { + homelab = { + apps = { + freshrss.enable = true; + traefik.enable = true; + }; + virtualisation.guest.enable = true; + }; + + networking = { + hostName = "Testing"; + hostId = "aaaa9200"; + domain = "roxanne.depeuter.dev"; + + useDHCP = false; + + enableIPv6 = true; + + defaultGateway = { + address = "192.168.0.1"; + interface = "ens18"; + }; + + # Open ports in the firewall. + firewall = { + enable = true; + }; + + interfaces.ens18 = { + ipv4.addresses = [ + { + address = "192.168.0.92"; + prefixLength = 24; + } + ]; + }; + + nameservers = [ + "1.1.1.1" # Cloudflare + "1.0.0.1" # Cloudflare + ]; + }; + + system.stateVersion = "24.05"; + }; +} diff --git a/hosts/Vaultwarden/default.nix b/hosts/Vaultwarden/default.nix new file mode 100644 index 0000000..5ded575 --- /dev/null +++ b/hosts/Vaultwarden/default.nix @@ -0,0 +1,49 @@ +{ config, pkgs, lib, system, ... }: + +{ + config = { + homelab = { + apps.vaultwarden = { + enable = true; + domain = "https://vault.depeuter.dev"; + name = "Hugo's Vault"; + }; + virtualisation.guest.enable = true; + + users.admin = { + enable = true; + authorizedKeys = [ + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJnihoyozOCnm6T9OzL2xoMeMZckBYR2w43us68ABA93" + ]; + }; + }; + + networking = { + hostId = "aaaa1300"; + domain = "depeuter.dev"; + + enableIPv6 = true; + + useDHCP = false; + defaultGateway = { + address = "192.168.0.1"; + interface = "ens18"; + }; + interfaces.ens18 = { + ipv4.addresses = [ + { + address = "192.168.0.22"; + prefixLength = 24; + } + ]; + }; + + nameservers = [ + "1.1.1.1" # Cloudflare + "1.0.0.1" # Cloudflare + ]; + }; + + system.stateVersion = "24.05"; + }; +} diff --git a/modules/common/default.nix b/modules/common/default.nix index 60e4048..eab0450 100644 --- a/modules/common/default.nix +++ b/modules/common/default.nix @@ -1,6 +1,7 @@ { imports = [ ./docker.nix + ./gitops.nix ./monitoring.nix ./nfs.nix ./traefik.nix @@ -11,6 +12,7 @@ homelab = { services.openssh.enable = true; users.admin.enable = true; + gitops.enable = true; }; nix.settings.experimental-features = [ diff --git a/modules/common/gitops.nix b/modules/common/gitops.nix new file mode 100644 index 0000000..b2731e0 --- /dev/null +++ b/modules/common/gitops.nix @@ -0,0 +1,141 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.homelab.gitops; + + updateScript = pkgs.writeShellApplication { + name = "homelab-gitops-update"; + runtimeInputs = [ pkgs.git pkgs.nixos-rebuild pkgs.jq pkgs.coreutils ]; + text = '' + set -euo pipefail + + REMOTE_URL="${cfg.repoUrl}" + BRANCH="${cfg.branch}" + + echo "Checking remote hash for $REMOTE_URL branch $BRANCH..." + + # Fetch remote hash, fallback to unknown if it fails + REMOTE_HASH=$(git ls-remote "$REMOTE_URL" "refs/heads/$BRANCH" | awk '{print $1}' || true) + + if [ -z "$REMOTE_HASH" ]; then + echo "WARNING: Could not fetch remote hash. Forcing rebuild to be safe." + REMOTE_HASH="unknown_remote" + fi + + LOCAL_HASH="unknown_local" + if [ -f /run/current-system/configurationRevision ]; then + LOCAL_HASH=$(cat /run/current-system/configurationRevision) + fi + + echo "Remote hash: $REMOTE_HASH" + echo "Local hash: $LOCAL_HASH" + + if [ "$REMOTE_HASH" = "$LOCAL_HASH" ] && [ "$REMOTE_HASH" != "unknown_remote" ] && [ "$LOCAL_HASH" != "unknown" ]; then + echo "Hashes match. No update needed." + exit 0 + fi + + echo "Hashes differ or unknown. Triggering nixos-rebuild..." + + # Trigger the build and switch + nixos-rebuild switch --flake "git+$REMOTE_URL?ref=$BRANCH" + + echo "Update successful." + ''; + }; + +in { + options.homelab.gitops = { + enable = lib.mkEnableOption "Custom GitOps native deployment system"; + + repoUrl = lib.mkOption { + type = lib.types.str; + default = "https://git.depeuter.dev/Bos55/nix-config.git"; + description = "The repository URL to pull configurations from."; + }; + + branch = lib.mkOption { + type = lib.types.str; + default = "v2"; + description = "The branch to deploy."; + }; + + useBuilder = lib.mkOption { + type = lib.types.bool; + default = true; + description = "Whether to use the central Builder host to compile packages."; + }; + }; + + config = lib.mkIf cfg.enable { + # 1. Systemd Service and Timer for polling + systemd.services.homelab-gitops = { + description = "Homelab GitOps Update Service"; + wants = [ "network-online.target" ]; + after = [ "network-online.target" ]; + serviceConfig = { + Type = "oneshot"; + ExecStart = "${updateScript}/bin/homelab-gitops-update"; + # Must run as root to rebuild the system + User = "root"; + }; + }; + + systemd.timers.homelab-gitops = { + description = "Timer for Homelab GitOps Update Service"; + wantedBy = [ "timers.target" ]; + timerConfig = { + OnBootSec = "5m"; + OnUnitActiveSec = "5m"; + RandomizedDelaySec = "30s"; + }; + }; + + # 2. Webhook listener for instant trigger + sops.secrets."webhook-secret" = {}; + + services.webhook = { + enable = true; + port = 9000; + hooks = { + gitops = { + execute-command = "${pkgs.systemd}/bin/systemctl"; + pass-arguments-to-command = [ + { source = "string"; name = "start"; } + { source = "string"; name = "homelab-gitops.service"; } + ]; + trigger-rule = { + match = { + type = "payload-hash-sha256"; + secret = "{{ getenv \"WEBHOOK_SECRET\" }}"; + parameter = { + source = "header"; + name = "X-Forgejo-Signature"; + }; + }; + }; + }; + }; + }; + + # Inject the secret as an environment variable into the webhook service + systemd.services.webhook.serviceConfig.EnvironmentFile = config.sops.secrets."webhook-secret".path; + + # 3. Builder Configuration + sops.secrets."builder-ssh-key" = lib.mkIf cfg.useBuilder {}; + + nix.buildMachines = lib.mkIf cfg.useBuilder [ + { + hostName = "builder.depeuter.dev"; # Must be routable from nodes + system = "x86_64-linux"; + sshUser = "builder"; + sshKey = config.sops.secrets."builder-ssh-key".path; + maxJobs = 4; + speedFactor = 2; + supportedFeatures = [ "nixos-test" "benchmark" "big-parallel" "kvm" ]; + } + ]; + + nix.distributedBuilds = lib.mkIf cfg.useBuilder true; + }; +}