Commit Graph
1258 Commits
Author SHA1 Message Date
Ruben Hensen 7bb7813d1d longhorn: upgrade to 1.10.2
Third hop towards 1.11.2. 1.10 removes the longhorn.io/v1beta1 API, which
requires every Longhorn CRD to store only v1beta2; all 22 already do (the
check from the 1.10 important notes). Settings move to a per-data-engine
JSON format, but the chart renders our defaultSettings and
defaultBackupStore values unchanged.

Checked beforehand: 17 healthy volumes plus 2 detached, no backing
images, system backup pre-upgrade-v1-9-2-202610221421 is Ready.
2026-10-22 14:23:18 +02:00
Ruben Hensen 53f01c65dc docs: tick off Longhorn 1.9.2
All volumes on engine v1.9.2, backup target fine, 17 test backups written. Every Longhorn CRD already stores only v1beta2.
2026-10-22 14:19:28 +02:00
Ruben Hensen a73bdb8fb4 longhorn: upgrade to 1.9.2
Second hop towards 1.11.2. 1.9 stops serving longhorn.io/v1beta1 (nothing
in this repo uses it) and renames orphan-auto-deletion (false here) to
orphan-resource-auto-deletion during the upgrade. Chart values are
unchanged from 1.8.2.

Checked beforehand: 17 healthy volumes plus 2 detached, no backing
images, every Longhorn CRD already stores only v1beta2, and system backup
pre-upgrade-v1-8-2-202610221408 is Ready.
2026-10-22 14:11:09 +02:00
Ruben Hensen f390b3a70d docs: tick off Longhorn 1.8.2
All 19 volumes on engine v1.8.2, BackupTarget default points at the NAS, and a manual run of backup-daily wrote 17 backups.
2026-10-22 14:01:30 +02:00
Ruben Hensen fae03f3b8b longhorn: upgrade to 1.8.2
First hop from 1.7.3 to 1.11.2 (docs/upgrade-k8s-1.33.md). 1.8 drops the
backup-target settings in favour of a "default" BackupTarget resource, so
the NFS target and poll interval move into defaultBackupStore. Until now
they only lived in the cluster.

Checked beforehand: no Faulted volumes, no backing images, all volumes on
engine v1.7.3, system backup pre-upgrade-v1-7-3-20261022 and Velero
backup pre-longhorn-1-8-202610221326 both complete.
2026-10-22 13:52:13 +02:00
Ruben Hensen f2c1b9ac06 docs: fix the Longhorn important-notes links
Docs for older Longhorn versions live under /docs/archives/.
2026-10-22 13:27:28 +02:00
Ruben Hensen 119f7729f6 docs: tick off upgrade step 1
All Longhorn volumes run engine v1.7.3, the 1.7.1 and 1.7.2 engine images are gone, and system backup pre-upgrade-v1-7-3-20261022 is Ready.
2026-10-22 12:58:59 +02:00
Ruben Hensen 6da1b3e88e longhorn: upgrade volume engines automatically, one at a time
concurrentAutomaticEngineUpgradePerNodeLimit was 0, so volumes stayed on
the engine image of whatever Longhorn version created them: three on
1.7.1, nine on 1.7.2, seven on 1.7.3. Every hop of the upgrade to 1.11.2
needs all volumes on the current engine first.
2026-10-22 12:56:21 +02:00
Ruben Hensen 8aaf2ec8ec Plan the move to Kubernetes 1.33
Envoy Gateway v1.9 needs Kubernetes 1.33, and microk8s 1.32 is out of
upstream support. docs/upgrade-k8s-1.33.md lists what has to move first
and in which order. Renovate is held below Longhorn 1.11.3, since that
and newer releases need Kubernetes 1.34.
2026-10-22 12:56:07 +02:00
Ruben Hensen f2716e1169 home-assistant: mark the EndpointSlice endpoint ready
The API server fills in conditions on every endpoint, which left Argo CD showing the slice as OutOfSync. Spell it out so desired and live state match.
2026-10-22 11:07:42 +02:00
Ruben Hensen a10dbecc94 home-assistant: replace the ExternalName service with an EndpointSlice
After the microk8s ingress addon went from ingress-nginx 1.8.0 to 1.13.8,
ha.rubenhensen.nl returned 503: the controller now resolves externalName
as a DNS name and "192.168.1.202" fails the lookup. A selector-less
Service with an EndpointSlice pointing at the IP is the supported way to
route to an address outside the cluster.
2026-10-22 11:03:19 +02:00
Ruben Hensen 2ce9c33dbd Remove ghost-blog and whishper
Neither is used any more: the blog moves to a self-written site, and
whishper needs more compute than this setup has. Ghost also ran Node 18,
which won't get the eMudhra root store fix.

