128 lines
5.6 KiB
Markdown
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.
|