Context / problem
cluster/scaleway/ can only be brought up or torn down from a developer's laptop
via mise run scaleway-up / scaleway-destroy, which needs a fully-configured
local toolchain and privileged creds. We want to start/stop the homelab cluster
on demand from GitHub Actions — operable from anywhere, auditable, not
machine-bound — now that the github-ci Scaleway identity exists.
Blocker: the current github-ci key is scoped to ObjectStorageReadOnly (a
connectivity PoC), which cannot provision a Kapsule cluster. So this change
also broadens that policy.
Scope
- New manually-triggered workflow (
workflow_dispatch) with a required
action input: up (full scaleway-up: cluster + node pool + ArgoCD) or
destroy (full terraform destroy).
- Broaden the
github-ci IAM policy so the existing SCW_* secrets can drive
a cluster apply/destroy; keep it project-scoped.
- Wire Infisical machine-identity creds into CI (needed for the ArgoCD bootstrap
on up).
- Update
github-ci/README.md to match the broadened privilege.
Non-goals: scheduled/cron start-stop; node_count pause/resume (stays local);
keyless OIDC→Scaleway (blocked upstream); any change to cluster/local/ or the
gitops repo.
Acceptance criteria
Design notes
- Single workflow +
action choice input (not two workflows).
- Configure
~/.config/scw/config.yaml from secrets, then run the mise task.
Critical subtlety: mise.toml's [env] derives AWS_ACCESS_KEY_ID/SECRET
from scw config get …; if you only export SCW_* env vars, mise blanks out
the backend's AWS creds. Writing the scw config makes both provider and backend
authenticate with zero drift from local behaviour. Install the toolchain via
jdx/mise-action (pin by commit SHA) so Terraform matches mise.toml.
destroy needs -auto-approve — the existing scaleway-destroy task lacks
it. Run terraform directly for the destroy branch (or add a scaleway-destroy-ci
task); don't add -auto-approve to the local scaleway-destroy task.
- Permission sets (verified via
scw iam permission-set list, all
project-scoped): KubernetesFullAccess, VPCFullAccess, and
ObjectStorageReadOnly + ObjectStorageObjectsWrite (read and write the
state object) — or the simpler single ObjectStorageFullAccess. The cluster
root creates no IAM resources, so no IAM permission set is needed.
- Safety:
environment: scaleway + permissions: contents: read + a
fail-fast credential assertion (mirrors .github/workflows/scaleway-auth-check.yml,
the reference workflow); optional typed-confirmation (e.g. homelab) for
destroy.
Tasks
1. Broaden the github-ci IAM policy
2. Wire CI credentials
3. Add the lifecycle workflow
4. Docs & validation
5. End-to-end verification
Constraints
- Target repo:
infrastructure. Branch ci/<desc>, draft PR to main.
- Never
terraform apply/destroy the github-ci root without explicit approval
(billable identity change).
up/destroy mutate the live, billable Scaleway cluster and its remote
state — destructive by design; hence manual + environment-gated.
- No secret values in code or the PR.
Open questions (dev agent's call)
- Granular object-storage sets vs single
ObjectStorageFullAccess.
- Whether to add the typed-confirmation guard for
destroy.
Context / problem
cluster/scaleway/can only be brought up or torn down from a developer's laptopvia
mise run scaleway-up/scaleway-destroy, which needs a fully-configuredlocal toolchain and privileged creds. We want to start/stop the homelab cluster
on demand from GitHub Actions — operable from anywhere, auditable, not
machine-bound — now that the
github-ciScaleway identity exists.Blocker: the current
github-cikey is scoped toObjectStorageReadOnly(aconnectivity PoC), which cannot provision a Kapsule cluster. So this change
also broadens that policy.
Scope
workflow_dispatch) with a requiredactioninput:up(fullscaleway-up: cluster + node pool + ArgoCD) ordestroy(fullterraform destroy).github-ciIAM policy so the existingSCW_*secrets can drivea cluster apply/destroy; keep it project-scoped.
on
up).github-ci/README.mdto match the broadened privilege.Non-goals: scheduled/cron start-stop;
node_countpause/resume (stays local);keyless OIDC→Scaleway (blocked upstream); any change to
cluster/local/or thegitopsrepo.Acceptance criteria
workflow_dispatch, not push/PR;actioninput accepts exactly
up/destroy.action=upruns the equivalent ofmise run scaleway-up(init+apply -auto-approve,bootstrap_argocd=true).action=destroyrunsterraform destroy -auto-approveand doesn't hang onconfirmation.
mise.toml.the
github-cikey; missing creds fail fast; nothing secret is logged.upsupplies Infisicalclient_id/client_secretso the ArgoCD bootstrapsucceeds.
write state, while staying scoped to
var.project_id.github-ci/README.mdno longer claims the identity is read-only /least-privilege read-only.
Design notes
actionchoice input (not two workflows).~/.config/scw/config.yamlfrom secrets, then run the mise task.Critical subtlety:
mise.toml's[env]derivesAWS_ACCESS_KEY_ID/SECRETfrom
scw config get …; if you only exportSCW_*env vars, mise blanks outthe backend's AWS creds. Writing the scw config makes both provider and backend
authenticate with zero drift from local behaviour. Install the toolchain via
jdx/mise-action(pin by commit SHA) so Terraform matchesmise.toml.destroyneeds-auto-approve— the existingscaleway-destroytask lacksit. Run terraform directly for the destroy branch (or add a
scaleway-destroy-citask); don't add
-auto-approveto the localscaleway-destroytask.scw iam permission-set list, allproject-scoped):
KubernetesFullAccess,VPCFullAccess, andObjectStorageReadOnly+ObjectStorageObjectsWrite(read and write thestate object) — or the simpler single
ObjectStorageFullAccess. The clusterroot creates no IAM resources, so no IAM permission set is needed.
environment: scaleway+permissions: contents: read+ afail-fast credential assertion (mirrors
.github/workflows/scaleway-auth-check.yml,the reference workflow); optional typed-confirmation (e.g.
homelab) fordestroy.Tasks
1. Broaden the github-ci IAM policy
github-ci/main.tf, replace thescaleway_iam_policy.github_cirule's
permission_set_names = ["ObjectStorageReadOnly"]with the setsabove. Keep
project_ids = [var.project_id].name/description(currentlygithub-ci-object-storage-ro) and surrounding comments to reflect thebroadened scope.
mise run github-ci-planand review; do NOTapplywithout explicitapproval (billable identity change).
2. Wire CI credentials
SCW_ACCESS_KEY/SCW_SECRET_KEYGitHub secrets fromthe broadened key (Terraform output / Infisical) per
github-ci/README.md;don't echo values.
client_id/client_secret) — recommend scoping them to thescalewayenvironmentalongside
SCW_*. Used asinfisical_client_id/infisical_client_secreton the
uppath.3. Add the lifecycle workflow
.github/workflows/scaleway-cluster-lifecycle.yml, triggeredonly by
workflow_dispatchwith a requiredchoiceinputaction(
up|destroy). Setpermissions: contents: readandenvironment: scaleway.SCW_ACCESS_KEY/SCW_SECRET_KEY(and,for
up, the Infisical creds) are present.jdx/mise-action(pinned by SHA).~/.config/scw/config.yamlfrom secrets/variables (access_key,secret_key,default_project_id←vars.SCW_DEFAULT_PROJECT_ID,default_organization_id←vars.SCW_DEFAULT_ORGANIZATION_ID). Add acomment explaining why env-only is insufficient.
upbranch: exportTF_VAR_infisical_client_id/TF_VAR_infisical_client_secretand runmise run scaleway-up.destroybranch: run a non-interactive destroy (terraform directly viamise exec, or ascaleway-destroy-citask).destroybehind a typed-confirmation input.4. Docs & validation
github-ci/README.md"What it creates" / least-privilegesections to match the broadened permission sets.
actionlint .github/workflows/scaleway-cluster-lifecycle.yml.mise run lockonly if provider constraints changed (not expected).5. End-to-end verification
action=up; confirm cluster + node pool + ArgoCD come up andstate is written to
id-terraform-state.action=destroy; confirm a clean teardown, nopermission-denied, job exits non-zero on any Terraform failure.
Constraints
infrastructure. Branchci/<desc>, draft PR tomain.terraform apply/destroythegithub-ciroot without explicit approval(billable identity change).
up/destroymutate the live, billable Scaleway cluster and its remotestate — destructive by design; hence manual + environment-gated.
Open questions (dev agent's call)
ObjectStorageFullAccess.destroy.