Skip to content

feat(LinuxTentacleE2E): Phase 12.L.E.8 — E15.h-Linux config preservation across upgrade

Summary

Linux mirror of the Windows E15.h ship-blocker (PR #198). Pins the contract that upgrade-linux-tentacle.sh's Phase B mv swap operates on INSTALL_DIR ONLY — sibling /etc/squid-tentacle/{instances.json, instances/<name>/...} MUST survive byte-for-byte.

Why ship-blocking

Without this test, a future polish that "tidies up" the upgrade flow could shred operator identity material:

File path (Linux production) Holds Lost-without-pin failure mode
/etc/squid-tentacle/instances.json Registry: name → ConfigPath map for every instance Server sees every Tentacle "disappear"; operators re-register all
/etc/squid-tentacle/instances/<name>.config.json Server URL + thumbprint + subscription ID Agent can't dial in post-restart; service marks itself idle
/etc/squid-tentacle/instances/<name>/certs/<...> Per-instance mTLS material Polling subscription rejected at TLS handshake; permanent disconnection

Test approach

Same pattern as Windows E15.h:

  1. Stage v1 service with healthz responder enabled (Phase B needs 200/OK).
  2. Pre-stage instance state in a test-private config dir (sibling to INSTALL_DIR, not under host /etc/).
  3. SHA256 each file (pre-upgrade hashes).
  4. Run a normal v1→v2 upgrade (same shape as E1.h-Linux).
  5. Assert exitCode == 0 + marker swapped to v2 — proves Phase B actually ran (without this, preservation assertions would pass vacuously).
  6. Assert all three files still exist + SHA256 unchanged.

Why test-private config dir, not real /etc/squid-tentacle?

The .sh has no config-dir env override — it genuinely never reads or writes outside INSTALL_DIR + STATE_DIR. So any sibling tree is provably untouched simply by NOT being INSTALL_DIR. A test-private dir under Path.GetTempPath() pins the same structural contract without polluting the host's real /etc/squid-tentacle.

Infrastructure additions

LinuxLifecycleContext:

  • ConfigDirOverride property (lazy-created on first StageInstanceState)
  • StageInstanceState(instanceName) → pre-stages the three files, returns InstanceConfigPaths record
  • HashFile(path) static helper (SHA256 lower-hex)
  • Dispose now best-effort cleans up ConfigDirOverride if present

Fidelity tier

🟢 High (Rule 12.4): real production .sh + real systemd-run --scope + real sudo + real bash + real LocalReleaseMirror + real OS filesystem hashing. No mocks at any layer.

Test plan

  • Linux E2E workflow runs Squid.LinuxTentacleE2ETests to completion (manual workflow_dispatch after merge — workflow doesn't auto-trigger on PRs)
  • E15h_UpgradePreservesInstanceConfigAndCertFiles_Linux passes
  • No regression on E1.h-Linux / E1.u1-Linux / E12.u1-Linux (same fixture; preserves J.L.E.7 stability)

🤖 Generated with Claude Code

Merge request reports

Loading