agent (unreleased): fixes after an independent review — one lock for the writers of agent.json, the trust run serialised and bounded, the CA copy keeps its previous root, the profile stanza from the wrapper's answer, a stale give-up ends, both boot tokens from one read
gates / gates (push) Successful in 48s

Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
admin committed 2026-11-14 19:09:21 +01:00
1 parent 61feb95c06
commit ec7e699fd7
18 files changed
+333 -59

No files matched your search

+17 -2
View File
@@ -25,6 +25,7 @@ import (
"context"
"encoding/json"
"fmt"
"gitea.dooplex.hu/admin/felhom-agent/internal/config"
"log/slog"
"os"
"path/filepath"
@@ -81,12 +82,16 @@ type Manager struct {
// MinGap between two runs that a failed call asked for (default 10 minutes).
MinGap time.Duration
runMu sync.Mutex // serialises Ensure
mu sync.Mutex
cur proxmox.TLSConfig
status Status
lastRun time.Time
}
// copyStepTimeout bounds the root step that makes the CA copy.
const copyStepTimeout = 90 * time.Second
func (m *Manager) now() time.Time {
if m.Now != nil {
return m.Now()
@@ -207,6 +212,11 @@ func causeText(cause string) string {
// Ensure runs the order of the package comment once. why is a log word ("start", "daily", a trust cause). It returns
// true when the running trust changed.
func (m *Manager) Ensure(ctx context.Context, why string) bool {
// One run at a time. Ensure is called from the start, the timer, a failed collect and after a bundle install; two
// overlapping runs would both read the config, and the second would park the ALREADY-MOVED file as the "before"
// copy (losing the fingerprint from the recovery copy). Found by review, 2026-11-14.
m.runMu.Lock()
defer m.runMu.Unlock()
m.mu.Lock()
cur := m.cur
m.lastRun = m.now()
@@ -230,8 +240,11 @@ func (m *Manager) Ensure(ctx context.Context, why string) bool {
if m.EnsureCopy == nil || m.Probe == nil {
return false
}
// 1. the copy
c, err := m.EnsureCopy(ctx)
// 1. the copy. The root step gets a deadline of its own: Ensure runs on the daemon's start path and inside the
// report loop, and a wrapper that hangs (sudo, a stuck read of /etc/pve) must not stop either.
cctx, ccancel := context.WithTimeout(ctx, copyStepTimeout)
c, err := m.EnsureCopy(cctx)
ccancel()
if err != nil {
lg.Warn("pvetrust: the root step gave no CA copy — the trust stays as it is", "err", err)
set(modeOf(cur), ProblemCopyUnavailable, truncate(err.Error(), 240), nil)
@@ -341,6 +354,8 @@ func truncate(s string, n int) string {
// writeConfig sets proxmox.tls.ca_file and clears proxmox.tls.fingerprint in ONE write of agent.json. Every other key is
// kept byte for byte. A recovery copy is parked in the state folder first; the result is read back and checked.
func (m *Manager) writeConfig(caPath string) error {
config.FileMu.Lock() // one writer of agent.json at a time in this process (config.FileMu)
defer config.FileMu.Unlock()
raw, err := os.ReadFile(m.ConfigPath)
if err != nil {
return err