Merge branch 'claude/stoic-edison-841399'

* Remove k8s-server host
* Clean up duplicate sections in README
* Track latest gitea release
* Add konnektor + api/auth/mailpit reverse proxies
* Point konnektor.home at konnektor-web

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-24 00:58:42 +02:00
11 changed files with 32 additions and 677 deletions
-164
View File
@@ -1,164 +0,0 @@
{ config, pkgs, ... }:
{
########################################
# Docker Registry v2
########################################
services.dockerRegistry = {
enable = true;
port = 5000;
listenAddress = "0.0.0.0";
# Enable image deletion and garbage collection
enableDelete = true;
enableGarbageCollect = true;
garbageCollectDates = "Sun 03:00";
# Storage location (root partition)
storagePath = "/var/lib/docker-registry";
# TLS configuration (for internal access)
extraConfig = {
http = {
tls = {
certificate = "/var/lib/docker-registry/certs/registry.crt";
key = "/var/lib/docker-registry/certs/registry.key";
};
};
};
};
########################################
# Firewall
########################################
networking.firewall.allowedTCPPorts = [ 5000 ];
########################################
# Setup: directories, certificates, htpasswd
########################################
systemd.tmpfiles.rules = [
"d /var/lib/docker-registry 0755 root root -"
"d /var/lib/docker-registry/certs 0755 root root -"
];
# Generate self-signed certificate
systemd.services.docker-registry-setup = {
description = "Docker Registry initial setup";
wantedBy = [ "multi-user.target" ];
before = [ "docker-registry.service" ];
path = with pkgs; [ openssl apacheHttpd ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
script = ''
# Create self-signed certificate if it doesn't exist
if [ ! -f /var/lib/docker-registry/certs/registry.crt ]; then
echo "Generating self-signed certificate for Docker Registry..."
${pkgs.openssl}/bin/openssl req -x509 -newkey rsa:4096 -nodes \
-keyout /var/lib/docker-registry/certs/registry.key \
-out /var/lib/docker-registry/certs/registry.crt \
-days 3650 \
-subj "/CN=registry.home.lindenfelser.de" \
-addext "subjectAltName=DNS:registry.home.lindenfelser.de,DNS:k8s-server,IP:10.202.82.7"
chmod 644 /var/lib/docker-registry/certs/registry.key
chmod 644 /var/lib/docker-registry/certs/registry.crt
echo "Certificate generated successfully"
fi
# Ensure correct ownership
chown -R docker-registry:docker-registry /var/lib/docker-registry
'';
};
########################################
# Storage Monitoring (80% threshold)
########################################
systemd.services.docker-registry-storage-check = {
description = "Check Docker Registry storage usage";
path = with pkgs; [ coreutils util-linux ];
serviceConfig = {
Type = "oneshot";
User = "root";
};
script = ''
REGISTRY_PATH="/var/lib/docker-registry"
THRESHOLD=80
if [ ! -d "$REGISTRY_PATH" ]; then
echo "Registry path does not exist yet"
exit 0
fi
# Get disk usage percentage of the filesystem containing the registry
USAGE=$(df -h "$REGISTRY_PATH" | awk 'NR==2 {print $5}' | sed 's/%//')
REGISTRY_SIZE=$(du -sh "$REGISTRY_PATH" | cut -f1)
echo "Docker Registry storage: $REGISTRY_SIZE (filesystem usage: $USAGE%)"
if [ "$USAGE" -gt "$THRESHOLD" ]; then
echo "WARNING: Filesystem usage ($USAGE%) exceeds threshold ($THRESHOLD%)"
echo "Consider cleaning up old images or expanding storage"
fi
'';
};
systemd.timers.docker-registry-storage-check = {
description = "Timer for Docker Registry storage check";
wantedBy = [ "timers.target" ];
timerConfig = {
OnCalendar = "daily";
Persistent = true;
};
};
########################################
# Daily Storage Growth Tracking
########################################
systemd.services.docker-registry-growth-tracker = {
description = "Track Docker Registry storage growth";
path = with pkgs; [ coreutils ];
serviceConfig = {
Type = "oneshot";
User = "root";
};
script = ''
REGISTRY_PATH="/var/lib/docker-registry"
LOG_FILE="/var/log/docker-registry-growth.log"
if [ ! -d "$REGISTRY_PATH" ]; then
echo "Registry path does not exist yet"
exit 0
fi
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
SIZE_BYTES=$(du -sb "$REGISTRY_PATH" | cut -f1)
SIZE_HUMAN=$(du -sh "$REGISTRY_PATH" | cut -f1)
echo "$TIMESTAMP | Size: $SIZE_HUMAN ($SIZE_BYTES bytes)" >> "$LOG_FILE"
# Keep only last 90 days of logs
if [ -f "$LOG_FILE" ]; then
tail -n 90 "$LOG_FILE" > "$LOG_FILE.tmp"
mv "$LOG_FILE.tmp" "$LOG_FILE"
fi
'';
};
systemd.timers.docker-registry-growth-tracker = {
description = "Timer for Docker Registry growth tracking";
wantedBy = [ "timers.target" ];
timerConfig = {
OnCalendar = "daily";
Persistent = true;
};
};
}
+1 -1
View File
@@ -180,7 +180,7 @@
};
virtualisation.oci-containers.containers.gitea = {
image = "gitea/gitea:1.25";
image = "gitea/gitea:latest";
autoStart = true;
ports = [ "3000:3000" "2222:2222" ];
volumes = [
+30 -16
View File
@@ -42,17 +42,38 @@
reverse_proxy 10.202.82.6:3000
'';
};
"registry.home.lindenfelser.de" = {
# konnektor-web — Frontend
"konnektor.home.lindenfelser.de" = {
extraConfig = ''
@registry host registry.home.lindenfelser.de
basicauth @registry {
admin $2a$14$Ga5BCiHvtlfRjdnlI9bhseFnNZ8dwXsLz4t1FdSemA1mAUV/vA1oi
reverse_proxy 10.202.82.49:8082
'';
};
# konnektor-api Frontend REST
"api.konnektor.home.lindenfelser.de" = {
extraConfig = ''
reverse_proxy 10.202.82.49:8080
'';
};
# Zitadel OIDC
# Wichtig: `header_up Host {host}` — Zitadel routet anhand des Host-
# Headers; ohne das sieht es den Upstream-Container-Hostname und failt.
"auth.konnektor.home.lindenfelser.de" = {
extraConfig = ''
reverse_proxy 10.202.82.49:8081 {
header_up Host {host}
}
reverse_proxy @registry https://10.202.82.7:5000 {
transport http {
tls
tls_insecure_skip_verify
}
'';
};
# Mailpit Web UI — NUR aus dem LAN 10.202.82.0/24 erreichbar.
# Alle anderen IPs bekommen 403, ohne dass Mailpit jemals angerufen wird.
"mailpit.konnektor.home.lindenfelser.de" = {
extraConfig = ''
@lan client_ip 10.202.82.0/24
handle @lan {
reverse_proxy 10.202.82.49:8025
}
handle {
respond "forbidden mailpit ist nur aus dem LAN 10.202.82.0/24 erreichbar" 403
}
'';
};
@@ -69,7 +90,6 @@
80 # HTTP (Caddy)
443 # HTTPS (Caddy)
2222 # External SSH to Gitea
6443 # Kubernetes API to k8s-server
];
networking.firewall.allowedUDPPorts = [
@@ -85,7 +105,6 @@
# Hairpin NAT for LAN clients hitting gateway:2222 so replies go back via gateway
extraCommands = ''
iptables -t nat -A POSTROUTING -p tcp -d 10.202.82.6 --dport 2222 -j MASQUERADE
iptables -t nat -A POSTROUTING -p tcp -d 10.202.82.7 --dport 6443 -j MASQUERADE
'';
forwardPorts = [
{
@@ -93,11 +112,6 @@
sourcePort = 2222;
destination = "10.202.82.6:2222";
}
{
proto = "tcp";
sourcePort = 6443;
destination = "10.202.82.7:6443";
}
];
};
}
-49
View File
@@ -1,49 +0,0 @@
{ config, pkgs, ... }:
{
########################################
# k3s Single-Node (Traefik disabled)
########################################
services.k3s = {
enable = true;
role = "server";
# Initialize single-server cluster and disable bundled Traefik ingress.
extraFlags = toString [
"--cluster-init"
"--disable=traefik"
"--flannel-backend=vxlan"
"--tls-san=k8s.home.lindenfelser.de"
];
};
########################################
# Firewall for k3s
########################################
networking.firewall.enable = true;
networking.firewall.allowedTCPPorts = [
6443 # Kubernetes API
10250 # Kubelet metrics
5000 # Docker Registry
];
networking.firewall.allowedUDPPorts = [
8472 # flannel VXLAN
];
########################################
# Containerd registry configuration
########################################
environment.etc."rancher/k3s/registries.yaml" = {
text = ''
mirrors:
registry.home.lindenfelser.de:
endpoint:
- "https://10.202.82.7:5000"
configs:
"10.202.82.7:5000":
tls:
insecure_skip_verify: true
'';
mode = "0644";
};
}