docs: record live CI bootstrap
lint / yaml (push) Successful in 15s
lint / yaml (pull_request) Successful in 15s
lint / terraform (pull_request) Successful in 34s
lint / terraform (push) Successful in 31s
lint / ansible (push) Successful in 3m26s
lint / ansible (pull_request) Successful in 4m7s

This commit is contained in:
2026-09-09 18:28:45 +00:00
parent 10a21df890
commit 0984a2d0ef
3 changed files with 30 additions and 20 deletions
+2 -1
View File
@@ -28,9 +28,10 @@ What changed in this homelab, when, and why. Newest first.
| cleanup | Removed the retired NapCat tree, the Contour and Kanidm archive trees, and seven generated Terraform plan files before establishing the clean Git baseline; plans may embed complete state and remain globally ignored | | cleanup | Removed the retired NapCat tree, the Contour and Kanidm archive trees, and seven generated Terraform plan files before establishing the clean Git baseline; plans may embed complete state and remain globally ignored |
| CI | Added a review-first Gitea Actions runner bootstrap: official actions chart 0.1.1, pinned runner 2.3.0, one persistent instance-scoped Kubernetes runner with capacity four, plus an ESO reference to its registration token in OpenBao. The first deployment proved that rootlesskit is blocked by the node's AppArmor unprivileged-userns policy; because the chart requires privileged DinD in either mode, the reviewed fix uses regular DinD instead of weakening the host-wide policy. The runner image intentionally carries neither `uv` nor Terraform: Terraform uses its versioned setup action, while `uv` is pinned and installed from official PyPI because the nested job network reaches PyPI but times out against the GitHub API queried by `setup-uv`. Ansible installs only `ansible-core` in its tool venv and puts declared Galaxy collections in a shared path visible to ansible-lint; installing the `ansible` meta-package had made Galaxy falsely skip that shared installation | | CI | Added a review-first Gitea Actions runner bootstrap: official actions chart 0.1.1, pinned runner 2.3.0, one persistent instance-scoped Kubernetes runner with capacity four, plus an ESO reference to its registration token in OpenBao. The first deployment proved that rootlesskit is blocked by the node's AppArmor unprivileged-userns policy; because the chart requires privileged DinD in either mode, the reviewed fix uses regular DinD instead of weakening the host-wide policy. The runner image intentionally carries neither `uv` nor Terraform: Terraform uses its versioned setup action, while `uv` is pinned and installed from official PyPI because the nested job network reaches PyPI but times out against the GitHub API queried by `setup-uv`. Ansible installs only `ansible-core` in its tool venv and puts declared Galaxy collections in a shared path visible to ansible-lint; installing the `ansible` meta-package had made Galaxy falsely skip that shared installation |
| identity | Declared the Samba AD `gitea-admins` group with `panxiao81` as its initial member. Gitea already maps this OIDC group to site administrators; the local `gitea_admin` account remains as break-glass access | | identity | Declared the Samba AD `gitea-admins` group with `panxiao81` as its initial member. Gitea already maps this OIDC group to site administrators; the local `gitea_admin` account remains as break-glass access |
| docs | Reconciled the redesign and CI status with reality: the Gitea remote, instance-scoped runner, OpenBao-projected registration token and green Stage 1 are live; Flux, off-site mirroring and recovery verification remain pending |
`Carried forward`: re-verify OpenBao/ESO recovery and remaining Secret inventory; `Carried forward`: re-verify OpenBao/ESO recovery and remaining Secret inventory;
configure a Git remote and off-site mirror; confirm the running Gitea version; configure an off-site Git mirror; plan the Gitea upgrade beyond 1.25.5;
take an encrypted independent OCI state copy before enabling bucket versioning; take an encrypted independent OCI state copy before enabling bucket versioning;
reconstruct the missing root to a zero-change plan; bootstrap Gitea Actions and Flux on a low-risk reconstruct the missing root to a zero-change plan; bootstrap Gitea Actions and Flux on a low-risk
service; then move Tunnel origins to Envoy one hostname at a time. service; then move Tunnel origins to Envoy one hostname at a time.
+8 -7
View File
@@ -1,6 +1,7 @@
# CI/CD — what we are building # CI/CD — what we are building
Status: **design, partly built.** Stage 1 is live. The substrate is undecided. Status: **partly built.** Stage 1 and the Kubernetes runner are live; Flux and
credentialed stages are not yet installed.
Started 2026-07-28. Started 2026-07-28.
## Goal ## Goal
@@ -22,9 +23,9 @@ to make drift between this repo and reality visible when it happens.
| | | | | |
|---|---| |---|---|
| git | History since 2026-07-28. Four commits, **no remote yet** | | git | `homelab-infra` is hosted on the local Gitea; an independent off-site mirror is still missing |
| stage 1 | Live and green — `yamllint`, `ansible-lint`, `terraform fmt`/`validate`. Configs tuned against a real run | | stage 1 | Live and green on the Gitea runner — `yamllint`, `ansible-lint`, `terraform fmt`/`validate` |
| gitea | 1.25.5, Actions **enabled**, `DEFAULT_ACTIONS_URL=github`. **No runner deployed**, so nothing executes | | gitea | 1.25.5, Actions enabled, `DEFAULT_ACTIONS_URL=github`; one instance-scoped Kubernetes runner is deployed with capacity four |
| ansible | 33 roles across `infrastructure/proxmox/`, `infrastructure/samba-ad/`, `infrastructure/openbao/` | | ansible | 33 roles across `infrastructure/proxmox/`, `infrastructure/samba-ad/`, `infrastructure/openbao/` |
| terraform | 4 roots, **local state**, each with **different interactive auth** (`bao login -method=oidc`, `az login`) | | terraform | 4 roots, **local state**, each with **different interactive auth** (`bao login -method=oidc`, `az login`) |
| k8s | ~13 Helm releases, all deployed by hand | | k8s | ~13 Helm releases, all deployed by hand |
@@ -171,10 +172,10 @@ the CI system itself.
## Sequencing ## Sequencing
1. Externalise secrets — unblocks everything, valuable on its own 1. Externalise secrets — **partly complete**; ESO delivery works, recovery and the remaining inventory are pending
2. Capture `e5renew` and `rustfs` into the repo (`helm get values`) 2. Capture `e5renew` and `rustfs` into the repo (`helm get values`)
3. Git remote 3. Git remote — **complete locally**; off-site mirror pending
4. Pick the substrate; stand it up in an isolated namespace 4. Pick the substrate; stand it up in an isolated namespace — **complete**
5. Stage 2, then stage 3 on **one** container-friendly role first 5. Stage 2, then stage 3 on **one** container-friendly role first
6. Flux on one low-stakes namespace (`http-echo` or `marker`) 6. Flux on one low-stakes namespace (`http-echo` or `marker`)
7. Drift detection for Terraform and Ansible — scoped machine identities 7. Drift detection for Terraform and Ansible — scoped machine identities
+20 -12
View File
@@ -1,6 +1,6 @@
# Homelab GitOps and IaC redesign # Homelab GitOps and IaC redesign
Status: **design; no infrastructure changes have been applied.** Status: **implementation in progress; CI bootstrap is live, Flux is not yet installed.**
Started 2026-09-09. This is the durable record of the redesign discussion. It Started 2026-09-09. This is the durable record of the redesign discussion. It
separates observations, decisions and open work so an assumption cannot silently separates observations, decisions and open work so an assumption cannot silently
@@ -41,7 +41,11 @@ or reconcile later CPU, memory, NIC or boot drift.
- Repository history records External Secrets Operator 2.8.0 as deployed. Five - Repository history records External Secrets Operator 2.8.0 as deployed. Five
`ExternalSecret` resources cover Authelia, Gitea, Cloudflared and SeaweedFS. `ExternalSecret` resources cover Authelia, Gitea, Cloudflared and SeaweedFS.
All five reported `SecretSynced=True` during a live check on 2026-09-09. All five reported `SecretSynced=True` during a live check on 2026-09-09.
- The repository has no configured Git remote yet. - Gitea Actions now has one instance-scoped runner in namespace `gitea-actions`.
Its Helm release is deployed, its Pod is `2/2 Running`, its identity PVC is
bound, and a sixth `ExternalSecret` delivers the registration token from OpenBao.
- The repository is hosted at `panxiao81/homelab-infra` on the local Gitea
instance. A one-way off-site mirror is still missing.
- Terraform roots remain per-service and must not be merged. - Terraform roots remain per-service and must not be merged.
- The k3s node was `Ready` on 2026-09-09. The Snap-packaged `kubectl` could - The k3s node was `Ready` on 2026-09-09. The Snap-packaged `kubectl` could
not start because the user systemd session was degraded; `k3s kubectl` with the not start because the user systemd session was degraded; `k3s kubectl` with the
@@ -84,10 +88,12 @@ without becoming another deployment controller.
### CI execution ### CI execution
Most CI uses Gitea Actions for its GitHub Actions compatibility. A persistent Most CI uses Gitea Actions for its GitHub Actions compatibility. A persistent
Gitea Runner StatefulSet runs in Kubernetes with rootless Docker-in-Docker and Gitea Runner StatefulSet runs in Kubernetes with Docker-in-Docker and capacity
capacity four; individual job containers are created dynamically. Rootless DinD four; individual job containers are created dynamically. The chart requires a
still requires a privileged Pod, so the runner is repository-scoped and accepts privileged DinD container in both modes, and rootlesskit is blocked by the node's
trusted workflows only. AppArmor unprivileged-userns policy, so regular DinD is used instead of weakening
that host-wide policy. The runner is instance-scoped and accepts trusted
repositories and workflows only.
Only explicitly labelled jobs needing privilege, nested virtualization, Only explicitly labelled jobs needing privilege, nested virtualization,
amd64-only software or isolation from k3s use an ephemeral Proxmox VM. IaC owns amd64-only software or isolation from k3s use an ephemeral Proxmox VM. IaC owns
@@ -196,13 +202,15 @@ offline break-glass path. ESO-generated Secrets are projections, not backups.
## Implementation phases ## Implementation phases
1. Preserve OCI state and sanitized libvirt/Helm evidence. 1. **In progress:** sanitized libvirt/Helm evidence is recorded; the independent
2. Re-verify the existing OpenBao/ESO delivery and recovery path, inventory encrypted OCI state backup is still missing.
2. **In progress:** live OpenBao/ESO delivery is verified; verify recovery and inventory
remaining manually managed Secrets, then migrate them incrementally. remaining manually managed Secrets, then migrate them incrementally.
3. Configure Gitea remote plus a one-way off-site mirror. Confirm whether the 3. **In progress:** the Gitea remote exists; add a one-way off-site mirror and
running Gitea supports the 1.27 Terraform State Registry. revisit the Terraform State Registry after upgrading beyond Gitea 1.25.5.
4. Manually deploy the reviewed Gitea Runner bootstrap, then bootstrap Flux on 4. **In progress:** the reviewed Gitea Runner is deployed and Stage 1 CI is green;
`http-echo` or `marker` without enabling prune until live ownership is audited. bootstrap Flux on `http-echo` or `marker` without enabling prune until live
ownership is audited.
5. Move Tunnel origins to Envoy and consolidate split DNS through Blocky. 5. Move Tunnel origins to Envoy and consolidate split DNS through Blocky.
6. Deploy Backstage read-only with Catalog, Kubernetes, Flux and TechDocs. 6. Deploy Backstage read-only with Catalog, Kubernetes, Flux and TechDocs.
7. Reconstruct the OCI root to a zero-change plan and add libvirt drift reports. 7. Reconstruct the OCI root to a zero-change plan and add libvirt drift reports.