feat(gitops): implement native custom gitops solution with webhooks
This commit is contained in:
parent
501cda6402
commit
78a20fbe00
25 changed files with 1804 additions and 18 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
.idea
|
||||
138
NixOS GitOps Migration Specification.md
Normal file
138
NixOS GitOps Migration Specification.md
Normal file
|
|
@ -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 \<service\>.
|
||||
* **Docker Healthchecks:** docker inspect \--format='{{json .State.Health.Status}}' \<container\>.
|
||||
* **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.
|
||||
102
flake.lock
generated
Normal file
102
flake.lock
generated
Normal file
|
|
@ -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
|
||||
}
|
||||
21
flake.nix
21
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";
|
||||
})
|
||||
];
|
||||
};
|
||||
|
|
|
|||
41
hosts/ACE/default.nix
Normal file
41
hosts/ACE/default.nix
Normal file
|
|
@ -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";
|
||||
};
|
||||
}
|
||||
90
hosts/Binnenpost/default.nix
Normal file
90
hosts/Binnenpost/default.nix
Normal file
|
|
@ -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";
|
||||
};
|
||||
}
|
||||
50
hosts/Builder/default.nix
Normal file
50
hosts/Builder/default.nix
Normal file
|
|
@ -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";
|
||||
};
|
||||
}
|
||||
72
hosts/Development/default.nix
Normal file
72
hosts/Development/default.nix
Normal file
|
|
@ -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;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
45
hosts/Gitea/default.nix
Normal file
45
hosts/Gitea/default.nix
Normal file
|
|
@ -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";
|
||||
};
|
||||
}
|
||||
263
hosts/Ingress/default.nix
Normal file
263
hosts/Ingress/default.nix
Normal file
|
|
@ -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";
|
||||
};
|
||||
}
|
||||
0
hosts/Isabel/.keep
Normal file
0
hosts/Isabel/.keep
Normal file
32
hosts/Isabel/dashboard/config/bookmarks.yaml
Normal file
32
hosts/Isabel/dashboard/config/bookmarks.yaml
Normal file
|
|
@ -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
|
||||
|
||||
30
hosts/Isabel/dashboard/config/services.yaml
Normal file
30
hosts/Isabel/dashboard/config/services.yaml
Normal file
|
|
@ -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
|
||||
|
||||
|
||||
255
hosts/Isabel/default.nix
Normal file
255
hosts/Isabel/default.nix
Normal file
|
|
@ -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;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
39
hosts/Isabel/hardware-configuration.nix
Normal file
39
hosts/Isabel/hardware-configuration.nix
Normal file
|
|
@ -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.<interface>.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;
|
||||
}
|
||||
119
hosts/Niko/default.nix
Normal file
119
hosts/Niko/default.nix
Normal file
|
|
@ -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";
|
||||
}
|
||||
53
hosts/Niko/hardware-configuration.nix
Normal file
53
hosts/Niko/hardware-configuration.nix
Normal file
|
|
@ -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;
|
||||
}
|
||||
48
hosts/Production/default.nix
Normal file
48
hosts/Production/default.nix
Normal file
|
|
@ -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";
|
||||
};
|
||||
}
|
||||
48
hosts/ProductionArr/default.nix
Normal file
48
hosts/ProductionArr/default.nix
Normal file
|
|
@ -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";
|
||||
};
|
||||
}
|
||||
98
hosts/ProductionGPU/default.nix
Normal file
98
hosts/ProductionGPU/default.nix
Normal file
|
|
@ -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;
|
||||
};
|
||||
};
|
||||
}
|
||||
36
hosts/Template/default.nix
Normal file
36
hosts/Template/default.nix
Normal file
|
|
@ -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";
|
||||
};
|
||||
}
|
||||
49
hosts/Testing/default.nix
Normal file
49
hosts/Testing/default.nix
Normal file
|
|
@ -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";
|
||||
};
|
||||
}
|
||||
49
hosts/Vaultwarden/default.nix
Normal file
49
hosts/Vaultwarden/default.nix
Normal file
|
|
@ -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";
|
||||
};
|
||||
}
|
||||
|
|
@ -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 = [
|
||||
|
|
|
|||
141
modules/common/gitops.nix
Normal file
141
modules/common/gitops.nix
Normal file
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue