run gradient cache

This commit is contained in:
goeranh 2026-04-29 13:03:45 +02:00
parent 66d6857710
commit 05c8508c18
No known key found for this signature in database
10 changed files with 424 additions and 130 deletions

View file

@ -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: keys:
# Admin/user keys - add GPG public keys here - &goeranh age16m8vvvpw4azfy6gygtstyyj6nd2sf848f7f7argaghwhct38muxsgxpeek
# Example: - &gradient age1kfxhahmxprheer63shv68slpmk5qz29nyx3kp4q6n879zz9ha34q04n50x
# - &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
# Host-specific secrets (example) # Host-specific secrets (example)
# - path_regex: secrets/proxy/.*\.yaml$ creation_rules:
# key_groups: - path_regex: hosts/gradient/secrets.sops.yaml$
# - pgp: key_groups:
# - *user_admin_key - age:
# - *host_proxy_key - *gradient
- *goeranh
# - path_regex: secrets/git/.*\.yaml$
# key_groups:
# - pgp:
# - *user_admin_key
# - *host_git_key

113
flake.lock generated
View file

@ -8,7 +8,9 @@
"flake-parts": "flake-parts", "flake-parts": "flake-parts",
"flake-utils": "flake-utils", "flake-utils": "flake-utils",
"napalm": "napalm", "napalm": "napalm",
"nixpkgs": "nixpkgs", "nixpkgs": [
"nixpkgs"
],
"pyproject-build-systems": "pyproject-build-systems", "pyproject-build-systems": "pyproject-build-systems",
"pyproject-nix": "pyproject-nix", "pyproject-nix": "pyproject-nix",
"systems": "systems", "systems": "systems",
@ -77,6 +79,21 @@
"type": "gitlab" "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": { "disko": {
"inputs": { "inputs": {
"nixpkgs": [ "nixpkgs": [
@ -168,6 +185,24 @@
"type": "github" "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": { "git-hooks": {
"inputs": { "inputs": {
"flake-compat": [ "flake-compat": [
@ -216,12 +251,34 @@
"type": "github" "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": { "mailserver": {
"inputs": { "inputs": {
"blobs": "blobs", "blobs": "blobs",
"flake-compat": "flake-compat_2", "flake-compat": "flake-compat_2",
"git-hooks": "git-hooks", "git-hooks": "git-hooks",
"nixpkgs": "nixpkgs_2" "nixpkgs": [
"nixpkgs"
]
}, },
"locked": { "locked": {
"lastModified": 1773912645, "lastModified": 1773912645,
@ -266,11 +323,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1771848320, "lastModified": 1775423009,
"narHash": "sha256-0MAd+0mun3K/Ns8JATeHT1sX28faLII5hVLq0L3BdZU=", "narHash": "sha256-vPKLpjhIVWdDrfiUM8atW6YkIggCEKdSAlJPzzhkQlw=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "2fc6539b481e1d2569f25f8799236694180c0993", "rev": "68d8aa3d661f0e6bd5862291b5bb263b2a6595c9",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -297,27 +354,11 @@
}, },
"nixpkgs_2": { "nixpkgs_2": {
"locked": { "locked": {
"lastModified": 1773831496, "lastModified": 1777077449,
"narHash": "sha256-JW2/QPyCVzmouqEp1H9kNa8JXd7xEhlam9sy3TYfhDY=", "narHash": "sha256-AIiMJiqvGrN4HyLEbKAoCSRRYn0rnlW5VbKNIMIYqm4=",
"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=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "10e7ad5bbcb421fe07e3a4ad53a634b0cd57ffac", "rev": "a4bf06618f0b5ee50f14ed8f0da77d34ecc19160",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -381,8 +422,9 @@
"inputs": { "inputs": {
"authentik": "authentik", "authentik": "authentik",
"disko": "disko", "disko": "disko",
"gradient": "gradient",
"mailserver": "mailserver", "mailserver": "mailserver",
"nixpkgs": "nixpkgs_3", "nixpkgs": "nixpkgs_2",
"sops": "sops" "sops": "sops"
} }
}, },
@ -393,11 +435,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1776771786, "lastModified": 1777338324,
"narHash": "sha256-DRFGPfFV6hbrfO9a1PH1FkCi7qR5FgjSqsQGGvk1rdI=", "narHash": "sha256-bc+ZZCmOTNq86/svGnw0tVpH7vJaLYvGLLKFYP08Q8E=",
"owner": "Mic92", "owner": "Mic92",
"repo": "sops-nix", "repo": "sops-nix",
"rev": "bef289e2248991f7afeb95965c82fbcd8ff72598", "rev": "8eaee5c45428b28b8c47a83e4c09dccec5f279b5",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -421,6 +463,21 @@
"type": "github" "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": { "uv2nix": {
"inputs": { "inputs": {
"nixpkgs": [ "nixpkgs": [

View file

@ -6,9 +6,11 @@
nixpkgs.url = "github:nixos/nixpkgs/nixos-25.11"; nixpkgs.url = "github:nixos/nixpkgs/nixos-25.11";
authentik = { authentik = {
url = "github:nix-community/authentik-nix"; url = "github:nix-community/authentik-nix";
inputs.nixpkgs.follows = "nixpkgs";
}; };
mailserver = { mailserver = {
url = "git+https://gitlab.com/simple-nixos-mailserver/nixos-mailserver?ref=nixos-25.11"; url = "git+https://gitlab.com/simple-nixos-mailserver/nixos-mailserver?ref=nixos-25.11";
inputs.nixpkgs.follows = "nixpkgs";
}; };
sops = { sops = {
url = "github:Mic92/sops-nix"; url = "github:Mic92/sops-nix";
@ -18,6 +20,9 @@
url = "github:nix-community/disko"; url = "github:nix-community/disko";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
}; };
gradient = {
url = "github:wavelens/gradient";
};
}; };
outputs = outputs =
@ -28,6 +33,7 @@
mailserver, mailserver,
disko, disko,
sops, sops,
gradient
}: }:
let let
sshkeys = [ sshkeys = [
@ -44,15 +50,6 @@
pkgs = nixpkgs.legacyPackages.x86_64-linux; pkgs = nixpkgs.legacyPackages.x86_64-linux;
in in
pkgs.mkShell { 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 = [ nativeBuildInputs = [
sops.packages.x86_64-linux.sops-import-keys-hook sops.packages.x86_64-linux.sops-import-keys-hook
]; ];
@ -197,6 +194,8 @@
disko.nixosModules.disko disko.nixosModules.disko
authentik.nixosModules.default authentik.nixosModules.default
mailserver.nixosModules.mailserver mailserver.nixosModules.mailserver
gradient.nixosModules.default
sops.nixosModules.sops
{ {
_module.args = { inherit self modulesPath; }; _module.args = { inherit self modulesPath; };
} }

210
hosts/gradient/README.md Normal file
View file

@ -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)

View file

@ -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";
}

View file

@ -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

18
keys/.gitignore vendored
View file

@ -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

View file

@ -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

View file

View file