Commit Graph
46 Commits
Author SHA1 Message Date
Scooby HuskyandClaude Sonnet 5 166ff0ffb7 GitLab cross-site replication Phase 2b: home-side Gitaly network exposure
Per-pod NodePort Services for the 3 existing Gitaly nodes
(ha-gitaly-nodeport.yaml) plus home's Rails internal API
(ha-gitlab-internal-api-nodeport.yaml, needed for gitlab-shell hooks
whenever the VPS node is primary for a repo). PeerAuthentication
PERMISSIVE + AuthorizationPolicy port rules for both, matching the
proven Postgres pattern. CoreDNS rewrites so home's own Praefect
resolves the floating hostnames locally instead of round-tripping.

Reached over the Netbird mesh, not the UniFi WAN forward used for
Postgres - Gitaly gRPC here is plaintext (carries the gitaly_token,
gitlab_shell_secret, and raw repo contents), unlike CNPG's
TLS-negotiated stream.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-22 13:59:03 -05:00
Scooby HuskyandClaude Sonnet 5 6cf1d89278 GitLab cross-site replication Phase 2a (corrected): ignoreDifferences approach
The CONFIG_TEMPLATE_DIRECTORY redirect from the previous commit doesn't
work - confirmed live that extraVolumes is a dead values key for the
Praefect subchart specifically (its statefulset.yaml never calls the
gitlab.extraVolumes helper in its volumes: list, only volumeMounts
calls the corresponding helper - a real chart limitation, not a config
mistake). A dangling volumeMount with no matching volume would have
failed to schedule.

Real fix: ignoreDifferences on ConfigMap gitlab-praefect's data field
(argocd-apps/apps/gitlab.yaml) lets Helm create the object normally
while ArgoCD stops reconciling its content afterward - the actual
config gets kubectl-patched onto the live object directly.
praefect-ha-configmap.yaml is now a git-tracked reference/documentation
copy (deployed under its own harmless name) rather than something
Helm/ArgoCD wires in on its own.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-22 13:54:43 -05:00
Scooby HuskyandClaude Sonnet 5 a309495e1e Fix YAML syntax error from previous commit (leftover maxUnavailable line)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-22 13:49:18 -05:00
Scooby HuskyandClaude Sonnet 5 38dad209e3 GitLab cross-site replication Phase 2a: Praefect config override mechanism
Proves the CONFIG_TEMPLATE_DIRECTORY redirect works before Phase 2b
adds any actual cross-site dependency. Content is byte-identical to
the chart's current rendering (confirmed live) - this commit only
tests the override plumbing itself: a new ConfigMap
(praefect-ha-configmap.yaml) mounted at a different path than the
chart's own /etc/gitaly/templates (can't reuse that path/volume name -
Kubernetes rejects duplicates), with CONFIG_TEMPLATE_DIRECTORY
overridden via extraEnv to point at it instead (last-wins env
semantics, confirmed Kubernetes-documented behavior).

Also removes gitlab.praefect.virtualStorages - confirmed live dead
config, global.praefect.virtualStorages is what the chart's template
actually reads.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-22 13:48:35 -05:00
Scooby HuskyandClaude Sonnet 5 4a0de188df GitLab cross-site replication Phase 1c (stage 2): live streaming replication
Adds externalClusters connectionParameters + replica.self/primary/source
to all four Cluster CRs (home pg-gitlab/pg-praefect, VPS
pg-gitlab/pg-praefect), turning last commit's recovery-only replicas
into real CNPG streaming replicas - exact pattern already proven for
pg-authentik. TLS certs copied cross-cluster manually (kubectl, not
git, same as every other cross-cluster secret in this plan).

Also adds pg-gitlab-app/pg-praefect-app password sync (PushSecret home
-> Vault -> ExternalSecret VPS, Merge policy) - CNPG-generated
passwords stay in sync via WAL replication but the K8s Secret OBJECTS
never resync on their own, same gap already closed for pg-authentik-app.

