# 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 . 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:`. 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.