Drops both apps, the blog DNS record, Gatus check and node001 tunnel
entry, their ca-bundle namespaces, and the leftover ExternalSecret for the
old hensen.io blog namespace. Final Velero backups keep the data:
ghost-blog-final-202610220955 until 22 October 2027 and
whisper-final-202610220955 until 20 January 2027.
2026-10-22 10:11:06 +02:00
Ruben Hensen 8a66913a54 Drop the nas.rubenhensen.nl and alv.rubenhensen.nl ingresses
Neither host has a DNS record, so both were unreachable and cert-manager
has been failing to renew their certificates since 6 April. DSM stays
reachable on the LAN at 192.168.1.99. The nas app only held that ingress
and its ExternalName service, so it goes entirely.
2026-10-22 09:36:26 +02:00
Ruben Hensen 1f736cd4c7 Remove phocas-lustrum
The folder went in April (bb308a3) but the app and its DNS record stayed,
so the demo site kept serving at phocaslustrum.rubenhensen.nl.
2026-10-22 09:36:26 +02:00
Ruben Hensen e5d82b69fd Remove the old in-cluster Stalwart
Its folder went in March (44c31cd) but the Argo CD app stayed and kept the
old hensen.io mail server running, with a NodePort for SMTP/IMAP and a
10Gi volume. Mail runs on node001 now and this one logged nothing in three
days. A final Velero backup (stalwart-final-202610220935) keeps its data
until 20 January 2027. The app has no finalizer, so the namespace gets
deleted by hand.
2026-10-22 09:36:26 +02:00
Ruben Hensen fe678d4b4b ca-bundle: also serve argocd, authentik and actual-budget
Those three namespaces had their own ~190KB copy of patched-ca-bundle.
Let the ca-bundle chart render them too, so the bundle lives in one file.
2026-10-22 09:26:43 +02:00
Ruben Hensen ff73f99d43 velero: name the release resources "velero" so repo maintenance runs
Velero 1.15 builds kopia repository maintenance jobs from a Deployment
called "velero" in its namespace. Ours was velero-helm, so every
BackupRepository reported 'error to build maintenance job: Deployment.apps
"velero" not found' and the repos on the NAS were never cleaned up, some
not since March 2025. fullnameOverride renames the Deployment, Service,
ServiceAccount and RBAC; the old velero-helm objects get deleted by hand
since this app doesn't prune.
2026-10-22 09:23:51 +02:00
Ruben Hensen ea86611deb cert-manager: mount a CA bundle without the compromised eMudhra roots
Mount patched-ca-bundle (see ca-bundle/) over /etc/ssl/certs so it stops trusting the compromised eMudhra intermediates (https://emudhra.we-have-a-problem.com). Only the controller, which talks ACME to Let's Encrypt.
2026-10-22 09:13:02 +02:00
Ruben Hensen 7820b8e0bf immich: use a CA bundle without the compromised eMudhra roots
Mount patched-ca-bundle (see ca-bundle/) over /etc/ssl/certs so it stops trusting the compromised eMudhra intermediates (https://emudhra.we-have-a-problem.com). The server gets --use-openssl-ca for Node, machine-learning points requests at the bundle instead of certifi.
2026-10-22 09:13:02 +02:00
Ruben Hensen de7ca770a0 whishper: use a CA bundle without the compromised eMudhra roots
Mount patched-ca-bundle (see ca-bundle/) over /etc/ssl/certs so it stops trusting the compromised eMudhra intermediates (https://emudhra.we-have-a-problem.com). whishper also gets --use-openssl-ca for Node, and both point requests at the bundle instead of certifi.
2026-10-22 09:13:02 +02:00
Ruben Hensen 767facc5e2 bolletjestrui: use a CA bundle without the compromised eMudhra roots
Mount patched-ca-bundle (see ca-bundle/) over /etc/ssl/certs so it stops trusting the compromised eMudhra intermediates (https://emudhra.we-have-a-problem.com). Node uses its built-in roots, so also switch it to OpenSSL's store with --use-openssl-ca.
2026-10-22 09:13:02 +02:00
Ruben Hensen 746cd1283e ghost-blog: use a CA bundle without the compromised eMudhra roots
Mount patched-ca-bundle (see ca-bundle/) over /etc/ssl/certs so it stops trusting the compromised eMudhra intermediates (https://emudhra.we-have-a-problem.com). Node uses its built-in roots, so also switch it to OpenSSL's store with --use-openssl-ca.
2026-10-22 09:13:02 +02:00
Ruben Hensen 345055e3e6 serpbear: use a CA bundle without the compromised eMudhra roots
Mount patched-ca-bundle (see ca-bundle/) over /etc/ssl/certs so it stops trusting the compromised eMudhra intermediates (https://emudhra.we-have-a-problem.com). Node uses its built-in roots, so also switch it to OpenSSL's store with --use-openssl-ca.
2026-10-22 09:13:02 +02:00
Ruben Hensen f52b6973e1 gatus: mount a CA bundle without the compromised eMudhra roots
Mount patched-ca-bundle (see ca-bundle/) over /etc/ssl/certs so it stops trusting the compromised eMudhra intermediates (https://emudhra.we-have-a-problem.com).
2026-10-22 09:13:01 +02:00
Ruben Hensen 177dbef15b ntfy: mount a CA bundle without the compromised eMudhra roots
Mount patched-ca-bundle (see ca-bundle/) over /etc/ssl/certs so it stops trusting the compromised eMudhra intermediates (https://emudhra.we-have-a-problem.com).
2026-10-22 09:13:01 +02:00
Ruben Hensen 103aed46ed gitea: mount a CA bundle without the compromised eMudhra roots
Mount patched-ca-bundle (see ca-bundle/) over /etc/ssl/certs so it stops trusting the compromised eMudhra intermediates (https://emudhra.we-have-a-problem.com).
2026-10-22 09:13:01 +02:00
Ruben Hensen 9a984c724c Add a ca-bundle chart for the patched CA bundle
Nine more namespaces need patched-ca-bundle, the bundle from Alpine
ca-certificates-bundle 20261020-r0 without the roots above the compromised
eMudhra intermediates. Rather than another nine ~190KB copies, a small
chart renders the ConfigMap from one ca-certificates.crt into every
namespace in values.yaml. argocd, authentik and actual-budget keep their
own copies for now.
2026-10-22 09:11:01 +02:00
Ruben Hensen 3e45ddaf4a Remove lingo
lingo-api ran PHP 8.1.34 (php:8.1-apache) and was reachable at
https://lingo.rubenhensen.nl/api/. PHP 8.1 is EOL and gets no fix for
CVE-2026-109618 and CVE-2026-109619, so it was exposed to the PHP2Shell
RCE. It is no longer used, so drop it entirely: the app, its Gatus
check, the DNS record and the node001 tunnel entry.
2026-10-22 09:06:52 +02:00
Ruben Hensen 842f087da7 mbgwp: start again on PHP 8.3.36
wordpress:7.1.3-php8.3-apache was rebuilt at 15:57 UTC on PHP 8.3.36,
which fixes CVE-2026-109618 and CVE-2026-109619. WordPress core lives on
the volume, so the tag only moves PHP (8.3.30 -> 8.3.36) and Apache. Its
ca-certificates (20250419+deb13u1) no longer carries the roots above the
compromised eMudhra intermediates either.
2026-10-21 20:36:53 +02:00
Ruben Hensen 307bd23500 freshrss: start again on 1.30.3 with fixed PHP
freshrss/freshrss:1.30.3, released today, runs PHP 8.4.27 from Debian
trixie security (8.4.27-1~deb13u1), which fixes CVE-2026-109618 and
CVE-2026-109619. Pin it instead of :latest so the running version is the
checked one. Its ca-certificates (20250419+deb13u1) also no longer
carries the roots above the compromised eMudhra intermediates.
2026-10-21 17:43:08 +02:00
Ruben Hensen 6d9e405126 Point Velero and the NAS services back at 192.168.1.99
Reverts 8c2bd89. The NAS had fallen back to a DHCP address (.10), which
also moved the UniFi controller it hosts away from where the gateway
and access points inform, and left the Longhorn backup target at
nfs://192.168.1.99 unreachable. It now has 192.168.1.99 set statically
in DSM again, so everything that pointed at .99 works without changes.
2026-10-21 15:25:16 +02:00
Ruben Hensen dfdc4451fb velero: raise the server memory limit to 2Gi
At 512Mi the server has been OOMKilled 314 times. With the bucket
reachable again it died 23 seconds into a manual backup of ~14.7k items,
which then got marked Failed on restart. opti1 has 32GB.
2026-10-21 12:33:31 +02:00
Ruben Hensen e19f4b8db5 velero: skip the CRD upgrade hook
The hook's init container pulls docker.io/bitnami/kubectl:1.32, which
Bitnami has removed, so the sync for the new NAS address hung on
ImagePullBackOff. The chart stays at 8.4.0 and the CRDs are already in
place.
2026-10-21 12:22:57 +02:00
Ruben Hensen 8c2bd89444 Point Velero and the NAS services at 192.168.1.10
The NAS moved from 192.168.1.99 to .10, so the backup storage location
has been Unavailable ("no route to host"). Every backup since at least
26 July failed, and 154 expired ones are stuck in Deleting because Velero
can't reach the bucket to remove them. MinIO answers on .10:9000 and DSM
on .10:5000.
2026-10-21 12:16:19 +02:00
Ruben Hensen 400d46c462 actual-budget: use a CA bundle without the compromised eMudhra roots
Node uses its built-in roots, which still include emSign ECC Root CA - G3,
and the Node fix is still pending. Switch to OpenSSL's store with
--use-openssl-ca and point it at patched-ca-bundle, mounted over
/etc/ssl/certs. That covers bank sync and the authentik OIDC calls.
2026-10-21 12:09:41 +02:00
Ruben Hensen 25a6bf3a05 authentik: mount a CA bundle without the compromised eMudhra roots
Server and worker trusted the leaked eMudhra intermediates through the
Debian bundle (ca-certificates 20250419), Python's ssl module and certifi
2026.01.04. Mount patched-ca-bundle over /etc/ssl/certs and point
SSL_CERT_FILE and REQUESTS_CA_BUNDLE at it until an authentik image ships
a fixed bundle and certifi.
2026-10-21 12:09:41 +02:00
Ruben Hensen a507282103 argocd: mount a CA bundle without the compromised eMudhra roots
The Argo CD v2.14.2 image (Ubuntu 24.04, ca-certificates 20240203) and the
dex image still trust the eMudhra intermediates whose keys leaked
(https://emudhra.we-have-a-problem.com), and the repo server fetches git
and Helm repos over that trust store. A forged certificate there means
arbitrary manifests in the cluster.

patched-ca-bundle holds the bundle from Alpine ca-certificates-bundle
20261020-r0 and replaces /etc/ssl/certs in every Argo CD container except
redis. The whole directory goes, since OpenSSL and Go also read the
per-root files next to ca-certificates.crt. It uses server-side apply to
keep the bundle out of the last-applied annotation.
2026-10-21 12:09:41 +02:00
Ruben Hensen 19eacd35f7 Remove ip-updater
Every run has failed with "Permission denied (publickey)" on
root@46.224.26.65, the last successful job was 219 days ago, and one pod
had been hanging for 138 days. The home IP is pinned in
nix-infra-machine/nodes/node001.nix instead, so nothing used it.

It also ran alpine:3.19, which is EOL and still trusts the compromised
eMudhra intermediates.
2026-10-21 11:49:28 +02:00
Ruben Hensen bb7e2f2bac node001: distrust the roots above the compromised eMudhra intermediates
The private keys of eight eMudhra intermediates are recoverable
(https://emudhra.we-have-a-problem.com). node001 runs NixOS 25.11 from
March with nss-cacert 3.121, and curl there still accepted a chain via
emSign ECC Root CA - G3. Blacklist that root and Comodo AAA Services root
until nixpkgs ships NSS 3.131.1. Everything node001 talks to chains to
ISRG Root X1.

Already deployed with ./cli update node001, and Gatus, ntfy and Stalwart
were restarted so they reload the trust store.
2026-10-21 11:49:28 +02:00
Ruben Hensen 1efa132f9d Stop FreshRSS + MBG WordPress until PHP is patched
CERT-EU 2026-018 (CVE-2026-109618, CVE-2026-109619, CVSS 9.2): possible
unauthenticated RCE in PHP 5 and later. Both are internet-facing and are
the only PHP workloads (FreshRSS on PHP 8.2.29, WordPress on 8.3.30), so
scale them to 0 until their images ship a fixed PHP. PVCs and the MariaDB
cluster stay as they are.
2026-10-21 09:18:24 +02:00
Ruben Hensen 054117ad6d vault: point External Secrets at the in-cluster Vault service
The store went through https://vault.rubenhensen.nl, so via Hetzner and
the home internet connection. When home internet dropped, the store
validation failed and External Secrets backed off long enough that it
needed a controller restart after the connection came back.

vault-active.vault.svc talks to the active Vault pod directly. It's plain
HTTP, same as the ingress → Vault hop already is.
2026-10-20 15:45:55 +02:00
Ruben Hensen ac61c346af node001: run ntfy at ntfy1.rubenhensen.nl and alert from s1
ntfy listens on 127.0.0.1:2586 behind the same SNI-to-8444 setup as s1.
Users and tokens come from encrypted nix-infra secrets, exported as env
vars by a small wrapper since ntfy and Gatus only read them from the
environment.

s1 alerts on the home services, Scouting MBG and s2. It stays quiet on
mail, which lives on this box and is covered by s2.
2026-10-20 15:34:42 +02:00
Ruben Hensen e23779044f gatus: alert through self-hosted ntfy
Add ntfy at ntfy2.rubenhensen.nl on the home cluster, pointed straight at
the home IP like s2. s2 now alerts on mail and on s1 through it. The
home services are left to s1, which sees them from outside.

ntfy denies everything except the provisioned user. The user, its
password hash and the Gatus token come from Vault kv/ntfy.
2026-10-20 15:34:42 +02:00
Ruben Hensen 53a079dc22 node001: run Gatus at s1.rubenhensen.nl
Gatus listens on 127.0.0.1:8090. nginx gets s1 in the SNI map, sends it
to a local TLS vhost on 8444 with its own ACME cert, and proxies to
Gatus from there. The endpoint list is built from tunneledHosts plus
mail, Scouting MBG and s2, matching gatus/config.yaml.
2026-10-20 11:03:58 +02:00
Ruben Hensen 3ffb10b627 gatus: add s2.rubenhensen.nl status page on the home cluster
Gatus with sqlite history, checking every public service plus its twin
s1.rubenhensen.nl on Hetzner. Replaces the GitHub-hosted Upptime page.

DNS gets s1 (Hetzner) and s2. s2 points straight at the home IP rather
than through the Hetzner tunnel, so it stays reachable when Hetzner is
down.

gatus/README.md explains how to read the two pages and writes down the
plan for a single self-hosted failover URL (PowerDNS LUA records).
2026-10-20 11:03:58 +02:00
Ruben Hensen 145f4eaac4 gitea: pin to 1.27.3 (CVE-2026-60004, CVE-2026-20896, CVE-2026-27771)
The floating gitea/gitea:1 tag with IfNotPresent pull policy left the
node on a cached 1.26.1 image. Pin the version and let Renovate track it.
2026-09-24 11:40:12 +02:00
Ruben Hensen 20cf119dbd Extend authentik + FreshRSS session lifetimes
Authentik's default-authentication-login stage ships with
session_duration: seconds=0, so the SSO session died with the browser.
New blueprint raises it to 30 days for every app on that flow.

FreshRSS runs OIDC through Apache mod_auth_openidc, whose defaults are a
5 minute inactivity timeout and a ~7.5 hour max duration in a non-persistent
cookie. Bumped to a 30 day sliding inactivity window (refreshed on every
request) with a 90 day hard cap, and made the cookie persistent so it
survives a browser restart.
2026-09-03 15:21:07 +02:00
Ruben Hensen ec5405fc29 bolletjestrui: deploy main-10268f1 (routes, voting, push, ride uploads)
Adds the VAPID keypair for web push and raises the ingress body limit to 25m to
match the app's BODY_SIZE_LIMIT, since a bulk route import posts every GPX in one
request.

The VAPID env refs are optional:true on purpose — a missing key in a secretKeyRef
puts the pod in CreateContainerConfigError, and the app already handles absent
keys by sending no notifications.
2026-08-15 17:55:37 +02:00
Ruben Hensen b50f8dc5b6 bolletjestrui: deploy main-8f17b5d (editable etappe titles) 2026-07-24 11:36:21 +02:00
Ruben Hensen 06dd19e5de bolletjestrui: deploy main-878b462 (select chevron fix) 2026-07-24 11:13:54 +02:00