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 := securePrivateFile(temporaryPath); 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 }