Commit Graph
77 Commits
Author SHA1 Message Date
jakman85 bc0982c213 fix(grafana): roll Grafana to load the OIDC client-secret (R-IDP-01)
Grafana's pod predated the grafana-oidc-credentials secret, so generic_oauth started with
an empty client_secret (disabled) → /login/generic_oauth didn't redirect to Keycloak.
Add a podAnnotation to force one roll; bump on future OIDC secret changes.
2026-08-24 22:57:00 -05:00
jakman85 4a6e0d3d37 feat: re-enable oauth2-proxy on the LE-prod-trusted wildcard cert (R-IDP-02) 2026-08-24 22:47:07 -05:00
jakman85 fc62345f0d chore: temp-defer oauth2-proxy to break the prod-cert deadlock (re-enable after) 2026-08-24 22:42:19 -05:00
jakman85andClaude Opus 4.8 3a1e0e6887 feat(dns): split-horizon in-cluster DNS via coredns-custom (R-DNS-01/R-DNS-03)
In-cluster clients resolving a platform hostname over its PUBLIC name got the
off-cluster answer, so pod->keycloak.home.keithjackson.dev hit a path that does not
serve the *.home.keithjackson.dev SNI -> `tls: unrecognized name` (oauth2-proxy OIDC
discovery crash; Grafana generic_oauth would hit the same wall).

