diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml deleted file mode 100644 index fbfe88a..0000000 --- a/.gitea/workflows/deploy.yml +++ /dev/null @@ -1,60 +0,0 @@ -name: Validate and deploy Argo CD -on: - push: - pull_request: -env: - ARGOCD_CHART_VERSION: 10.7.2 - HELM_VERSION: v4.2.2 - KUBECTL_VERSION: v1.36.3 - KUBERNETES_API: https://host.containers.internal:6443 - KUBERNETES_TLS_SERVER_NAME: 192.168.0.2 - NAMESPACE: argocd -jobs: - validate: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Install Helm - run: | - curl -fsSL -o "${RUNNER_TEMP}/helm.tgz" "https://get.helm.sh/helm-${HELM_VERSION}-linux-amd64.tar.gz" - tar -xzf "${RUNNER_TEMP}/helm.tgz" -C "${RUNNER_TEMP}" --strip-components=1 linux-amd64/helm - echo "${RUNNER_TEMP}" >> "${GITHUB_PATH}" - - run: bash test.sh - deploy: - if: gitea.ref == 'refs/heads/main' - needs: validate - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Install clients - run: | - curl -fsSL -o "${RUNNER_TEMP}/helm.tgz" "https://get.helm.sh/helm-${HELM_VERSION}-linux-amd64.tar.gz" - tar -xzf "${RUNNER_TEMP}/helm.tgz" -C "${RUNNER_TEMP}" --strip-components=1 linux-amd64/helm - curl -fsSL -o "${RUNNER_TEMP}/kubectl" "https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl" - chmod 0700 "${RUNNER_TEMP}/helm" "${RUNNER_TEMP}/kubectl" - echo "${RUNNER_TEMP}" >> "${GITHUB_PATH}" - - name: Configure cluster - env: - KUBE_CONFIG_BASE64: ${{ secrets.KUBE_CONFIG_BASE64 }} - run: | - test -n "${KUBE_CONFIG_BASE64}" - printf '%s' "${KUBE_CONFIG_BASE64}" | base64 -d > "${RUNNER_TEMP}/kubeconfig" - chmod 0600 "${RUNNER_TEMP}/kubeconfig" - export KUBECONFIG="${RUNNER_TEMP}/kubeconfig" - kubectl config set-cluster cluster --server="${KUBERNETES_API}" --tls-server-name="${KUBERNETES_TLS_SERVER_NAME}" - - name: Apply and verify - run: | - export KUBECONFIG="${RUNNER_TEMP}/kubeconfig" - ./render.sh "${RUNNER_TEMP}/argocd.yaml" - kubectl apply --server-side --force-conflicts --dry-run=server --validate=false -f "${RUNNER_TEMP}/argocd.yaml" - kubectl apply --server-side --force-conflicts --validate=false -f "${RUNNER_TEMP}/argocd.yaml" - for resource in deployment/argocd-applicationset-controller deployment/argocd-notifications-controller deployment/argocd-repo-server deployment/argocd-server deployment/argocd-dex-server deployment/argocd-redis statefulset/argocd-application-controller; do - for attempt in {1..120}; do - IFS='|' read -r generation observed desired updated ready available <<< "$(kubectl -n "${NAMESPACE}" get "${resource}" -o jsonpath='{.metadata.generation}|{.status.observedGeneration}|{.spec.replicas}|{.status.updatedReplicas}|{.status.readyReplicas}|{.status.availableReplicas}')" - echo "${resource} ${attempt}/120: ${updated}/${desired} updated, ${ready}/${desired} ready" - [[ "${observed}" == "${generation}" && "${updated}" == "${desired}" && "${ready}" == "${desired}" && "${available}" == "${desired}" ]] && break - [[ "${attempt}" == 120 ]] && { echo "${resource} rollout timed out" >&2; exit 1; } - sleep 5 - done - done - curl -fsS --max-time 15 https://cd.brunner.ninja/api/version >/dev/null diff --git a/README.md b/README.md index 636426e..7505f52 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,10 @@ -# Argo CD on Kubernetes +# Argo CD deployment (retired) -Argo CD is pinned to chart `10.7.2` (Argo CD `v3.5.2`) and deployed in -`argocd`. CRDs, notifications, Dex, and the chart-supported Redis backend stay -enabled. Login is delegated directly to the Authentik OIDC provider at -`/application/o/argocd/`; members of `authentik Admins` receive Argo CD admin -access. The committed values deliberately render no Secrets: existing -`argocd-secret`, `argocd-notifications-secret`, `argocd-redis`, and -`argocd-oidc` stay inside Kubernetes and continue to be consumed by the -enabled components. +Argo CD now manages its own pinned upstream chart from the public +[homelab-gitops application](https://code.brunner.ninja/feedc0de/homelab-gitops/src/branch/main/apps/argocd). +That repository contains the canonical chart version, cluster values, Argo +Application, AppProject, and administrator recovery installer. -Run `./install.sh` for administrator bootstrap or `./test.sh` for local chart -rendering and schema validation. Gitea Actions validates every change and -updates only exact existing Argo CD objects and its three exact CRDs on `main`; -it cannot read Secrets, create objects, or delete objects. Because it manages -privileged Argo CD workloads and their exact RBAC objects, protect and review -`main`. - -Create the CI identity with `./create-ci-kubeconfig.sh` (or set -`KUBECTL_SSH_HOST=arschrock`), then store only its final line as the repository -Actions secret `KUBE_CONFIG_BASE64`. - -The Authentik repository's administrator install provisions and synchronizes -the OIDC client secret into `authentik/authentik-runtime` and -`argocd/argocd-oidc`. CI can reference that Secret but cannot read or alter it. +This repository no longer contains deployment automation or cluster +credentials and may be archived. Its Git history remains available as the +record of the former direct-deployment and bootstrap approach. diff --git a/argocd-values.yaml b/argocd-values.yaml deleted file mode 100644 index d62af22..0000000 --- a/argocd-values.yaml +++ /dev/null @@ -1,43 +0,0 @@ -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: - # Preserve the existing argocd-secret; no secret values belong in Git. - createSecret: false - params: - server.insecure: true - -notifications: - secret: - # Preserve the existing argocd-notifications-secret. - create: false - -redisSecretInit: - # Preserve the existing argocd-redis Secret without giving CI Secret access. - enabled: false - -server: - ingress: - enabled: true -# ingressClassName: traefik - annotations: - traefik.ingress.kubernetes.io/router.entrypoints: websecure diff --git a/ci-deployer.yaml b/ci-deployer.yaml deleted file mode 100644 index d382930..0000000 --- a/ci-deployer.yaml +++ /dev/null @@ -1,82 +0,0 @@ -apiVersion: v1 -kind: ServiceAccount -metadata: - name: argocd-deployer - namespace: argocd -automountServiceAccountToken: false ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: argocd-deployer -rules: - - apiGroups: [apiextensions.k8s.io] - resources: [customresourcedefinitions] - resourceNames: [applications.argoproj.io, applicationsets.argoproj.io, appprojects.argoproj.io] - verbs: [get, patch, update] - - apiGroups: [networking.k8s.io] - resources: [networkpolicies] - resourceNames: [argocd-application-controller, argocd-notifications-controller, argocd-repo-server, argocd-server, argocd-dex-server, argocd-redis] - verbs: [get, patch, update] - - apiGroups: [""] - resources: [serviceaccounts] - resourceNames: [argocd-application-controller, argocd-applicationset-controller, argocd-notifications-controller, argocd-repo-server, argocd-server, argocd-dex-server] - verbs: [get, patch, update] - - apiGroups: [""] - resources: [configmaps] - resourceNames: [argocd-cm, argocd-cmd-params-cm, argocd-gpg-keys-cm, argocd-notifications-cm, argocd-rbac-cm, argocd-ssh-known-hosts-cm, argocd-tls-certs-cm, argocd-redis-health-configmap] - verbs: [get, patch, update] - - apiGroups: [""] - resources: [services] - resourceNames: [argocd-applicationset-controller, argocd-repo-server, argocd-server, argocd-dex-server, argocd-redis] - verbs: [get, patch, update] - - apiGroups: [apps] - resources: [deployments] - resourceNames: [argocd-applicationset-controller, argocd-notifications-controller, argocd-repo-server, argocd-server, argocd-dex-server, argocd-redis] - verbs: [get, patch, update] - - apiGroups: [apps] - resources: [statefulsets] - resourceNames: [argocd-application-controller] - verbs: [get, patch, update] - - apiGroups: [networking.k8s.io] - resources: [ingresses] - resourceNames: [argocd-server] - verbs: [get, patch, update] - - apiGroups: [rbac.authorization.k8s.io] - resources: [clusterroles] - resourceNames: [argocd-application-controller, argocd-notifications-controller, argocd-server] - verbs: [get, patch, update, escalate, bind] - - apiGroups: [rbac.authorization.k8s.io] - resources: [clusterrolebindings] - resourceNames: [argocd-application-controller, argocd-notifications-controller, argocd-server] - verbs: [get, patch, update] - - apiGroups: [rbac.authorization.k8s.io] - resources: [roles] - resourceNames: [argocd-application-controller, argocd-applicationset-controller, argocd-notifications-controller, argocd-repo-server, argocd-server, argocd-dex-server] - verbs: [get, patch, update, escalate, bind] - - apiGroups: [rbac.authorization.k8s.io] - resources: [rolebindings] - resourceNames: [argocd-application-controller, argocd-applicationset-controller, argocd-notifications-controller, argocd-repo-server, argocd-server, argocd-dex-server] - verbs: [get, patch, update] ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: argocd-deployer -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: argocd-deployer -subjects: - - kind: ServiceAccount - name: argocd-deployer - namespace: argocd ---- -apiVersion: v1 -kind: Secret -metadata: - name: argocd-deployer-token - namespace: argocd - annotations: - kubernetes.io/service-account.name: argocd-deployer -type: kubernetes.io/service-account-token diff --git a/create-ci-kubeconfig.sh b/create-ci-kubeconfig.sh deleted file mode 100755 index 0bc8cb7..0000000 --- a/create-ci-kubeconfig.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail -project_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd) -namespace=${NAMESPACE:-argocd} -service_account=argocd-deployer -secret=argocd-deployer-token -ssh_host=${KUBECTL_SSH_HOST:-} -cluster_kubectl() { if [[ -n "${ssh_host}" ]]; then ssh "${ssh_host}" kubectl "$@"; else kubectl "$@"; fi; } -if [[ -n "${ssh_host}" ]]; then cluster_kubectl apply -f - < "${project_dir}/ci-deployer.yaml" >&2; else cluster_kubectl apply -f "${project_dir}/ci-deployer.yaml" >&2; fi -for attempt in {1..30}; do - token_data=$(cluster_kubectl -n "${namespace}" get secret "${secret}" -o jsonpath='{.data.token}' 2>/dev/null || true) - [[ -n "${token_data}" ]] && break - [[ "${attempt}" == 30 ]] && { echo "Timed out waiting for token" >&2; exit 1; } - sleep 1 -done -workdir=$(mktemp -d); trap 'rm -rf "${workdir}"' EXIT -server=$(cluster_kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}') -cluster_kubectl -n "${namespace}" get secret "${secret}" -o jsonpath='{.data.ca\.crt}' | base64 -d > "${workdir}/ca.crt" -token=$(printf '%s' "${token_data}" | base64 -d) -export KUBECONFIG="${workdir}/config" -kubectl config set-cluster cluster --server="${server}" --certificate-authority="${workdir}/ca.crt" --embed-certs=true >/dev/null -kubectl config set-credentials "${service_account}" --token="${token}" >/dev/null -kubectl config set-context argocd --cluster=cluster --user="${service_account}" --namespace="${namespace}" >/dev/null -kubectl config use-context argocd >/dev/null -echo "Store the following line as KUBE_CONFIG_BASE64; do not commit it." >&2 -base64 -w0 "${KUBECONFIG}"; printf '\n' diff --git a/install.sh b/install.sh deleted file mode 100755 index 78e31b6..0000000 --- a/install.sh +++ /dev/null @@ -1,64 +0,0 @@ -#!/bin/sh - -set -eu - -chart_version=10.10.5 -project_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) -expected_context=kubernetes-admin@kubernetes - -current_context=$(kubectl config current-context) -if [ "$current_context" != "$expected_context" ]; then - echo "Refusing mutation: expected context $expected_context, got $current_context" >&2 - exit 1 -fi - -for command in helm kubectl openssl base64 curl; 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}" -fi -if ! kubectl --namespace argocd get secret argocd-oidc >/dev/null 2>&1; then - echo "Missing argocd/argocd-oidc; run ../authentik/install.sh first." >&2 - exit 1 -fi - -chart_archive=$(mktemp) -trap 'rm -f "$chart_archive"' EXIT HUP INT TERM -curl --fail --silent --show-error --location --output "$chart_archive" \ - "https://github.com/argoproj/argo-helm/releases/download/argo-cd-${chart_version}/argo-cd-${chart_version}.tgz" - -helm upgrade --install argocd "$chart_archive" \ - --namespace argocd \ - --version "$chart_version" \ - --values "$project_dir/argocd-values.yaml" \ - --wait \ - --timeout 10m diff --git a/render.sh b/render.sh deleted file mode 100755 index 46fa25a..0000000 --- a/render.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail -project_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd) -version=${ARGOCD_CHART_VERSION:-10.10.5} -output=${1:?Usage: render.sh OUTPUT_FILE} -chart=$(mktemp) -trap 'rm -f "${chart}"' EXIT -curl --fail --silent --show-error --location --output "${chart}" \ - "https://github.com/argoproj/argo-helm/releases/download/argo-cd-${version}/argo-cd-${version}.tgz" -helm template argocd "${chart}" --namespace argocd \ - --values "${project_dir}/argocd-values.yaml" > "${output}" diff --git a/test.sh b/test.sh deleted file mode 100755 index e1730b1..0000000 --- a/test.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail -project_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd) -workdir=$(mktemp -d); trap 'rm -rf "${workdir}"' EXIT -sh -n "${project_dir}/install.sh" -bash -n "${project_dir}/render.sh" "${project_dir}/create-ci-kubeconfig.sh" "${project_dir}/test.sh" -"${project_dir}/render.sh" "${workdir}/argocd.yaml" -if grep -Eq '^kind: Secret$' "${workdir}/argocd.yaml"; then - echo "Rendered Argo CD state must not contain Secrets" >&2; exit 1 -fi -image=ghcr.io/yannh/kubeconform:v0.7.0 -validate() { if command -v kubeconform >/dev/null; then kubeconform -strict -exit-on-error -summary "$@"; else local file=${!#}; podman run --rm -i "${image}" -strict -exit-on-error -summary "${@:1:$#-1}" < "$file"; fi; } -validate -ignore-missing-schemas "${workdir}/argocd.yaml" -validate "${project_dir}/ci-deployer.yaml" -echo "Argo CD chart and CI resources are valid."