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)