Replace DNS-flip failover watcher with static vps.huskypup.net subdomains

The DNS-flip watcher (scripts/vps-dns-failover/) was designed but never
actually installed on the VPS despite being tracked as done - real gap,
found when asked whether the standby services are actually reachable.

New design: instead of dynamically flipping *.kube.huskypup.net between
home and VPS IPs, give the VPS site its own permanent, always-resolving
subdomain - vault/gitea/auth/n8n/nextcloud.vps.huskypup.net, each with
real Ingress+TLS on the VPS's own Traefik+cert-manager (both already
installed by Phase 0 bootstrap, just never wired up). No token-scoping
decision needed since there's no dynamic flipping - reuses the same
cert-manager token pattern as home.

Also scales Authentik/n8n/Nextcloud from 0 to 1 replica on the VPS so
the replicated data is actually browsable at all times, not just
present-but-unreachable. Their CNPG clusters are still read-only
replicas (spec.replica.enabled: true) - writes will error until a
deliberate manual promotion, but reads/browsing work now. Vault and
Gitea were already running continuously.
This commit is contained in:
Scooby Husky
2026-08-18 18:23:08 -05:00
parent f98c997293
commit 5bc1be2f00
13 changed files with 224 additions and 178 deletions
@@ -0,0 +1,34 @@
---
# See infrastructure/vps-standby/vault/manifests/ingress.yaml for the
# vps.huskypup.net subdomain design rationale.
#
# CAVEAT: Authentik's provider/application configs (redirect_uris, etc.)
# are replicated byte-for-byte from home and point at *.kube.huskypup.net
# - SSO logins between VPS-hosted apps and this VPS Authentik instance
# won't complete correctly (redirect mismatch) until that's addressed
# separately. Direct/local Authentik admin login still works fine for
# browsing and verifying replicated data.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: vps-authentik
namespace: authentik
annotations:
cert-manager.io/cluster-issuer: letsencrypt-production
spec:
ingressClassName: traefik
tls:
- hosts:
- auth.vps.huskypup.net
secretName: vps-authentik-tls
rules:
- host: auth.vps.huskypup.net
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: vps-authentik-server
port:
number: 80
@@ -1,11 +1,11 @@
# Authentik warm standby on the VPS - Phase 2. The CNPG replica cluster
# (manifests/cnpg-cluster.yaml) continuously replays WAL from home in the
# background; the app itself is NOT run in normal operation (replicas: 0)
# since the replica DB is read-only until a deliberate manual promotion -
# an Authentik pod trying to write against a read-only DB would just
# crashloop uselessly. Promotion runbook: flip the CNPG cluster's
# spec.replica.enabled to false (see cnpg-cluster.yaml), then scale
# server/worker up from 0.
# background. The app itself DOES run continuously (replicas: 1, reachable
# at auth.vps.huskypup.net - see manifests/ingress.yaml) so the replicated
# data is browsable/verifiable at all times, even though the underlying DB
# is a read-only CNPG replica - writes (new logins, session creation) will
# error until a deliberate manual promotion. Promotion runbook: flip the
# CNPG cluster's spec.replica.enabled to false (see cnpg-cluster.yaml).
#
# The `authentik:` block below (secret_key/postgresql "env://" indirection)
# is copied VERBATIM from infrastructure/authentik/values.yaml on purpose -
@@ -34,7 +34,7 @@ global:
prefix: AUTHENTIK_POSTGRESQL__
env:
- name: AUTHENTIK_URL
value: "https://auth.kube.huskypup.net"
value: "https://auth.vps.huskypup.net"
- name: AUTHENTIK_POSTGRESQL__HOST
valueFrom:
secretKeyRef:
@@ -59,7 +59,7 @@ global:
value: "5432"
server:
replicas: 0
replicas: 1
containerSecurityContext:
runAsNonRoot: true
allowPrivilegeEscalation: false
@@ -78,7 +78,7 @@ server:
enabled: false
worker:
replicas: 0
replicas: 1
containerSecurityContext:
runAsNonRoot: true
allowPrivilegeEscalation: false
@@ -0,0 +1,28 @@
---
# cert-manager itself is installed directly by scripts/vps-bootstrap.sh
# (Phase 0, out-of-band like k3s/Netbird) - not GitOps-managed here. This
# ClusterIssuer just rides the same GitOps pipeline as everything else in
# vps-standby, DNS-01 via Cloudflare (same pattern as home's
# infrastructure/cert-manager/manifests/letsencrypt-issuer.yaml).
#
# cloudflare-token-secret is a plain Secret copied here manually
# (kubectl, not git - same reasoning as every other VPS secret):
# kubectl -n cert-manager create secret generic cloudflare-token-secret \
# --from-literal=cloudflare-token=<same token as home's cert-manager>
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-production
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: garrettstone499@gmail.com
privateKeySecretRef:
name: letsencrypt-production
solvers:
- dns01:
cloudflare:
email: garrettstone499@gmail.com
apiTokenSecretRef:
name: cloudflare-token-secret
key: cloudflare-token
@@ -0,0 +1,27 @@
---
# See infrastructure/vps-standby/vault/manifests/ingress.yaml for the
# vps.huskypup.net subdomain design rationale.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: vps-gitea
namespace: gitea
annotations:
cert-manager.io/cluster-issuer: letsencrypt-production
spec:
ingressClassName: traefik
tls:
- hosts:
- gitea.vps.huskypup.net
secretName: vps-gitea-tls
rules:
- host: gitea.vps.huskypup.net
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: vps-gitea-http
port:
number: 3000
@@ -0,0 +1,27 @@
---
# See infrastructure/vps-standby/vault/manifests/ingress.yaml for the
# vps.huskypup.net subdomain design rationale.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: vps-n8n
namespace: n8n
annotations:
cert-manager.io/cluster-issuer: letsencrypt-production
spec:
ingressClassName: traefik
tls:
- hosts:
- n8n.vps.huskypup.net
secretName: vps-n8n-tls
rules:
- host: n8n.vps.huskypup.net
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: vps-n8n
port:
number: 80
+10 -8
View File
@@ -1,8 +1,11 @@
# n8n warm standby on the VPS - Phase 2. Same discipline as
# infrastructure/vps-standby/authentik/values.yaml: the CNPG replica
# cluster keeps the DB continuously warm in the background, but the app
# itself stays at replicaCount: 0 until a deliberate manual promotion
# (flip pg-n8n's spec.replica.enabled to false, then scale this up).
# cluster keeps the DB continuously warm in the background, and the app
# runs continuously too (replicaCount: 1, reachable at
# n8n.vps.huskypup.net - see manifests/ingress.yaml) - workflow
# saves/executions will error against the read-only DB until a
# deliberate manual promotion (flip pg-n8n's spec.replica.enabled to
# false), but the UI and existing workflow definitions are browsable.
#
# N8N_ENCRYPTION_KEY must be byte-identical to home's - it decrypts
# stored credentials (API keys, OAuth tokens, etc.) that live encrypted
@@ -28,7 +31,7 @@ config:
generic:
timezone: America/New_York
path: /
host: n8n.kube.huskypup.net
host: n8n.vps.huskypup.net
port: 5678
protocol: https
executions:
@@ -41,8 +44,7 @@ config:
secret: {}
# Not run in normal operation - see file header.
replicaCount: 0
replicaCount: 1
service:
type: ClusterIP
@@ -85,8 +87,8 @@ extraEnvSecrets:
key: encryption-key
extraEnv:
WEBHOOK_URL: https://n8n.kube.huskypup.net/
N8N_EDITOR_BASE_URL: https://n8n.kube.huskypup.net
WEBHOOK_URL: https://n8n.vps.huskypup.net/
N8N_EDITOR_BASE_URL: https://n8n.vps.huskypup.net
N8N_LOG_LEVEL: error
postgresql:
@@ -0,0 +1,27 @@
---
# See infrastructure/vps-standby/vault/manifests/ingress.yaml for the
# vps.huskypup.net subdomain design rationale.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: vps-nextcloud
namespace: nextcloud
annotations:
cert-manager.io/cluster-issuer: letsencrypt-production
spec:
ingressClassName: traefik
tls:
- hosts:
- nextcloud.vps.huskypup.net
secretName: vps-nextcloud-tls
rules:
- host: nextcloud.vps.huskypup.net
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: vps-nextcloud
port:
number: 8080
@@ -1,19 +1,20 @@
# Nextcloud warm standby on the VPS - Phase 2. Same discipline as the
# other vps-standby apps: CNPG replica cluster (manifests/cnpg-cluster.yaml)
# keeps the DB warm, manifests/pvc-restore-cronjob.yaml keeps file content
# warm, but the app itself stays at replicaCount: 0 until a deliberate
# manual promotion.
# warm, and the app runs continuously too (replicaCount: 1, reachable at
# nextcloud.vps.huskypup.net - see manifests/ingress.yaml) so replicated
# files/users are browsable at all times - uploads/changes will error
# against the read-only DB until a deliberate manual promotion.
#
# No Redis here - the home instance uses the redis-operator
# (infrastructure/vps-standby has no redis-operator deployed, out of
# scope for a standby that isn't actually serving traffic). Nextcloud
# runs fine without Redis (falls back to DB-based locking, just slower) -
# acceptable for an emergency-promotion scenario; add a real Redis at
# promotion time if desired.
replicaCount: 0
# scope for a standby that isn't actually serving writable traffic).
# Nextcloud runs fine without Redis (falls back to DB-based locking,
# just slower) - add a real Redis at promotion time if desired.
replicaCount: 1
nextcloud:
host: nextcloud.kube.huskypup.net
host: nextcloud.vps.huskypup.net
username: ""
password: ""
@@ -39,7 +40,7 @@ nextcloud:
1 => '172.16.0.0/12',
),
'overwriteprotocol' => 'https',
'overwrite.cli.url' => 'https://nextcloud.kube.huskypup.net',
'overwrite.cli.url' => 'https://nextcloud.vps.huskypup.net',
'allow_local_remote_servers' => true,
);
temp.config.php: |-
@@ -0,0 +1,30 @@
---
# Public-ish access to the VPS Vault standby, replacing the old
# "reachable over Netbird only" model with a stable, permanent hostname
# (vault.vps.huskypup.net) that doesn't depend on home being up at all -
# see the vps.huskypup.net subdomain design note in
# infrastructure/vps-standby/cert-manager/manifests/cluster-issuer.yaml.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: vps-vault
namespace: vault
annotations:
cert-manager.io/cluster-issuer: letsencrypt-production
spec:
ingressClassName: traefik
tls:
- hosts:
- vault.vps.huskypup.net
secretName: vps-vault-tls
rules:
- host: vault.vps.huskypup.net
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: vps-vault-ui
port:
number: 8200