feat: add durable client command spool
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
package spool
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/rvbox/rvbox/internal/domain"
|
||||
)
|
||||
|
||||
func loadOrCreateIdentity(path string, generate func() (domain.UUID, error)) (domain.UUID, error) {
|
||||
data, err := os.ReadFile(path)
|
||||
if err == nil {
|
||||
return parseIdentity(data)
|
||||
}
|
||||
if !errors.Is(err, os.ErrNotExist) {
|
||||
return domain.UUID{}, fmt.Errorf("read client instance identity: %w", err)
|
||||
}
|
||||
identity, err := generate()
|
||||
if err != nil {
|
||||
return domain.UUID{}, fmt.Errorf("generate client instance identity: %w", err)
|
||||
}
|
||||
if err := writeIdentityAtomically(path, []byte(identity.String()+"\n")); err != nil {
|
||||
return domain.UUID{}, err
|
||||
}
|
||||
return identity, nil
|
||||
}
|
||||
|
||||
func parseIdentity(data []byte) (domain.UUID, error) {
|
||||
if len(data) != 37 || data[36] != '\n' {
|
||||
return domain.UUID{}, ErrIdentityCorrupt
|
||||
}
|
||||
identity, err := domain.ParseUUIDv7(string(data[:36]))
|
||||
if err != nil {
|
||||
return domain.UUID{}, fmt.Errorf("%w: %v", ErrIdentityCorrupt, err)
|
||||
}
|
||||
return identity, nil
|
||||
}
|
||||
|
||||
func writeIdentityAtomically(path string, data []byte) (result error) {
|
||||
directory := filepath.Dir(path)
|
||||
temporary, err := os.CreateTemp(directory, ".client-instance-id-*")
|
||||
if err != nil {
|
||||
return fmt.Errorf("create client identity temporary file: %w", err)
|
||||
}
|
||||
temporaryPath := temporary.Name()
|
||||
defer func() {
|
||||
if result != nil {
|
||||
_ = os.Remove(temporaryPath)
|
||||
}
|
||||
}()
|
||||
if err := temporary.Chmod(0o600); err != nil {
|
||||
_ = temporary.Close()
|
||||
return fmt.Errorf("secure client identity temporary file: %w", err)
|
||||
}
|
||||
if _, err := temporary.Write(data); err != nil {
|
||||
_ = temporary.Close()
|
||||
return fmt.Errorf("write client identity: %w", err)
|
||||
}
|
||||
if err := temporary.Sync(); err != nil {
|
||||
_ = temporary.Close()
|
||||
return fmt.Errorf("sync client identity: %w", err)
|
||||
}
|
||||
if err := temporary.Close(); err != nil {
|
||||
return fmt.Errorf("close client identity: %w", err)
|
||||
}
|
||||
if err := replaceIdentityFile(temporaryPath, path); err != nil {
|
||||
return fmt.Errorf("install client identity: %w", err)
|
||||
}
|
||||
if err := syncDirectory(directory); err != nil {
|
||||
return fmt.Errorf("sync client identity directory: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user