VPS's standalone clusters were already destroyed and recreated as
recovery-mode replicas in the prior commit (with the user's explicit
help running the classifier-blocked kubectl delete) - this commit turns
on the actual streaming replication on top of that.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-22 13:31:09 -05:00
Scooby HuskyandClaude Sonnet 5 ca94282552 GitLab cross-site replication Phase 1b: secret material sync
PushSecret (home) + ExternalSecret (VPS) pairs for the four
chart-generated secrets whose values must match before Phase 1c's
Postgres replication goes live: gitlab-rails-secret (secret_key_base/
db_key_base/otp_key_base/openid_connect_signing_key, all in one
secrets.yml key - confirmed live, not four separate keys as first
assumed), gitlab-gitlab-shell-secret, gitlab-gitaly-secret,
gitlab-praefect-secret (the latter two also needed for Phase 2's
cross-site gRPC auth). Same pattern as
infrastructure/authentik/manifests/ha-postgres-app-pushsecret.yaml.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-22 13:08:22 -05:00
Scooby HuskyandClaude Sonnet 5 04fa80ece3 GitLab cross-site replication Phase 1a: Postgres network plumbing
Per the approved plan (jiggly-snacking-iverson.md, 'GitLab True
Cross-Site Replication' section) - replacing the git-mirror CronJob
with real CNPG streaming replication for pg-gitlab and pg-praefect,
reusing the exact pattern already proven for pg-authentik.

This commit is network plumbing only, no DB replication yet:
- Repoint both CNPG clusters' barmanObjectStore backup target from
  in-cluster gitlab-minio-svc to VPS MinIO (also becomes the initial
  seed source for the VPS's replicas in Phase 1c).
- New ha-gitlab-postgres/ha-praefect-postgres NodePort Services
  (bypass CNPG's own -rw Services) on both home and VPS.
- New PeerAuthentication with portLevelMtls PERMISSIVE on the replication
  ports - ambient mesh STRICT default would otherwise reset the
  VPS/witness's connection before the Postgres TLS handshake starts.
- Extended allow-gitlab-access AuthorizationPolicy with a port-scoped
  rule for the same traffic (no source.namespaces rule can match
  external, non-mesh peers).
- CoreDNS rewrites for pg-gitlab.ha.huskypup.net / pg-praefect.ha.huskypup.net
  so home's own pods reach the floating hostname locally instead of
  round-tripping through the WAN forward (no NAT hairpin support).

Still needed before Phase 1b/1c: UniFi WAN port-forwards for external
ports 61442/61443 -> talos-cp-01:32442/32443, source-restricted to the
VPS + witness public IPs (manual, same as the existing 61432 rule).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-22 12:47:03 -05:00
Scooby HuskyandClaude Sonnet 5 1ab292e185 Fix GitLab Authentik SSO: restore providers list, populate real provider key
GITLAB_OMNIBUS_CONFIG (gitlab_rails['omniauth_providers'] = [...]) is
an omnibus-Docker-image-only convention. This chart's CNG webservice
image never processes it - confirmed via gitlab/charts/gitlab/
templates/_omniauth.tpl, which only reads
global.appConfig.omniauth.providers (a list of {secret,key} refs, each
pointing at a Secret key holding a whole YAML-encoded provider block
loaded via Ruby's YAML.load_file). So GITLAB_OMNIBUS_CONFIG was always
a silent no-op on both home and VPS - neither ever actually had SSO
configured despite gitlab-oidc-secret existing and looking correct.

Yesterday's fix for 'FailedMount: references non-existent secret key:
provider' removed the providers: list entirely instead of populating
that key - stopped the crash, but also silently deleted the only real
OIDC config path on both sites (no error, login page just lost its SSO
button). This restores providers: on both, and gitlab-oidc-secret's
ExternalSecret template now actually renders a provider key containing
a real YAML provider block (with the templated client_id/secret
substituted in), matching what _omniauth.tpl expects.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-20 23:14:25 -05:00
Scooby HuskyandClaude Sonnet 5 989d42dd50 Fix GitLab OIDC secret FailedMount blocking webservice/sidekiq startup
Real bug affecting both home and VPS GitLab, found live diagnosing why
VPS's webservice pod was stuck at Init:0/3 for 74+ minutes (surfacing as
404s - nothing was ever actually serving):

global.appConfig.omniauth.providers: [{secret: gitlab-oidc-secret, key:
provider}] requires that secret to have a 'provider' key (a full YAML
omniauth provider config) - gitlab-oidc-secret never has one, only
GITLAB_OIDC_CLIENT_ID/SECRET, which get consumed via extraEnvFrom +
GITLAB_OMNIBUS_CONFIG's gitlab_rails['omniauth_providers'] instead (the
actual, complete mechanism already configuring OIDC - this providers:
list was always redundant). Removed it from both values.yaml files.

Home's GitLab has run fine so far because Kubernetes doesn't re-validate
already-mounted volumes when a referenced Secret's shape changes -
webservice/sidekiq there just haven't restarted since gitlab-oidc-secret
was last (if ever) in a shape with that key. VPS's identical values
shape hit it immediately on first pod creation. This was a live landmine
for home too - fixed before it could bite on a future restart.

Also: global.kas.enabled: false added to the VPS values (gitlab.kas.enabled
alone doesn't stop KAS pods - found live, separate toggle).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-20 22:36:16 -05:00
Scooby HuskyandClaude Sonnet 5 d9a4c16481 Move all 8 Authentik OIDC client_secrets out of plaintext git
Completes the 'make the repo proper' cleanup from earlier this session -
these were flagged but deliberately not touched in 185e9c2 given the
blast radius (live SSO for 8 apps). User confirmed: fix all 8 now.

infrastructure/authentik/{argocd,gitlab,nextcloud,grafana,n8n,guacamole,
rancher,vault}-blueprint.yaml: converted from plain ConfigMap (client_secret
hardcoded) to ExternalSecret with a templated blueprint body
(client_secret: "{{ .clientSecret }}") pulling from Vault. Chart already
supports mounting blueprints from Secrets (blueprints.secrets, alongside
blueprints.configMaps) - infrastructure/authentik/values.yaml updated to
route these 8 there instead.

For argocd/nextcloud/n8n/guacamole/rancher: Vault already had the matching
value at secret/<app>-oauth (the APP side was already Vault-backed via its
own ExternalSecret) - the blueprint was the only remaining plaintext copy.

For gitlab/grafana/vault: Vault had no copy at all yet - created
secret/{gitlab,grafana,vault}-oauth with the EXISTING live values (not
rotated - these are the actual working credentials right now, rotating
would break login until every consumer is updated in lockstep, which is
out of scope for a cleanup pass). Also fixed the OTHER plaintext copies
that existed for these three specifically:
  - apps/gitlab/manifests/external-secret-oidc.yaml (new): replaces a
    manually kubectl-created, never-git-tracked gitlab-oidc-secret.
  - infrastructure/grafana/manifests/grafana-oauth-secret.yaml: was a
    plain Secret whose own comment said 'hardcoded from blueprint'.
  - infrastructure/vault/manifests/vault-init-{configmap,job}.yaml: this
    one COULDN'T be converted to the same ExternalSecret-from-Vault
    pattern - it's the PostSync Job that grants ESO's own Kubernetes-auth
    role in Vault, so ESO can't yet authenticate to pull anything from
    Vault at the point this script runs (genuinely circular). Sourced
    from a new vault-oidc-bootstrap Secret instead - created once
    manually (kubectl, not git, matching how Vault's own root/unseal
    material is already handled), independent of the ESO pipeline.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-20 20:04:22 -05:00
Scooby Husky a869a5a67f Add CNPG ScheduledBackup for authentik/n8n/nextcloud - WAL archiving alone isn't restorable
Found while building the VPS replica clusters (Phase 2): all three
CNPG clusters' backup.barmanObjectStore blocks only configure continuous
WAL archiving. CNPG's bootstrap.recovery needs at least one real Backup
object in the object store before it has anything to restore -
'no target backup found', confirmed live against pg-authentik. This gap
was systemic (gitlab has it too, not fixed here - out of scope for
tonight, no VPS standby depends on it).

immediate: true fires one backup right away in addition to the daily
01:00 schedule, to unblock the in-progress VPS restore now rather than
waiting up to 24h for the first scheduled run.
2026-08-17 22:49:50 -05:00
Scooby HuskyandClaude Sonnet 5 7d73542645 Fix nextcloud-pvc-sync: add fsGroup for data access, fix restic cache dir
Now that the endpoint/credentials are fixed and this job can actually
reach the VPS, it surfaced two more real bugs: no fsGroup (couldn't read
the PVC data at all - nextcloud's real Deployment uses fsGroup 33/www-data,
this job never matched it) and no writable cache dir for runAsUser 65534
(restic defaults to $HOME/.cache).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 21:05:38 -05:00
Scooby HuskyandClaude Sonnet 5 7a6ecd14fe Bump rancher memory limit 2Gi -> 3Gi, was OOMKilling during restart recovery
Repeated restarts from tonight's network disruption kept OOMKilling
rancher - each restart's startup reconciliation is memory-hungry, and 2Gi
wasn't enough headroom for that burst even though steady-state usage is
normally fine. Nodes have ample free memory (26-36% used).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 21:01:48 -05:00
Scooby HuskyandClaude Sonnet 5 2fc7fb5b59 Fix Nextcloud Redis config: use externalRedis, not redis (bundled subchart toggle)
redis.enabled: true was unintentionally deploying the chart's BUNDLED redis
subchart (redis-21.1.3, StatefulSet nextcloud-redis-replicas) alongside
the actual redis-operator instance - that key's schema is pure subchart
passthrough (auth/image/master/replica), it has no host field. The
host/existingSecret keys previously set there were silently ignored.
Found while investigating why nextcloud-redis-replicas-0 was
crash-looping on failed liveness probes (confirmed via : the correct key for pointing at an external Redis
is the separate externalRedis: block).

This means Nextcloud's config.php may never have been correctly pointed
at the redis-operator instance despite apps/nextcloud/manifests/redis-cr.yaml
existing and being healthy - worth confirming file-locking/caching
actually engages after this deploys.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 20:54:28 -05:00
Scooby HuskyandClaude Sonnet 5 018eb3570f Build real pod-egress routing to the VPS, fix everything blocked by its absence
Root cause of tonight's earlier CrowdSec/CNPG-backup workarounds: no node
in the cluster had any route into the Netbird mesh CIDR (100.108.0.0/16)
for pod-originated traffic. The per-namespace netbird 'router' pods are
inbound-only infrastructure (external peers reaching K8s services); their
own architecture has no reverse path.

Fix, in two parts:

1. infrastructure/netbird/manifests/egress-daemonset.yaml - one netbird
   client per node, hostNetwork so its wt0 interface lives in the node's
   real network namespace, plus a sidecar that adds a host route sending
   100.108.0.0/16 out via it. hostNetwork requires a scoped Kyverno
   PolicyException (infrastructure/kyverno/policies/netbird-egress-exception.yaml)
   to the disallow-host-namespaces STIG policy - narrowly for this one
   DaemonSet by name, not a namespace-wide exclusion.

2. Discovered the route alone wasn't enough for k3s NodePort traffic
   (vps-minio:30900): Netbird manages its own nftables ACLs independent of
   iptables/Kyverno, and its forward chain (netbird-rt-fwd) only permits
   *established* connections through a peer acting as a router - never new
   ones, by design, unless a Netbird 'Network Route' policy is explicitly
   configured (it isn't, for this VPS). Locally-terminated connections
   (tinyproxy) go through a separate, already-permissive ACL chain, which
   is why the CrowdSec proxy fix from earlier tonight worked. Replicated
   that working pattern for MinIO: minio-forward.service on the VPS host
   (systemd, socat) forwards 100.108.113.41:9000 -> MinIO's ClusterIP,
   avoiding the NodePort path entirely.

Re-enabled everything that was disabled/suspended earlier tonight because
of this gap, pointed at the new endpoint:
- CrowdSec CAPI/console-enroll (removed DISABLE_ONLINE_API, restored the
  VPS proxy env vars)
- n8n/nextcloud/authentik CNPG backup.barmanObjectStore
- vault-raft-snapshot CronJob (unsuspended)
- nextcloud PVC content sync CronJob endpoint

vps-minio.netbird.internal is retired everywhere - it was never actually
resolvable (Netbird has no DNS configured) even before today's routing
fix.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 20:36:13 -05:00
Scooby HuskyandClaude Sonnet 5 2c8d29c51f Disable VPS MinIO backup on n8n/nextcloud/authentik CNPG clusters
Discovered while debugging pg-n8n's recurring 'Instance Status Extraction
Error': the root cause is the same missing pod-egress route to the Netbird
mesh found tonight while fixing CrowdSec - continuousArchiving kept failing
to connect to vps-minio.netbird.internal, and CNPG correctly holds
Ready=False while archiving is broken (a real condition, not cosmetic).
That's what was driving these three apps' ArgoCD health flapping.

No node in the cluster has any route into 100.108.0.0/16 for
pod-originated traffic - the per-namespace netbird 'router' pods
(gitlab, vault, argocd, etc.) are inbound-only infrastructure, not egress
gateways. gitlab's own CNPG backup is unaffected (points at a local
in-cluster MinIO, not the VPS).

Commented out rather than deleted - re-enable once real pod-egress
routing exists, tracked as a separate task. No data loss: this is WAL
archiving/backup, not the live database.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 19:45:04 -05:00
Scooby HuskyandClaude Sonnet 5 c8643ca025 Add dedicated netbird router for crowdsec namespace
Every other major namespace (gitlab, vault, argocd, nextcloud, etc.) has
its own 3-replica netbird-router deployment auto-provisioned from an
NBRoutingPeer CR; crowdsec never got one since it never previously needed
mesh connectivity. Without it, crowdsec pods had no dedicated mesh routing
path, causing intermittent connection resets/hangs reaching the VPS proxy
used to route around CrowdSec's WAF block on home's public IP.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 19:27:39 -05:00
Scooby Husky 9bb4ada1f5 Fix Redis CR permission denied on PVC (needs fsGroup)
redis-nextcloud-0 was CrashLoopBackOff: "mkdir: can't create directory
'/node-conf': Permission denied" and same for appendonlydir - the pod
had no podSecurityContext, so it couldn't write to its own PVC-backed
data directories. Added fsGroup/runAsUser: 1000 matching the
opstree/redis image's default non-root user. Verified live: Redis now
starts cleanly ("Ready to accept connections").
2026-08-17 17:20:16 -05:00
Scooby Husky a8b16fa4cb Fix Redis CR apiVersion/schema - v1beta2 doesn't exist, blocked whole nextcloud sync
apps/nextcloud/manifests/redis-cr.yaml used redis.redis.opstreelabs.in/v1beta2
(guessed, flagged as unverified when written) - the actually-installed CRD
only has v1beta1. ArgoCD validates all resource API groups/versions before
syncing anything, so this one bad apiVersion blocked the ENTIRE nextcloud
Application from syncing at all (every resource showed OutOfSync, not just
this one).

Also fixed against the live CRD schema: redisSecret belongs nested under
kubernetesConfig, not top-level, and redisExporter.image is required by the
schema even when enabled: false.

Verified live: kubectl apply succeeded and the Redis resource was created
before committing this.
2026-08-17 16:56:20 -05:00
Scooby Husky 7b3669e9a6 Fix CNPG operator -> instance status communication + add CNPG health check
Two related fixes, found while working through why authentik/n8n/gitlab
ArgoCD Applications showed Unknown/stuck health despite pods being fine:

1. ArgoCD has no built-in health check for CNPG's Cluster CRD, so it
   always reported "Unknown" - and since app-level health rolls up to
   the worst resource status, every app with a CNPG Cluster showed
   Unknown/Progressing regardless of actual state. Added a Lua health
   check (resource.customizations.health.postgresql.cnpg.io_Cluster)
   reading .status.conditions[Ready] / .status.phase.

2. Once that started reporting real status instead of masking it,
   pg-authentik showed a genuine problem: CNPG's operator couldn't
   reach its Postgres instances' status API (port 8000) - "Cannot
   extract Pod status ... context deadline exceeded" - because:
   a) authentik-ingress's CiliumNetworkPolicy never allowed cnpg-system
      as a source (crowdsec-ingress already had this exception,
      authentik-ingress was just missing it - inconsistency, not
      deliberate).
   b) Even after fixing (a), still blocked - pg-authentik's pods are
      ambient-mesh-enrolled, so the connection actually goes through
      ztunnel's HBONE tunnel (port 15008) first, same underlying issue
      as the argocd-redis fix from earlier today. Rather than keep
      finding and patching this per-namespace as it recurs, added a
      cluster-wide CiliumClusterwideNetworkPolicy allowing HBONE
      broadly - ztunnel's own mTLS/SPIFFE identity verification is the
      real security boundary for mesh traffic; Cilium blocking the
      tunnel port itself was only breaking legitimate traffic, not
      adding meaningful protection on top.

Verified live: CNPG operator immediately went from failing status
extraction to successfully reconciling (recreating a pod to reattach
its PVC) once both fixes were in place.
2026-08-17 16:41:11 -05:00
Scooby Husky 99b8ca6be9 Fix Nextcloud OIDC client_id/secret - was hardcoded empty
apps/nextcloud/values.yaml had oidc_login_client_id/secret as literal
empty strings, causing "client_id is missing or invalid" on login.
The matching Authentik blueprint (infrastructure/authentik/nextcloud-
blueprint.yaml) already defines fixed credentials for this OAuth2
provider - just never got copied over. Filled in to match.

Also found and left alone: an orphaned nextcloud-oidc-config ConfigMap
(from January) that already had the correct values but isn't actually
mounted by the current Deployment (which uses nextcloud-config instead)
- harmless dead resource, not the active config path.
2026-08-17 16:33:47 -05:00
Scooby Husky ee71e4f46f Fix DDNS/failover Cloudflare zone name and add restricted PSS securityContext
- ZONE_NAME was "kube.huskypup.net" in both the home-ip-ddns CronJob and
  the VPS failover watcher - that's a record, not a Cloudflare zone (the
  actual zone is the parent "huskypup.net"). Caused home-ip-ddns to fail
  every run (zone lookup returned zero results, curl -f exit 22) -
  confirmed live and fixed.