Add a GitOps-managed coredns-custom ConfigMap (kube-system) that k3s CoreDNS imports as
a `*.server` block. The template plugin answers the whole home.keithjackson.dev zone with
${GATEWAY_VIP} (the shared Gateway VIP), so pod->public-hostname traffic hairpins through
the Gateway with the correct SNI -> wildcard cert validates. CoreDNS reloads the ConfigMap
live (no pod restart). Wired into platform/kustomization.yaml (Phase 2 network core).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-24 22:28:46 -05:00
jakman85 01bcb99337 Merge feature/phase4-image-automation: Phase 4 hands-off updates (R-UPD-01/02/03) 2026-08-24 22:17:44 -05:00
jakman85andClaude Opus 4.8 951973b74d fix(oauth2-proxy): cookie-secret must be 32 raw bytes (hex16), not base64-44
oauth2-proxy read the base64 cookie value as raw bytes (44) → 'must be 16/24/32 bytes'
crashloop. Regenerated cookie-secret as openssl rand -hex 16 (32 bytes). client-id +
client-secret unchanged (still match the realm oauth2-proxy client). Value tracked in
~/.config/home-k3s/keycloak-sso-secrets.txt.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-24 22:17:20 -05:00
jakman85andClaude Opus 4.8 bfec9d869e feat(updates): alert on failed / rolled-back updates (R-UPD-02 + R-OBS)
Add platform-health.automated-updates PrometheusRule group carried by platform-config:
ImageUpdateAutomationFailing (write-back broken -> advances silently stop),
ImagePolicyStalled (registry/range can't resolve a stable tag), and UpdateHealthGateFailed
(a bad update health-gated and auto-rolling-back via HelmRelease remediation). Routes to
Alertmanager email so a bad update is visible.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-24 22:14:54 -05:00
jakman85andClaude Opus 4.8 f538f8ff38 feat(updates): per-service update-policy render contract + Renovate config (R-UPD-01/R-UPD-03)
Map a ServiceDefinition `update` block to ImageRepository/ImagePolicy + the
$imagepolicy setter marker: stable-only (semver range + bare-X.Y.Z filterTags,
pre-release excluded, R-UPD-01), pin -> fixed image with no marker / suspended
policy (R-UPD-03). Worked pihole example under per-service/ (reference only, not
deployed; WS-GITOPS emits the equivalent into apps/<svc>/). Add renovate.json for
Helm chart / dependency / operator-manifest bumps: auto-merge minor/patch/digest on
green CI, major needs review, image managers disabled so Renovate never fights Flux
over a tag.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-24 22:14:48 -05:00
jakman85andClaude Opus 4.8 37f4fa6767 feat(updates): install Flux image controllers + cluster-wide write-back engine (R-UPD-02)
Install image-reflector-controller + image-automation-controller as part of the
flux-system install (bootstrap.sh --components-extra) so the image.toolkit CRDs
pre-exist before reconcile — no F-MON1 split needed for the ImageUpdateAutomation CR.
Refine the cluster-wide ImageUpdateAutomation for machine-authored git write-back
(setter strategy, path ./apps) reusing the flux-system GitRepository secretRef, and
document the operator git-write-credential hand-off + SOPS secret template
(gitea-image-automation-credentials). image-automation stays deferred in
platform/kustomization.yaml until that credential is provisioned.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-24 22:14:39 -05:00
jakman85andClaude Opus 4.8 ed6a772599 feat(keycloak): provision SSO client secrets + re-enable oauth2-proxy (R-IDP-01/02/04)
Generate the 3 OIDC client secrets (SOPS-encrypted, minimal manifests) so the realm
import can inject the grafana + oauth2-proxy client secrets and both consumers match:
- platform/keycloak/keycloak-realm-clients.sops.yaml (grafana + oauth2-proxy client secrets)
- platform/oauth2-proxy/oauth2-proxy-credentials.sops.yaml (client-id/secret/cookie)
- platform/monitoring/grafana-oidc-credentials.sops.yaml (grafana client secret)
Wire each into its component kustomization and re-enable oauth2-proxy in platform +
platform-config (reverses the temp deferral now that its secret exists).

Values generated by PM per operator instruction (random; recorded out-of-repo at
~/.config/home-k3s/keycloak-sso-secrets.txt for later rotation). Encrypted via sops in the
dev container to the age recipient in .sops.yaml; plaintext never committed.

Serves: R-IDP-01, R-IDP-02, R-IDP-04, R-SEC-01.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-24 22:08:04 -05:00
jakman85andClaude Opus 4.8 ef5b7bd57b chore(phase6): temp-defer oauth2-proxy until its SOPS secret is provisioned
oauth2-proxy's HelmRelease mounts existingSecret oauth2-proxy-credentials, an operator
hand-off not yet filled. Under the unified graph (platform wait:true), the not-ready HR
blocks platform -> platform-config -> the whole downstream graph including KubeVirt.
Deferring it (comment its line in platform/kustomization.yaml + its two entries in
platform-config) lets the no-secret components (KubeVirt/CDI, CNPG, Keycloak operator +
instance) reconcile and be certified now. Re-enable both together once the operator fills
platform/oauth2-proxy/oauth2-proxy-credentials.sops.yaml.

Serves: R-IDP-02 (deferred). PM sequencing correction — enable per phase AND readiness.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-24 21:43:12 -05:00
jakman85 b95b540468 Merge feature/keycloak-auth-stack: Phase 6 Keycloak IdP + oauth2-proxy + CNPG (R-IDP-01..06, R-OBS)
# Conflicts:
#	platform-config/kustomization.yaml
#	platform/kustomization.yaml
2026-08-24 21:15:46 -05:00
jakman85 cb4fdf4a55 keycloak: README — operator+CNPG design, runbook, hand-offs 2026-08-24 21:10:39 -05:00
jakman85 4bebd4b6d2 keycloak: coverage tests + dashboard + reword cross-cutting tags 2026-08-24 21:07:27 -05:00
jakman85 ce83c6af3b WIP keycloak stack (gate probe) 2026-08-24 19:52:25 -05:00
jakman85andClaude Opus 4.8 dbffb8fab8 Phase 5: KubeVirt + CDI IaaS proof (R-PLAT-03/04, R-STATE-01, R-NET-01)
- platform/kubevirt: pinned KubeVirt v1.9.0 + CDI v1.66.0 operators (remote bases);
  KubeVirt/CDI CRs tuned for Longhorn; useEmulation=true (prefer real KVM, emulate fallback).
- Controllers/configs split (F-MON1): CRs + ServiceMonitors + alerts via platform-config.
- Observability: ServiceMonitors, KubeVirt platform-health alerts (R-OBS-04), VM dashboard.
- catalog/vm-demo.yaml (kind: vm) + rendered VirtualMachine/DataVolume fixture.
- Conformance: vm-lifecycle (R-PLAT-03/04), vm-data-survival (R-STATE-01, L3),
  vm-default-deny (R-NET-01) — self-skipping until KubeVirt+longhorn exist.
- ci/SPEC_PIN: adopt accepted spec b633b7f (ADR-0015 egress work already merged).
- conformance/run.sh: document R-ENV-02/R-GITOPS-01 coverage gaps (WS-GITOPS/INFRA-owned).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-24 19:51:30 -05:00
jakman85andClaude Opus 4.8 d920aad74a Retire unused pia-* cluster SOPS secrets (ADR-0015 cleanup)
pia-credentials and pia-custom-ovpn were gluetun's PIA creds; gluetun is retired and the node
tunnel uses host-side Ansible vault creds (docs/07), so these cluster Secrets are unused. Remove
them from the kustomization + delete the encrypted files (recoverable from git history). Flux
will prune the Secrets from the vpn-egress namespace.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-24 15:03:11 -05:00
jakman85andClaude Opus 4.8 b6e8adc6c6 docs: mark egress-steering.yaml as live (remove stale NOT-WIRED banner + done TODO)
The policy is wired into the kustomization, reconciled by Flux, and conformance-green; gluetun
is retired. Update the header banner + bottom checklist to reflect that. Comment-only — the
CiliumEgressGatewayPolicy spec is unchanged.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-24 14:53:39 -05:00
jakman85andClaude Opus 4.8 aafb4ca378 docs(vpn-egress): reflect ADR-0015 node-tunnel egress, retire gluetun narrative
The in-cluster Gluetun gateway is retired; VPN egress is now a node-level host
OpenVPN split-tunnel (Ansible role vpn_egress_gateway) steered by a Cilium
Egress Gateway policy with a host nftables kill-switch (ADR-0015). Credentials
moved host-side to the Ansible vault. Update docs/comments only:

- platform/vpn-egress/README.md: rewrite for ADR-0015 (live + conformance-green);
  drop the resolved "option 1 not viable with Gluetun" finding to a brief history note.
- platform/kustomization.yaml: Phase 3 comment -> node tunnel + Cilium steering.
- platform/kyverno/.../require-egress-class-label.yaml: ADR-0013 -> ADR-0015 steering.
- schemas/servicedefinition.schema.json: egress=vpn description -> node tunnel + Cilium.
- clusters/base/cluster-vars.yaml: reframe PIA_REGION / CLUSTER_POD_CIDR WHY comments
  to ADR-0015 (Cilium excludedCIDRs); both keys retained (still referenced/documented).
- platform/vpn-egress/namespace.yaml: no privileged gateway pod under ADR-0015.
- pia-credentials.sops.yaml / pia-custom-ovpn.sops.yaml: comment headers only note
  they are now UNUSED (creds host-side), retained pending cleanup; payloads untouched.

No runtime manifest behavior, alert exprs, kustomization resources, or SOPS payloads
changed. No spec-repo edits.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-24 14:50:08 -05:00
jakman85andClaude Opus 4.8 d519ede4e3 monitoring: retarget VPNTunnelDown to node tun0 presence; drop obsolete EgressGatewayUnhealthy
gluetun retirement removes the gluetun_vpn_up gauge and the in-cluster gateway pod. Point
VPNTunnelDown at absent(node_network_receive_bytes_total{device=tun0}) — tun0 exists iff the
host OpenVPN tunnel is up (node_network_up unreliable: tun0 operstate=unknown). This also
fires if the gateway node/node-exporter is down (series goes stale). Remove the pod-readiness
EgressGatewayUnhealthy alert — there is no gateway pod under ADR-0015.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-24 14:41:20 -05:00
jakman85andClaude Opus 4.8 7b7982e28d Retire gluetun gateway Deployment (ADR-0015: node tunnel validated + conformance-green)
Prepared on a feature branch for operator review — NOT merged/pushed (retiring the running
fallback needs explicit approval). The node-level host OpenVPN tunnel steered by Cilium Egress
Gateway is validated end-to-end and gated by green conformance (egress-class + vpn-killswitch).
A Gluetun pod's tun0 was never steerable by Cilium egress gateway (the reason ADR-0015 moved the
tunnel to the node), so gluetun serves no steering role. The now-unused pia-* SOPS secrets are
retained pending a separate cleanup (creds are host-side in the Ansible vault).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-24 14:21:59 -05:00
jakman85andClaude Opus 4.8 c74e05bda9 fix: restore egress-steering.yaml (dropped by an earlier branch merge)
The CiliumEgressGatewayPolicy authored in 38fcfad was inadvertently removed from main when
feature/vpn-egress-gateway-role (which carried a git-rm of the duplicated file) was merged.
The kustomization now references it, so restore the file. Content identical to 38fcfad.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-24 08:08:35 -05:00
jakman85andClaude Opus 4.8 54341964fc Enable ADR-0015 vpn-egress steering (host tunnel validated on k3s-dev-3)
Host OpenVPN split-tunnel on k3s-dev-3 validated 2026-08-24: tun0 up, VPN egress
158.173.241.36, node default unchanged (WAN), scoped nft kill-switch active. Wire the
CiliumEgressGatewayPolicy into the runtime kustomization so egress-class=vpn pods steer
to the node tun0.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-24 08:07:20 -05:00
jakman85andClaude Opus 4.8 2a2ead8b28 chore: drop egress-steering.yaml from this branch (owned by feature/vpn-egress-cilium-steering; working-tree race)
Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-24 07:31:26 -05:00
jakman85andClaude Opus 4.8 38fcfadbf0 phase3(vpn): author Cilium Egress Gateway steering policy (ADR-0015)
Add platform/vpn-egress/egress-steering.yaml — a CiliumEgressGatewayPolicy
that steers homelab.keithjackson.dev/egress-class=vpn pods out a gateway
node's host tun0 split-tunnel interface, per ADR-0015 (node-level tunnel +
Cilium Egress Gateway). Honors all four ADR-0015 invariants: only vpn-class
pods are selected (default/internet + node stay on the local/WAN path),
RFC1918 + cluster ranges are excludedCIDRs so only public internet is
tunneled and tun0 is never a default route, and the host nftables kill-switch
(Ansible) fails vpn-class CLOSED while internet-class fails OPEN.

