feat!: migrate homelab-workspace template toolchain to OpenTofu - #898
Merged
Conversation
6 tasks
homelab-agent-bot
Bot
force-pushed
the
opentofu-migration
branch
3 times, most recently
from
August 31, 2026 22:45
7b2f551 to
358af94
Compare
homelab-agent-bot Bot
pushed a commit
to ppat/homelab-ops-kubernetes-apps
that referenced
this pull request
Aug 31, 2026
`provisionerd` (bundled in the `ghcr.io/coder/coder` image this Deployment runs)
resolves its provisioning binary purely by `LookPath("terraform")` plus a numeric
version check - there is no config surface to point it at a differently named
binary, and no vendor awareness at all. `ppat/coder#897`/`#898` migrated the
`homelab-workspace` template's authoring toolchain to OpenTofu and proved the
mechanism - and its live-deployment implication - end to end in a disposable
test control plane there: bind-mount an OpenTofu binary directly over the
image's own `/usr/local/bin/terraform`.
Tried first, and rejected by this cluster's containerd (2.3.2-k3s2) at pod
creation: a Kubernetes image volume (`coder.volumes[].image`) mounting
`ghcr.io/opentofu/opentofu` directly, subPath-ed to just the `tofu` binary.
"ImageVolumeMountFailed: ... only directory subpath is supported" - file-level
subPath on image volumes isn't supported on this runtime, and directory-level
would replace the whole `/usr/local/bin` with the OpenTofu image's own, which
names the binary `tofu`, not `terraform`; renaming isn't possible without a
copy step.
So this uses a small init container instead, `coder.initContainers` - still
sourcing the binary from the official OpenTofu image, but copying (and
renaming) it into a plain `emptyDir`, whose file-level subPath support is
universal and unaffected by the image-volume limitation above. The main
`coder` container's `volumeMounts` entry then subPath-mounts just that one
file onto `/usr/local/bin/terraform`, read-only - the image's real binary
there is root-owned 0755 and the container runs as uid 1000, so nothing
inside it can overwrite that path in place, but a uid-1000 process can still
execute a root-owned, world-executable file mounted read-only over it. Same
access pattern the disposable test's bind mount already proved works, and
independently re-verified here with the equivalent Docker volume-subpath
mount against the real `ghcr.io/coder/coder:v2.35.6` image as uid 1000:
`terraform version` reports `OpenTofu v1.12.6`.
Rendered with `helm template` against this exact values block before landing:
the initContainer, volumeMount, and volume land on the Deployment's `coder`
container exactly as intended, nothing else in the manifest changes.
`coder.initContainers`/`coder.volumes`/`coder.volumeMounts` were previously
unset here, so nothing existing is replaced.
The `opentofu` image tag is not Renovate-managed: Renovate's `kubernetes`
manager has native support for image-volume references, but only on raw
Pod/Deployment/etc. manifests it parses directly - not on values embedded in a
Flux `HelmRelease`'s opaque `values:` blob (moot here anyway, since this ended
up as an initContainer image reference, not an image volume). Bump it by hand
alongside `ppat/coder`'s `mise.toml` `opentofu` pin, which this is meant to
track (the version this repo's disposable test control plane actually
verified the template against).
Template *authoring* moved to OpenTofu; this is what makes this Deployment's
provisioner the thing that actually *applies* templates with OpenTofu, matching
the disposable test control plane `ppat/coder` already runs both under.
Refs ppat/coder#897, ppat/coder#898.
homelab-agent-bot
Bot
force-pushed
the
opentofu-migration
branch
from
September 1, 2026 11:11
358af94 to
034946f
Compare
homelab-agent-bot Bot
pushed a commit
to ppat/homelab-ops-kubernetes-apps
that referenced
this pull request
Sep 1, 2026
`provisionerd` (bundled in the `ghcr.io/coder/coder` image this Deployment runs)
resolves its provisioning binary purely by `LookPath("terraform")` plus a numeric
version check - there is no config surface to point it at a differently named
binary, and no vendor awareness at all. `ppat/coder#897`/`#898` migrated the
`homelab-workspace` template's authoring toolchain to OpenTofu and proved the
mechanism - and its live-deployment implication - end to end in a disposable
test control plane there: bind-mount an OpenTofu binary directly over the
image's own `/usr/local/bin/terraform`.
Tried first, and rejected by this cluster's containerd (2.3.2-k3s2) at pod
creation: a Kubernetes image volume (`coder.volumes[].image`) mounting
`ghcr.io/opentofu/opentofu` directly, subPath-ed to just the `tofu` binary.
"ImageVolumeMountFailed: ... only directory subpath is supported" - file-level
subPath on image volumes isn't supported on this runtime, and directory-level
would replace the whole `/usr/local/bin` with the OpenTofu image's own, which
names the binary `tofu`, not `terraform`; renaming isn't possible without a
copy step.
So this uses a small init container instead, `coder.initContainers` - still
sourcing the binary from the official OpenTofu image, but copying (and
renaming) it into a plain `emptyDir`, whose file-level subPath support is
universal and unaffected by the image-volume limitation above. The main
`coder` container's `volumeMounts` entry then subPath-mounts just that one
file onto `/usr/local/bin/terraform`, read-only - the image's real binary
there is root-owned 0755 and the container runs as uid 1000, so nothing
inside it can overwrite that path in place, but a uid-1000 process can still
execute a root-owned, world-executable file mounted read-only over it. Same
access pattern the disposable test's bind mount already proved works, and
independently re-verified here with the equivalent Docker volume-subpath
mount against the real `ghcr.io/coder/coder:v2.35.6` image as uid 1000:
`terraform version` reports `OpenTofu v1.12.6`.
Rendered with `helm template` against this exact values block before landing:
the initContainer, volumeMount, and volume land on the Deployment's `coder`
container exactly as intended, nothing else in the manifest changes.
`coder.initContainers`/`coder.volumes`/`coder.volumeMounts` were previously
unset here, so nothing existing is replaced.
The `opentofu` image tag is not Renovate-managed: Renovate's `kubernetes`
manager has native support for image-volume references, but only on raw
Pod/Deployment/etc. manifests it parses directly - not on values embedded in a
Flux `HelmRelease`'s opaque `values:` blob (moot here anyway, since this ended
up as an initContainer image reference, not an image volume). Bump it by hand
alongside `ppat/coder`'s `mise.toml` `opentofu` pin, which this is meant to
track (the version this repo's disposable test control plane actually
verified the template against).
Template *authoring* moved to OpenTofu; this is what makes this Deployment's
provisioner the thing that actually *applies* templates with OpenTofu, matching
the disposable test control plane `ppat/coder` already runs both under.
BREAKING CHANGE: this Deployment's provisioner now applies every workspace
template - not just homelab-workspace - with OpenTofu instead of the image's
bundled Terraform. OpenTofu's compatibility promise covers standard HCL, but
any template (present or future) relying on Terraform-specific behavior
outside that promise now behaves differently in this live deployment and
needs re-verification against OpenTofu, the same way ppat/coder#898 verified
homelab-workspace.
Refs ppat/coder#897, ppat/coder#898.
homelab-agent-bot
Bot
force-pushed
the
opentofu-migration
branch
from
September 1, 2026 11:48
034946f to
fc17cf8
Compare
Deliberate migration of the authoring/linting/CI toolchain for templates/kubernetes/homelab-workspace from HashiCorp Terraform to OpenTofu. The HCL itself needed no change: every construct the template uses is backward compatible, verified by running tofu init/fmt/validate/tflint for real rather than assuming it. Full research backing this decision, and remaining status, is tracked in issue #897 (three linked private gists cover Coder-provisioner compatibility, provider-registry/licensing, and language deltas). Authoring toolchain: - mise.toml now pins `opentofu` (installs the `tofu` binary) instead of `terraform`; .pre-commit-config.yaml uses gruntwork-io/pre-commit's `tofu-fmt`/`tofu-validate` hook ids (same pinned revision). - required_providers now pins explicit `registry.opentofu.org` hosts for both providers, removing the tool-dependent implicit registry default that would otherwise make the lock file's provider addresses depend on which binary ran `init`. - .terraform.lock.hcl regenerated via `tofu init -upgrade`: same versions. coder/coder's hashes are byte-identical (OpenTofu's registry points at the same GitHub release artifacts for third-party providers); hashicorp/kubernetes's hashes changed even though the version didn't move, because OpenTofu rebuilds `hashicorp`-namespace providers from source under its own release pipeline rather than redistributing HashiCorp's binaries (HashiCorp's registry ToS blocks that redistribution). - Renovate: renamed template-terraform-provider.json to template-opentofu-provider.json; exceptions.json's tool-version grouping rule now targets the `opentofu` mise tool. The terraform-provider Renovate *datasource* name is unrelated to the authoring tool and needed no change - it already tracks provider registry versions regardless of which tool consumes them. - commitlint.config.js's allowed scopes swap terraform-provider/ terraform-version for opentofu-provider/opentofu-version, since this is a full toolchain switch, not dual support. Historical commits keep their old scope text; only current and future ones follow the new vocabulary. CI and the disposable test control plane: - The disposable local/CI Coder deployment (.github/compose/compose.yaml, test-template.yaml) is the only place this repo controls the binary that actually *applies* the template - `coder template push` never execs terraform/tofu itself (it's a local, pure-Go HCL parse), and Coder's provisioner resolves its binary purely via `LookPath("terraform")` plus a numeric version check, with zero vendor awareness. The official ghcr.io/coder/coder image ships its real Terraform binary at /usr/local/bin/terraform, root-owned 0755, in a container that runs as uid 1000 - so compose.yaml now bind-mounts an OpenTofu binary directly over that lookup path, since the bind mount replaces the mount point at the kernel level regardless of that ownership. test-template.yaml sources that binary via ppat/homelab-ops-actions/actions/setup-repository-tools - the same checkout+mise action every reusable lint workflow in ppat/github-workflows already uses - with no mise_toml override, so it installs straight from the checked-out mise.toml's own `opentofu` pin rather than a second hardcoded version string. That action does its own checkout into current/, so every path this job touches after that step is prefixed accordingly. This wasn't the first attempt. opentofu/setup-opentofu is rejected outright by this repo's locked-down Actions allowlist ("selected" patterns, not including that action) at workflow-intake time - a real failure mode invisible to actionlint, yamllint, or any local run, surfacing only as a bare "workflow file issue" with zero jobs created. The fix that replaced it, jdx/mise-action called directly, worked (it happened to already be allow-listed, transitively, via setup-repository-tools's own dependency on it) but broke with this repo's established convention of using setup-repository-tools itself for exactly this, for no reason beyond expedience - corrected here. - A second real-CI-only failure, also invisible locally: `echo "CODER_TOFU_BINARY=$(...)" >> "$GITHUB_ENV"` immediately followed by `docker compose up` in the *same* step silently resolved to an empty value, and docker compose failed with an opaque "invalid spec: empty section between colons". `$GITHUB_ENV` writes only take effect for steps that run *after* the one that wrote them, never for later commands within that same step. `CODER_KUBECONFIG` a few lines above worked all along for an unrelated reason - it's this step's own `env:`, set from the start; the `$GITHUB_ENV` echo next to it exists only to propagate it to *later* steps, not to make it usable here. Fixed by resolving `CODER_TOFU_BINARY` via `mise which tofu` into a local shell variable, `export`ing it so this step's own `docker compose up` sees it, and *also* appending it to `$GITHUB_ENV` so the later steps that run `docker compose exec`/`logs` - which re-resolve the whole compose file, volumes included, on every subcommand - find it too. - Verified end-to-end against a disposable Kind/Compose stack, not just wired: a real template push's provisioner log reads "OpenTofu 1.12.6" / "OpenTofu has been successfully initialized!" before "Apply complete!", `docker compose exec coder terraform version` reports the OpenTofu banner, and the resulting workspace came up, pinged 3/3 over p2p wireguard, and answered `coder ssh -- env` for real. - The live homelab Coder deployment's provisioner is a separate repo (homelab-ops-kubernetes-apps) and out of scope for this PR - it's addressed separately in homelab-ops-kubernetes-apps#3900, since this template is now genuinely coupled to OpenTofu's own registry (see below), not merely backward-compatible with either tool in the abstract. - lint.yaml's terraform job now points at ppat/github-workflows's new lint-opentofu.yaml, pinned to the ppat/github-workflows@v6.1.0 tagged commit that shipped it (ppat/github-workflows#631). Renamed the job/output/detect-changed-files keys involved from terraform-* to opentofu-* to match. - actions/coder-cli-login and actions/coder-template-push needed no changes - neither ever touches a terraform/tofu binary, only the coder CLI and HTTP calls. - Fixed a pre-existing, unrelated bug hit while validating locally: TESTING.md's documented `coder login --username --password` never worked against the pinned Coder CLI version - those flags apply only to first-user bootstrap. Replaced with the session-token HTTP login flow actions/coder-cli-login already uses. Docs (CLAUDE.md, DESIGN.md, README.md, TESTING.md) updated throughout to describe this as the current state, including a new CLAUDE.md implementation gotcha naming the exact tofu-binary-injection mechanism and its verification evidence, and a DESIGN.md entry on the authored-vs-applied-under-OpenTofu asymmetry between this repo's test control plane and the live deployment. BREAKING CHANGE: the template's authoring and CI toolchain is now OpenTofu, not Terraform - anyone working on templates/kubernetes/homelab-workspace needs `tofu` (via `mise install`) rather than `terraform` on PATH, and Renovate/commitlint commit scopes for provider/tool-version bumps are now opentofu-provider/opentofu-version instead of terraform-provider/ terraform-version. Refs #897.
homelab-agent-bot
Bot
force-pushed
the
opentofu-migration
branch
from
September 1, 2026 11:52
fc17cf8 to
a1cae04
Compare
homelab-agent-bot Bot
pushed a commit
to ppat/homelab-ops-kubernetes-apps
that referenced
this pull request
Sep 1, 2026
`provisionerd` (bundled in the `ghcr.io/coder/coder` image this Deployment runs)
resolves its provisioning binary purely by `LookPath("terraform")` plus a numeric
version check - there is no config surface to point it at a differently named
binary, and no vendor awareness at all. `ppat/coder#897`/`#898` migrated the
`homelab-workspace` template's authoring toolchain to OpenTofu and proved the
mechanism - and its live-deployment implication - end to end in a disposable
test control plane there: bind-mount an OpenTofu binary directly over the
image's own `/usr/local/bin/terraform`.
Tried first, and rejected by this cluster's containerd (2.3.2-k3s2) at pod
creation: a Kubernetes image volume (`coder.volumes[].image`) mounting
`ghcr.io/opentofu/opentofu` directly, subPath-ed to just the `tofu` binary.
"ImageVolumeMountFailed: ... only directory subpath is supported" - file-level
subPath on image volumes isn't supported on this runtime, and directory-level
would replace the whole `/usr/local/bin` with the OpenTofu image's own, which
names the binary `tofu`, not `terraform`; renaming isn't possible without a
copy step.
So this uses a small init container instead, `coder.initContainers` - still
sourcing the binary from the official OpenTofu image, but copying (and
renaming) it into a plain `emptyDir`, whose file-level subPath support is
universal and unaffected by the image-volume limitation above. The main
`coder` container's `volumeMounts` entry then subPath-mounts just that one
file onto `/usr/local/bin/terraform`, read-only - the image's real binary
there is root-owned 0755 and the container runs as uid 1000, so nothing
inside it can overwrite that path in place, but a uid-1000 process can still
execute a root-owned, world-executable file mounted read-only over it. Same
access pattern the disposable test's bind mount already proved works, and
independently re-verified here with the equivalent Docker volume-subpath
mount against the real `ghcr.io/coder/coder:v2.35.6` image as uid 1000:
`terraform version` reports `OpenTofu v1.12.6`.
Rendered with `helm template` against this exact values block before landing:
the initContainer, volumeMount, and volume land on the Deployment's `coder`
container exactly as intended, nothing else in the manifest changes.
`coder.initContainers`/`coder.volumes`/`coder.volumeMounts` were previously
unset here, so nothing existing is replaced.
The `opentofu` image tag is not Renovate-managed: Renovate's `kubernetes`
manager has native support for image-volume references, but only on raw
Pod/Deployment/etc. manifests it parses directly - not on values embedded in a
Flux `HelmRelease`'s opaque `values:` blob (moot here anyway, since this ended
up as an initContainer image reference, not an image volume). Bump it by hand
alongside `ppat/coder`'s `mise.toml` `opentofu` pin, which this is meant to
track (the version this repo's disposable test control plane actually
verified the template against).
Template *authoring* moved to OpenTofu; this is what makes this Deployment's
provisioner the thing that actually *applies* templates with OpenTofu, matching
the disposable test control plane `ppat/coder` already runs both under.
BREAKING CHANGE: this Deployment's provisioner now applies every workspace
template - not just homelab-workspace - with OpenTofu instead of the image's
bundled Terraform. OpenTofu's compatibility promise covers standard HCL, but
any template (present or future) relying on Terraform-specific behavior
outside that promise now behaves differently in this live deployment and
needs re-verification against OpenTofu, the same way ppat/coder#898 verified
homelab-workspace.
Refs ppat/coder#897, ppat/coder#898.
ppat
added a commit
to ppat/homelab-ops-kubernetes-apps
that referenced
this pull request
Sep 1, 2026
`provisionerd` (bundled in the `ghcr.io/coder/coder` image this Deployment runs)
resolves its provisioning binary purely by `LookPath("terraform")` plus a numeric
version check - there is no config surface to point it at a differently named
binary, and no vendor awareness at all. `ppat/coder#897`/`#898` migrated the
`homelab-workspace` template's authoring toolchain to OpenTofu and proved the
mechanism - and its live-deployment implication - end to end in a disposable
test control plane there: bind-mount an OpenTofu binary directly over the
image's own `/usr/local/bin/terraform`.
Tried first, and rejected by this cluster's containerd (2.3.2-k3s2) at pod
creation: a Kubernetes image volume (`coder.volumes[].image`) mounting
`ghcr.io/opentofu/opentofu` directly, subPath-ed to just the `tofu` binary.
"ImageVolumeMountFailed: ... only directory subpath is supported" - file-level
subPath on image volumes isn't supported on this runtime, and directory-level
would replace the whole `/usr/local/bin` with the OpenTofu image's own, which
names the binary `tofu`, not `terraform`; renaming isn't possible without a
copy step.
So this uses a small init container instead, `coder.initContainers` - still
sourcing the binary from the official OpenTofu image, but copying (and
renaming) it into a plain `emptyDir`, whose file-level subPath support is
universal and unaffected by the image-volume limitation above. The main
`coder` container's `volumeMounts` entry then subPath-mounts just that one
file onto `/usr/local/bin/terraform`, read-only - the image's real binary
there is root-owned 0755 and the container runs as uid 1000, so nothing
inside it can overwrite that path in place, but a uid-1000 process can still
execute a root-owned, world-executable file mounted read-only over it. Same
access pattern the disposable test's bind mount already proved works, and
independently re-verified here with the equivalent Docker volume-subpath
mount against the real `ghcr.io/coder/coder:v2.35.6` image as uid 1000:
`terraform version` reports `OpenTofu v1.12.6`.
Rendered with `helm template` against this exact values block before landing:
the initContainer, volumeMount, and volume land on the Deployment's `coder`
container exactly as intended, nothing else in the manifest changes.
`coder.initContainers`/`coder.volumes`/`coder.volumeMounts` were previously
unset here, so nothing existing is replaced.
The `opentofu` image tag is not Renovate-managed: Renovate's `kubernetes`
manager has native support for image-volume references, but only on raw
Pod/Deployment/etc. manifests it parses directly - not on values embedded in a
Flux `HelmRelease`'s opaque `values:` blob (moot here anyway, since this ended
up as an initContainer image reference, not an image volume). Bump it by hand
alongside `ppat/coder`'s `mise.toml` `opentofu` pin, which this is meant to
track (the version this repo's disposable test control plane actually
verified the template against).
Template *authoring* moved to OpenTofu; this is what makes this Deployment's
provisioner the thing that actually *applies* templates with OpenTofu, matching
the disposable test control plane `ppat/coder` already runs both under.
BREAKING CHANGE: this Deployment's provisioner now applies every workspace
template - not just homelab-workspace - with OpenTofu instead of the image's
bundled Terraform. OpenTofu's compatibility promise covers standard HCL, but
any template (present or future) relying on Terraform-specific behavior
outside that promise now behaves differently in this live deployment and
needs re-verification against OpenTofu, the same way ppat/coder#898 verified
homelab-workspace.
Refs ppat/coder#897, ppat/coder#898.
Co-authored-by: Peter Pathirana <peter@latent.io>
ppat
added a commit
to ppat/homelab-ops-kubernetes-clusters
that referenced
this pull request
Sep 1, 2026
Rolls the coderd Deployment onto a provisioner that applies workspace templates with OpenTofu instead of the
image's bundled Terraform. An init container sources the binary from ghcr.io/opentofu/opentofu, copies it into
an emptyDir as `terraform`, and that file is subPath-mounted read-only over /usr/local/bin/terraform. Coder's
provisioner resolves its binary purely via LookPath("terraform") plus a numeric version check, with no vendor
awareness, so the rename is the whole mechanism.
The module release is breaking: every workspace template in this deployment now applies under OpenTofu, not just
homelab-workspace. OpenTofu's compatibility promise covers standard HCL, so templates that stay inside it are
unaffected; any template relying on Terraform-specific behaviour outside that promise needs re-verification.
Ordering matters and runs in one direction only. ppat/coder's template is coupled to OpenTofu's own registry -
required_providers pins explicit registry.opentofu.org hosts and the lock file was regenerated by `tofu init` -
and real Terraform rejects that registry's signing identity outright:
Error while installing registry.opentofu.org/coder/coder v2.18.0:
authentication signature from unknown issuer
So this deployment must be on OpenTofu before ppat/coder publishes its next template version. The reverse
direction is safe and was verified the same way: the template version live today initialises cleanly under
OpenTofu, which rewrites the lock file's registry.terraform.io entries to registry.opentofu.org in the
provisioner's disposable working directory and preserves the version selections.
Rollout is non-disruptive by construction. maxSurge rounds up to 1 and maxUnavailable rounds down to 0 at one
replica, so the new pod must clear its init container and readiness probe before the old one is terminated; a
failed init container leaves the previous pod serving and surfaces as a stuck HelmRelease. Running workspaces
are separate Deployments and are not restarted by a coderd roll.
The ghcr.io/opentofu/opentofu tag is not Renovate-managed - Renovate's kubernetes manager cannot see inside a
Flux HelmRelease's opaque values blob - so it must be bumped by hand in lockstep with ppat/coder's mise.toml
opentofu pin.
Refs ppat/homelab-ops-kubernetes-apps#3900, ppat/coder#897, ppat/coder#898.
Co-authored-by: Peter Pathirana <peter@latent.io>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Migrates
templates/kubernetes/homelab-workspace's authoring, linting, and CI toolchain from HashiCorp Terraform to OpenTofu. Deliberate breaking change to the toolchain, not a compatibility shim — see theBREAKING CHANGEfooter on the squashed commit for what a contributor needs to change locally.Full research, decision packet, and status tracking: #897.
Why / scope
Other Terraform-authored repos (e.g.
homelab-ops-terraform) stay on Terraform — this is scoped to this repo's one template. The live homelab Coder deployment's provisioner (owned byhomelab-ops-kubernetes-apps, a different repo) is untouched and keeps applying this same template with real Terraform; only this repo's disposable local/CI test control plane now applies it under OpenTofu. Both work because the HCL this template emits is fully backward-compatible with either binary — see #897's linked research for why.What changed
mise.tomlpinsopentofu(installstofu); pre-commit usestofu-fmt/tofu-validate;required_providerspins explicitregistry.opentofu.orghosts;.terraform.lock.hclregenerated for real viatofu init -upgradetemplate-terraform-provider.json→template-opentofu-provider.json; tool-version bump rule retargeted at theopentofumise toolterraform-provider/terraform-version→opentofu-provider/opentofu-versionincommitlint.config.jslint.yaml's job now callsppat/github-workflows's newlint-opentofu.yaml(ppat/github-workflows#631 — pinned to its PR head SHA until that merges,TODO(#897)left in place to re-pin).github/compose/compose.yamlbind-mounts an OpenTofu binary over the official Coder image's bundledterraform(root-owned, uid-1000 container can't overwrite it in place);test-template.yamlinstalls that binary viajdx/mise-action, versioned frommise.toml's own pin so it can't driftCLAUDE.md/DESIGN.md/README.md/TESTING.mdupdated throughout, including a new named implementation gotcha for the binary-injection mechanismVerification
pre-commit run --all-filesclean, including realtofu fmt/tofu validate/tflintagainst the template.test-template.yaml'sintegrationjob — green run), not just locally: the workspace stood up under OpenTofu, pinged 3/3, and answeredcoder ssh -- env.actionlint/yamllint, both detailed in the squashed commit message:opentofu/setup-opentofu, which this repo's locked-down Actions allowlist rejects outright at workflow-intake time (startup_failure, zero jobs, generic "workflow file issue"). Fixed by installing viajdx/mise-actioninstead, already trusted transitively through the shared lint workflows.CODER_TOFU_BINARYwas set via$GITHUB_ENVand used bydocker compose upin the same step —$GITHUB_ENVwrites only take effect for later steps, never later commands in the step that wrote them. Fixed by resolving it into a local shell variable andexporting it for the same-step use, while still appending to$GITHUB_ENVfor the later steps that also need it.actionlintclean on the touched workflow files.Sequencing note
ppat/github-workflows#631should land first (or at least be reviewed alongside this one) — this PR'slint.yamlchange references its PR head SHA as an interim pin and has aTODO(#897)to re-pin once that PR is tagged onmain.Refs #897.