101 lines
5 KiB
Markdown
101 lines
5 KiB
Markdown
# 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 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-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.<domain>/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 `https://argocd.` / `kibana.` /
|
|
`unleash.lab.holtlabs.xyz` (`INGRESS_DOMAIN`). Credentials and conventions:
|
|
homelab `PLATFORM.md`.
|
|
- `INGRESS_DOMAIN` (`lab.holtlabs.xyz`) fills `__DOMAIN__` in the gitops
|
|
templates, so services get `https://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 to `INGRESS_IP` with `curl --resolve` but still verify TLS.
|