- Added seccompProfile/non-root/dropped-capabilities securityContext to
  the three CronJobs added this session that were missing it (flagged by
  the cluster's "restricted" PodSecurity admission). Repointed the raft
  snapshot job's mc binary install from /usr/local/bin to /tmp so it
  still works running as non-root.
2026-08-17 15:55:47 -05:00
Scooby Husky 983671ef50 Fix ArgoCD Redis connectivity broken by ambient mesh HBONE NetworkPolicy gap
Root cause (confirmed via ztunnel logs): the argo-cd chart's default
NetworkPolicies only allow each component's application port (e.g. 6379
for redis), but Istio ambient mode routes ALL pod-to-pod traffic through
ztunnel's HBONE tunnel on port 15008 first - so the tunnel itself was
being blocked even though the "real" port was allowed. Every inter-pod
connection in the argocd namespace hung for exactly 10s then reset;
ztunnel's own log named it directly ("connection timed out, maybe a
NetworkPolicy is blocking HBONE port 15008"). This broke argocd-server's
Redis-backed session/cluster-info caching cluster-wide and was silently
preventing the root Application from picking up new child Applications.

Fix: additive NetworkPolicy allowing ingress on 15008 for all argocd
pods (NetworkPolicies union across multiple policies selecting a pod,
so this doesn't touch/replace the chart's rendered ones - safe across
Helm upgrades).

