# platform One command from nothing to a running, observed service on the homelab k3s cluster, and one to take it all away again. Bash + `curl`, `jq`, `git`, `kubectl` (`kustomize` optional; falls back to `kubectl kustomize`). ```sh new-service orders --kind backend --with postgres,redis --wait new-service orders-web --kind frontend --api orders --wait delete-service orders-web --yes && delete-service orders --yes ``` ## Setup ```sh git clone http://192.168.1.230:3000/nholtware/platform.git ~/Code/forgejo.local/nholtware/platform cd ~/Code/forgejo.local/nholtware/platform cp platform.env.example platform.env # gitignored; defaults match the homelab export PATH="$PWD/bin:$PATH" # not installed globally ``` `platform.env` points at the homelab repo's gitignored `.secrets/` token files, the gitops clone, the kubeconfig and the ingress domain. The scripts refuse to run if any of them is missing, the gitops clone is dirty or not on `main`, the Forgejo token is rejected, or the cluster is unreachable. ## new-service ``` new-service NAME --kind backend|frontend [--with postgres,redis] [--api BACKEND] [--wait] [--timeout SECONDS] [--dry-run] ``` | Step | What | Skipped when | |------|------|--------------| | 1 | validate `NAME` (`^[a-z][a-z0-9-]{1,30}$`, not reserved), kind, flags; `--api` must be an existing backend | — | | 2 | generate public repo from `service-template-express` / `service-template-react` | repo exists | | 3 | set Actions secrets `REGISTRY_USER`, `REGISTRY_TOKEN`, `GITOPS_TOKEN` | never (PUT is idempotent) | | 4 | create namespace + Secret `unleash-client-token` (copied from `unleash/unleash-init-tokens`) | they exist | | 5 | render gitops `templates/` (+ `postgres`, `redis`) → `manifests/NAME`, `kustomize build` lint, commit, push | dir exists | | 6 | clone repo to `$CODE_DIR/NAME`, push empty commit `chore: kick ci` | CI already deployed, or a kick is running/succeeded | | 7 | wait for CI's `gitops-bump` to replace `newTag: latest` (fails fast if the CI run fails) | tag already bumped | | 8 | write `apps/NAME.yaml`, commit, push, refresh ArgoCD `root` | file exists | | 9 | `--wait`: ArgoCD app `Synced/Healthy`, then `/readyz` (backend) or `/healthz` + `/api/readyz` (frontend) → 200 | no `--wait` | Then it prints the service, ArgoCD, Kibana (pre-filtered on `service:"NAME"`) and Unleash URLs, and the elapsed time. **Why `apps/NAME.yaml` comes last:** ArgoCD only sees the service once CI has pushed a real image. Registering it earlier made the backend's migrate Job sit in `ImagePullBackOff` on `:latest` and hold the sync forever (Plan 04 caveat). **Resumable:** every step checks before acting. If a run dies (CI failure, timeout, Ctrl-C), fix the cause and re-run the same command; a failed CI run is re-kicked automatically. **Kinds:** - `backend` — Express/TS. Runtime config (`NAME-runtime-config` ConfigMap) is always included. `--with postgres` adds a CNPG `NAME-db` cluster + migrate Job, `--with redis` adds `NAME-redis`. Without them the env vars and Job are dropped. - `frontend` — Vite/React on nginx. `--api BACKEND` deploys into the backend's namespace and routes `NAME./api/*` to it (prefix stripped). Without `--api` it gets its own namespace and no `/api` route. ## delete-service ``` delete-service NAME [--yes] [--dry-run] [--timeout SECONDS] ``` Removes `apps/NAME.yaml` + `manifests/NAME` from gitops (the Application's `resources-finalizer` makes ArgoCD delete its workloads when `root` prunes it), waits for the Application to go, deletes the namespace if it is the service's own (CNPG PVCs go with it), deletes every version of the container package and the Forgejo repo, and removes the local clone. Asks you to type the name unless `--yes`. Refuses while a frontend still deploys into the service's namespace (delete the frontend first). Leaves a shared namespace and its `unleash-client-token` Secret alone. ## --dry-run Runs every read-only check for real (config, repo exists?, namespace exists?, render + `kustomize build` lint into a temp dir) and prints each mutating API call (secret values redacted), git push and kubectl change instead of doing it. ## Notes - The Forgejo admin token has no package scope; package list/delete uses the registry push token (same user). - Git pushes use the admin token through an inline credential helper; it is never written into a remote URL or `argv`. - Platform UIs: ArgoCD, Kibana and Unleash at `argocd.` / `kibana.` / `unleash.192.168.1.83.nip.io`. Credentials and conventions: homelab `PLATFORM.md`.