Interface contract shared verbatim with the parallel Ansible host-tunnel role:
  node label homelab.keithjackson.dev/egress-gateway=true, interface tun0,
  client label homelab.keithjackson.dev/egress-class=vpn.

Cilium Egress Gateway prerequisites confirmed enabled (egressGateway.enabled,
bpf.masquerade, kubeProxyReplacement all true, [parity]) — no HelmRelease change.

Intentionally NOT added to platform/vpn-egress/kustomization.yaml: the datapath
stays out of the runtime until the live tunnel exists and the egress-class /
vpn-killswitch conformance tests pass (README rule; DoD). Gluetun is retained
as the validated fallback until then. Serves R-NET-05/06/07.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-24 00:59:36 -05:00
jakman85andClaude Opus 4.8 34f41a0fba phase3(vpn): egress-class conformance fixtures + steering design finding
Author the internet/vpn egress-class fixture pods for the egress-class + vpn-killswitch
conformance tests. Record the datapath finding: ADR-0013's preferred steering (Cilium
egress gateway -> gluetun tun0) is NOT viable because gluetun must run hostNetwork to
put tun0 on the node, and gluetun then redirects the NODE's default route through the
VPN (breaks k3s/etcd). Escalated: remaining viable path is option 2 (gateway-pod
routing). No steering datapath committed until the mechanism is chosen + validated.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-24 00:26:26 -05:00
jakman85andClaude Opus 4.8 7b619f8bb0 fix(monitoring): Prometheus memory limit 1Gi -> 2Gi (OOMKilled on compaction)
prometheus-...-0 was OOMKilled (exit 137) every ~4min during TSDB compaction/checkpoint
(261 restarts/37h). Raise limit to 2Gi + request to 512Mi; nodes have headroom.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-24 00:04:17 -05:00
jakman85andClaude Opus 4.8 4a781ace49 fix(vpn): exec (localhost) liveness probe — VPN routing breaks kubelet HTTP probe
Once the tunnel redirects the pod default route via tun0, the pod's response to the
kubelet's httpGet liveness probe egresses the tunnel and never returns → probe times
out → pod restarts ~every 2min despite the tunnel being up (PIA egress IP confirmed).
Switch to a local wget exec probe (stays on lo).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-23 23:57:31 -05:00
jakman85andClaude Opus 4.8 63f6e15d0e feat(vpn): new IP-based .ovpn secret + map its key to /config/pia.ovpn
Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-23 01:01:19 -05:00
jakman85andClaude Opus 4.8 c8b9653e33 fix(vpn): custom mode with IP-based .ovpn; gluetun v3.41.0 -> v3.41.3
Operator provides a new .ovpn with an IP remote (gluetun's custom parser rejects
hostnames), so drop the resolve-remote initContainer/VPN_ENDPOINT_IP attempts and
mount the .ovpn directly (key pia.ovpn). Bump image to latest v3.41.3.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-23 00:57:01 -05:00
jakman85andClaude Opus 4.8 66a76e6e33 fix(vpn): VPN_ENDPOINT_IP for custom mode (.ovpn hostname not parseable)
gluetun's custom parser rejects the .ovpn's 'remote sweden.privacy.network' (needs
an IP). sweden.privacy.network LB pool = 158.173.241.{22,34,55,112,239}; pin .22
(verified to handshake) via VPN_ENDPOINT_IP/PORT while the .ovpn supplies cipher/CA.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-23 00:50:54 -05:00
jakman85andClaude Opus 4.8 998b46df3d fix(vpn): map .ovpn secret key (filename) to /config/pia.ovpn
Operator's kubectl create secret keyed the file by its name
(sweden-aes-128-cbc-udp-dns.ovpn); mount item now references that key -> pia.ovpn.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-23 00:46:31 -05:00
jakman85andClaude Opus 4.8 47b6cb6afd feat(vpn): add operator-provisioned pia-custom-ovpn Secret (SOPS)
Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-23 00:45:39 -05:00
jakman85andClaude Opus 4.8 91aa7acab8 wip(vpn): gluetun custom-provider mode with operator's PIA .ovpn
PIA-provider server selection is flaky here (random dead IPs); the operator's .ovpn
uses sweden.privacy.network (load-balanced, reliable). Switch to custom mode
(VPN_SERVICE_PROVIDER=custom, OPENVPN_CUSTOM_CONFIG=/config/pia.ovpn), mount the
pia-custom-ovpn Secret, keep creds + kill-switch + tun-mtu 1300. Drops PIA-provider
server list/updater and native PF (Phase-6 concern). Pending the operator-provisioned
pia-custom-ovpn Secret (not merged until it exists).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-23 00:40:08 -05:00
jakman85andClaude Opus 4.8 be1e317456 fix(vpn): correct env name PRIVATE_INTERNET_ACCESS_OPENVPN_ENCRYPTION_PRESET
gluetun silently ignored PIA_ENCRYPTION_PRESET (renamed) and defaulted to strong
(RSA-4096) the whole time. The correct v3.x var is
PRIVATE_INTERNET_ACCESS_OPENVPN_ENCRYPTION_PRESET=normal → RSA-2048/AES-128-CBC/1198,
the smaller handshake that fits the 1450 pod MTU.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-23 00:18:03 -05:00
jakman85andClaude Opus 4.8 6a44d973b4 fix(vpn): PIA normal preset (RSA-2048/AES-128-CBC/SHA1, 1198) for MTU reliability
RSA-4096 strong handshake wouldn't hold over the pod's 1450 MTU (Cilium VXLAN);
MTU tuning is exhausted (no --tls-mtu in OpenVPN 2.6, PIA rejects --fragment).
Operator downloaded the lower-encryption PIA config (port 1198, RSA-2048,
AES-128-CBC, SHA1) = gluetun's 'normal' preset. Smaller handshake fits 1450.
Dropped the strong-handshake band-aid flags; kept OPENVPN_MTU 1300 for the data channel.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-23 00:09:47 -05:00
jakman85andClaude Opus 4.8 772d032d4c tune(vpn): --mtu-disc no so oversized handshake packets fragment (not drop)
RSA-4096 strong-preset handshake is marginal over the 1450 pod MTU (Cilium VXLAN).
--mtu-disc no clears DF so oversized control packets fragment through instead of
being silently dropped. Last MTU-path lever (OpenVPN 2.6 has no --tls-mtu).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-22 23:56:28 -05:00
jakman85andClaude Opus 4.8 0e2289a2a8 fix(vpn): valid OpenVPN flags only (--hand-window 120; --tls-mtu invalid in 2.6)
OpenVPN 2.6.16 rejects --tls-mtu (not a real option) and PIA doesn't support
--fragment, so control-channel packets can't be shrunk. Keep --hand-window 120 to
give the RSA-4096 handshake more time over a lossy/marginal path, plus tun-mtu 1300.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-22 23:50:00 -05:00
jakman85andClaude Opus 4.8 b859e52697 tune(vpn): shrink OpenVPN control-channel MTU + widen handshake window
RSA-4096 strong-preset handshake is marginal over the pod's 1450 MTU (Cilium VXLAN).
Lower tun-mtu to 1300 and add --tls-mtu 1200 (smaller control packets) + --hand-window
120 (more time to complete the handshake over a lossy path). Empirical test of the
MTU-tuning path (operator's choice over Cilium native routing).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-22 23:46:36 -05:00
jakman85andClaude Opus 4.8 41234b85f5 fix(vpn): PIA strong encryption preset (RSA-4096/AES-256-CBC/SHA256, port 1197)
Operator's working config is sweden.privacy.network:1197 with RSA-4096, AES-256-CBC,
SHA256 = PIA's 'strong' preset. gluetun defaults PIA to 'normal' (AES-128-GCM), a
cipher mismatch against the 1197 (strong) endpoint, so the handshake never completed
(silent after UDP link). PIA_ENCRYPTION_PRESET=strong matches it and selects 1197.
Reverts the 8080 port red herring.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-22 23:30:46 -05:00
jakman85andClaude Opus 4.8 b76de52b7f test(vpn): use port 8080 (avoid home 1197 return-path conflict), live servers
Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-22 23:26:17 -05:00
jakman85andClaude Opus 4.8 2e5fdd29d7 fix(vpn): gluetun update needs -enduser flag in the init container
Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-22 23:17:16 -05:00
jakman85andClaude Opus 4.8 a735784f94 fix(vpn): initContainer fetches PIA's current server list (bundled list stale)
UPDATER_PERIOD only refreshes AFTER the tunnel is ready (chicken-and-egg: can't
connect with stale servers), and gluetun's bundled PIA Sweden list is retired
46.246.x (current is 158.173.x). Add an `update-servers` initContainer running
`gluetun update -providers "private internet access"` into a shared /gluetun
emptyDir the main container reads, so it dials current servers. UPDATER_PERIOD
raised to 720h (docs-compliant) for ongoing in-pod refresh.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-22 23:14:42 -05:00
jakman85andClaude Opus 4.8 457edb7a38 fix(vpn): enable gluetun server-list updater (bundled list was stale) + re-enable
CORRECTION: the connection failures were gluetun dialing RETIRED PIA IPs (46.246.x
= dead → timeouts, which I mis-read as a network block). A fresh PIA .ovpn connects
from the same host, and PIA's current SE Stockholm servers (158.173.x) are reachable.
Cilium was independently cleared (default-allow, host-fw off, Hubble FORWARDED).

Fix: UPDATER_PERIOD=24h so gluetun fetches PIA's current servers; keep
OPENVPN_MTU/MSSFIX=1350 for the real Cilium-VXLAN 1450 pod MTU (data channel);
PIA default UDP port. Re-enable vpn-egress. Full correction in DECISIONS-LOG.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-22 23:08:18 -05:00
jakman85andClaude Opus 4.8 95a55952cc fix(vpn): disable vpn-egress — network blackholes PIA IPs (not ports/MTU/cluster)
Decisive diagnosis: OpenVPN's outbound init packets are 14 bytes with zero
replies (so MTU was never the blocker), pod UDP egress is healthy (NTP replies),
but TCP 501/443 to PIA IPs time out while Google/Cloudflare are OPEN — across many
PIA servers. This 192.168.2.x WiFi segment blackholes PIA's IP ranges (and
intercepts DNS). Not code-fixable. Reverted the port/MTU red-herring env to PIA
defaults; disabled vpn-egress pending an operator network fix (move to the
unfiltered 192.168.1.x LAN, or unblock PIA on the router). Full trace in
DECISIONS-LOG.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-22 18:15:11 -05:00
jakman85andClaude Opus 4.8 f17c46b767 fix(vpn): shrink OpenVPN tun-mtu/mssfix to 1350 (Cilium VXLAN pod MTU 1450)
Actual root cause (operator-directed layered MTU check): Cilium runs VXLAN
(routing-mode tunnel) so pods get MTU 1450 (1500-50 encap) while node, hypervisor,
and the Google Fiber WAN are all 1500. OpenVPN's ~1500-byte handshake packets
exceed 1450 and are dropped (PMTUD broken). The ports were a red herring.

