diff --git a/README.md b/README.md index 0cfbf69..5fcccd7 100644 --- a/README.md +++ b/README.md @@ -1,141 +1,95 @@ # Homelab GitOps -Argo CD is the only component that deploys the resources in this repository. Application CI publishes immutable -container images and then commits the selected image tag here; it never receives Kubernetes credentials. +Argo CD is the only component that deploys resources from this repository. Application repositories publish +immutable images and/or Helm charts, then commit the exact chart version selected by the corresponding Argo +`Application`. CI never receives Kubernetes credentials. -## Repository layout +## Ownership boundary -- `apps/immich-sync/` is the desired Kubernetes state for Immich Share Sync. -- `apps/immichframe/` wraps the reusable ImmichFrame chart with this cluster's - hostname, album selection, resources, and encrypted API credential. -- `apps/metallb/` pins the upstream native MetalLB installation and owns this - cluster's L2 address pool. -- `apps/argocd/` wraps the pinned upstream Argo CD chart with this cluster's - ingress, Authentik OIDC, RBAC, and secret-reference configuration. -- `apps/brgkepler-com/`, `apps/gschissenes-equipment/`, - `apps/immich-crack/`, `apps/pfostendynamics-com/`, and - `apps/watschn-studio/` pin the shared PHP homepage chart and reference their - existing CephFS content claims. -- `apps/brunner-ninja/`, `apps/flaresolverr/`, `apps/ombi/`, - `apps/tautulli/`, `apps/uptime-kuma/`, and `apps/wiki-js/` pin their existing - upstream chart releases and cluster values. -- `apps/mosquitto/` and `apps/visual-studio-code/` preserve their small local - manifest contracts; `apps/artcraft/` and `apps/coturn/` use local adapter - charts for their repeated or multi-resource third-party integrations. -- `bootstrap/` contains Argo CD projects and Applications, including the - upstream Sealed Secrets controller and the five PHP sites above. -- `scripts/update-immich-sync-image.sh` is the single image-promotion interface used by CI. -- `scripts/update-immichframe-chart.sh` is the chart-promotion interface used - by ImmichFrame chart CI. +- An application repository owns its image, reusable Kubernetes behavior, probes, services, ingress capability, + persistence capability, monitoring capability, tests, and Helm chart. +- This repository owns only Argo Applications, exact chart selections, cluster values such as domains, addresses, + storage classes and sizes, existing PVC names, resource sizing, and sealed credentials. +- A maintained chart records the upstream or custom application release in `appVersion`. +- One independently releasable workload has one chart and one Argo Application. The 13 Artcraft applications are + separate charts and Applications but all target the shared `artcraft` namespace. +- Upstream charts are referenced directly. GitOps-local dependency wrappers are forbidden. +- Secrets are either references to existing Secrets or `SealedSecret` ciphertext. Plaintext never belongs here. -The only canonical Immich Share Sync image version is `images[0].newTag` in -`apps/immich-sync/kustomization.yaml`. The resource manifest deliberately omits a tag, and Kustomize supplies the -immutable full Git SHA while rendering. +See [docs/chart-ownership.md](docs/chart-ownership.md) for the complete chart versioning and promotion contract. -The only canonical ImmichFrame chart version is the `immichframe` dependency -in `apps/immichframe/Chart.yaml`. Chart CI publishes a unique immutable version -and commits that dependency update here. The chart owns the reusable workload -shape; `apps/immichframe/values.yaml` owns only this cluster's choices. +## Layout -## First-time setup +- `bootstrap/` contains AppProjects and Applications. +- `apps//values.yaml` contains only this cluster's values for that chart. +- `apps//resources/` is allowed only for genuinely cluster-owned resources, such as a MetalLB address pool or + an ImmichFrame `SealedSecret`. +- `scripts/update-chart-version.sh` is the common CI promotion interface. -The cluster already runs Argo CD in the `argocd` namespace and exposes its UI at . +## Version and promotion flow -Argo CD manages its own chart after bootstrap. If the controller does not -exist, recover it first from the same canonical chart dependency and values, -then recreate the AppProjects and Applications: +Maintained chart repositories tag releases with `vMAJOR.MINOR.PATCH` (or `-vMAJOR.MINOR.PATCH` in the shared +chart repository). `git describe --tags --long` is canonical: + +- tag `v1.0.0` packages chart `1.0.0`; +- the next commit packages `1.0.0-1-g41627ab`; +- the Git hash is retained to prevent two histories from producing the same immutable package version; +- dirty trees are rejected. + +After publishing succeeds, CI checks out this repository with `GITOPS_TOKEN`, runs: + +```sh +./scripts/update-chart-version.sh APPLICATION CHART_VERSION +``` + +and commits `bootstrap/APPLICATION.yaml`. Argo CD observes that Git commit, renders the published chart with the +cluster values, and reconciles Kubernetes. + +Upstream chart upgrades have no repository under our control, so their exact `targetRevision` is updated here +manually or by a dependency updater. They do not need a fake promotion repository. + +## Gitea credentials + +Prefer owner- or organization-level Actions secrets so every maintained chart repository receives the same narrowly +scoped credentials: + +- `PACKAGE_USERNAME` and `PACKAGE_TOKEN`: upload-only credentials for the `feedc0de` Helm package repository; +- `GITOPS_TOKEN`: a token with `write:repository` access to only `feedc0de/homelab-gitops`; +- `QUAY_USERNAME` and `QUAY_TOKEN`: required only by repositories that publish container images. + +The workflow commits as the normal `feedc0de` Git identity. The token determines authorization; the commit name is +not a separate Gitea account. A dedicated promoter account is still preferable if a one-repository permission scope +is desired. + +## Bootstrap and recovery + +The expected kubectl context is `kubernetes-admin@kubernetes`. ```sh ./bootstrap/install-argocd.sh ./bootstrap/install.sh ``` -The recovery installer preserves `argocd-secret`, `argocd-oidc`, -`argocd-notifications-secret`, and `argocd-redis`. Back up or provision those -Secrets separately; none of their values belongs in this public repository. +Argo CD manages its own upstream chart after recovery. The repo-server is given cluster-local host aliases for +`code.brunner.ninja` and `brunner.ninja`, routed through the Traefik ClusterIP, because this cluster cannot hairpin to +the public WAN address. The canonical alias configuration is in `apps/argocd/values.yaml`. -1. Push this repository so Argo CD can read its public HTTPS URL. -2. Configure the `immich-sync` repository's cross-repository token as described below. -3. Review the rendered workload: +Before adopting a stateful workload, record its PVC UID and volume, render and dry-run the new chart, then verify the +same values after synchronization: - ```sh - kubectl kustomize apps/immich-sync - ``` +```sh +kubectl get pvc -A -o custom-columns='NAMESPACE:.metadata.namespace,NAME:.metadata.name,UID:.metadata.uid,VOLUME:.spec.volumeName' +kubectl --namespace argocd get applications \ + -o custom-columns='NAME:.metadata.name,SYNC:.status.sync.status,HEALTH:.status.health.status' +``` -4. Record the existing persistent volume before the first synchronization: - - ```sh - kubectl --namespace default get pvc immich-sync-stats \ - -o jsonpath='{.metadata.uid}{"|"}{.spec.volumeName}{"\n"}' - ``` - -5. Bootstrap the Argo CD resources: - - ```sh - ./bootstrap/install.sh - ``` - -6. Inspect the result in the UI or CLI: - - ```sh - argocd app get immich-sync - argocd app diff immich-sync - kubectl --namespace default rollout status deployment/immich-sync - kubectl --namespace default get deployment immich-sync \ - -o jsonpath='{.spec.template.spec.containers[0].image}{"\n"}' - ``` - -7. Confirm that the PVC UID and volume name are unchanged. Only after Argo CD reports the application `Synced` and - `Healthy`, remove the obsolete CI deployer and delete the old `KUBE_CONFIG_BASE64` secret from Gitea: - - ```sh - kubectl --namespace default delete rolebinding/immich-sync-deployer \ - role/immich-sync-deployer serviceaccount/immich-sync-deployer \ - secret/immich-sync-deployer-token - ``` - -The bootstrap Application enables automatic synchronization, pruning, and self-healing. Consequently, direct edits -to its managed Kubernetes resources are reverted to Git. Change Git when experimenting, observe `OutOfSync`, and then -watch Argo CD return the application to `Synced` and `Healthy`. - -## Give Immich Share Sync CI permission to promote images - -The built-in Gitea Actions job token cannot write to another repository. Create a dedicated bot account, for example -`gitops-promoter`, add it to `feedc0de/homelab-gitops` with repository **Write** permission, and create a personal -access token for that account with only the `write:repository` scope. - -In `feedc0de/immich-sync`, open **Settings → Actions → Secrets** and create `GITOPS_TOKEN` containing that token. The -workflow passes it directly to `actions/checkout` while checking out this repository; the checkout action configures -authenticated HTTPS for the final `git push` without placing the token in a committed URL. - -The main-branch workflow now performs this sequence: - -1. Build and push `registry.brunner.ninja/feedc0de/immich-sync:`. -2. Verify that the completed build still belongs to the current `main` commit. -3. Update `apps/immich-sync/kustomization.yaml` with that SHA. -4. Commit and push the promotion to this repository. -5. Let Argo CD detect and synchronize the Git commit. - -For a personal token owned by `feedc0de`, `write:repository` also applies to other repositories that account can -write. A dedicated bot restricted to this one repository therefore gives the token a meaningfully smaller blast -radius. - -## Manual recovery path - -Argo CD is the normal deployment mechanism. If it is unavailable, `apps/immich-sync/install.sh` renders, performs a -server-side dry-run, applies the same Kustomize source, and waits for the Deployment rollout. A later Argo CD refresh -will see the same desired resources. +Automatic sync, pruning, and self-healing mean direct live edits are temporary. Change Git and observe Argo return +the Application to `Synced` and `Healthy`. ## Sealed Secrets -Argo CD installs the official Sealed Secrets chart pinned in -`bootstrap/sealed-secrets.yaml`. Public Git stores only `SealedSecret` -ciphertext. The controller decrypts that ciphertext into namespace-scoped -Kubernetes Secrets; the private sealing key never belongs in this repository. - -Install the matching `kubeseal` client, then seal a Secret for its final name -and namespace. The default strict scope prevents ciphertext from being moved to -another name or namespace: +Argo CD installs the official Sealed Secrets chart. Public Git stores only `SealedSecret` ciphertext; the controller's +private sealing key must be backed up to encrypted offline storage and never committed. ```sh kubectl --namespace default create secret generic example \ @@ -146,9 +100,3 @@ kubectl --namespace default create secret generic example \ --controller-namespace sealed-secrets \ --format yaml > example-sealed-secret.yaml ``` - -The controller's generated private key is the disaster-recovery key for every -sealed value. Back up the Secrets labelled -`sealedsecrets.bitnami.com/sealed-secrets-key=active` from namespace -`sealed-secrets` to an encrypted offline store. Do not commit that backup. A -cluster rebuilt without one of those keys cannot decrypt existing ciphertext. diff --git a/docs/chart-ownership.md b/docs/chart-ownership.md index 004bbbc..e0fd404 100644 --- a/docs/chart-ownership.md +++ b/docs/chart-ownership.md @@ -9,7 +9,10 @@ Each independently versioned application has one chart and one Argo CD Application. This includes each Artcraft application: WordCraft, GridCraft, DeckCraft, PhotoCraft, LightCraft, VectorCraft, FilmCraft, EffectCraft, DesignCraft, FigCraft, PdfCraft, SoundCraft, and CADCraft are separate releases, -not one suite chart. +not one suite chart. They share the `artcraft` namespace but do not share +Kubernetes resource ownership. Their static upstream release bundles and nginx +configuration are baked into distinct immutable images; pods never download +application code at startup. Maintained chart repositories set `Chart.yaml.appVersion` to the tested application/image version. CI derives the chart package version from Git: @@ -18,6 +21,9 @@ application/image version. CI derives the chart package version from Git: - the next commit produces a unique version such as `1.0.0-1-g41627abcdef`. +The shared chart repository uses per-chart tags such as +`wordcraft-v1.0.0`; a single-chart repository uses `v1.0.0`. + The hash is retained to prevent package-version collisions. CI publishes to `https://brunner.ninja/charts`, then uses `GITOPS_TOKEN` to update only the matching Argo Application's exact Helm `targetRevision`. Argo CD performs the