diff --git a/docs/deploy-apis-flux-talos.md b/docs/deploy-apis-flux-talos.md new file mode 100644 index 0000000..93e5ee7 --- /dev/null +++ b/docs/deploy-apis-flux-talos.md @@ -0,0 +1,120 @@ +# Deploying the APIs (Flux GitOps → Talos) + +> Handoff doc. The APIs are **not** deployed by Gitea Actions — they are deployed +> by **Flux** running on the Talos Kubernetes cluster. The Gitea Actions in this +> repo only build the docs-site image, validate Flux manifests, and publish +> OpenAPI specs to RapidAPI. + +## How a deploy actually happens + +``` +API repo (e.g. leeworks-agents/zip-enrichment) + └─ its own CI builds & pushes registry.leeworks.dev/zip-enrichment/server: + └─ Flux image-automation (flux/image-automation/) rewrites the + {"$imagepolicy": "flux-system:"} marker in flux//helmrelease.yaml + └─ Flux GitRepository "api-company" (polls main every 5m) + └─ HelmRelease per API (flux//helmrelease.yaml, bedag/raw chart) + └─ Deployment rolls out in the cluster namespace +``` + +Each API has its own directory under `flux/`: + +| API | Namespace | HelmRelease path | Image | +|---|---|---|---| +| zip-enrichment | `zip-enrichment` | `flux/zip-enrichment/helmrelease.yaml` | `registry.leeworks.dev/zip-enrichment/server` | +| holidays | `holidays` | `flux/holidays/helmrelease.yaml` | `registry.leeworks.dev/holidays/server` | +| air-quality | `air-quality` | `flux/air-quality/helmrelease.yaml` | `registry.leeworks.dev/air-quality/server` | +| vin-decoder | `vin-decoder` | `flux/vin-decoder/helmrelease.yaml` | `registry.leeworks.dev/vin-decoder/server` | + +(Confirm each path's exact image with `grep -r imagepolicy flux/`.) + +## Prerequisite: the manifests must be live in the cluster's Flux source + +`flux/api-company-source/gitrepository.yaml` is **reference only**. The +*authoritative* copy must be committed to **`0xWheatyz/Talos`** at: + +``` +testing1/first-cluster/cluster/flux/api-company/ +``` + +If that path does not point Flux at this repo's `flux/` directory, Flux never +sees these HelmReleases and nothing deploys. Verify the Talos repo references +this repo's `main` branch and that a Flux `Kustomization` includes the +`api-company` path. + +Per-API the cluster also needs (already templated under `flux//`): +- `namespace.yaml` — the target namespace +- the `gitea-registry` imagePullSecret in that namespace +- `externalsecret.yaml` — pulls API keys (e.g. RapidAPI) via external-secrets +- `servicemonitor.yaml` — Prometheus scraping (optional for deploy) + +## One-time local setup (machine with cluster access) + +You need tools that are **not** installed on the dev machine yet: + +```bash +# Talos kubeconfig — export from the Talos controlplane, e.g.: +# talosctl kubeconfig ~/.kube/talos-leeworks +export KUBECONFIG=~/.kube/talos-leeworks +kubectl cluster-info # must succeed before continuing + +# Flux CLI +brew install fluxcd/tap/flux +# Helm (optional, for debugging charts) +brew install helm + +flux check # confirm Flux is installed & healthy in-cluster +``` + +## Deploy / sync all APIs + +```bash +export KUBECONFIG=~/.kube/talos-leeworks + +# 1. Pull the latest main into the cluster's Git source +flux reconcile source git api-company -n flux-system + +# 2. Apply the manifests (name may differ — check: flux get kustomizations -A) +flux reconcile kustomization api-company -n flux-system + +# 3. Reconcile each API's HelmRelease +flux reconcile helmrelease zip-enrichment -n zip-enrichment +flux reconcile helmrelease holidays -n holidays +flux reconcile helmrelease air-quality -n air-quality +flux reconcile helmrelease vin-decoder -n vin-decoder + +# 4. Verify everything is Ready +flux get helmreleases -A +kubectl get pods -A | grep -E 'zip-enrichment|holidays|air-quality|vin-decoder' +``` + +## Troubleshooting + +```bash +# Why is a release not Ready? +flux get helmrelease -n +kubectl describe helmrelease -n + +# Is image automation picking up new tags? +flux get image policy -n flux-system +flux get image update -A + +# Pod won't start (image pull / secret issues) +kubectl describe pod -n +kubectl get events -n --sort-by=.lastTimestamp | tail -20 +``` + +## Force a fresh deploy of a single API + +```bash +flux suspend helmrelease -n +flux resume helmrelease -n # triggers a fresh reconcile +# or restart the workload directly: +kubectl rollout restart deployment/ -n +``` + +## Related docs in this repo +- `docs/operator-runbook.md` — day-2 operations +- `docs/registry.md` — container registry (`registry.leeworks.dev`) setup +- `docs/secrets-checklist.md` — required cluster secrets +- `flux/image-automation/` — automatic image tag bumping diff --git a/scripts/setup-sibling-repos-token.sh b/scripts/setup-sibling-repos-token.sh new file mode 100755 index 0000000..5aadd94 --- /dev/null +++ b/scripts/setup-sibling-repos-token.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +# Create a Gitea PAT with read access to the sibling API repos and store it as +# the SIBLING_REPOS_TOKEN action secret on leeworks-agents/api-company. +# +# Why this script exists: +# - Gitea's build-docs workflow checks out sibling repos. The auto-injected +# GITEA_TOKEN is scoped to THIS repo only and cannot read them, and +# GITEA_TOKEN is a reserved secret name that cannot be overridden. +# - `tea` cannot CREATE a PAT (no such command), and Gitea's token-creation +# API requires BASIC AUTH (your password) — a token cannot mint a token. +# - `tea` CAN set the action secret using its existing login. +# +# So: this prompts for your password ONCE, mints the PAT via the API, and pipes +# it straight into `tea` as the secret. The PAT value is never written to disk. +# +# Usage: bash scripts/setup-sibling-repos-token.sh +set -euo pipefail + +GITEA_URL="https://gitea.leeworks.dev" +GITEA_USER="0xWheatyz" +REPO="leeworks-agents/api-company" +SECRET_NAME="SIBLING_REPOS_TOKEN" +TOKEN_NAME="sibling-repos-readonly-$(date +%Y%m%d)" + +command -v curl >/dev/null || { echo "curl required"; exit 1; } +command -v tea >/dev/null || { echo "tea required"; exit 1; } +command -v python3 >/dev/null || { echo "python3 required"; exit 1; } + +echo "Gitea user: $GITEA_USER ($GITEA_URL)" +read -r -s -p "Gitea password (for $GITEA_USER): " GITEA_PASS +echo + +# Create a read-only PAT. scope read:repository lets the build-docs workflow +# clone the sibling repos. Adjust scopes here if your Gitea version differs. +resp="$(curl -fsS -X POST \ + -u "${GITEA_USER}:${GITEA_PASS}" \ + -H 'Content-Type: application/json' \ + -d "{\"name\":\"${TOKEN_NAME}\",\"scopes\":[\"read:repository\"]}" \ + "${GITEA_URL}/api/v1/users/${GITEA_USER}/tokens")" || { + echo "Token creation failed. Check password / 2FA (2FA blocks basic-auth token creation)." >&2 + exit 1 + } +unset GITEA_PASS + +PAT="$(printf '%s' "$resp" | python3 -c 'import sys,json; print(json.load(sys.stdin)["sha1"])')" +[ -n "$PAT" ] || { echo "Could not parse token from response: $resp" >&2; exit 1; } +echo "PAT '${TOKEN_NAME}' created." + +# Store it as the action secret via tea (overwrites if it already exists). +printf '%s' "$PAT" | tea actions secrets create "$SECRET_NAME" --repo "$REPO" --stdin +unset PAT + +echo "Secret '${SECRET_NAME}' set on ${REPO}." +echo "Verify: tea actions secrets list --repo ${REPO}" +echo "Then re-run build-docs: tea actions runs ... (or push to main / workflow_dispatch)"