From b2ac4a6ac167b3771a9826696a7a962cb105105c Mon Sep 17 00:00:00 2001 From: goeranh Date: Thu, 30 Apr 2026 17:50:22 +0200 Subject: [PATCH 01/10] haproxy acme rule ordering --- hosts/proxy/default.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosts/proxy/default.nix b/hosts/proxy/default.nix index 40740fb..a6108bb 100644 --- a/hosts/proxy/default.nix +++ b/hosts/proxy/default.nix @@ -475,8 +475,8 @@ prev: name: value: prev + '' - use_backend ${name}_80 if is_${name} http-request redirect scheme https code 301 if !is_acme is_${name} + use_backend ${name}_80 if is_${name} '' ) "" forwards} From 1e5cd756528f79676a85c5de0ef10146b2935748 Mon Sep 17 00:00:00 2001 From: goeranh Date: Thu, 30 Apr 2026 17:59:32 +0200 Subject: [PATCH 02/10] set http mode for port 80 --- hosts/proxy/default.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/hosts/proxy/default.nix b/hosts/proxy/default.nix index a6108bb..8bba097 100644 --- a/hosts/proxy/default.nix +++ b/hosts/proxy/default.nix @@ -452,6 +452,7 @@ frontend http-in bind *:80 + mode http tcp-request connection expect-proxy layer4 if { src 178.104.18.93 } maxconn 60000 backlog 8192 From 855cd7bd9bb200b09036d2d3da3eb339a856b35d Mon Sep 17 00:00:00 2001 From: goeranh Date: Fri, 1 May 2026 23:16:24 +0200 Subject: [PATCH 03/10] fix build failure because of new upstream release --- hosts/wiki/default.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hosts/wiki/default.nix b/hosts/wiki/default.nix index c73a5cb..179780d 100644 --- a/hosts/wiki/default.nix +++ b/hosts/wiki/default.nix @@ -201,8 +201,8 @@ #### https://www.mediawiki.org/wiki/Extension:UserMerge UserMerge = pkgs.fetchzip { # url = "https://extdist.wmflabs.org/dist/extensions/UserMerge-REL1_43-ed4a689.tar.gz"; - url = "https://extdist.wmflabs.org/dist/extensions/UserMerge-REL1_45-437c211.tar.gz"; - sha256 = "sha256-DWdcvubqZkvtywuDEOjui68WYuETt5hGpJJlpZ+pJgE="; + url = "https://extdist.wmflabs.org/dist/extensions/UserMerge-REL1_45-433f6c2.tar.gz"; + sha256 = "sha256-JyY4pJNBKQ9bOKrilPWCheZ5ihWwPM6ZJ0qHsZ3coPk="; }; }; From d0a8fb0c091f59d1139accb72d7857a06df6e0d1 Mon Sep 17 00:00:00 2001 From: goeranh Date: Fri, 1 May 2026 23:54:32 +0200 Subject: [PATCH 04/10] enable nginx access logs for now --- hosts/redmine/default.nix | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hosts/redmine/default.nix b/hosts/redmine/default.nix index 3e3e183..2bbd398 100644 --- a/hosts/redmine/default.nix +++ b/hosts/redmine/default.nix @@ -134,9 +134,9 @@ #### Der StuRa speichert nicht! services.nginx.logError = ''/dev/null emerg''; - services.nginx.appendHttpConfig = '' - access_log off; - ''; + # services.nginx.appendHttpConfig = '' + # access_log off; + # ''; services.nginx.commonHttpConfig = '' real_ip_header proxy_protocol; set_real_ip_from 141.56.51.1/32; From 66d68577101ed1deae245de93775f5b03440ebb0 Mon Sep 17 00:00:00 2001 From: goeranh Date: Sat, 2 May 2026 00:29:06 +0200 Subject: [PATCH 05/10] use nftables on all haproxy host for better blacklisting --- hosts/proxy/default.nix | 96 +++++++++++++++++++++++++++++++++------ hosts/v6proxy/default.nix | 84 ++++++++++++++++++++++++++++++---- 2 files changed, 157 insertions(+), 23 deletions(-) diff --git a/hosts/proxy/default.nix b/hosts/proxy/default.nix index 8bba097..7fa2238 100644 --- a/hosts/proxy/default.nix +++ b/hosts/proxy/default.nix @@ -20,22 +20,49 @@ } ]; defaultGateway.address = "141.56.51.254"; - firewall = { - allowedTCPPorts = [ - 22 - 53 # DNS - 80 - 443 - 1005 - 2142 - ]; - allowedUDPPorts = [ - 53 # DNS - 123 # NTP - ]; - }; + firewall.enable = false; nftables = { enable = true; + ruleset = '' + table inet filter { + set blacklist4 { + type ipv4_addr + flags interval + # manage: nft add element inet filter blacklist4 { 1.2.3.0/24 } + } + + set blacklist6 { + type ipv6_addr + flags interval + # manage: nft add element inet filter blacklist6 { 2001:db8::/32 } + } + + chain input { + type filter hook input priority filter; policy drop; + + iif "lo" accept + ct state established,related accept + + ip saddr @blacklist4 drop + ip6 saddr @blacklist6 drop + + # public ports + tcp dport { 80, 443, 1005, 2142 } accept + + # lan-only: dns and ntp + ip saddr 141.56.51.0/24 tcp dport 53 accept + ip saddr 141.56.51.0/24 udp dport { 53, 123 } accept + } + + chain forward { + type filter hook forward priority filter; policy drop; + } + + chain output { + type filter hook output priority filter; policy accept; + } + } + ''; }; }; @@ -611,6 +638,47 @@ stura.monitoring.extraLogInputs = [ "haproxy_geoip" ]; + users.users.root.packages = [ + (pkgs.writeShellScriptBin "nft-blacklist" '' + set -euo pipefail + + usage() { + echo "Usage: nft-blacklist " + echo " add - add entry to blacklist set" + echo " del - remove entry from blacklist set" + exit 1 + } + + [[ $# -ne 2 ]] && usage + + ACTION="$1" + ADDR="$2" + + if [[ "$ADDR" == *:* ]]; then + SET="blacklist6" + elif [[ "$ADDR" == *.* ]]; then + SET="blacklist4" + else + echo "Error: cannot determine address family for '$ADDR'" >&2 + exit 1 + fi + + case "$ACTION" in + add) + ${pkgs.nftables}/bin/nft add element inet filter "$SET" "{ $ADDR }" + echo "Added $ADDR to $SET" + ;; + del) + ${pkgs.nftables}/bin/nft delete element inet filter "$SET" "{ $ADDR }" + echo "Removed $ADDR from $SET" + ;; + *) + usage + ;; + esac + '') + ]; + environment.systemPackages = with pkgs; [ ]; diff --git a/hosts/v6proxy/default.nix b/hosts/v6proxy/default.nix index 270ef2c..39e2521 100644 --- a/hosts/v6proxy/default.nix +++ b/hosts/v6proxy/default.nix @@ -38,16 +38,44 @@ "9.9.9.9" "1.1.1.1" ]; - firewall = { - enable = true; - allowedTCPPorts = [ - 22 - 80 - 443 - ]; - }; + firewall.enable = false; nftables = { enable = true; + ruleset = '' + table inet filter { + set blacklist4 { + type ipv4_addr + flags interval + # manage at runtime: nft add element inet filter blacklist4 { 1.2.3.0/24 } + } + + set blacklist6 { + type ipv6_addr + flags interval + # manage at runtime: nft add element inet filter blacklist6 { 2001:db8::/32 } + } + + chain input { + type filter hook input priority filter; policy drop; + + iif "lo" accept + ct state established,related accept + + ip saddr @blacklist4 drop + ip6 saddr @blacklist6 drop + + tcp dport { 22, 80, 443 } accept + } + + chain forward { + type filter hook forward priority filter; policy drop; + } + + chain output { + type filter hook output priority filter; policy accept; + } + } + ''; }; }; @@ -102,7 +130,45 @@ }; }; - environment.systemPackages = with pkgs; [ + users.users.root.packages = [ + (pkgs.writeShellScriptBin "nft-blacklist" '' + set -euo pipefail + + usage() { + echo "Usage: nft-blacklist " + echo " add - add entry to blacklist set" + echo " del - remove entry from blacklist set" + exit 1 + } + + [[ $# -ne 2 ]] && usage + + ACTION="$1" + ADDR="$2" + + if [[ "$ADDR" == *:* ]]; then + SET="blacklist6" + elif [[ "$ADDR" == *.* ]]; then + SET="blacklist4" + else + echo "Error: cannot determine address family for '$ADDR'" >&2 + exit 1 + fi + + case "$ACTION" in + add) + ${pkgs.nftables}/bin/nft add element inet filter "$SET" "{ $ADDR }" + echo "Added $ADDR to $SET" + ;; + del) + ${pkgs.nftables}/bin/nft delete element inet filter "$SET" "{ $ADDR }" + echo "Removed $ADDR from $SET" + ;; + *) + usage + ;; + esac + '') ]; system.stateVersion = "25.11"; From 05c8508c18a610134fc9449c4b591c73c196fe6c Mon Sep 17 00:00:00 2001 From: goeranh Date: Wed, 29 Apr 2026 13:03:45 +0200 Subject: [PATCH 06/10] run gradient cache --- .sops.yaml | 43 ++----- flake.lock | 113 ++++++++++++----- flake.nix | 17 ++- hosts/gradient/README.md | 210 +++++++++++++++++++++++++++++++ hosts/gradient/default.nix | 86 +++++++++++++ hosts/gradient/secrets.sops.yaml | 27 ++++ keys/.gitignore | 18 --- keys/README.md | 40 ------ keys/hosts/.gitkeep | 0 keys/users/.gitkeep | 0 10 files changed, 424 insertions(+), 130 deletions(-) create mode 100644 hosts/gradient/README.md create mode 100644 hosts/gradient/default.nix create mode 100644 hosts/gradient/secrets.sops.yaml delete mode 100644 keys/.gitignore delete mode 100644 keys/README.md delete mode 100644 keys/hosts/.gitkeep delete mode 100644 keys/users/.gitkeep diff --git a/.sops.yaml b/.sops.yaml index fd76726..31d03aa 100644 --- a/.sops.yaml +++ b/.sops.yaml @@ -1,38 +1,11 @@ -# SOPS configuration for StuRa HTW Dresden infrastructure -# -# This file defines which keys can decrypt which secrets. -# Add GPG public keys (.asc files) or age keys to keys/hosts/ and keys/users/ -# to grant decryption access to hosts and users respectively. - keys: - # Admin/user keys - add GPG public keys here - # Example: - # - &user_admin_key age1... or pgp fingerprint - - # Host keys - add host-specific keys here - # Example: - # - &host_proxy_key age1... or pgp fingerprint - # - &host_git_key age1... or pgp fingerprint - -# Define which keys can access which files -creation_rules: - # Default rule: all secrets can be decrypted by admin keys - - path_regex: secrets/.*\.yaml$ - # key_groups: - # - pgp: - # - *user_admin_key - # - age: - # - *user_admin_key + - &goeranh age16m8vvvpw4azfy6gygtstyyj6nd2sf848f7f7argaghwhct38muxsgxpeek + - &gradient age1kfxhahmxprheer63shv68slpmk5qz29nyx3kp4q6n879zz9ha34q04n50x # Host-specific secrets (example) - # - path_regex: secrets/proxy/.*\.yaml$ - # key_groups: - # - pgp: - # - *user_admin_key - # - *host_proxy_key - - # - path_regex: secrets/git/.*\.yaml$ - # key_groups: - # - pgp: - # - *user_admin_key - # - *host_git_key +creation_rules: + - path_regex: hosts/gradient/secrets.sops.yaml$ + key_groups: + - age: + - *gradient + - *goeranh diff --git a/flake.lock b/flake.lock index 4e0f8b8..c764d39 100644 --- a/flake.lock +++ b/flake.lock @@ -8,7 +8,9 @@ "flake-parts": "flake-parts", "flake-utils": "flake-utils", "napalm": "napalm", - "nixpkgs": "nixpkgs", + "nixpkgs": [ + "nixpkgs" + ], "pyproject-build-systems": "pyproject-build-systems", "pyproject-nix": "pyproject-nix", "systems": "systems", @@ -77,6 +79,21 @@ "type": "gitlab" } }, + "crane": { + "locked": { + "lastModified": 1777335812, + "narHash": "sha256-bEg5xoAxAwsyfnGhkEX7RJViTIBIYPd8ISg4O1c0HFc=", + "owner": "ipetkov", + "repo": "crane", + "rev": "5e0fb2f64edff2822249f21293b8304dedaaf676", + "type": "github" + }, + "original": { + "owner": "ipetkov", + "repo": "crane", + "type": "github" + } + }, "disko": { "inputs": { "nixpkgs": [ @@ -168,6 +185,24 @@ "type": "github" } }, + "flake-utils_2": { + "inputs": { + "systems": "systems_2" + }, + "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" + } + }, "git-hooks": { "inputs": { "flake-compat": [ @@ -216,12 +251,34 @@ "type": "github" } }, + "gradient": { + "inputs": { + "crane": "crane", + "flake-utils": "flake-utils_2", + "nixpkgs": "nixpkgs" + }, + "locked": { + "lastModified": 1777458607, + "narHash": "sha256-KcPvJ3+MFyDBf8GyE4zThu3u/LnXYXTB8X3V9s6R9/0=", + "owner": "wavelens", + "repo": "gradient", + "rev": "62f3132a90d9bd4fadb4688d20a684a464d6e8dc", + "type": "github" + }, + "original": { + "owner": "wavelens", + "repo": "gradient", + "type": "github" + } + }, "mailserver": { "inputs": { "blobs": "blobs", "flake-compat": "flake-compat_2", "git-hooks": "git-hooks", - "nixpkgs": "nixpkgs_2" + "nixpkgs": [ + "nixpkgs" + ] }, "locked": { "lastModified": 1773912645, @@ -266,11 +323,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1771848320, - "narHash": "sha256-0MAd+0mun3K/Ns8JATeHT1sX28faLII5hVLq0L3BdZU=", + "lastModified": 1775423009, + "narHash": "sha256-vPKLpjhIVWdDrfiUM8atW6YkIggCEKdSAlJPzzhkQlw=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "2fc6539b481e1d2569f25f8799236694180c0993", + "rev": "68d8aa3d661f0e6bd5862291b5bb263b2a6595c9", "type": "github" }, "original": { @@ -297,27 +354,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1773831496, - "narHash": "sha256-JW2/QPyCVzmouqEp1H9kNa8JXd7xEhlam9sy3TYfhDY=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "826430a188181a750ffa5948daff334039c5d741", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixos-25.11-small", - "repo": "nixpkgs", - "type": "github" - } - }, - "nixpkgs_3": { - "locked": { - "lastModified": 1776734388, - "narHash": "sha256-vl3dkhlE5gzsItuHoEMVe+DlonsK+0836LIRDnm6MXQ=", + "lastModified": 1777077449, + "narHash": "sha256-AIiMJiqvGrN4HyLEbKAoCSRRYn0rnlW5VbKNIMIYqm4=", "owner": "nixos", "repo": "nixpkgs", - "rev": "10e7ad5bbcb421fe07e3a4ad53a634b0cd57ffac", + "rev": "a4bf06618f0b5ee50f14ed8f0da77d34ecc19160", "type": "github" }, "original": { @@ -381,8 +422,9 @@ "inputs": { "authentik": "authentik", "disko": "disko", + "gradient": "gradient", "mailserver": "mailserver", - "nixpkgs": "nixpkgs_3", + "nixpkgs": "nixpkgs_2", "sops": "sops" } }, @@ -393,11 +435,11 @@ ] }, "locked": { - "lastModified": 1776771786, - "narHash": "sha256-DRFGPfFV6hbrfO9a1PH1FkCi7qR5FgjSqsQGGvk1rdI=", + "lastModified": 1777338324, + "narHash": "sha256-bc+ZZCmOTNq86/svGnw0tVpH7vJaLYvGLLKFYP08Q8E=", "owner": "Mic92", "repo": "sops-nix", - "rev": "bef289e2248991f7afeb95965c82fbcd8ff72598", + "rev": "8eaee5c45428b28b8c47a83e4c09dccec5f279b5", "type": "github" }, "original": { @@ -421,6 +463,21 @@ "type": "github" } }, + "systems_2": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, "uv2nix": { "inputs": { "nixpkgs": [ diff --git a/flake.nix b/flake.nix index f0f177e..3f1e8af 100644 --- a/flake.nix +++ b/flake.nix @@ -6,9 +6,11 @@ nixpkgs.url = "github:nixos/nixpkgs/nixos-25.11"; authentik = { url = "github:nix-community/authentik-nix"; + inputs.nixpkgs.follows = "nixpkgs"; }; mailserver = { url = "git+https://gitlab.com/simple-nixos-mailserver/nixos-mailserver?ref=nixos-25.11"; + inputs.nixpkgs.follows = "nixpkgs"; }; sops = { url = "github:Mic92/sops-nix"; @@ -18,6 +20,9 @@ url = "github:nix-community/disko"; inputs.nixpkgs.follows = "nixpkgs"; }; + gradient = { + url = "github:wavelens/gradient"; + }; }; outputs = @@ -28,6 +33,7 @@ mailserver, disko, sops, + gradient }: let sshkeys = [ @@ -44,15 +50,6 @@ pkgs = nixpkgs.legacyPackages.x86_64-linux; in pkgs.mkShell { - # Import GPG keys from keys directory - sopsPGPKeyDirs = [ - "${toString ./.}/keys/hosts" - "${toString ./.}/keys/users" - ]; - - # Isolate sops GPG keys to .git/gnupg (optional) - # sopsCreateGPGHome = true; - nativeBuildInputs = [ sops.packages.x86_64-linux.sops-import-keys-hook ]; @@ -197,6 +194,8 @@ disko.nixosModules.disko authentik.nixosModules.default mailserver.nixosModules.mailserver + gradient.nixosModules.default + sops.nixosModules.sops { _module.args = { inherit self modulesPath; }; } diff --git a/hosts/gradient/README.md b/hosts/gradient/README.md new file mode 100644 index 0000000..d8116f3 --- /dev/null +++ b/hosts/gradient/README.md @@ -0,0 +1,210 @@ +# Git Host - Forgejo + +Forgejo git server at 141.56.51.7 running in an LXC container. + +## Overview + +- **Hostname**: git +- **FQDN**: git.adm.htw.stura-dresden.de +- **IP Address**: 141.56.51.7 +- **Type**: Proxmox LXC Container +- **Services**: Forgejo, Nginx (reverse proxy), OpenSSH + +## Services + +### Forgejo + +Forgejo is a self-hosted Git service (fork of Gitea) providing: +- Git repository hosting +- Web interface for repository management +- Issue tracking +- Pull requests +- OAuth2 integration support + +**Configuration**: +- **Socket**: `/run/forgejo/forgejo.sock` (Unix socket) +- **Root URL**: https://git.adm.htw.stura-dresden.de +- **Protocol**: HTTP over Unix socket (Nginx handles TLS) + +### Nginx + +Nginx acts as a reverse proxy between the network and Forgejo: +- Receives HTTPS requests (TLS termination) +- Forwards to Forgejo via Unix socket +- Manages ACME/Let's Encrypt certificates +- WebSocket support enabled for live updates + +### OAuth2 Auto-Registration + +OAuth2 client auto-registration is enabled: +- `ENABLE_AUTO_REGISTRATION = true` +- `REGISTER_EMAIL_CONFIRM = false` +- Username field: email + +This allows users to register automatically via OAuth2 providers without manual approval. + +## Deployment + +See the [main README](../../README.md) for deployment methods. + +### Initial Installation + +**Using nixos-anywhere:** +```bash +nix run github:nix-community/nixos-anywhere -- --flake .#git --target-host root@141.56.51.7 +``` + +**Using container tarball:** +```bash +nix build .#containers-git +scp result/tarball/nixos-system-x86_64-linux.tar.xz root@proxmox-host:/var/lib/vz/template/cache/ +pct create 107 /var/lib/vz/template/cache/nixos-system-x86_64-linux.tar.xz \ + --hostname git \ + --net0 name=eth0,bridge=vmbr0,ip=141.56.51.7/24,gw=141.56.51.254 \ + --memory 2048 \ + --cores 2 \ + --rootfs local-lvm:8 \ + --unprivileged 1 \ + --features nesting=1 +pct start 107 +``` + +### Updates + +```bash +# From local machine +nixos-rebuild switch --flake .#git --target-host root@141.56.51.7 + +# Or use auto-generated script +nix run .#git-update +``` + +## Post-Deployment Steps + +After deploying for the first time: + +1. **Access the web interface:** + ``` + https://git.adm.htw.stura-dresden.de + ``` + +2. **Complete initial setup:** + - Create the first admin account via web UI + - Configure any additional settings + - Set up SSH keys for git access + +3. **Configure OAuth2 (optional):** + - If using an external identity provider (e.g., authentik) + - Add OAuth2 application in the provider + - Configure OAuth2 settings in Forgejo admin panel + - Auto-registration is already enabled in configuration + +4. **Set up repositories:** + - Create organizations + - Create repositories + - Configure access permissions + +## Integration with Proxy + +The central proxy at 141.56.51.1 handles: +- **SNI routing**: Inspects TLS handshake and routes HTTPS traffic for git.adm.htw.stura-dresden.de +- **HTTP routing**: Routes HTTP traffic based on Host header +- **ACME challenges**: Forwards `/.well-known/acme-challenge/` requests to this host for Let's Encrypt verification +- **Auto-redirect**: Redirects HTTP to HTTPS (except ACME challenges) + +This host handles its own TLS certificates via ACME. The proxy passes through encrypted traffic without decryption. + +## Troubleshooting + +### Forgejo socket permissions + +If Forgejo fails to start or Nginx cannot connect: + +```bash +# Check socket exists +ls -l /run/forgejo/forgejo.sock + +# Check Forgejo service status +systemctl status forgejo + +# Check Nginx service status +systemctl status nginx + +# View Forgejo logs +journalctl -u forgejo -f +``` + +**Solution**: Ensure the Forgejo user has proper permissions and the socket path is correct in both Forgejo and Nginx configurations. + +### Nginx proxy configuration + +If the web interface is unreachable: + +```bash +# Check Nginx configuration +nginx -t + +# View Nginx error logs +journalctl -u nginx -f + +# Test socket connection +curl --unix-socket /run/forgejo/forgejo.sock http://localhost/ +``` + +**Solution**: Verify the `proxyPass` directive in Nginx configuration points to the correct Unix socket. + +### SSH access issues + +If git operations over SSH fail: + +```bash +# Check SSH service +systemctl status sshd + +# Test SSH connection +ssh -T git@git.adm.htw.stura-dresden.de + +# Check Forgejo SSH settings +cat /var/lib/forgejo/custom/conf/app.ini | grep -A 5 "\[server\]" +``` + +**Solution**: Ensure SSH keys are properly added to user accounts and SSH daemon is running. + +### ACME certificate issues + +If HTTPS is not working: + +```bash +# Check ACME certificate status +systemctl status acme-git.adm.htw.stura-dresden.de + +# View ACME logs +journalctl -u acme-git.adm.htw.stura-dresden.de -f + +# Manually trigger certificate renewal +systemctl start acme-git.adm.htw.stura-dresden.de +``` + +**Solution**: Verify DNS points to proxy (141.56.51.1) and proxy is forwarding ACME challenges correctly. + +## Files and Directories + +- **Configuration**: `/nix/store/.../forgejo/` (managed by Nix) +- **Data directory**: `/var/lib/forgejo/` +- **Custom config**: `/var/lib/forgejo/custom/conf/app.ini` +- **Repositories**: `/var/lib/forgejo/data/gitea-repositories/` +- **Socket**: `/run/forgejo/forgejo.sock` + +## Network + +- **Interface**: eth0 (LXC container) +- **IP**: 141.56.51.7/24 +- **Gateway**: 141.56.51.254 +- **Firewall**: Ports 22, 80, 443 allowed + +## See Also + +- [Main README](../../README.md) - Deployment methods and architecture +- [Proxy README](../proxy/README.md) - How the central proxy routes traffic +- [Forgejo Documentation](https://forgejo.org/docs/latest/) +- [NixOS Forgejo Options](https://search.nixos.org/options?query=services.forgejo) diff --git a/hosts/gradient/default.nix b/hosts/gradient/default.nix new file mode 100644 index 0000000..cb0e55f --- /dev/null +++ b/hosts/gradient/default.nix @@ -0,0 +1,86 @@ +{ + config, + lib, + pkgs, + modulesPath, + ... +}: +{ + imports = [ + "${modulesPath}/virtualisation/proxmox-lxc.nix" + ]; + + sops = { + defaultSopsFile = ./secrets.sops.yaml; + secrets = { + "gradient-jwt".owner = "gradient"; + "gradient-crypt".owner = "gradient"; + "gradient-worker".owner = "gradient-worker"; + }; + }; + + networking = { + hostName = "gradient"; + fqdn = "gradient.adm.htw.stura-dresden.de"; + interfaces.eth0.ipv4.addresses = [ + { + address = "141.56.51.127"; + prefixLength = 24; + } + ]; + + defaultGateway = { + address = "141.56.51.254"; + interface = "eth0"; + }; + firewall.allowedTCPPorts = [ + 80 + 443 + ]; + }; + + services = { + openssh.enable = true; + + gradient = { + enable = true; + frontend.enable = true; + domain = "${config.networking.fqdn}"; + jwtSecretFile = "/run/secrets/gradient-jwt"; + cryptSecretFile = "/run/secrets/gradient-crypt"; + configurePostgres = true; + configureNginx = true; + # serveCache = true; + reportErrors = true; # optional: will send crash reports to us + }; + + nginx.virtualHosts."${config.networking.fqdn}".listen = [ + { + port = 80; + addr = "0.0.0.0"; + } + { + port = 443; + addr = "0.0.0.0"; + ssl = true; + proxyProtocol = true; + } + ]; + + gradient.worker = { + enable = true; + serverUrl = "ws://127.0.0.1:3000/proto"; + workerId = "8f56dd3a-5698-4512-8bf7-ab8dcfaed46c"; + peersFile = "/run/secrets/gradient-worker-peers"; + capabilities = { + fetch = true; + eval = true; + build = true; + }; + }; + }; + + + system.stateVersion = "25.11"; + +} diff --git a/hosts/gradient/secrets.sops.yaml b/hosts/gradient/secrets.sops.yaml new file mode 100644 index 0000000..6e4f9fa --- /dev/null +++ b/hosts/gradient/secrets.sops.yaml @@ -0,0 +1,27 @@ +gradient-jwt: ENC[AES256_GCM,data:0RgUOHbz5qtBOE1+wldyhzE6b4275JTkzdjgQBbVUnHtNVvqmQUs94JGfB2HRteVJS2pRmRxyh+YYUpuErkmaw==,iv:C6AqWjVs6MGjTJ/QEFq9kz7kSglMXi+rtlmkEK0i4r0=,tag:HvmCN947JveYFDITZfAEMA==,type:str] +gradient-crypt: ENC[AES256_GCM,data:j6KRaxQItKtolZXPxN1Rp4NalX5rYnHvQzL/R0naobgM2nMUiOJJeKiZ5yooXbaNC1wrwWNfMrNDYkm8bxVJeA==,iv:2wiiyJu3u9cEwwos0DhgKiwp0qYSw1z6MdOvpWsf+Is=,tag:GOfN78rlovnPXgiCAE9diA==,type:str] +gradient-worker: ENC[AES256_GCM,data:IktOl14QzBee16ZxZZMmseMomlyF+teJoxsNmqDXcPHq4ZZv7QWDQAjIct4hz1CHofaBfXzVM372WSLTX51/zw==,iv:OZsC9EX4fRUv7Q9AbnXBaskX9hS/wgFctOepz39NyDU=,tag:GHPx4yXb7ta5pj4+yI2PRQ==,type:str] +sops: + age: + - recipient: age1kfxhahmxprheer63shv68slpmk5qz29nyx3kp4q6n879zz9ha34q04n50x + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB2OHc0aWRocmx2QkUzcnFa + T3Z5MWp3U2I1c2RZMytnU1hkd0p4ZHNWSW5vCmFuZHBJYjMrSUwranVDNHR1RmFr + dVhSVmdPUGd1czFjM3p3dlBLcTU0T1UKLS0tIDl1eHBkL1lBUWh0ckhya1dJTjdY + WmhPVEl3ZytOdmdaQ3pkN2lLTnRPMzAKCKj7VvRPTBXfsqa6FnJi3ZkWNUXN8JG8 + NlcK9QL/pMoExpoLHfw8ram4Y2i9up4oONeA2iKR12Dh86Y8RUUJfg== + -----END AGE ENCRYPTED FILE----- + - recipient: age16m8vvvpw4azfy6gygtstyyj6nd2sf848f7f7argaghwhct38muxsgxpeek + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBRVXNoVktTL0VvdVIrVWx6 + cFpLMnhJcmFWYlVKWWZIVUR1NGMzRWhOWngwClVRZ092dDFEMHJ3d3JkdFkwVVI2 + YW9BK0hBNnB6UmM0bzBYYVNqS0QxcFEKLS0tIHljSm03TTRjTVlSam4xN2NhMUJ6 + aDNFUi9SL1BhZHMxVUFkTzR6bk16cWsKeS3Y8b/WlvdgmY5yLjTfTHJwBZoZ7RU8 + GPLB8ezNB3U7XxO05hwlUQJbTkMVhSzu+nKfEavdS1KMoXaxfxhrwA== + -----END AGE ENCRYPTED FILE----- + lastmodified: "2026-04-22T08:34:30Z" + mac: ENC[AES256_GCM,data:74FyYHQ/bMZ3wxodMlvAXYl2UWNkv8arWSDeJwCEfRWz05bzXWy6UaMLWc+dSoqJsVvT3SRVuBMrtilsckVqCVQ4C96c730IVWB/b5juIXtEsp1JiWgS+F3yC992HDGmoGnAkSE/vzZBu3DRA8/eMwkoTtGscpDhnAzUVrkCNUk=,iv:BzhopR3jxZyKZhwRxh1lKaIaGgE5IbIe/AG35D1juZA=,tag:frnRzapbiU49TpkISZ4GEQ==,type:str] + unencrypted_suffix: _unencrypted + version: 3.12.1 diff --git a/keys/.gitignore b/keys/.gitignore deleted file mode 100644 index ce29a38..0000000 --- a/keys/.gitignore +++ /dev/null @@ -1,18 +0,0 @@ -# Prevent accidental commit of private keys -*.key -*.priv -*.private -*_priv -*-priv -*.sec -*secret* - -# Only allow public keys -!*.asc -!*.gpg -!*.pub -!*.age - -# Allow this gitignore and README -!.gitignore -!README.md diff --git a/keys/README.md b/keys/README.md deleted file mode 100644 index 587f370..0000000 --- a/keys/README.md +++ /dev/null @@ -1,40 +0,0 @@ -# Keys Directory - -This directory contains GPG/age public keys for sops encryption. - -## Structure - -- `hosts/` - Host-specific public keys (for servers to decrypt their own secrets) -- `users/` - User/admin public keys (for team members to decrypt secrets) - -## Adding Keys - -### GPG Keys - -Export your GPG public key: -```bash -gpg --export --armor YOUR_KEY_ID > keys/users/yourname.asc -``` - -Export a host's public key: -```bash -gpg --export --armor HOST_KEY_ID > keys/hosts/hostname.asc -``` - -### Age Keys - -For age keys, save the public key to a file: -```bash -echo "age1..." > keys/users/yourname.age -echo "age1..." > keys/hosts/hostname.age -``` - -## Usage - -When you enter the dev shell (`nix develop`), all keys in these directories will be automatically imported into your GPG keyring via the sops-import-keys-hook. - -## Important - -- Only commit **public** keys (.asc, .age files with public keys) -- Never commit private keys -- Update `.sops.yaml` to reference the fingerprints/keys for access control diff --git a/keys/hosts/.gitkeep b/keys/hosts/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/keys/users/.gitkeep b/keys/users/.gitkeep deleted file mode 100644 index e69de29..0000000 From 20b1103a6c8a9e3ca880930c4a52c87f524765f5 Mon Sep 17 00:00:00 2001 From: goeranh Date: Thu, 30 Apr 2026 15:41:39 +0200 Subject: [PATCH 07/10] set worker log level to debug --- hosts/gradient/default.nix | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/hosts/gradient/default.nix b/hosts/gradient/default.nix index cb0e55f..3701eba 100644 --- a/hosts/gradient/default.nix +++ b/hosts/gradient/default.nix @@ -52,6 +52,21 @@ configureNginx = true; # serveCache = true; reportErrors = true; # optional: will send crash reports to us + + worker = { + enable = true; + serverUrl = "ws://127.0.0.1:3000/proto"; + workerId = "8f56dd3a-5698-4512-8bf7-ab8dcfaed46c"; + peersFile = "/run/secrets/gradient-worker-peers"; + capabilities = { + fetch = true; + eval = true; + build = true; + }; + settings = { + logLevel.default = "debug"; + }; + }; }; nginx.virtualHosts."${config.networking.fqdn}".listen = [ @@ -66,21 +81,7 @@ proxyProtocol = true; } ]; - - gradient.worker = { - enable = true; - serverUrl = "ws://127.0.0.1:3000/proto"; - workerId = "8f56dd3a-5698-4512-8bf7-ab8dcfaed46c"; - peersFile = "/run/secrets/gradient-worker-peers"; - capabilities = { - fetch = true; - eval = true; - build = true; - }; - }; }; - system.stateVersion = "25.11"; - } From b5329ad61f18ec9fd04ef85f018171fe9bceaf76 Mon Sep 17 00:00:00 2001 From: goeranh Date: Sun, 3 May 2026 13:50:47 +0200 Subject: [PATCH 08/10] run gradient server and worker all in one --- flake.lock | 29 +++++++++++++++++++++++------ flake.nix | 8 +++++--- hosts/gradient/default.nix | 12 ++++++++++-- 3 files changed, 38 insertions(+), 11 deletions(-) diff --git a/flake.lock b/flake.lock index c764d39..0a573e0 100644 --- a/flake.lock +++ b/flake.lock @@ -258,11 +258,11 @@ "nixpkgs": "nixpkgs" }, "locked": { - "lastModified": 1777458607, - "narHash": "sha256-KcPvJ3+MFyDBf8GyE4zThu3u/LnXYXTB8X3V9s6R9/0=", + "lastModified": 1777807825, + "narHash": "sha256-LIHC5ayGLbEXY7wBrd71EE12xZDBltYqeE4qdEvWbC0=", "owner": "wavelens", "repo": "gradient", - "rev": "62f3132a90d9bd4fadb4688d20a684a464d6e8dc", + "rev": "0f5779845044d2a39e5f599f781d6cfb9248a219", "type": "github" }, "original": { @@ -323,11 +323,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1775423009, - "narHash": "sha256-vPKLpjhIVWdDrfiUM8atW6YkIggCEKdSAlJPzzhkQlw=", + "lastModified": 1777268161, + "narHash": "sha256-bxrdOn8SCOv8tN4JbTF/TXq7kjo9ag4M+C8yzzIRYbE=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "68d8aa3d661f0e6bd5862291b5bb263b2a6595c9", + "rev": "1c3fe55ad329cbcb28471bb30f05c9827f724c76", "type": "github" }, "original": { @@ -352,6 +352,22 @@ "type": "github" } }, + "nixpkgs-unstable": { + "locked": { + "lastModified": 1777268161, + "narHash": "sha256-bxrdOn8SCOv8tN4JbTF/TXq7kjo9ag4M+C8yzzIRYbE=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "1c3fe55ad329cbcb28471bb30f05c9827f724c76", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, "nixpkgs_2": { "locked": { "lastModified": 1777077449, @@ -425,6 +441,7 @@ "gradient": "gradient", "mailserver": "mailserver", "nixpkgs": "nixpkgs_2", + "nixpkgs-unstable": "nixpkgs-unstable", "sops": "sops" } }, diff --git a/flake.nix b/flake.nix index 3f1e8af..dd25bed 100644 --- a/flake.nix +++ b/flake.nix @@ -4,6 +4,7 @@ inputs = { nixpkgs.url = "github:nixos/nixpkgs/nixos-25.11"; + nixpkgs-unstable.url = "github:nixos/nixpkgs/nixos-unstable"; authentik = { url = "github:nix-community/authentik-nix"; inputs.nixpkgs.follows = "nixpkgs"; @@ -29,12 +30,13 @@ { self, nixpkgs, + nixpkgs-unstable, authentik, mailserver, disko, sops, gradient - }: + }@inputs: let sshkeys = [ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINABEf0jBjtDdezDDtvl1v27l0DbHP2XUgMARTZXC+MR goeranh@node5" @@ -181,7 +183,7 @@ result: input: result // { - "${input}" = nixpkgs.lib.nixosSystem { + "${input}" = nixpkgs-unstable.lib.nixosSystem { system = "x86_64-linux"; modules = let @@ -197,7 +199,7 @@ gradient.nixosModules.default sops.nixosModules.sops { - _module.args = { inherit self modulesPath; }; + _module.args = { inherit self inputs modulesPath; }; } ]; }; diff --git a/hosts/gradient/default.nix b/hosts/gradient/default.nix index 3701eba..09cadee 100644 --- a/hosts/gradient/default.nix +++ b/hosts/gradient/default.nix @@ -1,5 +1,6 @@ { config, + inputs, lib, pkgs, modulesPath, @@ -51,7 +52,7 @@ configurePostgres = true; configureNginx = true; # serveCache = true; - reportErrors = true; # optional: will send crash reports to us + reportErrors = true; worker = { enable = true; @@ -63,12 +64,19 @@ eval = true; build = true; }; + packages.nix = inputs.nixpkgs-unstable.legacyPackages.x86_64-linux.nix; settings = { - logLevel.default = "debug"; + logLevel = { + default = "debug"; + }; }; }; }; + nginx.commonHttpConfig = '' + real_ip_header proxy_protocol; + set_real_ip_from 141.56.51.1/32; + ''; nginx.virtualHosts."${config.networking.fqdn}".listen = [ { port = 80; From 9c50baee2e6c75fcc2910422bfb4a3b6ddff0c67 Mon Sep 17 00:00:00 2001 From: goeranh Date: Sun, 3 May 2026 14:05:26 +0200 Subject: [PATCH 09/10] add gradient nix cache --- default.nix | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/default.nix b/default.nix index 1a1f679..39d3355 100644 --- a/default.nix +++ b/default.nix @@ -40,6 +40,12 @@ in ]; # trusted-users = [ "administration" ]; auto-optimise-store = true; + trusted-substituters = [ + "https://gradient.adm.htw.stura-dresden.de/cache/stura" + ]; + trusted-public-keys = [ + "gradient.adm.htw.stura-dresden.de-stura:rVy7JDos8CTvKJsrotP2/y/ICbCNv69HKy0Dfsgrafs=" + ]; }; optimise.automatic = true; gc = { From d0fc6af26526562338ec2f25c134a47026be6a1a Mon Sep 17 00:00:00 2001 From: goeranh Date: Sun, 3 May 2026 14:13:19 +0200 Subject: [PATCH 10/10] add blake3 hashing experimental feature --- default.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/default.nix b/default.nix index 39d3355..17b85ef 100644 --- a/default.nix +++ b/default.nix @@ -37,6 +37,7 @@ in experimental-features = [ "nix-command" "flakes" + "blake3-hashes" ]; # trusted-users = [ "administration" ]; auto-optimise-store = true;