Template
chore: seed gitea-repo-template cookie-cutter
Reusable repo template for homelab services on git.aridgwayweb.com. Bakes in (all verified live on armistace/wedding-photos): - Hard commit guard: shared pre-commit hook (git config core.hooksPath ~/dev/git-hooks) + master branch protection (push whitelist [armistace], merge whitelist [hermes, armistace]). - Gitea Actions CI (.gitea/workflows/build_push.yml): test + build-deploy, persistent remote buildkit cache, registry push, idempotent deploy that preserves hand-provisioned Secrets, cluster injection from repo secrets/vars via scripts/reconcile-cluster-inject.sh. - Persistent buildkit cache (ci/buildkit/): single-replica Longhorn backing. - scripts/reconcile-cluster-inject.sh: reconcile live Secret/ConfigMap from Gitea secrets/vars without clobbering hand-provisioned values. - RUNBOOK.md: handoff-complete ops doc. Placeholders (<APP> <OWNER> <NS> <KEY_*>) are filled per-service on repo creation.
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
#!/usr/bin/env bash
|
||||
# Reconciles the live <NS> Secret and ConfigMap from Gitea repo secrets/vars,
|
||||
# WITHOUT ever clobbering hand-provisioned real credentials.
|
||||
#
|
||||
# Design rationale (post CreateContainerConfigError lesson):
|
||||
# The deployments reference DB creds / JWT / etc. unconditionally. If any key
|
||||
# is missing from the live Secret, the pod fails to start (rollout timeout in
|
||||
# CI). The Secret has historically been provisioned by hand (RUNBOOK §4.2);
|
||||
# git holds placeholders only.
|
||||
#
|
||||
# This script makes CI the reconciler. For each managed key it applies EXACTLY
|
||||
# one rule (priority order):
|
||||
# 1. Gitea supplies a NON-EMPTY value -> that value wins (seed / rotate).
|
||||
# 2. The key MISSING from the live resource -> write the placeholder so the
|
||||
# app's env refs always resolve and the rollout never breaks.
|
||||
# 3. Otherwise (key present, Gitea empty or absent) -> PRESERVE the live
|
||||
# value untouched. A hand-provisioned credential is never overwritten by
|
||||
# an empty Gitea secret.
|
||||
#
|
||||
# The patch payload is built by Python's json module (no shell interpolation),
|
||||
# so arbitrary values — quotes, newlines, unicode — cannot corrupt the JSON or
|
||||
# inject flags into kubectl.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SECRET="${1:?secret name}"
|
||||
CM="${2:?configmap name}"
|
||||
NS="${3:?namespace}"
|
||||
KUBECTL="${KUBECTL:-kubectl}"
|
||||
|
||||
# key -> Gitea-var-env:placeholder (placeholder applies to the Secret)
|
||||
# EDIT ME: add every Secret key your deployments reference.
|
||||
declare -A SECRET_KEYS=(
|
||||
[<KEY_1>]=<KEY_1>:placeholder<KEY_1>
|
||||
[<KEY_2>]=<KEY_2>:""
|
||||
)
|
||||
|
||||
# key -> Gitea-var-env:placeholder (placeholders apply to the ConfigMap)
|
||||
# EDIT ME: add every ConfigMap key your deployments reference.
|
||||
declare -A CM_KEYS=(
|
||||
[<CONFIG_KEY_1>]=<CONFIG_KEY_1>:""
|
||||
)
|
||||
|
||||
live_has_key() { # kind name key -> 0 if the resource has the key
|
||||
local v
|
||||
v="$($KUBECTL get "$1" "$2" -n "$NS" -o "jsonpath={.data.$3}" 2>/dev/null || true)"
|
||||
[[ -n "$v" ]]
|
||||
}
|
||||
|
||||
# Build a JSON patch body {"data": {...}} safely via Python json.
|
||||
# Args: kind newline-joined "key<TAB>value" lines.
|
||||
build_patch() {
|
||||
python3 -c '
|
||||
import sys, json, base64
|
||||
kind = sys.argv[1]
|
||||
data = {}
|
||||
for line in sys.argv[2].split("\n"):
|
||||
if not line:
|
||||
continue
|
||||
key, _, val = line.partition("\t")
|
||||
data[key] = base64.b64encode(val.encode("utf-8")).decode("ascii") if kind == "secret" else val
|
||||
print(json.dumps({"data": data}, ensure_ascii=False))
|
||||
' "$1" "$2"
|
||||
}
|
||||
|
||||
# emit_patch KIND assoc-name resource-kind resource-name
|
||||
emit_patch() {
|
||||
local kind="$1" declare_var="$2" reskind="$3" resname="$4"
|
||||
local -n MAP="$declare_var"
|
||||
local lines=() key envvar placeholder val
|
||||
for key in "${!MAP[@]}"; do
|
||||
envvar="${MAP[$key]%%:*}"
|
||||
placeholder="${MAP[$key]#*:}"
|
||||
val="${!envvar:-}"
|
||||
if [[ -n "$val" ]]; then
|
||||
lines+=("$key"$'\t'"$val")
|
||||
elif ! live_has_key "$reskind" "$resname" "$key"; then
|
||||
lines+=("$key"$'\t'"$placeholder")
|
||||
fi
|
||||
done
|
||||
if [[ ${#lines[@]} -gt 0 ]]; then
|
||||
build_patch "$kind" "$(printf '%s\n' "${lines[@]}")"
|
||||
else
|
||||
echo '{}'
|
||||
fi
|
||||
}
|
||||
|
||||
apply_secret() {
|
||||
local body
|
||||
body="$(emit_patch secret SECRET_KEYS secret "$SECRET")"
|
||||
[[ "$body" == '{}' ]] || $KUBECTL patch secret "$SECRET" -n "$NS" --type merge -p "$body" >/dev/null
|
||||
echo "reconciled secret $SECRET ($NS): non-empty Gitea applied, missing seeded, present preserved"
|
||||
}
|
||||
|
||||
apply_cm() {
|
||||
local body
|
||||
body="$(emit_patch configmap CM_KEYS configmap "$CM")"
|
||||
[[ "$body" == '{}' ]] || $KUBECTL patch configmap "$CM" -n "$NS" --type merge -p "$body" >/dev/null
|
||||
echo "reconciled configmap $CM ($NS): non-empty Gitea applied, missing seeded, present preserved"
|
||||
}
|
||||
|
||||
apply_secret
|
||||
apply_cm
|
||||
Reference in New Issue
Block a user