Let Argo CD manage its own deployment

This commit is contained in:
2026-10-18 15:22:15 +02:00
parent ff1ae78f66
commit b83e27f27d
9 changed files with 210 additions and 0 deletions
+15
View File
@@ -10,6 +10,8 @@ container images and then commits the selected image tag here; it never receives
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.
- `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.
@@ -29,6 +31,19 @@ shape; `apps/immichframe/values.yaml` owns only this cluster's choices.
The cluster already runs Argo CD in the `argocd` namespace and exposes its UI at <https://cd.brunner.ninja>.
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:
```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.
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:
+2
View File
@@ -0,0 +1,2 @@
Chart.lock
charts/
+9
View File
@@ -0,0 +1,9 @@
apiVersion: v2
name: argocd
description: Self-managed Argo CD deployment for the homelab cluster
type: application
version: 0.1.0
dependencies:
- name: argo-cd
version: 10.10.5
repository: https://argoproj.github.io/argo-helm
+40
View File
@@ -0,0 +1,40 @@
argo-cd:
global:
domain: cd.brunner.ninja
crds:
install: true
configs:
cm:
oidc.config: |
name: Authentik
issuer: https://auth.brunner.ninja/application/o/argocd/
clientID: argocd
clientSecret: $argocd-oidc:client-secret
requestedScopes: ["openid", "profile", "email", "groups"]
requestedIDTokenClaims:
groups:
essential: true
rbac:
policy.csv: |
g, authentik Admins, role:admin
policy.default: ''
scopes: '[groups]'
secret:
createSecret: false
params:
server.insecure: true
notifications:
secret:
create: false
redisSecretInit:
enabled: false
server:
ingress:
enabled: true
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
+39
View File
@@ -0,0 +1,39 @@
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: argocd
namespace: argocd
spec:
description: Argo CD self-management
sourceRepos:
- https://code.brunner.ninja/feedc0de/homelab-gitops.git
- https://argoproj.github.io/argo-helm
destinations:
- namespace: argocd
server: https://kubernetes.default.svc
clusterResourceWhitelist:
- group: apiextensions.k8s.io
kind: CustomResourceDefinition
- group: rbac.authorization.k8s.io
kind: ClusterRole
- group: rbac.authorization.k8s.io
kind: ClusterRoleBinding
namespaceResourceWhitelist:
- group: ""
kind: ConfigMap
- group: ""
kind: Service
- group: ""
kind: ServiceAccount
- group: apps
kind: Deployment
- group: apps
kind: StatefulSet
- group: networking.k8s.io
kind: Ingress
- group: networking.k8s.io
kind: NetworkPolicy
- group: rbac.authorization.k8s.io
kind: Role
- group: rbac.authorization.k8s.io
kind: RoleBinding
+23
View File
@@ -0,0 +1,23 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: argocd
namespace: argocd
spec:
project: argocd
source:
repoURL: https://code.brunner.ninja/feedc0de/homelab-gitops.git
targetRevision: main
path: apps/argocd
helm:
releaseName: argocd
destination:
server: https://kubernetes.default.svc
namespace: argocd
syncPolicy:
automated:
enabled: true
prune: false
selfHeal: true
syncOptions:
- ServerSideApply=true
+74
View File
@@ -0,0 +1,74 @@
#!/bin/sh
set -eu
expected_context=kubernetes-admin@kubernetes
current_context=$(kubectl config current-context)
if [ "${current_context}" != "${expected_context}" ]; then
echo "Refusing recovery through unexpected context: ${current_context}" >&2
exit 1
fi
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
repository_dir=$(dirname -- "${script_dir}")
app_dir="${repository_dir}/apps/argocd"
for command in helm kubectl openssl base64 ruby; do
if ! command -v "${command}" >/dev/null 2>&1; then
echo "Required command not found: ${command}" >&2
exit 1
fi
done
kubectl create namespace argocd --dry-run=client --output=yaml \
| kubectl apply --filename=-
if ! kubectl --namespace argocd get secret argocd-secret >/dev/null 2>&1; then
kubectl --namespace argocd create secret generic argocd-secret
fi
if [ -z "$(kubectl --namespace argocd get secret argocd-secret --output='jsonpath={.data.server\.secretkey}')" ]; then
secret_key=$(openssl rand -hex 32)
secret_key_base64=$(printf '%s' "${secret_key}" | base64 | tr -d '\n')
secret_patch=$(mktemp)
chmod 600 "${secret_patch}"
printf '{"data":{"server.secretkey":"%s"}}\n' "${secret_key_base64}" > "${secret_patch}"
kubectl --namespace argocd patch secret argocd-secret \
--type=merge --patch-file="${secret_patch}" >/dev/null
rm -f "${secret_patch}"
unset secret_key secret_key_base64
fi
kubectl --namespace argocd label secret argocd-secret \
app.kubernetes.io/name=argocd-secret \
app.kubernetes.io/part-of=argocd --overwrite >/dev/null
if ! kubectl --namespace argocd get secret argocd-notifications-secret >/dev/null 2>&1; then
kubectl --namespace argocd create secret generic argocd-notifications-secret
fi
if ! kubectl --namespace argocd get secret argocd-redis >/dev/null 2>&1; then
redis_password=$(openssl rand -hex 32)
kubectl --namespace argocd create secret generic argocd-redis \
--from-literal="auth=${redis_password}"
unset redis_password
fi
if ! kubectl --namespace argocd get secret argocd-oidc >/dev/null 2>&1; then
echo "Missing argocd/argocd-oidc; restore or provision it before recovery." >&2
exit 1
fi
helm dependency update "${app_dir}"
set -- "${app_dir}"/charts/argo-cd-*.tgz
if [ "$#" -ne 1 ] || [ ! -f "$1" ]; then
echo "Expected exactly one downloaded argo-cd chart archive" >&2
exit 1
fi
chart_archive=$1
child_values=$(mktemp)
trap 'rm -f "${child_values}"' EXIT HUP INT TERM
ruby -ryaml -e 'puts YAML.dump(YAML.load_file(ARGV.fetch(0)).fetch("argo-cd"))' \
"${app_dir}/values.yaml" > "${child_values}"
helm upgrade --install argocd "${chart_archive}" \
--namespace argocd \
--values "${child_values}" \
--wait \
--timeout 10m
+6
View File
@@ -35,3 +35,9 @@ kubectl --namespace argocd wait application/metallb \
kubectl --namespace argocd wait application/metallb \
--for=jsonpath='{.status.health.status}'=Healthy \
--timeout=5m
kubectl --namespace argocd wait application/argocd \
--for=jsonpath='{.status.sync.status}'=Synced \
--timeout=10m
kubectl --namespace argocd wait application/argocd \
--for=jsonpath='{.status.health.status}'=Healthy \
--timeout=10m
+2
View File
@@ -1,6 +1,8 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- argocd-project.yaml
- argocd.yaml
- project.yaml
- platform-project.yaml
- sealed-secrets.yaml