Also: dropped argocd from the namespace-enrollment job's waypoint list.
argocd's only AuthorizationPolicy (allow-argocd-access) is a plain
source-namespace/IP match with no L7 rules - its own status shows
"attached to ztunnel", not waypoint - so forcing L7 waypoint processing
onto the namespace was unnecessary overhead, not a security requirement.
ztunnel's mTLS still fully covers it. (This was a red herring for the
HBONE bug itself, not the fix, but a valid simplification found along
the way.)
2026-08-17 15:08:33 -05:00
Scooby Husky 7990f1fa47 Add VPS warm-standby/backup site (Phase 0-1b)
Foundation for a DR/backup path using an always-on VPS as a second
ArgoCD-managed cluster, plus DB/backup standardization work that fell
out of it:

- vps-standby ArgoCD cluster destination + AppProject, MinIO backup
  receiver, VPS bootstrap script (k3s, Netbird, cert-manager)
- Dual-site DNS failover watcher + home-IP DDNS CronJob, Cloudflare
  token moved out of git into Vault+ExternalSecret
- Nextcloud migrated from ad-hoc MariaDB to CNPG + redis-operator
  (matches n8n/Authentik/GitLab's backup-native pattern)
- Authentik's CNPG manifests moved into the actual ArgoCD-synced
  manifests/ path (were present but never wired into the sync path)
- Vault raft-snapshot CronJob, CNPG barmanObjectStore backups
  (Authentik/n8n/Nextcloud), Nextcloud file-PVC restic sync - all
  targeting the new VPS MinIO receiver

See VPS Warm-Standby plan doc for full design rationale.
2026-08-17 14:59:26 -05:00
Scooby HuskyandClaude Opus 4.6 5163403e24 Scale down non-critical replicas for homelab resource savings
Reduce replicas to 1 for workloads that don't need HA in a homelab:
- Prometheus 2→1, Alertmanager 2→1 (~4.4GB RAM saved)
- cert-manager 3→1
- GitLab: registry 2→1, kas 2→1, sidekiq 2→1, praefect 2→1,
  pgbouncer-ro 3→1
- Guacamole + guacd 2→1
- Kiali 2→1, ArgoCD server 2→1
- Kyverno background-controller 2→1
- Scylla operator 2→1
- ext-authz-proxy 2→1, netbird-cluster-router 2→1

Kept multi-replica: coredns, envoy-gateway, kyverno admission,
vault, argocd-repo-server, gitlab-webservice, istiod, rook-ceph CSI.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 23:00:48 -05:00
Scooby HuskyandClaude Opus 4.6 fa23e5084c Reduce GitLab latency: increase Gitaly CPU limit and Puma workers
Gitaly CPU limit was 250m causing throttling on git operations (485ms
p99 to webservice). Increased to 1 core with 100m request. Also added
workerProcesses: 3 to webservice for better request concurrency
(was default 2).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 22:47:39 -05:00
Scooby HuskyandClaude Opus 4.6 0c16785116 Fix Guacamole OIDC session persistence via Istio DestinationRule
Traffic goes through Istio (not Envoy Gateway), so cookie-based
consistent hashing must be on the Istio DestinationRule. Removes
the unused Envoy Gateway BackendTrafficPolicy.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 15:11:15 -05:00
Scooby HuskyandClaude Opus 4.6 f71dbdb689 Add Envoy Gateway cookie-based session persistence for Guacamole OIDC
Fixes OIDC nonce validation failures with 2 replicas by using a
BackendTrafficPolicy with cookie-based consistent hashing instead of
Service-level ClientIP affinity (which doesn't work behind Envoy proxy).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 14:55:53 -05:00
Scooby HuskyandClaude Opus 4.6 b9fd5a9461 Add session affinity to Guacamole service for OIDC flow
With 2 replicas, OIDC callbacks can hit a different pod than the
one that generated the state/nonce, causing auth failures. Session
affinity ensures the same client always reaches the same pod.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 14:47:25 -05:00
Scooby HuskyandClaude Opus 4.6 e29eb9611b Make Authentik the primary login method for Guacamole
Change EXTENSION_PRIORITY from "*,openid" to "openid,*" so
unauthenticated users are redirected to Authentik OIDC instead
of seeing the database login form.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 14:24:53 -05:00
Scooby HuskyandClaude Opus 4.6 7eae427d1a Scale services to 2+ replicas for HA
- ArgoCD: server and repo-server → 2 replicas
- GitLab: webservice, sidekiq, registry, KAS, praefect → min 2 replicas
- Guacamole: client and guacd → 2 replicas
- Kiali: 1 → 2 replicas
- Alertmanager: add 2 replicas
- TeslaMate CNPG: 1 → 2 instances

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 03:08:49 -05:00
Scooby HuskyandClaude Opus 4.6 5d09fb86e7 Add GitLab Redis initializer ordering fix
Fixes NameError: uninitialized constant Gitlab::Redis::ALL_CLASSES
caused by 7_prometheus_metrics.rb loading before 7_redis.rb
(alphabetically 'p' < 'r'). The ConfigMap provides a 0_redis_early.rb
initializer that requires gitlab/redis before other initializers run.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 20:26:24 -05:00
Scooby HuskyandClaude Opus 4.6 2643d5e0bf Fix TeslaMate DB corruption and Rook-Ceph reconcile hang
- Scale CNPG pg-teslamate to 1 instance: on-disk storage corruption
  (corrupted sequences, system catalog indexes) means replicas cloned
  via pg_basebackup inherit corruption and can't open any database.
  Primary works from cached catalogs. Sequences already rebuilt live.

- Remove talos-cp-02 from Rook-Ceph nodes list: ceph-volume inventory
  hangs on BIOS partition via nsenter lvs in host namespace. Talos
  read-only /etc prevents LVM filter fix. OSD 1 keeps running from
  its existing deployment.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 14:53:31 -05:00
Scooby HuskyandClaude Opus 4.6 34a7cf95d0 Add HTTP proxy trust config to Home Assistant init container
Home Assistant was returning 400 errors because it received
X-Forwarded-For headers from the Istio ingress gateway but wasn't
configured to trust reverse proxies. Add use_x_forwarded_for and
trusted_proxies to the init container's configuration.yaml template.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 21:51:24 -05:00
Scooby HuskyandClaude Opus 4.6 7a8eb2046f Fix n8n, gitlab, and netbird-operator degraded states
- n8n: Set replicaCount=1 (RWO PVC incompatible with multiple replicas),
  add resource limits to satisfy Kyverno policy
- gitlab: Add ignoreDifferences for redis-gitlab-additional service
  (port names and selectors managed by Redis operator)
- netbird-operator: Add ExternalSecret for netbird-mgmt-api-key in
  netbird namespace and add manifests source to Application

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 19:53:10 -05:00
Scooby HuskyandClaude Opus 4.6 8bde33f4d1 Remove nessus app and fix envoy-gateway OCI chart bug
- Delete nessus Application CRD, manifests, and bootstrap script
- Vendor envoy-gateway Helm chart (v1.6.3) locally to work around
  ArgoCD v3.3.3 Docker Hub OCI resolution bug
- Re-enable auto-sync for envoy-gateway

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 19:41:36 -05:00
Scooby HuskyandClaude Opus 4.6 d304bd257d Increase GitLab webservice resources to fix slow initial response
Webservice was at 94% memory (1875Mi/2Gi) with only 50m CPU request,
causing CPU throttling and slow first requests after idle periods.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 19:39:32 -05:00
Scooby HuskyandClaude Opus 4.6 131cbca4a6 Fix ArgoCD ServerSideDiff permanent OutOfSync diffs
Add explicit CRD/API defaults to manifests that were causing ArgoCD's
SSA dry-run to produce results different from live state:

- HTTPRoutes: add group, kind, weight defaults to parentRefs/backendRefs
- Kyverno ClusterPolicies: add skipBackgroundRequests, allowExistingViolations
- Tetragon TracingPolicies: add return, maxData, resolve, returnCopy defaults
- Gateway certificateRefs: add group="" default
- Guacamole Gateway: add group="" to certificateRefs

Add ignoreDifferences for resources that legitimately differ:
- Cilium cert Secrets (auto-generated, data always differs)
- Istio ValidatingWebhookConfiguration failurePolicy (istiod mutates)
- Crowdsec LAPI Secrets (randomly generated)
- ServiceMonitor/PodMonitor relabeling action defaults
- StatefulSet volumeClaimTemplates apiVersion/kind defaults

Persist argocd-cm ignoreDifferences config in ArgoCD Helm values.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 17:32:59 -05:00
Scooby HuskyandClaude Opus 4.6 ee6a0bb402 Add FOWNER capability to frigate for chmod on shm logs
s6-overlay's log-prepare needs chmod on /dev/shm/logs/* which
requires CAP_FOWNER when all capabilities are dropped.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 06:25:11 -05:00
Scooby HuskyandClaude Opus 4.6 ce23f9282c Fix prometheus secret namespace and frigate capabilities
- Revert talos-client-cert secret to prometheus namespace (prometheus
  pods mount this secret, not kube-system pods)
- Add CHOWN/DAC_OVERRIDE/SETUID/SETGID capabilities to frigate
  (s6-overlay needs chown for log directories)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 05:59:23 -05:00
Scooby HuskyandClaude Opus 4.6 7436462a10 Fix nextcloud PVC size to match cluster capacity (200Gi)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 04:09:14 -05:00
Scooby HuskyandClaude Opus 4.6 7640fb895b Fix degraded ArgoCD applications
- frigate: Remove duplicate PVCs (Helm manages them), update config size to 15Gi
- gitlab: Fix sync-admin-job secretKeyRef schema error, fetch password via kubectl
- nextcloud: Remove duplicate PVC, remove invalid pod-level securityContext
- rook-ceph: Update cephfs-smb-pvc to 50Gi, remove unsupported dashboard.config
- nessus: Add privileged PSS label for NET_ADMIN/NET_RAW capabilities
- scylla-manager: Add privileged PSS label for SYS_NICE capability
- n8n: Create missing n8n-main-persistence PVC
- projects: Add authentik namespace to applications project destinations

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 00:05:29 -05:00
Scooby Husky 4b28424719 Fix ArgoCD RBAC: add Scooby user and preferred_username scope 2026-03-09 22:51:46 -05:00
Scooby Husky 086a2bf406 Fix degraded apps: add nessus to project, remove duplicate guacamole route, update prometheus CRDs to v0.89.0 2026-03-09 22:32:15 -05:00
Scooby Husky 11eddf4971 Set GitLab repo URL and update ArgoCD hostAliases 2026-03-09 22:18:26 -05:00
Scooby Husky aacb8eebbe Initial commit 2026-03-09 20:21:35 -05:00