Feature Specification: Effect dev-tf-incus-bootstrap
Created: 2026-10-07
Status: Implemented
Input: Reconstructed from the code at 093e68b6f
(core/effects/tf/incus/host/bootstrap.sh,
core/effects/tf/incus/common.nix, core/render/catalog.nix).
The shared behavior of a standalone effect is in spec.md. The
graph grammar, the verbs and the state rules are in
../004-deployment-programs/spec.md.
The incus deploy that reads the env file is in
../009-deployment-methods/tf-incus.md.
The image upload is in image.md. This file does not repeat
them.
User Scenarios & Testing (mandatory)
User Story 1 - Prepare a local deployer (Priority: P1)
A developer has a local incus server and a running TF backend. They run
dev-tf-incus-bootstrap with OUT_TF_ENV and INCUS_CONFIG_DIR. The
runner uploads the base image and mints a trust token. It writes the
values that the incus deploy needs to OUT_TF_ENV.
Why this priority: The local incus deploy needs an image fingerprint and a credential. Without the runner, the developer collects them by hand.
Independent Test: No check covers this runner.
Acceptance Scenarios:
- Given a local incus server and a TF backend on
127.0.0.1:8888, When the developer runs the runner, ThenOUT_TF_ENVholdsTF_VAR_token,TF_VAR_image_fingerprintandTF_VAR_incus_config_dir. - Given the runner is done, When the developer lists the incus
images, Then the server holds image
nixos-fediversitywith the written fingerprint. - Given the runner is done, When the developer lists the trust
entries, Then the server holds tokens
deployer-imageanddeployer-hosts.
User Story 2 - Run the bootstrap again (Priority: P2)
A developer runs the runner a second time. The runner deletes the old image and uploads a new one. It writes a new env file.
Why this priority: The incus provider has no upsert for an image. A second upload under the same alias fails without the delete.
Independent Test: No check covers a second run.
Acceptance Scenarios:
- Given image
nixos-fediversityexists, When the runner runs, Then the old image is gone and a new one has the alias. - Given no image has the alias, When the runner deletes it, Then the runner ignores the "not found" error and continues.
User Story 3 - Fail early without a backend (Priority: P3)
A developer runs the runner with no TF backend. The runner stops before
it mints a token. It prints a hint to run
nix run .#terraform-backend-dev.
Why this priority: A token is single-use. A run that fails after the mint wastes it.
Independent Test: No check covers this case.
Acceptance Scenarios:
- Given nothing listens on
127.0.0.1:8888, When the runner runs, Then it fails within 5 seconds and mints no token.
Edge Cases
OUT_TF_ENVorINCUS_CONFIG_DIRis unset: the runner fails at once.IMAGE_FLAKEis unset: the runner builds the image from the flake in the current directory.- The image upload returns an empty fingerprint: the runner fails and writes no env file.
- The parent directory of
OUT_TF_ENVdoes not exist: the runner creates it.
Requirements (mandatory)
Functional Requirements
- FR-001: The runner MUST fail when
OUT_TF_ENVorINCUS_CONFIG_DIRis unset. - FR-002: The runner MUST check that the TF backend at
devTfBackendofcore/effects/tf/incus/common.nix(127.0.0.1:8888) accepts a connection within 5 seconds, before it mints a token. - FR-003: The runner MUST mint trust token
deployer-imagewithincus config trust add. - FR-004: The runner MUST delete the image alias of
dev-tf-incus-image-deploymentincore/render/catalog.nix(nixos-fediversity) before the upload, and ignore a "not found" error. - FR-005: The runner MUST upload the image with
nix run ${IMAGE_FLAKE}#dev-tf-incus-image, and passTF_VAR_incus_config_dirandTF_VAR_token. - FR-006: The runner MUST read the fingerprint from the
fingerprintoutput and fail when it is empty. - FR-007: The runner MUST mint trust token
deployer-hostsfor the incus deploy. - FR-008: The runner MUST write
TF_VAR_token,TF_VAR_image_fingerprintandTF_VAR_incus_config_dirtoOUT_TF_ENV. - FR-009: The catalog MUST expose the runner as
dev-tf-incus-bootstrap, with no verbs and no group. - FR-010: The catalog entry MUST declare
OUT_TF_ENV,INCUS_CONFIG_DIRandIMAGE_FLAKEas its env keys, from theincusBootstrapreader ofcore/effects/common/standalone-env.nix. - FR-011: The runner MUST give
bootstrap.shthe image alias and the TF backend host and port as arguments, from the definitions in FR-002 and FR-004.bootstrap.shMUST hold no copy of them.core.lib.devTfBackendMUST give the same backend to the front-end that serves it.
Key Entities
- Env file: the file at
OUT_TF_ENV. The incus deploy reads itsTF_VAR_*values. - Trust token: a one-time incus token.
deployer-imageserves the image upload, anddeployer-hostsserves the deploy. - Base image: the image under the alias of
dev-tf-incus-image-deployment. - Dev TF backend: the OpenTofu HTTP state backend at
devTfBackend.terraform-backend-devlistens on it, andapi-http-stackpoints its deploys at it. - Incus config directory: the directory that holds the client certificate.
Success Criteria (mandatory)
Measurable Outcomes
- SC-001: After one run, the incus deploy starts with only the env file as its incus input.
- SC-002: A second run ends with exactly one image under the alias.
- SC-003: A run without a backend mints zero tokens.
- SC-004:
nix-unit-env-configpasses, so the runner reads no env key that its catalog entry does not declare. - SC-005:
nix-unit-effects-tf-incus-bootstrappasses, so the runner arguments equal the definitions that own them.
Assumptions
- The developer runs the runner on the host of the incus server, as a user that can run
incus config trust add. - The TF backend from
terraform-backend-devruns ondevTfBackend, unless the developer setsTERRAFORM_BACKEND_ADDR. - The front-end that calls the runner sets the file owner, the paths and the service restart.
Known Deviations
- The runner checks the TF backend, but the image upload keeps its state in a local file.
- No check runs the runner.