feat: D1 Part 2 — agent self-update Go plumbing (op class, opsign, executor, commit, report)
- reconcile: ClassAgentUpdate op class; always Destructive (no provenance
blesses replacing the root-adjacent binary). classify test + companion
(TestClassify_AgentUpdateAlwaysDestructive).
- opsign: `-op agent_update` with -agent-version + -sha256 (isHex64-validated);
params {version,sha256}. isHex64 test (Group D).
- config: SelfUpdateConfig{URLTemplate,Username,Token,StateDir,DwellSeconds}
+ WithDefaults + Token redaction.
- internal/selfupdate: Executor (download → verify vs the SIGNED sha → sudo -n
wrapper `apply`; sha is the only integrity root — mismatch refuses + removes,
agent untouched); Manager (startup dwell → `commit`; version-mismatch → no
commit + loud WARN + marker left for report visibility; shutdown-before-dwell
leaves pending). WrapperRunner seam → tests never shell out.
- hub report: additive selfupdate_pending(+version) via SetSelfUpdateReporter
seam; both omitempty (Wireguard precedent) so the cross-repo golden contract
stays byte-stable — no hub change.
- capability manifest: 3 non-critical FELHOM_SELFUPDATE probes.
- main.go: updateExec appended to the executor chain; commit-manager wired to
the report seam + MaybeCommit goroutine after core init.
Tests: Group A (executor happy/sha-mismatch+companion/bad-params/wrapper-fail),
B (agent_update rides the real gate: pinned-key executes, non-pinned +
retarget rejected), C (commit/version-mismatch/no-pending/shutdown), D (opsign).
C2 companion red-proof verified (neutered Go verify → bad binary reaches apply
→ test fails), reverted. Full go test ./... green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
@@ -32,9 +32,43 @@ type Config struct {
|
||||
LocalAPI LocalAPIConfig `json:"local_api"`
|
||||
LANResolver LANResolverConfig `json:"lan_resolver"`
|
||||
WGTunnel WGTunnelConfig `json:"wg_tunnel"`
|
||||
SelfUpdate SelfUpdateConfig `json:"selfupdate"`
|
||||
LogLevel string `json:"log_level"` // debug|info|warn|error (default info)
|
||||
}
|
||||
|
||||
// SelfUpdateConfig configures the operator-signed agent self-update (TASK D1). The artifact HOST
|
||||
// is operator-controlled config; the artifact INTEGRITY comes only from the sha256 pinned inside
|
||||
// the operator-signed op — the hub's Day-0 manifest plays no role here, and a compromised Gitea
|
||||
// can serve garbage but never a binary that passes the signed sha.
|
||||
type SelfUpdateConfig struct {
|
||||
// URLTemplate is the download URL with a literal "{version}" placeholder. Default mirrors the
|
||||
// day-0 host-install scheme (Gitea generic package).
|
||||
URLTemplate string `json:"url_template"`
|
||||
// Username/Token are optional HTTP basic-auth credentials for the artifact host (the same git
|
||||
// read token day-0 uses). Token is a secret — redacted in Config.Redacted.
|
||||
Username string `json:"username,omitempty"`
|
||||
Token string `json:"token,omitempty"`
|
||||
// StateDir holds the staging subdir (<StateDir>/selfupdate/); default /var/lib/felhom-agent.
|
||||
StateDir string `json:"state_dir,omitempty"`
|
||||
// DwellSeconds is how long the NEW binary must run cleanly (after core init) before it commits
|
||||
// the update; default 60.
|
||||
DwellSeconds int `json:"dwell_seconds,omitempty"`
|
||||
}
|
||||
|
||||
// WithDefaults fills the artifact URL template, state dir and dwell.
|
||||
func (s SelfUpdateConfig) WithDefaults() SelfUpdateConfig {
|
||||
if s.URLTemplate == "" {
|
||||
s.URLTemplate = "https://gitea.dooplex.hu/api/packages/admin/generic/felhom-agent/{version}/felhom-agent"
|
||||
}
|
||||
if s.StateDir == "" {
|
||||
s.StateDir = "/var/lib/felhom-agent"
|
||||
}
|
||||
if s.DwellSeconds == 0 {
|
||||
s.DwellSeconds = 60
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// WGTunnelConfig configures the offsite WireGuard tunnel (S3, doc 06). **Enabled DEFAULTS TO
|
||||
// FALSE — the safety gate:** agent releases roll to near-production boxes, and auto-registering
|
||||
// one into the DEV endpoint on update would be wrong. Enable explicitly per box; the default
|
||||
@@ -579,6 +613,9 @@ func (c Config) Redacted() Config {
|
||||
if c.Hub.APIKey != "" {
|
||||
c.Hub.APIKey = "********"
|
||||
}
|
||||
if c.SelfUpdate.Token != "" {
|
||||
c.SelfUpdate.Token = "********"
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user