76 lines
2.1 KiB
Go
76 lines
2.1 KiB
Go
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
|
|
}
|