From f8bea9068f682a10b5f1513f4357de8b23ce5d86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kier=C3=A1n=20Meinhardt?= Date: Tue, 1 Sep 2026 11:26:39 +0200 Subject: [PATCH 1/3] fix(lib): tofu init -upgrade so a provider version bump re-locks The generated required_providers version is pinned to the nixpkgs-packaged provider, so bumping nixpkgs moves the constraint while .terraform.lock.hcl under the service state dir still pins the previous version, and init aborts with "locked provider ... does not match configured version constraint". Re-selecting is offline: withPlugins' dir is the only source. --- modules/lib/default.nix | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/modules/lib/default.nix b/modules/lib/default.nix index c9af551..5462a00 100644 --- a/modules/lib/default.nix +++ b/modules/lib/default.nix @@ -563,7 +563,13 @@ rec { for id in ${lib.escapeShellArgs (lib.attrNames allCredentials)}; do export "TF_VAR_$id=$(cat "$CREDENTIALS_DIRECTORY/$id")" done - tofu init -no-color + # -upgrade: the provider version is pinned to whatever nixpkgs + # packages, so a nixpkgs bump moves the required_providers + # constraint while .terraform.lock.hcl in the state dir still pins + # the old version -- a plain init would fail. re-selecting stays + # offline: the plugin dir baked in by withPlugins is the only + # available source. + tofu init -upgrade -no-color tofu apply -auto-approve -input=false -no-color ''; }; From e7482aaeaef216635765d79372e6dd5e670e0f00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kier=C3=A1n=20Meinhardt?= Date: Tue, 1 Sep 2026 11:30:29 +0200 Subject: [PATCH 2/3] docs(readme): troubleshoot a stale .terraform.lock.hcl Hosts that ran a revision before `tofu init -upgrade` stay stuck on the old lock; document dropping it and restarting the reconciler. --- README.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/README.md b/README.md index e49de98..8123093 100644 --- a/README.md +++ b/README.md @@ -149,6 +149,28 @@ VM with the pairing enabled, let the reconciler apply, then assert the runtime state by querying the live service API — not the Terraform state. Eval-only or build-only success is not evidence the reconciliation works. +## Troubleshooting + +**`declarative-.service` fails with `locked provider … does not match +configured version constraint`.** The reconciler's `.terraform.lock.hcl` is host +state under the service's state directory and survives redeployments, while the +generated `required_providers` constraint is pinned to whatever provider version +nixpkgs packages — so bumping the `nixpkgs` input moves the constraint out from +under the lock. The reconciler runs `tofu init -upgrade` to re-lock by itself +(still offline: the plugin dir baked in by `opentofu.withPlugins` is the only +available source). On a host still stuck from an older revision, drop the lock +and re-run: + +```sh +rm /var/lib//declarative-terraform/.terraform.lock.hcl +systemctl restart declarative-.service +``` + +The `terraform.tfstate` next to it is untouched, so the next apply reconciles +against the existing state rather than recreating resources. For a +`DynamicUser=` service such as Keycloak the real directory lives under +`/var/lib/private/`, reachable as root through the `/var/lib/` symlink. + ## Design decisions - **Executor:** OpenTofu (MPL 2.0); `terraform` (BSL 1.1, unfree) is not used. From 8de9395e214a2b707f9fefa0f4a9ff699e4fb4cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kier=C3=A1n=20Meinhardt?= Date: Tue, 1 Sep 2026 11:38:26 +0200 Subject: [PATCH 3/3] test(services/forgejo): cover re-locking after a provider version bump Plants a stale .terraform.lock.hcl (recorded version rewritten to 0.0.1) and restarts the reconciler. Without `tofu init -upgrade` this fails with "locked provider ... does not match configured version constraint" -- verified by reverting the flag, which fails the test. The VM has no network, so re-locking here also proves the re-selection stays offline. --- services/forgejo/checks.nix | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/services/forgejo/checks.nix b/services/forgejo/checks.nix index 3bdbe45..5077ce0 100644 --- a/services/forgejo/checks.nix +++ b/services/forgejo/checks.nix @@ -10,7 +10,9 @@ # a repository by its numeric id — so a successful apply proves reference # resolution and apply ordering. A user with a `passwordFile` also proves # per-secret credential indirection — the value is loaded from a host file -# and kept out of the generated `.tf.json`. Requires KVM (a NixOS VM test). +# and kept out of the generated `.tf.json`. A stale +# .terraform.lock.hcl is then planted to prove the reconciler re-locks +# offline after a provider version bump. Requires KVM (a NixOS VM test). { pkgs, self }: { forgejo = pkgs.testers.runNixOSTest { @@ -145,6 +147,21 @@ assert "0 added, 0 changed, 0 destroyed" in apply_lines[-1], \ f"reapply was not a no-op: {apply_lines[-1]}" + # A provider version bump moves the generated required_providers + # constraint while .terraform.lock.hcl -- host state under the service's + # state dir -- still pins the old version. Simulate that skew by + # rewriting the recorded version: a plain `tofu init` aborts with + # "locked provider ... does not match configured version constraint", + # so this is what the reconciler's `-upgrade` buys. The VM has no + # network, so re-locking here also proves the re-selection stays offline + # (the plugin dir baked in by withPlugins is the only source). + lock = "/var/lib/forgejo/declarative-terraform/.terraform.lock.hcl" + machine.succeed(f"sed -i 's/version *=.*/version = \"0.0.1\"/' {lock}") + machine.succeed(f"grep -q 0.0.1 {lock}") + machine.succeed("systemctl restart declarative-forgejo.service") + relocked = machine.succeed(f"cat {lock}") + assert "0.0.1" not in relocked, f"stale lock was not re-locked: {relocked}" + # Adding an admin-scoped resource (a user needs write:admin + read:user) # mus work because the scopen is the maximal "all" token machine.succeed("/run/current-system/specialisation/widenScope/bin/switch-to-configuration test")