feat(P2): podman traefik reconciler + TLS model fix (REQ-172)

Replace internal/traefik/install.go binary+systemd installer with a
podman-container reconciler (R-024). The reconciler is idempotent:
inspect → start-if-stopped → pull+run-if-absent.

Container run flags (research-validated):
  --restart=unless-stopped (not always; research Topic 6)
  --network host (binds 127.0.0.1:8080/8443 on host/LXC loopback)
  -v /etc/traefik/traefik.yml:ro (overrides baked default; C-58)
  -v /etc/traefik/dynamic:ro (orca writes atomically via SSH-push)
  -v /etc/orca/step-ca-root.crt:ro (future mTLS; v0.14 uses tls:{})
  No :Z SELinux flag (research Topic 7)

C-50: ensurePodmanLocal/Remote installs podman if absent.
C-57: removeLegacySystemdUnitLocal/Remote stops+disables+removes
  the v0.13 orca-traefik.service + /usr/local/bin/traefik before
  starting the podman container (upgrade path).
  upgrade.go cutover rewritten to use the reconciler.

TLS model fix (research Topic 4): drop certResolver: orca from
dynamic config (traefik v3.3 only supports acme/tailscale resolvers,
not CA-file-based). Emit tls: {} instead. Real mTLS via dynamic
tls.certificates + clientAuth.caFiles deferred to v0.15 (grill
G-003, confidence 0.55 < 0.60).

Callsites updated:
  init.go: installTraefikLocal → ensureTraefikContainerLocal
  linux/bootstrap.go: traefik.InstallRemote → EnsureTraefikContainerRemote
  proxmox/bootstrap.go: same
  traefik_install.go: wrapper updated

Tests: internal/traefik/install_test.go (new) — ImageRef, podmanRunArgs,
  container-running/stopped/absent paths, legacy systemd removal (C-57).

---ci---
project: orca
phase: 2
milestone: v0.14
status: execute
---/ci---
This commit is contained in:
Jon Chery
2026-08-10 20:04:20 +00:00
parent dccdb746ea
commit dea472f443
9 changed files with 444 additions and 118 deletions
+14 -13
View File
@@ -48,13 +48,13 @@ const DefaultSSHPort = 22
// Options configures a Linux worker bootstrap run.
type Options struct {
Host string
SSHUser string
SSHKeyPath string
OrcaUser string
SSHPort int
HostKeyFingerprint string
Logger *slog.Logger
Host string
SSHUser string
SSHKeyPath string
OrcaUser string
SSHPort int
HostKeyFingerprint string
Logger *slog.Logger
}
// Result is the outcome of a successful bootstrap.
@@ -157,8 +157,8 @@ func BootstrapLinux(ctx context.Context, opts Options) (*Result, error) {
}
opts.Logger.Info("linux bootstrap: user created", "user", opts.OrcaUser)
// Step 4d: Install Traefik on the remote host (REQ-165, Phase B).
// Traefik is the data-plane ingress; SSH is control plane only.
// Step 4d: Ensure orca-traefik podman container on the remote host
// (REQ-172, R-024). Replaces v0.13 binary+systemd install.
sshExecFn := func(cmd string) ([]byte, error) {
session, err := client.NewSession()
if err != nil {
@@ -167,8 +167,10 @@ func BootstrapLinux(ctx context.Context, opts Options) (*Result, error) {
defer session.Close()
return session.CombinedOutput(cmd)
}
if err := traefik.InstallRemote("", sshExecFn); err != nil {
opts.Logger.Warn("linux bootstrap: traefik install failed", "err", err)
ctx, cancelContainer := context.WithTimeout(ctx, 120*time.Second)
defer cancelContainer()
if err := traefik.EnsureTraefikContainerRemote(ctx, "", sshExecFn); err != nil {
opts.Logger.Warn("linux bootstrap: traefik container ensure failed", "err", err)
}
// Step 5: Create the drift-events directory.
@@ -188,7 +190,7 @@ func BootstrapLinux(ctx context.Context, opts Options) (*Result, error) {
return &Result{
NodeName: opts.Host,
NodeAddress: fmt.Sprintf("%s:8443", opts.Host),
NodeAddress: fmt.Sprintf("%s:8443", opts.Host),
HostKeyFingerprint: hostKeyFP,
}, nil
}
@@ -226,5 +228,4 @@ func pinnedHostKeyCallback(expectedSHA256Base64 string, capturedKey *ssh.PublicK
return cb, nil
}
var _ = security.WriteAtomic