Fix: OPENVPN_MTU=1350 + OPENVPN_MSSFIX=1350 so encrypted packets (~1402) fit under
1450. Keeps OpenVPN + VXLAN unchanged. Re-enable vpn-egress. Full layered trace
(pod/node/host) in DECISIONS-LOG.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-22 18:03:46 -05:00
jakman85andClaude Opus 4.8 0aab1702e0 chore(vpn): disable vpn-egress, escalate OpenVPN→WireGuard decision
OpenVPN can't connect on this network (each port fails differently, so it's
OpenVPN-specific not a UDP block — NTP/123 works): 1197 = home OpenVPN-server
conflict; 53 = DNS interception; 8080 = TLS handshake fails across servers (MTU
on WiFi+triple-NAT). Recommend pivoting to WireGuard via thrnz/docker-wireguard-pia
(UDP 1337, MTU-robust, ADR-0013 fallback, operator's original preference) — a
spec-level (ADR-0013) change, so escalated. Disabled meanwhile. Full trace in
DECISIONS-LOG.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-22 16:47:18 -05:00
jakman85andClaude Opus 4.8 4cde690008 fix(vpn): PIA OpenVPN port 53 -> 8080 (53 is DNS-intercepted on this network)
Port 53 also got no PIA replies: this network transparently intercepts outbound
:53, so dig works but OpenVPN-over-53 is swallowed. An NTP/123 probe from the host
returned fine, proving arbitrary non-53 UDP works bidirectionally (so it's
port-specific: 1197 conflict + 53 DNS-intercept, not a UDP block). Move to 8080
(community-verified for PIA, non-DNS, non-1197).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-22 16:39:30 -05:00
jakman85andClaude Opus 4.8 734093cf92 fix(vpn): hardcode OPENVPN_ENDPOINT_PORT "53" as string (Flux var int-coercion)
Wiring the port via "${PIA_OPENVPN_PORT}" made Flux/kustomize emit an unquoted
53 (int) after substitution, which k8s rejects for an env value ("expected
string, got 53"). Same gotcha as the Longhorn numberOfReplicas fix. Hardcode the
quoted string; cluster-var removed with a NOTE explaining why.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-22 16:33:38 -05:00
jakman85andClaude Opus 4.8 ec5ceb328f fix(vpn): PIA OpenVPN on UDP/53 (avoid home WAN:1197 conflict); re-enable vpn-egress
Operator identified the block: the home network NATs its own OpenVPN server on
WAN:1197, colliding with the cluster's outbound PIA-on-1197 flow (port-specific,
not a general UDP block — UDP/53 worked in tcpdump). PIA accepts OpenVPN on
53/1194/1197/1198/8080/9201; chose 53 (empirically proven to traverse this net).

- cluster-vars: add PIA_OPENVPN_PORT="53"
- gluetun: OPENVPN_ENDPOINT_PORT=${PIA_OPENVPN_PORT}
- re-enable vpn-egress; DECISIONS-LOG resolution noted

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-22 16:28:37 -05:00