Lock the state directory and write vault files atomically (closes #34)
check / check (push) Successful in 1m31s
check / check (push) Successful in 1m31s
Each command that changes the state directory holds one lock: flock(2) on `lock` in the state directory, dropped by the kernel if the process dies, or a process-wide mutex on the in-memory test filesystem. It covers the state directory, not each vault, because `currentvault`, `vault create` and cross-vault moves span vaults, and a lock file in a vault would be deleted by `vault remove` under a waiting command. Files go through `secret.WriteFileAtomic`; versions, new secrets and cross-vault copies are built in a temporary directory and renamed into place; removals rename out of the way first. Left for later: replacing an unlocker (#71) and deleting what an interrupted command leaves under a `.tmp-` name (#75). Model: opus-5-5 Co-authored-by: clawbot <sneak+clawbot@sneak.cloud>
This commit was merged in pull request #69.
This commit is contained in:
@@ -172,7 +172,7 @@ func (p *PGPUnlocker) GetID() string {
|
||||
func (p *PGPUnlocker) Remove() error {
|
||||
// For PGP unlockers, we just need to remove the directory
|
||||
// No external resources (like keychain items) to clean up
|
||||
err := p.fs.RemoveAll(p.Directory)
|
||||
err := RemoveDirAtomic(p.fs, p.Directory)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to remove PGP unlocker directory: %w", err)
|
||||
}
|
||||
@@ -275,7 +275,7 @@ func CreatePGPUnlocker(
|
||||
ageRecipient := ageIdentity.Recipient().String()
|
||||
recipientPath := filepath.Join(unlockerDir, "pub.txt")
|
||||
|
||||
err = afero.WriteFile(fs, recipientPath, []byte(ageRecipient), FilePerms)
|
||||
err = WriteFileAtomic(fs, recipientPath, []byte(ageRecipient))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to write age recipient: %w", err)
|
||||
}
|
||||
@@ -298,7 +298,7 @@ func CreatePGPUnlocker(
|
||||
// Write encrypted long-term private key
|
||||
ltPrivKeyPath := filepath.Join(unlockerDir, "longterm.age")
|
||||
|
||||
err = afero.WriteFile(fs, ltPrivKeyPath, encryptedLtPrivKeyToAge, FilePerms)
|
||||
err = WriteFileAtomic(fs, ltPrivKeyPath, encryptedLtPrivKeyToAge)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to write encrypted long-term private key: %w", err)
|
||||
}
|
||||
@@ -315,7 +315,7 @@ func CreatePGPUnlocker(
|
||||
|
||||
agePrivKeyPath := filepath.Join(unlockerDir, "priv.age.gpg")
|
||||
|
||||
err = afero.WriteFile(fs, agePrivKeyPath, encryptedAgePrivKey, FilePerms)
|
||||
err = WriteFileAtomic(fs, agePrivKeyPath, encryptedAgePrivKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to write encrypted age private key: %w", err)
|
||||
}
|
||||
@@ -357,9 +357,8 @@ func writePGPUnlockerMetadata(
|
||||
return nil, fmt.Errorf("failed to marshal unlocker metadata: %w", err)
|
||||
}
|
||||
|
||||
err = afero.WriteFile(fs,
|
||||
filepath.Join(unlockerDir, "unlocker-metadata.json"),
|
||||
metadataBytes, FilePerms)
|
||||
err = WriteFileAtomic(fs,
|
||||
filepath.Join(unlockerDir, "unlocker-metadata.json"), metadataBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to write unlocker metadata: %w", err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user