From 3d1323cc0916cd016898ea6a2be81a3f0aac024a Mon Sep 17 00:00:00 2001 From: kisfenyo Date: Fri, 10 Jul 2026 21:59:51 +0200 Subject: [PATCH] =?UTF-8?q?pbsdr:=20escrow=20seed=20writes=20in=20place=20?= =?UTF-8?q?(config=20dir=20is=20root-owned;=20tmp+rename=20impossible=20fo?= =?UTF-8?q?r=20the=20non-root=20agent=20=E2=80=94=20demo=20live=20finding)?= =?UTF-8?q?;=20recovery=20copy=20parked=20in=20the=20state=20dir?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6 --- internal/pbsdr/manager.go | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/internal/pbsdr/manager.go b/internal/pbsdr/manager.go index 4390bf0..2733a13 100644 --- a/internal/pbsdr/manager.go +++ b/internal/pbsdr/manager.go @@ -407,12 +407,26 @@ func (m *Manager) seedEscrowStorageID(storageID string) error { if err != nil { return err } - tmp := m.configPath + ".pbsdr-tmp" - if err := os.WriteFile(tmp, out, st.Mode().Perm()); err != nil { + // The config DIRECTORY (/etc/felhom-agent) is root-owned while the FILE is agent-owned — + // tmp+rename is impossible for the non-root agent (rename mutates the directory; live + // finding on the demo). So: park a recovery copy in the agent state dir, then rewrite the + // file IN PLACE (O_TRUNC). The agent is the file's only writer and the content is small; + // a torn write is recoverable from the parked copy. + if err := os.MkdirAll(m.stateDir, 0o700); err != nil { return err } - if err := os.Rename(tmp, m.configPath); err != nil { - os.Remove(tmp) + if err := os.WriteFile(filepath.Join(m.stateDir, "agent.json.pre-seed"), raw, 0o600); err != nil { + return fmt.Errorf("parking the pre-seed copy: %w", err) + } + f, err := os.OpenFile(m.configPath, os.O_WRONLY|os.O_TRUNC, st.Mode().Perm()) + if err != nil { + return err + } + if _, err := f.Write(out); err != nil { + f.Close() + return err + } + if err := f.Close(); err != nil { return err } m.logger.Info("pbsdr: seeded escrow.pbs_storage_id (the ceremony one-liner needs no flags)", "storage_id", storageID)