···11-# Flux GitOps Migration Plan
22-33-## Final Step-By-Step Plan
44-55-1. **Bootstrap Flux With Option B**
66- - Use `flux bootstrap github` now.
77- - Bootstrap path: `k8s/flux/system`.
88- - Do not change NixOS during this migration.
99- - Treat future NixOS/k3s auto-deploy bootstrap as optional hardening later.
1010- - Ensure there is one root model: `k8s/flux/system` owns the Flux root and references child Flux `Kustomization`s.
1111-1212-2. **Add Flux SOPS Key**
1313- - Generate a dedicated Flux age keypair.
1414- - Commit only the public key as `keys/flux_k8s.pub`.
1515- - Store the private key out-of-band.
1616- - Create `flux-system/sops-age` manually as a bootstrap artifact.
1717- - Do not rely on Flux to decrypt/manage its own decryption key.
1818- - Disaster recovery procedure will be: rebuild k3s, bootstrap Flux, apply `sops-age`, then Flux reconciles encrypted resources.
1919-2020-3. **Update SOPS Policy**
2121- - Extend `.sops.yaml` generation so `k8s/flux/secrets/*.sops.yaml` encrypts to:
2222- - existing user keys
2323- - existing host keys
2424- - new `flux_k8s` key
2525- - Keep existing `secrets/*.yaml` encrypted only to user/host keys.
2626- - Flux must not be able to decrypt host/Terraform/local secrets.
2727- - Run `just sops-rekey` after updating recipients.
2828-2929-4. **Create Flux Directory Layout**
3030- - Create:
3131-3232- ```text
3333- k8s/flux/
3434- ├── system/
3535- ├── sources/
3636- ├── infra-crds/
3737- ├── infra-core/
3838- ├── platform/
3939- ├── apps/
4040- ├── external-routes/
4141- └── secrets/
4242- ```
4343-4444- - Keep all first-class k8s Secret manifests in flat `k8s/flux/secrets/`.
4545-4646-5. **Define Flux Layer Graph**
4747- - Use Flux `Kustomization.dependsOn` between layers:
4848-4949- ```text
5050- system -> sources -> infra-crds -> infra-core -> platform -> apps -> external-routes
5151- ```
5252-5353- - Use `HelmRelease.dependsOn` only for same-layer release ordering.
5454- - Set all Flux intervals to `15m`.
5555- - Use explicit `flux reconcile ...` for immediate deploys.
5656- - Empty layers carry temporary placeholder ConfigMaps so Flux/Kustomize can
5757- reconcile them before real resources are migrated. Remove each placeholder
5858- when that layer gets its first real resource.
5959-6060-6. **Create Flux Sources**
6161- - Add `GitRepository cute-haus` pointing at the monorepo.
6262- - Add `HelmRepository` resources for:
6363- - `grafana`
6464- - `prometheus-community`
6565- - `traefik`
6666- - `longhorn`
6767- - `cnpg`
6868- - `tailscale`
6969- - `jetstack`
7070- - `nfd`
7171- - `intel`
7272-7373-7. **Convert Global Values**
7474- - Convert `k8s/values/global.yaml` into non-secret `ConfigMap cute-haus-global`.
7575- - Reference it from HelmReleases with `valuesFrom`.
7676- - Document that this ConfigMap must stay non-secret.
7777- - Any secret-like value must go into `k8s/flux/secrets/*.sops.yaml`.
7878-7979-8. **Migrate Secrets To First-Class Manifests**
8080- - For charts that currently render Secrets, create equivalent SOPS-encrypted Kubernetes Secret manifests in `k8s/flux/secrets/`.
8181- - Delete chart secret templates after the first-class Secret exists.
8282- - Ensure each Secret is applied in the same or earlier Flux `Kustomization` than its consuming `HelmRelease`.
8383-8484-9. **Apply Secret-Specific Migrations**
8585- - `longhorn-creds`: move `longhorn-b2` to first-class Secret; keep chart for recurring job, UI ingress, and proxy class.
8686- - `cert-manager-issuers`: move `cloudflare-api-token` to first-class Secret.
8787- - `cluster-tls`: move TLS material to first-class `kubernetes.io/tls` Secrets.
8888- - `pg-shared`: move `pg-shared-b2` and `<role>-pg-creds` to first-class Secrets.
8989- - `tranquil-pds`: move `tranquil-pds-env` and `atcr-pull` to first-class Secrets.
9090- - `forward-auth`: move per-app `forward-auth-<app>-env` Secrets to first-class Secrets.
9191- - App env Secrets: move `<app>-env` Secrets to first-class Secrets.
9292- - `tailscale-operator`: use `valuesFrom: Secret` because the upstream chart consumes secret values directly.
9393-9494-10. **Preserve Forward-Auth Check**
9595- - Keep the forward-auth `apps:` declaration plaintext.
9696- - Move only signing secrets into first-class encrypted Secret manifests.
9797- - Keep `check-forward-auth.ts` conceptually unchanged.
9898-9999-11. **Convert Helmfile Releases To Flux HelmReleases**
100100- - Convert releases layer-by-layer.
101101- - Store Flux `HelmRelease` objects in `flux-system` and set
102102- `spec.targetNamespace` to the workload namespace. This keeps shared
103103- `valuesFrom` objects such as `cute-haus-global` in one namespace.
104104- - External charts use `HelmRepository`.
105105- - In-tree charts use `GitRepository` and `chart: ./k8s/charts/<name>`.
106106- - Inline non-secret values in `spec.values`.
107107- - Use `valuesFrom` for `cute-haus-global` and the `tailscale-operator` secret values.
108108- - Do not let Helmfile and Flux manage the same release at the same time.
109109-110110-12. **Smoke Test First**
111111- - Migrate `watsup` first.
112112- - Since `cluster-tls` is still Helmfile-managed at that point, omit temporary Flux `dependsOn` for `cluster-tls`.
113113- - Validate:
114114- - Flux source reconciliation
115115- - local chart HelmRelease
116116- - `helm-controller`
117117- - manual `just k8s` workflow
118118- - rollback path
119119-120120-13. **Migrate Layer-By-Layer**
121121- - After `watsup`, migrate:
122122- 1. `infra-crds`: `cert-manager`, `cnpg`
123123- 2. `infra-core`: `traefik`, `longhorn-creds`, `longhorn`, `tailscale-operator`, NFD, Intel device plugins, `cert-manager-issuers`
124124- 3. `platform`: `pg-shared`, `valkey`, `tika`, `gotenberg`, `cluster-tls`, monitoring
125125- 4. `apps`: app releases, env-only first, multi-secret apps later
126126- 5. `external-routes`: last
127127-128128-14. **Per-Release Cutover Checklist**
129129- - Create required first-class SOPS Secret manifests.
130130- - Add or update the `HelmRelease`.
131131- - Remove the release from `k8s/helmfile.yaml`.
132132- - Remove obsolete `k8s/values/secrets/<release>.yaml`.
133133- - Push.
134134- - Run:
135135-136136- ```bash
137137- flux reconcile source git cute-haus -n flux-system
138138- flux reconcile kustomization <layer> -n flux-system
139139- ```
140140-141141- - Verify:
142142-143143- ```bash
144144- flux get kustomizations -A
145145- flux get helmreleases -A
146146- helm list -A
147147- kubectl get all -n <namespace>
148148- ```
149149-150150-15. **Add `just k8s` Manual Workflow**
151151- - Add one short helper:
152152-153153- ```bash
154154- just k8s apply <release>
155155- just k8s diff <release>
156156- just k8s suspend <release>
157157- just k8s resume <release>
158158- just k8s reconcile <release>
159159- ```
160160-161161- - Scope: local in-tree charts only where release name equals `k8s/charts/<release>`.
162162- - Flux `HelmRelease` objects live in `flux-system`; the optional namespace
163163- argument remains the Helm target namespace.
164164- - `apply` suspends Flux first, then runs `helm upgrade --install`.
165165- - `diff` uses `helm diff upgrade --allow-unreleased`.
166166- - External charts are managed through Flux only.
167167-168168-16. **Update CI And Checks**
169169- - Keep existing chart render checks.
170170- - Update `check-release-names.ts` to read Flux `HelmRelease` resources instead of `helmfile.yaml`.
171171- - Update `check-pinned-images.ts` to derive deployed local charts from Flux `HelmRelease`s.
172172- - Keep `check-forward-auth.ts` reading plaintext forward-auth app declarations.
173173- - Add Flux/Kustomize validation:
174174-175175- ```bash
176176- kustomize build k8s/flux/system
177177- kustomize build k8s/flux/sources
178178- kustomize build k8s/flux/secrets
179179- kustomize build k8s/flux/infra-crds
180180- kustomize build k8s/flux/infra-core
181181- kustomize build k8s/flux/platform
182182- kustomize build k8s/flux/apps
183183- kustomize build k8s/flux/external-routes
184184- ```
185185-186186-17. **Rollback Strategy**
187187- - Keep `helmfile.yaml` usable until the final cleanup.
188188- - If one release fails:
189189-190190- ```bash
191191- flux suspend helmrelease <release> -n <namespace>
192192- ```
193193-194194- - Re-add the release to Helmfile if needed and apply it manually.
195195- - For a bad Flux commit, revert the commit and reconcile Flux.
196196- - Do not delete Helmfile until all releases have been verified under Flux.
197197-198198-18. **Final Cleanup**
199199- - Delete `k8s/helmfile.yaml`.
200200- - Delete obsolete `k8s/values/secrets/*.yaml`.
201201- - Audit for `vals` and `ref+sops://`.
202202- - Remove `vals` from the devShell if unused.
203203- - Update docs:
204204- - `AGENTS.md`
205205- - `README.md`
206206- - `k8s/charts/README.md`
207207- - Clarify that `longhorn-creds` no longer owns credential material despite retaining the release name.
208208-209209-19. **Post-Migration Hardening**
210210- - Rotate duplicated shared credentials toward one credential per consumer.
211211- - Optionally migrate Flux bootstrap from Option B to Option A:
212212- - NixOS writes Flux controller manifests into k3s auto-deploy dir.
213213- - Flux continues managing app/platform resources from Git.
214214- - Optionally reorganize legacy `secrets/` later, outside this migration.
+1-1
k8s/charts/README.md
···1313charts/
1414├── aly-codes/ # Static site (aly.codes)
1515├── audiobookshelf/ # Audiobook library with rclone-mounted media
1616-├── bluesky-pds/ # Reference atproto Personal Data Server
1616+├── tranquil-pds/ # Reference atproto Personal Data Server
1717├── cert-manager-issuers/ # Let's Encrypt ClusterIssuer + wildcard Certificates
1818├── cluster-tls/ # Cloudflare Origin TLS Secrets per domain
1919├── external-routes/ # Ingress + Service + EndpointSlice for off-cluster targets
-5
k8s/charts/bluesky-pds/Chart.yaml
···11-apiVersion: v2
22-name: bluesky-pds
33-description: Bluesky PDS — atproto Personal Data Server
44-type: application
55-version: 0.1.0
···11# Flux System
2233-`flux bootstrap github --path=k8s/flux/system` installs Flux and may generate
44-or update files in this directory. Preserve `layers.yaml` and keep it included
55-from `kustomization.yaml`; it wires the child reconciliation layers for this
66-repo.
33+Flux was bootstrapped from exported manifests (Option B), not via
44+`flux bootstrap github`. The manifests in this directory were applied manually
55+to the cluster. Preserve `layers.yaml` and keep it included from
66+`kustomization.yaml`; it wires the child reconciliation layers for this repo.
7788Under the current bootstrap model, `flux-system/sops-age` is a bootstrap
99artifact applied out-of-band, not an encrypted Flux-managed Secret.