|
|
||
|---|---|---|
| bin | ||
| lib | ||
| .gitignore | ||
| .shellcheckrc | ||
| platform.env.example | ||
| README.md | ||
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).
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
git clone https://git.lab.holtlabs.xyz/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/<kind> (+ postgres, redis; __APP__, __API__, __DOMAIN__) → 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 https://NAME.$INGRESS_DOMAIN/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-configConfigMap) is always included.--with postgresadds a CNPGNAME-dbcluster + migrate Job,--with redisaddsNAME-redis. Without them the env vars and Job are dropped.frontend— Vite/React on nginx.--api BACKENDdeploys into the backend's namespace and routesNAME.<domain>/api/*to it (prefix stripped). Without--apiit gets its own namespace and no/apiroute.
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
https://argocd./kibana./unleash.lab.holtlabs.xyz(INGRESS_DOMAIN). Credentials and conventions: homelabPLATFORM.md. INGRESS_DOMAIN(lab.holtlabs.xyz) fills__DOMAIN__in the gitops templates, so services gethttps://NAME.$INGRESS_DOMAIN/. The wildcard DNS record and the wildcard certificate are cluster-level (Plan 11); a new service creates no DNS record and requests no certificate. Health checks pin the host toINGRESS_IPwithcurl --resolvebut still verify TLS.