Files
homelab-gitops/README.md
T

128 lines
5.6 KiB
Markdown

# 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.
## Repository layout
- `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.
- `bootstrap/` contains Argo CD projects and Applications, including the
upstream Sealed Secrets controller.
- `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.
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.
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.
## First-time setup
The cluster already runs Argo CD in the `argocd` namespace and exposes its UI at <https://cd.brunner.ninja>.
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:
```sh
kubectl kustomize apps/immich-sync
```
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:<source SHA>`.
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.
## 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:
```sh
kubectl --namespace default create secret generic example \
--from-literal=token='replace-me' \
--dry-run=client --output=yaml \
| kubeseal \
--controller-name sealed-secrets-controller \
--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.