unrip/.forgejo/workflows/deploy.yml
philipp 3cd88c682e Expose operator dashboard with basic auth
Proof: The rendered Kubernetes manifest now includes a Traefik TLS ingress for operator-dashboard and production basic auth, deploy workflow/bootstrap preserve the dashboard password as a secret, and static plus full node tests pass.

Assumptions: doran.133011.xyz is the intended public host because unrip.doran.133011.xyz and dashboard.doran.133011.xyz do not currently resolve.

Still fake: the public dashboard is not deployed or externally verified yet because the cluster host, Forgejo, and Kubernetes API timed out from this machine during this turn.
2026-05-13 18:08:27 +02:00

188 lines
7.2 KiB
YAML

name: deploy
on:
push:
branches:
- main
workflow_dispatch:
jobs:
deploy:
runs-on: linux-amd64
env:
IMAGE_TAG: ${{ github.sha }}
REGISTRY_HOST: ${{ vars.REGISTRY_HOST }}
PROJECT_NAME: ${{ vars.PROJECT_NAME || 'unrip' }}
PROJECT_NAMESPACE: ${{ vars.PROJECT_NAMESPACE || vars.PROJECT_NAME || 'unrip' }}
PROJECT_REGISTRY_SECRET_NAME: ${{ vars.PROJECT_REGISTRY_SECRET_NAME || format('{0}-registry-creds', vars.PROJECT_NAME || 'unrip') }}
REPO_CLONE_URL: ${{ github.server_url }}/${{ github.repository }}.git
steps:
- name: Install tooling
run: |
if command -v git >/dev/null 2>&1 && command -v kubectl >/dev/null 2>&1 && command -v python3 >/dev/null 2>&1; then
exit 0
fi
if command -v apk >/dev/null 2>&1; then
apk add --no-cache git kubectl python3
exit 0
fi
if command -v apt-get >/dev/null 2>&1; then
apt-get update
apt-get install -y git curl ca-certificates python3
curl -fsSLo /usr/local/bin/kubectl "https://dl.k8s.io/release/$(curl -fsSL https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
chmod +x /usr/local/bin/kubectl
exit 0
fi
echo "missing git/kubectl/python3 and no supported package manager found" >&2
exit 1
- name: Prepare workspace
run: |
workspace_root="${RUNNER_TEMP:-/tmp}"
workspace_dir="$(mktemp -d "${workspace_root%/}/unrip-deploy-XXXXXX")"
echo "WORKSPACE_DIR=$workspace_dir" >> "$GITHUB_ENV"
echo "runner workspace: $workspace_dir"
- name: Load kubeconfig
run: |
mkdir -p "$HOME/.kube"
printf '%s' '${{ secrets.KUBECONFIG_B64 }}' | base64 -d > "$HOME/.kube/config"
kubectl get ns
- name: Checkout repo
env:
REPO_TOKEN: ${{ github.token }}
run: |
git -c credential.username=oauth2 -c http.extraHeader="Authorization: Bearer ${REPO_TOKEN}" clone --depth=1 "${REPO_CLONE_URL}" "$WORKSPACE_DIR"
cd "$WORKSPACE_DIR"
current_sha="$(git rev-parse HEAD)"
if [ "$current_sha" != "$GITHUB_SHA" ]; then
git -c credential.username=oauth2 -c http.extraHeader="Authorization: Bearer ${REPO_TOKEN}" fetch --depth=1 origin "${GITHUB_SHA}"
git checkout --detach "${GITHUB_SHA}"
else
git checkout --detach "$current_sha"
fi
git rev-parse HEAD
- name: Resolve deployment settings
run: |
IMAGE="$REGISTRY_HOST/$PROJECT_NAME:$IMAGE_TAG"
BUILD_JOB="image-build-$(printf '%s' "$GITHUB_SHA" | cut -c1-12)"
{
echo "IMAGE=$IMAGE"
echo "BUILD_JOB=$BUILD_JOB"
} >> "$GITHUB_ENV"
- name: Ensure namespace exists
run: |
kubectl apply -f "$WORKSPACE_DIR/deploy/k8s/base/namespace.yaml"
- name: Upsert runtime secrets
env:
OPERATOR_DASHBOARD_AUTH_PASSWORD: ${{ secrets.OPERATOR_DASHBOARD_AUTH_PASSWORD }}
run: |
if [ -z "$OPERATOR_DASHBOARD_AUTH_PASSWORD" ]; then
echo "missing required repo action secret OPERATOR_DASHBOARD_AUTH_PASSWORD" >&2
exit 1
fi
patch_file="$(mktemp)"
cleanup() {
rm -f "$patch_file"
}
trap cleanup EXIT
python3 - "$OPERATOR_DASHBOARD_AUTH_PASSWORD" >"$patch_file" <<'PY'
import json
import sys
print(json.dumps({
"stringData": {
"OPERATOR_DASHBOARD_AUTH_PASSWORD": sys.argv[1],
},
}))
PY
kubectl -n "$PROJECT_NAMESPACE" patch secret "${PROJECT_NAME}-secrets" \
--type merge \
--patch-file "$patch_file"
- name: Build and push image in-cluster
env:
REPO_TOKEN: ${{ github.token }}
run: |
kubectl -n "$PROJECT_NAMESPACE" delete job "$BUILD_JOB" --ignore-not-found=true
cat <<EOF | kubectl apply -f -
apiVersion: batch/v1
kind: Job
metadata:
name: ${BUILD_JOB}
namespace: ${PROJECT_NAMESPACE}
spec:
backoffLimit: 0
ttlSecondsAfterFinished: 3600
template:
spec:
restartPolicy: Never
volumes:
- name: workspace
emptyDir: {}
- name: registry-creds
secret:
secretName: ${PROJECT_REGISTRY_SECRET_NAME}
items:
- key: .dockerconfigjson
path: config.json
initContainers:
- name: checkout
image: alpine/git:2.47.2
env:
- name: REPO_TOKEN
value: ${REPO_TOKEN}
- name: REPO_CLONE_URL
value: ${REPO_CLONE_URL}
- name: GITHUB_SHA
value: ${GITHUB_SHA}
command: ["/bin/sh", "-lc"]
args:
- >-
git -c credential.username=oauth2 -c http.extraHeader="Authorization: Bearer ${REPO_TOKEN}" clone --depth=1 "${REPO_CLONE_URL}" /workspace &&
cd /workspace &&
git -c credential.username=oauth2 -c http.extraHeader="Authorization: Bearer ${REPO_TOKEN}" fetch --depth=1 origin "${GITHUB_SHA}" &&
git checkout --detach "${GITHUB_SHA}"
volumeMounts:
- name: workspace
mountPath: /workspace
containers:
- name: kaniko
image: gcr.io/kaniko-project/executor:v1.23.2-debug
args:
- --context=/workspace
- --dockerfile=/workspace/Dockerfile
- --destination=${IMAGE}
- --cache=false
volumeMounts:
- name: workspace
mountPath: /workspace
- name: registry-creds
mountPath: /kaniko/.docker
EOF
kubectl -n "$PROJECT_NAMESPACE" wait --for=condition=Complete --timeout=20m "job/$BUILD_JOB"
kubectl -n "$PROJECT_NAMESPACE" logs "job/$BUILD_JOB"
- name: Apply release manifests and wait for rollout
run: |
# Apply the rendered image after the build so no deployment ever falls back to bootstrap placeholders.
kubectl kustomize "$WORKSPACE_DIR/deploy/k8s/base" \
| python3 "$WORKSPACE_DIR/scripts/deploy/render_release_manifest.py" --image "$IMAGE" \
| kubectl apply -f -
python3 "$WORKSPACE_DIR/scripts/deploy/repo_deployments.py" --format lines \
| while IFS= read -r deployment; do
[ -n "$deployment" ] || continue
kubectl -n "$PROJECT_NAMESPACE" set image "deployment/$deployment" app="$IMAGE"
kubectl -n "$PROJECT_NAMESPACE" rollout status "deployment/$deployment" --timeout=180s
done