This repository has been archived on 2026-09-17. You can view files and clone it, but you cannot make any changes to its state, such as pushing and creating new issues, pull requests or comments.
nixos-config/hosts/pinix/localServices/traefik.nix
pijukka 823af825f4 host(pinix): add personal git server on pinix's own tailnet
pinix's Headscale now allocates its own range (100.96.0.0/24,
fd7a:115c:a1e0:96::/64). The pinix-dns namespace node forwards exactly
TCP 443 to a new Traefik web-internal entrypoint on the veth host side
(:8443, websecure untouched) and TCP 22 to sshd. unbound-pinix answers
git.lukasrosier.com (AAAA only), distributed via split DNS for that name
only.

gitserver/: cgit via services.cgit, reduced to reading and snapshot
downloads, built without its Python filters; git@ with git-shell and a
from= restricted key. Verified from tnix's pinixnet namespace (DNS,
HTTPS, snapshot, ls-remote, push) and refused from the public IPv6,
the blunix tailnet and the LAN.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JGodmgtRqahbT52gApsM8N
2026-09-14 12:55:11 +02:00

100 lines
4.5 KiB
Nix

{ config, ... }:
{
# Minimaler öffentlicher Eingang auf pinix, abgeleitet von
# hosts/blunix/localServices/traefik/default.nix. Dort ist Traefik über
# Jahre gewachsen (Cloudflare-DNS-Challenge samt Wildcard, Dashboard mit
# BasicAuth, fail2ban-Accesslog, zweiter Entrypoint fürs Tailnet). Hier
# bleibt bewusst nur, was der IPv6-Eingang braucht --
# docs/intent/pinix-ipv6-traefik-webserver.md, Schritt 3.
#
# Was absichtlich fehlt und warum:
#
# - DNS-Challenge über Cloudflare. Stattdessen HTTP-01: kein API-Token,
# kein agenix-Secret, eine externe Abhängigkeit weniger -- und genau
# das Verfahren, das der tnix-Test schon bewiesen hat. Der Preis ist
# der Verzicht auf Wildcards, also ein Zertifikat pro Hostname.
# - api/Dashboard. Der Intent schließt ein öffentliches Dashboard aus.
# - log.level = "TRACE" von blunix. Auf einer SD-Karte ist das
# Schreiblast ohne Gegenwert; INFO reicht, bis etwas zu suchen ist.
# Ziel von Mangos IPv6-DNAT ist pinix' SLAAC-Adresse auf end0,
# fdee:3c40:44f0:0:dea6:32ff:fe48:c6c8 -- deterministisch aus dem
# ULA-Praefix des Mango und der MAC dc:a6:32:48:c6:c8 gebildet, also
# stabil, solange beide es sind.
#
# Sie ist hier bewusst NICHT als networking.interfaces.end0.ipv6.addresses
# deklariert, obwohl hosts/tnix/localServices/ipv6-web-test.nix genau das
# tut. Am 12.09.2026 gemessen: sobald ein Interface eine statische
# IPv6-Adresse bekommt, schreibt das NixOS-dhcpcd-Modul fuer dieses
# Interface `noipv6rs` in die generierte dhcpcd.conf. Damit entfaellt die
# Router-Solicitation und mit ihr die RA-Default-Route -- auf einem Host,
# dessen Uplink IPv6-only ist, heisst das schlicht: kein Internet mehr.
# Die Adresse doppelt zu deklarieren haette hier ohnehin nichts
# gewonnen, weil SLAAC exakt dieselbe bildet.
networking.firewall.allowedTCPPorts = [ 80 443 ];
# Dasselbe Henne-Ei-Problem wie auf blunix (dort IPv4): web-internal
# bindet an die veth-Adresse, die erst pinix-dns-netns.service anlegt.
# Mit nonlocal_bind startet Traefik trotzdem, der Listener wird aktiv,
# sobald die Adresse existiert.
boot.kernel.sysctl."net.ipv6.ip_nonlocal_bind" = 1;
services.traefik = {
enable = true;
staticConfigOptions = {
log.level = "INFO";
# ":80"/":443" nimmt IPv4 und IPv6 an. Öffentlich erreichbar ist
# davon nur, was Mangos DNAT weiterreicht; im LAN hängt pinix
# zusätzlich unter seiner ULA-Adresse daran.
entryPoints = {
web = {
address = ":80";
# Weiterleitung auf HTTPS. Die ACME-HTTP-01-Validierung laeuft
# trotzdem ueber diesen Entrypoint: Traefik legt dafuer einen
# eigenen Router mit hoeherer Prioritaet an, der vor der
# Weiterleitung greift.
http.redirections.entryPoint = {
to = "websecure";
scheme = "https";
};
};
websecure.address = ":443";
# Tailnet-only-Routen, wie web-internal auf blunix -- aber fuer das
# eigene pinix-Tailnet. Dessen Adresse liegt im Namespace pinix-dns;
# der reicht 443 auf die Host-Seite des veth-Paars an diesen Port
# weiter (tailnet-dns.nix). Nur von dort erreichbar. Eigener Port,
# damit websecure auf ":443" unangetastet bleibt.
web-internal.address = "[fd42:7069:6e69:78::1]:8443";
};
# Scharf geschaltet am 12.09.2026, gemeinsam mit dem Umlegen von
# Mangos DNAT auf pinix (scripts/configure-mango-ipv6-web.sh).
# Vorher waere hier keine Validierung angekommen: 80/443 zeigten
# noch auf tnix, das fuer denselben Hostnamen ein eigenes
# Zertifikat hielt. Zwei Hosts gleichzeitig ausstellen zu lassen
# bringt nur fehlgeschlagene Validierungen gegen Let's Encrypts
# Rate Limit -- deshalb war die Reihenfolge hier wichtig.
certificatesResolvers.le.acme = {
email = config.adminEmail;
storage = "/var/lib/traefik/acme.json";
httpChallenge.entryPoint = "web";
};
};
# Das Wildcard-Zertifikat fuer domain2 holt nicht Traefik, sondern
# security.acme per DNS-01 gegen das eigene BIND (acme.nix). Hier wird es
# nur eingehaengt, damit Router unter domain2 kein eigenes Zertifikat
# anfordern muessen. Erneuerung loest dort reloadServices aus.
dynamicConfigOptions.tls.certificates = [
{
certFile = "/var/lib/acme/${config.domain2}/fullchain.pem";
keyFile = "/var/lib/acme/${config.domain2}/key.pem";
}
];
};
}