Documentation
¶
Index ¶
- Constants
- Variables
- func ApplyCompiled(root string, result CompileResult, preserveCreatedAt string) error
- func BlobID(content []byte) string
- func ClassifyKeyID(keyID, active, legacy string) string
- func CleanMaterializedEnv(root string) (bool, error)
- func DeclarationPath(root string) string
- func DecryptBlob(keys CryptoKeys, envelope EncryptedEnvelope, aad []byte) ([]byte, error)
- func EncryptionSecretRef(config Config) string
- func EnvAssetPath(root string) string
- func EnvDocumentDigest(data []byte) string
- func EnvRuntimePath(root string) string
- func FormatDotenv(values map[string]string) []byte
- func HexDigest(data []byte) string
- func IsNotConfigured(err error) bool
- func IsSupportedScheme(scheme string) bool
- func IsSyncConfigError(err error, code string) bool
- func KeyID(secretRef string) string
- func Logout(root string) error
- func MarshalForDigest(c SyncConfig) []byte
- func MaterializeEnv(root string, snapshot *EnvSnapshot, allowlist []string) (string, error)
- func ParseDotenv(data []byte) (map[string]string, error)
- func PathHash(path string) string
- func RedactedData(state State) map[string]any
- func Register(scheme string, factory StoreFactory)
- func SaveEnvAsset(root string, asset EnvAsset) error
- func SaveSecretEnvelope(root string, env SecretEnvelope) error
- func SecretsAssetPath(root string) string
- func SetKeyDerivationIterationsForTesting(iterations int)
- func SourceMarkerPath(root string) string
- func SupportedSyncBackendKind(kind string) bool
- func SyncKeyVersions(secretRef string) (active, legacy string, err error)
- func ValidateS3Credentials(c S3Credentials) error
- func WriteConfig(root string, config Config) error
- type BlobStore
- type CompileRequest
- type CompileResult
- type ConditionalWriteCapability
- type Config
- type CryptoKey
- type CryptoKeys
- type DeviceSession
- type DoctorResult
- type DotenvError
- type DriftItem
- type DriftReport
- type EncryptedEnvelope
- type EnvAsset
- type EnvAssetError
- type EnvReloader
- type EnvSnapshot
- func (s *EnvSnapshot) AllKeys() []string
- func (s *EnvSnapshot) ApplyAllowlist(allowlist []string) map[string]string
- func (s *EnvSnapshot) Digest() string
- func (s *EnvSnapshot) IsEmpty() bool
- func (s *EnvSnapshot) Lookup(key string) (string, bool)
- func (s *EnvSnapshot) OverlayEnv(base []string, allowlist []string) []string
- func (s *EnvSnapshot) Source() string
- type EnvUnlockProvider
- type ExtendedBlobStore
- type FakeUnlockProvider
- type FileBackend
- func (b *FileBackend) BatchStat(ctx context.Context, keys []string) (map[string]string, error)
- func (b *FileBackend) Delete(ctx context.Context, key string) error
- func (b *FileBackend) Exists(ctx context.Context, key string) (bool, error)
- func (b *FileBackend) Get(ctx context.Context, key string) ([]byte, string, error)
- func (b *FileBackend) List(ctx context.Context, prefix string) ([]ObjectInfo, error)
- func (b *FileBackend) Put(ctx context.Context, key string, data []byte, baseRev string) (string, error)
- func (b *FileBackend) Stat(ctx context.Context, key string) (string, error)
- func (b *FileBackend) SupportsConditionalWrites() bool
- type LoginRequest
- type Manifest
- type ManifestDelete
- type ManifestEntry
- type ManifestFileTooLargeError
- type ManifestIdentity
- type ManifestUnsafePathError
- type ObjectInfo
- type RcloneBackend
- func (b *RcloneBackend) BatchStat(ctx context.Context, keys []string) (map[string]string, error)
- func (b *RcloneBackend) Delete(ctx context.Context, key string) error
- func (b *RcloneBackend) Exists(ctx context.Context, key string) (bool, error)
- func (b *RcloneBackend) Get(ctx context.Context, key string) ([]byte, string, error)
- func (b *RcloneBackend) List(ctx context.Context, prefix string) ([]ObjectInfo, error)
- func (b *RcloneBackend) Put(ctx context.Context, key string, data []byte, _ string) (string, error)
- func (b *RcloneBackend) Stat(ctx context.Context, key string) (string, error)
- func (b *RcloneBackend) SupportsConditionalWrites() bool
- type ReloadStatus
- type S3Backend
- func (s *S3Backend) BatchStat(ctx context.Context, keys []string) (map[string]string, error)
- func (s *S3Backend) Delete(ctx context.Context, key string) error
- func (s *S3Backend) Exists(ctx context.Context, key string) (bool, error)
- func (s *S3Backend) Get(ctx context.Context, key string) ([]byte, string, error)
- func (s *S3Backend) List(ctx context.Context, prefix string) ([]ObjectInfo, error)
- func (s *S3Backend) Put(ctx context.Context, key string, data []byte, baseRev string) (string, error)
- func (s *S3Backend) Stat(ctx context.Context, key string) (string, error)
- func (s *S3Backend) SupportsConditionalWrites() bool
- type S3BackendOptions
- type S3Config
- type S3Credentials
- type SecretEntry
- type SecretEntryKind
- type SecretEnvelope
- type SecretEnvelopeError
- type SecretMetadata
- type SourceMarker
- type State
- type StoreFactory
- type SyncBackend
- type SyncConfig
- type SyncConfigError
- type SyncPolicy
- type SyncRequires
- type SyncSecretRefs
- type SyncWorkspace
- type UnlockProvider
Constants ¶
const ( ManifestSchemaVersionV1 = syncwire.ManifestSchemaVersionV1 ManifestSchemaVersionV2 = syncwire.ManifestSchemaVersionV2 ManifestSchemaVersion = syncwire.ManifestSchemaVersion )
const ( ConfigSchemaVersion = "pinax.cloud.config.v1" SessionSchemaVersion = "pinax.cloud.session.v1" )
const ( CredentialModeDeviceProfile = "device-profile" CredentialModeRepositoryEncrypted = "repository-encrypted" )
Credential mode constants for S3Config.
const ( SyncConfigSchemaVersion = "pinax.sync.config.v1" SyncSecretsSchemaVersion = "pinax.sync.secrets.v1" SourceMarkerSchemaVersion = "pinax.sync.source-marker.v1" )
Declaration schema versions. These are the repository-tracked, portable declaration layer — distinct from the device-owned pinax.cloud.config.v1 runtime state that the compiler emits.
const ( DeclarationFileName = "pinax-sync.yaml" SecretsAssetFileName = "pinax-sync.secrets.yaml" SourceMarkerFileName = "pinax-sync.source.yaml" )
DeclarationFilePaths are the CLI-authored structured assets inside the vault. Business code and agents MUST NOT assemble these by hand.
const CreateIfAbsentRevision = "__pinax_create_if_absent__"
const CryptoEnvelopeSchemaVersion = syncwire.EnvelopeSchemaVersion
const EnvAssetFileName = "pinax-sync.env.age"
EnvAssetFileName is the fixed, repository-tracked ciphertext dotenv asset. The path is fixed (not user-selectable) to prevent path-escape and protected- path bypass. Plaintext runtime files live under a separate managed directory.
const EnvAssetSchemaVersion = "pinax.sync.env.v1"
EnvAssetSchemaVersion is the frozen contract version for the encrypted dotenv asset. The asset is repository-tracked; its plaintext is never required to be committed and lives only in the in-memory EnvSnapshot at runtime.
const EnvRuntimeDir = "runtime"
EnvRuntimeDir is the managed, 0600, Git-ignored directory for materialized plaintext env files. Only --materialize writes here; default runs stay in memory.
const EnvRuntimeFileName = "pinax-sync.env"
EnvRuntimeFileName is the single managed materialized plaintext filename.
const MaxManifestFileBytes = 100 * 1024 * 1024
const S3CredentialFormat = "s3_credentials.v1"
S3CredentialFormat is the typed payload format identifier stored as the entry format in the secrets envelope and the declaration.
Variables ¶
var ( ErrObjectNotFound = errors.New("object not found") ErrConflict = errors.New("revision conflict") )
var ErrEnvAssetMissing = errors.New("sync env asset not found")
ErrEnvAssetMissing is returned when no pinax-sync.env.age exists.
var ErrNotConfigured = errors.New("cloud not configured")
var ErrSecretsAssetMissing = errors.New("sync secrets asset not found")
ErrSecretsAssetMissing is returned when no pinax-sync.secrets.yaml exists.
var ErrSyncDeclarationMissing = errors.New("sync repository declaration not found")
ErrSyncDeclarationMissing is returned when no pinax-sync.yaml exists.
var SupportedSyncBackendKinds = map[string]bool{ "s3-direct": true, "rclone-direct": true, "server": true, "embedded": true, }
SupportedSyncBackendKinds is the closed set of backend kinds the declaration layer accepts. Unknown kinds are rejected so an unsupported backend cannot silently compile to a half-usable runtime config.
var ValidCredentialModes = map[string]bool{ "": true, CredentialModeDeviceProfile: true, CredentialModeRepositoryEncrypted: true, }
ValidCredentialModes is the closed set of accepted credential_mode values.
var ValidRemoteDeletePolicies = map[string]bool{ "": true, "deny": true, "require-approval": true, }
ValidRemoteDeletePolicies enumerates the conservative policy values.
Functions ¶
func ApplyCompiled ¶ added in v0.1.6
func ApplyCompiled(root string, result CompileResult, preserveCreatedAt string) error
ApplyCompiled writes the runtime config and source marker atomically through the canonical authoring boundary, preserving the existing CreatedAt when regenerating the same workspace. It backs up the prior config before writing so apply is restorable.
func ClassifyKeyID ¶ added in v0.2.0
ClassifyKeyID labels a remote envelope KeyID against the vault's derivations.
func CleanMaterializedEnv ¶ added in v0.1.6
CleanMaterializedEnv removes only the Pinax-managed materialized env file. It never removes arbitrary user-selected files and refuses symlink targets.
func DeclarationPath ¶ added in v0.1.6
DeclarationPath returns the path to the repository sync declaration.
func DecryptBlob ¶
func DecryptBlob(keys CryptoKeys, envelope EncryptedEnvelope, aad []byte) ([]byte, error)
func EncryptionSecretRef ¶
func EnvAssetPath ¶ added in v0.1.6
EnvAssetPath returns the fixed path to the encrypted dotenv asset.
func EnvDocumentDigest ¶ added in v0.1.6
EnvDocumentDigest returns a short stable hex digest of a plaintext dotenv document, used for daemon reload identity checks. It is computed over the raw bytes so it does not reveal structure beyond the digest.
func EnvRuntimePath ¶ added in v0.1.6
EnvRuntimePath returns the fixed path to the materialized plaintext env file.
func FormatDotenv ¶ added in v0.1.6
FormatDotenv renders values back into a canonical strict dotenv document with stable key ordering. Values are single-quoted when they contain special chars so the round-trip survives ParseDotenv without ambiguity.
func HexDigest ¶ added in v0.1.6
HexDigest returns a short stable hex digest of arbitrary bytes, used for the source marker's declaration_digest.
func IsNotConfigured ¶
func IsSupportedScheme ¶
IsSupportedScheme returns true if the scheme has a registered factory.
func IsSyncConfigError ¶ added in v0.1.6
IsSyncConfigError reports whether err is a *SyncConfigError with the given code.
func KeyID ¶ added in v0.1.6
KeyID resolves the secret reference and returns the stable key identifier for secretRef, or an empty string when the reference cannot be resolved.
func MarshalForDigest ¶ added in v0.1.6
func MarshalForDigest(c SyncConfig) []byte
MarshalForDigest serializes a SyncConfig canonically so equal configs produce equal digests regardless of map ordering or whitespace.
func MaterializeEnv ¶ added in v0.1.6
func MaterializeEnv(root string, snapshot *EnvSnapshot, allowlist []string) (string, error)
MaterializeEnv writes the snapshot's plaintext to the managed runtime path with 0600 permissions. It is the compatibility exit for external tools that cannot consume in-memory injection. Callers MUST pass an allowlist so only declared keys are written; the full snapshot is never dumped.
func ParseDotenv ¶ added in v0.1.6
ParseDotenv parses a strict, safe dotenv subset. It accepts:
KEY=value KEY="quoted value" KEY='quoted value'
and rejects shell execution, include directives, recursive ${...} expansion, command substitution $() and backticks, NUL / control characters, empty keys, duplicate keys and multi-line heredocs. Errors report line number and key name only — never the rejected value.
This is intentionally a strict subset of POSIX dotenv so Pinax never evaluates attacker-controlled shell syntax stored in the repository.
func RedactedData ¶
func Register ¶
func Register(scheme string, factory StoreFactory)
Register registers a new BlobStore factory for a URI scheme.
func SaveEnvAsset ¶ added in v0.1.6
SaveEnvAsset persists the encrypted dotenv asset with restrictive permissions. It never writes plaintext; ciphertext + redacted metadata only.
func SaveSecretEnvelope ¶ added in v0.1.6
func SaveSecretEnvelope(root string, env SecretEnvelope) error
SaveSecretEnvelope persists the envelope with restrictive permissions. It never writes plaintext; entries carry only ciphertext.
func SecretsAssetPath ¶ added in v0.1.6
SecretsAssetPath returns the path to the encrypted secrets asset.
func SetKeyDerivationIterationsForTesting ¶ added in v0.2.0
func SetKeyDerivationIterationsForTesting(iterations int)
SetKeyDerivationIterationsForTesting is TEST-ONLY: it overrides the PBKDF2-SHA256 iteration counts for both the v2 and legacy derivations in this process. Never call it from non-test code — it would silently weaken every key derived here. Legitimate callers are _test.go files and TestMain functions only; TestKeyDerivationOverrideIsTestOnly enforces this by walking the module and failing if any production file references it.
func SourceMarkerPath ¶ added in v0.1.6
SourceMarkerPath returns the device-local source marker path (under cloud/ so it is treated as device runtime state, never shared).
func SupportedSyncBackendKind ¶ added in v0.1.6
SupportedSyncBackendKind reports whether kind is a supported declaration backend.
func SyncKeyVersions ¶ added in v0.2.0
SyncKeyVersions reports the active (v2) and legacy (v1) key identifiers for a secret reference, so `pinax sync keys` can classify a remote envelope as v2, legacy, or foreign without decrypting it.
func ValidateS3Credentials ¶ added in v0.1.9
func ValidateS3Credentials(c S3Credentials) error
ValidateS3Credentials reports field-level problems without echoing values.
func WriteConfig ¶ added in v0.1.6
WriteConfig persists the Capsa runtime config through the application-layer authoring boundary: atomic directory creation, restrictive permissions, and removal of the legacy JSON path. The declaration→runtime compiler reuses this writer so generated config never bypasses the canonical write path.
Types ¶
type BlobStore ¶
type BlobStore interface {
// Get retrieves the object. If not found, returns ErrObjectNotFound.
Get(ctx context.Context, key string) (data []byte, rev string, err error)
// Put uploads the object. baseRev is the expected current revision.
// If baseRev is CreateIfAbsentRevision, the object must not exist.
// If baseRev is not empty and doesn't match, returns ErrConflict.
// Returns the new revision string.
Put(ctx context.Context, key string, data []byte, baseRev string) (newRev string, err error)
// Stat retrieves the revision of the object. If not found, returns ErrObjectNotFound.
Stat(ctx context.Context, key string) (rev string, err error)
// Delete removes the object.
Delete(ctx context.Context, key string) error
}
BlobStore abstracts the underlying blind storage system (S3, File, etc.).
type CompileRequest ¶ added in v0.1.6
type CompileRequest struct {
Declaration SyncConfig
ResolvedSecret string // device-local secret ref resolved from credential identity
ResolvedEncrypt string // device-local encryption secret ref resolved from key identity
DeviceID string
Now time.Time
}
CompileRequest bundles the inputs the declaration→runtime compiler needs: the portable declaration, the device-local resolved secret references, the unique device id and a clock for receipts/marker timestamps.
type CompileResult ¶ added in v0.1.6
type CompileResult struct {
RuntimeConfig Config
SourceMarker SourceMarker
DeclarationDigest string
}
CompileResult reports what the compiler would write, without touching disk. Apply uses Apply(); plan/doctor read Result fields directly.
func Compile ¶ added in v0.1.6
func Compile(req CompileRequest) (CompileResult, error)
Compile produces the device runtime Config and source marker from a declaration and resolved secrets. It does NOT write to disk; callers use ApplyCompiled or inspect the result for plan output. The compiler reuses the existing Capsa config normalization so generated state is identical to a manually-logged-in device.
type ConditionalWriteCapability ¶
type ConditionalWriteCapability interface {
SupportsConditionalWrites() bool
}
ConditionalWriteCapability reports whether Put enforces baseRev preconditions durably.
type Config ¶
type Config struct {
SchemaVersion string `json:"schema_version" yaml:"schema_version"`
BackendKind string `json:"backend_kind,omitempty" yaml:"backend_kind,omitempty"`
Endpoint string `json:"endpoint,omitempty" yaml:"endpoint,omitempty"`
WorkspaceID string `json:"workspace_id" yaml:"workspace_id"`
DeviceID string `json:"device_id" yaml:"device_id"`
SecretRef string `json:"secret_ref,omitempty" yaml:"secret_ref,omitempty"`
EncryptionSecretRef string `json:"encryption_secret_ref,omitempty" yaml:"encryption_secret_ref,omitempty"`
S3 *S3Config `json:"s3,omitempty" yaml:"s3,omitempty"`
CreatedAt string `json:"created_at" yaml:"created_at"`
UpdatedAt string `json:"updated_at" yaml:"updated_at"`
}
type CryptoKey ¶
type CryptoKey struct {
KeyID string
// contains filtered or unexported fields
}
func DeriveKeyLegacy ¶ added in v0.2.0
DeriveKeyLegacy derives the pre-v2 key (static global salt, 100k iterations). It exists so envelopes written before the v2 derivation remain readable.
func DeriveKeyV2 ¶ added in v0.2.0
DeriveKeyV2 derives the active v2 key: 600k PBKDF2-SHA256 iterations over the resolved secret and a salt derived from the secret itself. All devices sharing the secret derive the same key; distinct secrets get distinct salts, so one precomputed table cannot serve multiple users.
type CryptoKeys ¶ added in v0.2.0
CryptoKeys is the decryption keychain: the active write key plus legacy read keys. Envelopes are decrypted with the key whose KeyID matches the envelope, so a vault migrated to the v2 derivation keeps reading blobs that were written under the legacy derivation until they are re-pushed.
func DeriveKeychain ¶ added in v0.2.0
func DeriveKeychain(secretRef string) (CryptoKeys, error)
DeriveKeychain derives the active v2 key plus the legacy fallback in one pass.
type DeviceSession ¶
type DoctorResult ¶
type DoctorResult struct {
Configured bool `json:"configured"`
Status string `json:"status"`
Code string `json:"code,omitempty"`
Message string `json:"message"`
BackendKind string `json:"backend_kind,omitempty"`
AuthBoundary string `json:"auth_boundary,omitempty"`
ServerAudit bool `json:"server_audit"`
Endpoint string `json:"endpoint,omitempty"`
Workspace string `json:"workspace_id,omitempty"`
DeviceID string `json:"device_id,omitempty"`
}
func Doctor ¶
func Doctor(root string) DoctorResult
type DotenvError ¶ added in v0.1.6
DotenvError reports a strict dotenv parse failure with a stable English code and line number. Key is included only when known; the rejected value is never attached so errors stay safe to log and surface in receipts.
func (*DotenvError) Error ¶ added in v0.1.6
func (e *DotenvError) Error() string
type DriftItem ¶ added in v0.1.6
type DriftItem struct {
Field string `json:"field" yaml:"field"`
Reason string `json:"reason" yaml:"reason"`
}
DriftItem is one redacted divergence between declaration and runtime state.
type DriftReport ¶ added in v0.1.6
type DriftReport struct {
InDrift bool `json:"in_drift" yaml:"in_drift"`
Reasons []DriftItem `json:"reasons,omitempty" yaml:"reasons,omitempty"`
}
DriftReport describes the difference between a declaration and the local generated runtime config. Field is a redacted path; Value is never included.
func DetectDrift ¶ added in v0.1.6
func DetectDrift(declaration SyncConfig, runtime Config, marker SourceMarker) DriftReport
DetectDrift compares a declaration against the loaded runtime state and the recorded source marker. It returns redacted field names only.
type EncryptedEnvelope ¶
EncryptedEnvelope is the syncwire envelope; remote aliases it so the encryption helpers and the transport layer share one wire schema.
func EncryptBlob ¶
func EncryptBlob(key CryptoKey, plaintext, aad []byte) (EncryptedEnvelope, error)
func EncryptManifest ¶
func EncryptManifest(key CryptoKey, manifest Manifest) (EncryptedEnvelope, error)
type EnvAsset ¶ added in v0.1.6
type EnvAsset struct {
SchemaVersion string `json:"schema_version" yaml:"schema_version"`
Provider string `json:"provider" yaml:"provider"`
// Ciphertext is provider-specific opaque encrypted material for the whole
// dotenv document (not per-key). Keeping one ciphertext preserves key
// ordering and avoids leaking key names into the asset metadata.
Ciphertext string `json:"ciphertext" yaml:"ciphertext"`
// Digest is a short stable hex digest of the plaintext document, used for
// daemon reload identity checks. It does NOT reveal plaintext contents.
Digest string `json:"digest,omitempty" yaml:"digest,omitempty"`
// KeyNames is the redacted list of declared keys (no values) so list/doctor
// can report what is present without unlocking.
KeyNames []string `json:"key_names,omitempty" yaml:"key_names,omitempty"`
}
EnvAsset is the repository-tracked encrypted dotenv asset. The schema carries ciphertext and redacted metadata only; plaintext key/value pairs live only in the in-memory EnvSnapshot. The asset is safe to commit, while plaintext env files are Git-ignored and Capsa-protected.
func LoadEnvAsset ¶ added in v0.1.6
LoadEnvAsset reads the encrypted dotenv asset. A missing file is reported via ErrEnvAssetMissing so init/doctor can distinguish absent from corrupt.
type EnvAssetError ¶ added in v0.1.6
EnvAssetError is the stable error type for env asset operations. Code is a stable English identifier; Message never carries plaintext values.
func (*EnvAssetError) Error ¶ added in v0.1.6
func (e *EnvAssetError) Error() string
type EnvReloader ¶ added in v0.1.6
type EnvReloader struct {
// contains filtered or unexported fields
}
EnvReloader is the daemon-side reload coordinator. It checks the encrypted env asset identity (content digest) between sync runs; on change it unlocks and parses a new snapshot, then atomically swaps it in for the NEXT run. The current run retains its original snapshot. On failure it keeps the last successful snapshot and reports a structured degraded code.
func NewEnvReloader ¶ added in v0.1.6
func NewEnvReloader(root string, provider UnlockProvider) *EnvReloader
NewEnvReloader creates a reloader. The initial snapshot is loaded lazily on the first RunSnapshot call so a missing asset does not block daemon startup.
func (*EnvReloader) CurrentDigest ¶ added in v0.1.6
func (r *EnvReloader) CurrentDigest() string
CurrentDigest returns the digest of the currently-active snapshot (for doctor).
func (*EnvReloader) DegradedCode ¶ added in v0.1.6
func (r *EnvReloader) DegradedCode() string
DegradedCode returns the last reload error code ("" when healthy).
func (*EnvReloader) RunSnapshot ¶ added in v0.1.6
func (r *EnvReloader) RunSnapshot() (*EnvSnapshot, ReloadStatus)
type EnvSnapshot ¶ added in v0.1.6
type EnvSnapshot struct {
// contains filtered or unexported fields
}
EnvSnapshot is the immutable, in-memory decrypted dotenv view used at a command or sync-run boundary. It is frozen for the lifetime of one run: the daemon may reload a NEW snapshot between runs but a single run always sees the same values. Plaintext lives only here and is never persisted, logged or passed whole to child processes.
func NewEnvSnapshot ¶ added in v0.1.6
func NewEnvSnapshot(values map[string]string, digest, source string) *EnvSnapshot
NewEnvSnapshot wraps an unlocked value map into an immutable snapshot. The caller must not mutate values after hand-off.
func ResolveEnvSnapshot ¶ added in v0.1.6
func ResolveEnvSnapshot(root string, provider UnlockProvider) (*EnvSnapshot, error)
ResolveEnvSnapshot unlocks and parses the encrypted dotenv asset into an immutable snapshot. It returns (nil, ErrEnvAssetMissing) when no asset exists so callers can distinguish "not configured" from "failed to unlock".
func (*EnvSnapshot) AllKeys ¶ added in v0.1.6
func (s *EnvSnapshot) AllKeys() []string
AllKeys returns the sorted list of declared keys (no values) for redacted reporting in list/doctor output.
func (*EnvSnapshot) ApplyAllowlist ¶ added in v0.1.6
func (s *EnvSnapshot) ApplyAllowlist(allowlist []string) map[string]string
ApplyAllowlist returns a NEW environment map suitable for a child process: only the keys in the allowlist are copied from the snapshot. The full decrypted environment is never handed to a subprocess.
func (*EnvSnapshot) Digest ¶ added in v0.1.6
func (s *EnvSnapshot) Digest() string
Digest returns the plaintext document digest used for reload identity checks.
func (*EnvSnapshot) IsEmpty ¶ added in v0.1.6
func (s *EnvSnapshot) IsEmpty() bool
IsEmpty reports whether the snapshot carries no values.
func (*EnvSnapshot) Lookup ¶ added in v0.1.6
func (s *EnvSnapshot) Lookup(key string) (string, bool)
Lookup returns the value for a key and whether it was present. It is the typed-secret read path — callers must NOT dump the whole snapshot into a child process environment.
func (*EnvSnapshot) OverlayEnv ¶ added in v0.1.6
func (s *EnvSnapshot) OverlayEnv(base []string, allowlist []string) []string
OverlayEnv returns a child-process environment built from a base environment plus the allowlisted snapshot values, respecting the precedence contract:
explicit process environment > decrypted env snapshot
Explicit flags are applied by callers BEFORE this function (they choose the base). The snapshot only fills keys that are not already set in base and are on the allowlist.
func (*EnvSnapshot) Source ¶ added in v0.1.6
func (s *EnvSnapshot) Source() string
Source returns a redacted source description (e.g. "pinax-sync.env.age").
type EnvUnlockProvider ¶ added in v0.1.6
type EnvUnlockProvider struct{}
EnvUnlockProvider resolves secret values from environment variables referenced by identity (e.g. identity "personal-sync-key" resolves from PINAX_SYNC_SECRET_PERSONAL_SYNC_KEY). It keeps the no-plaintext-in-repo invariant while supporting ephemeral CI bootstrap without a keychain.
func (EnvUnlockProvider) Lock ¶ added in v0.1.6
func (p EnvUnlockProvider) Lock(name, plaintext, identity string, kind SecretEntryKind) (SecretEntry, error)
func (EnvUnlockProvider) Name ¶ added in v0.1.6
func (EnvUnlockProvider) Name() string
func (EnvUnlockProvider) Unlock ¶ added in v0.1.6
func (p EnvUnlockProvider) Unlock(env SecretEnvelope) (map[string]string, error)
type ExtendedBlobStore ¶
type ExtendedBlobStore interface {
BlobStore
List(ctx context.Context, prefix string) ([]ObjectInfo, error)
Exists(ctx context.Context, key string) (bool, error)
BatchStat(ctx context.Context, keys []string) (map[string]string, error)
}
ExtendedBlobStore extends BlobStore with list and batch operations.
type FakeUnlockProvider ¶ added in v0.1.6
FakeUnlockProvider is a deterministic AES-GCM provider keyed by a passphrase resolved from PINAX_SYNC_FAKE_KEY (or a fixed test key). It exists so the declaration layer is testable end-to-end without an age/keychain dependency. Production deployments must register a reviewed provider; doctor flags fake.
func (FakeUnlockProvider) Lock ¶ added in v0.1.6
func (p FakeUnlockProvider) Lock(name, plaintext, identity string, kind SecretEntryKind) (SecretEntry, error)
func (FakeUnlockProvider) Name ¶ added in v0.1.6
func (p FakeUnlockProvider) Name() string
func (FakeUnlockProvider) Unlock ¶ added in v0.1.6
func (p FakeUnlockProvider) Unlock(env SecretEnvelope) (map[string]string, error)
type FileBackend ¶
type FileBackend struct {
// contains filtered or unexported fields
}
func NewFileBackend ¶
func NewFileBackend(baseDir string) (*FileBackend, error)
func (*FileBackend) List ¶
func (b *FileBackend) List(ctx context.Context, prefix string) ([]ObjectInfo, error)
List returns objects under the given prefix.
func (*FileBackend) SupportsConditionalWrites ¶
func (b *FileBackend) SupportsConditionalWrites() bool
type LoginRequest ¶
type Manifest ¶
Wire types are owned by internal/syncwire; these aliases keep the existing remote.* identifiers working while guaranteeing a single on-wire schema.
func BuildManifest ¶
func BuildManifestV2 ¶ added in v0.1.6
func BuildManifestV2(root, deviceID string, identities map[string]ManifestIdentity) (Manifest, error)
func DecryptManifest ¶
func DecryptManifest(keys CryptoKeys, envelope EncryptedEnvelope) (Manifest, error)
type ManifestDelete ¶ added in v0.1.3
type ManifestDelete = syncwire.ManifestDelete
Wire types are owned by internal/syncwire; these aliases keep the existing remote.* identifiers working while guaranteeing a single on-wire schema.
type ManifestEntry ¶
type ManifestEntry = syncwire.ManifestEntry
Wire types are owned by internal/syncwire; these aliases keep the existing remote.* identifiers working while guaranteeing a single on-wire schema.
type ManifestFileTooLargeError ¶ added in v0.1.3
func (*ManifestFileTooLargeError) Error ¶ added in v0.1.3
func (e *ManifestFileTooLargeError) Error() string
type ManifestIdentity ¶ added in v0.1.6
type ManifestUnsafePathError ¶ added in v0.1.3
type ManifestUnsafePathError struct{ Path string }
func (*ManifestUnsafePathError) Error ¶ added in v0.1.3
func (e *ManifestUnsafePathError) Error() string
type ObjectInfo ¶
ObjectInfo describes a remote object.
type RcloneBackend ¶
type RcloneBackend struct {
// contains filtered or unexported fields
}
func NewRcloneBackend ¶
func NewRcloneBackend(endpoint string) (*RcloneBackend, error)
func (*RcloneBackend) Delete ¶
func (b *RcloneBackend) Delete(ctx context.Context, key string) error
func (*RcloneBackend) List ¶
func (b *RcloneBackend) List(ctx context.Context, prefix string) ([]ObjectInfo, error)
func (*RcloneBackend) SupportsConditionalWrites ¶
func (b *RcloneBackend) SupportsConditionalWrites() bool
type ReloadStatus ¶ added in v0.1.6
RunSnapshot returns the snapshot a sync run should use, reloading first if the encrypted asset changed since the last check. The returned snapshot is frozen for the run even if a subsequent reload succeeds. When no asset exists it returns (nil, nil). When reload fails it returns the last successful snapshot (which may be nil) and a structured ReloadStatus describing the failure.
type S3Backend ¶
type S3Backend struct {
// contains filtered or unexported fields
}
func NewS3BackendWithOptions ¶
func (*S3Backend) SupportsConditionalWrites ¶
type S3BackendOptions ¶
type S3BackendOptions struct {
EndpointURL string
Region string
Profile string
PathStyle bool
PathMode string
API string
// CredentialsProvider, when non-nil, is the explicit AWS SDK credentials
// provider used to authenticate S3 requests (for example, a StaticCredentialsProvider
// built from a repository-encrypted bundle). When nil, the SDK shared
// profile / default credential chain is used (existing device-profile behavior).
CredentialsProvider aws.CredentialsProvider
}
type S3Config ¶
type S3Config struct {
Bucket string `json:"bucket" yaml:"bucket"`
Prefix string `json:"prefix,omitempty" yaml:"prefix,omitempty"`
Endpoint string `json:"endpoint,omitempty" yaml:"endpoint,omitempty"`
Region string `json:"region,omitempty" yaml:"region,omitempty"`
Profile string `json:"profile,omitempty" yaml:"profile,omitempty"`
AddressingStyle string `json:"addressing_style,omitempty" yaml:"addressing_style,omitempty"`
PathStyle bool `json:"path_style,omitempty" yaml:"path_style,omitempty"`
// CredentialMode controls how S3 credentials resolve. "device-profile"
// (default) keeps the existing behavior: endpoint/profile or the AWS default
// credential chain. "repository-encrypted" requires resolving a typed
// credential bundle from the repository envelope; it must NOT silently fall
// back to a device-local AWS profile.
CredentialMode string `json:"credential_mode,omitempty" yaml:"credential_mode,omitempty"`
}
type S3Credentials ¶ added in v0.1.9
type S3Credentials struct {
AccessKeyID string `json:"access_key_id"`
SecretAccessKey string `json:"secret_access_key"`
SessionToken string `json:"session_token,omitempty"`
}
S3Credentials is the decrypted logical structure of one S3/COS static credential bundle. The session token is optional (used for STS-derived temporary credentials); access key id and secret access key are required.
func ParseS3Credentials ¶ added in v0.1.9
func ParseS3Credentials(payload []byte) (S3Credentials, error)
ParseS3Credentials decodes a strict s3_credentials.v1 payload. It rejects malformed JSON, duplicate keys, unknown fields and missing required fields. Errors reference field NAMES only; they never include the supplied value, control characters or the raw payload.
type SecretEntry ¶ added in v0.1.6
type SecretEntry struct {
Identity string `json:"identity" yaml:"identity"`
Ciphertext string `json:"ciphertext" yaml:"ciphertext"`
}
SecretEntry is one logical identity's encrypted material. Identity is the stable logical key id; ciphertext is provider-specific and opaque.
type SecretEntryKind ¶ added in v0.1.6
type SecretEntryKind string
SecretEntryKind classifies a stored secret so the unlock path and doctor can distinguish credential vs encryption-key material. Stored as opaque metadata in the envelope; unknown kinds degrade rather than fail-open.
const ( SecretKindCredential SecretEntryKind = "credential" SecretKindEncryptionKey SecretEntryKind = "encryption_key" )
type SecretEnvelope ¶ added in v0.1.6
type SecretEnvelope struct {
SchemaVersion string `json:"schema_version" yaml:"schema_version"`
Provider string `json:"provider" yaml:"provider"`
Secrets map[string]SecretEntry `json:"secrets,omitempty" yaml:"secrets,omitempty"`
}
SecretEnvelope is the repository-tracked encrypted secrets asset (pinax-sync.secrets.yaml). Plaintext values are available only during an authenticated runtime unlock; the ciphertext is safe to commit.
func LoadSecretEnvelope ¶ added in v0.1.6
func LoadSecretEnvelope(root string) (SecretEnvelope, error)
LoadSecretEnvelope reads the secrets asset. A missing file is reported via ErrSecretsAssetMissing so init/doctor can distinguish absent from corrupt.
type SecretEnvelopeError ¶ added in v0.1.6
SecretEnvelopeError is the stable error type for envelope operations.
func (*SecretEnvelopeError) Error ¶ added in v0.1.6
func (e *SecretEnvelopeError) Error() string
type SecretMetadata ¶ added in v0.1.6
type SecretMetadata struct {
Name string `json:"name" yaml:"name"`
Kind string `json:"kind" yaml:"kind"`
Identity string `json:"identity" yaml:"identity"`
Provider string `json:"provider" yaml:"provider"`
}
SecretMetadata is the redacted, safe-to-show view of an entry.
type SourceMarker ¶ added in v0.1.6
type SourceMarker struct {
SchemaVersion string `json:"schema_version" yaml:"schema_version"`
DeclarationDigest string `json:"declaration_digest,omitempty" yaml:"declaration_digest,omitempty"`
GeneratedAt string `json:"generated_at" yaml:"generated_at"`
DeviceID string `json:"device_id" yaml:"device_id"`
}
SourceMarker is the device-local record that a runtime config was generated from a declaration. It stores the declaration digest so doctor can detect drift without re-reading the (possibly absent) original declaration.
func LoadSourceMarker ¶ added in v0.1.6
func LoadSourceMarker(root string) (SourceMarker, error)
LoadSourceMarker reads the device-local source marker. Missing marker is not an error — it means the runtime config predates the declaration layer.
type State ¶
type State struct {
Config Config `json:"config"`
Session DeviceSession `json:"session"`
}
func (State) GetStoreWithCredentialProvider ¶ added in v0.1.9
func (s State) GetStoreWithCredentialProvider(ctx context.Context, provider aws.CredentialsProvider) (BlobStore, error)
GetStoreWithCredentialProvider returns the object store for the state's backend, injecting an explicit AWS SDK credentials provider when supplied. This is the repository-encrypted path: the S3 backend is constructed from the runtime S3 config (not the endpoint query string) so a resolved bundle can be injected without going through the device-local profile chain. When provider is nil or the backend is not S3-direct, it falls back to GetStore.
type StoreFactory ¶
StoreFactory is a function signature for building a BlobStore.
type SyncBackend ¶ added in v0.1.6
type SyncBackend struct {
Kind string `json:"kind" yaml:"kind"` // s3-direct, rclone-direct, server, embedded
Endpoint string `json:"endpoint,omitempty" yaml:"endpoint,omitempty"`
S3 *S3Config `json:"s3,omitempty" yaml:"s3,omitempty"`
}
SyncBackend describes the portable transport topology. Endpoint and S3 are topology only; provider credential values resolve at device-local unlock.
type SyncConfig ¶ added in v0.1.6
type SyncConfig struct {
SchemaVersion string `json:"schema_version" yaml:"schema_version"`
Backend SyncBackend `json:"backend" yaml:"backend"`
Workspace SyncWorkspace `json:"workspace" yaml:"workspace"`
Secrets SyncSecretRefs `json:"secrets" yaml:"secrets"`
Policy SyncPolicy `json:"policy,omitempty" yaml:"policy,omitempty"`
// Requires declares additive capabilities a binary MUST support before
// touching the remote (pinax-passphrase-s3-bootstrap task 6.8). A binary
// missing a declared capability fails closed with sync_capability_unsupported
// rather than silently producing a partial backup. The set is validated by
// the capability gate, not by Validate (unknown capabilities are rejected at
// gate time so an upgraded declaration does not break older validation).
Requires SyncRequires `json:"requires,omitempty" yaml:"requires,omitempty"`
}
SyncConfig is the versioned, portable repository sync declaration. It holds backend topology, logical credential/encryption identities and sync policy, but never plaintext credentials, tokens, absolute device paths or device runtime state.
func LoadSyncConfig ¶ added in v0.1.6
func LoadSyncConfig(root string) (SyncConfig, error)
LoadSyncConfig reads and validates the repository declaration. A missing file is reported via ErrSyncDeclarationMissing so callers can distinguish "not initialized" from "corrupt".
func (SyncConfig) EffectiveNamespace ¶ added in v0.1.6
func (c SyncConfig) EffectiveNamespace() string
EffectiveNamespace derives the deterministic remote namespace prefix from the stable workspace fields. It does NOT replace server-side authorization; it only makes the on-wire object layout collision-resistant.
func (SyncConfig) Normalized ¶ added in v0.1.6
func (c SyncConfig) Normalized() SyncConfig
Normalized returns a copy with trimmed/derived fields, suitable for stable comparison (drift detection) and compilation.
func (SyncConfig) Validate ¶ added in v0.1.6
func (c SyncConfig) Validate() error
Validate enforces the declaration contract: schema version, supported backend, required workspace/encryption identity and absence of plaintext-sensitive or absolute-path fields. It returns stable English error codes for every failure.
type SyncConfigError ¶ added in v0.1.6
SyncConfigError is the stable validation error returned by Validate. Code is a stable English identifier; Field is the redacted field path (never the offending value).
func (*SyncConfigError) Error ¶ added in v0.1.6
func (e *SyncConfigError) Error() string
type SyncPolicy ¶ added in v0.1.6
type SyncPolicy struct {
// RemoteDeletePolicy controls whether local deletions may propagate to the
// remote. "deny" (default) refuses; "require-approval" needs explicit --yes.
RemoteDeletePolicy string `json:"remote_delete_policy,omitempty" yaml:"remote_delete_policy,omitempty"`
// NewDeviceMode defaults to "pull-only": a device with no local sync receipt
// must not upload local deletions or replace remote state on first bootstrap.
NewDeviceMode string `json:"new_device_mode,omitempty" yaml:"new_device_mode,omitempty"`
}
SyncPolicy captures approval-gated, safety-critical knobs. The default is the most conservative posture so an accidental apply cannot widen the blast radius.
type SyncRequires ¶ added in v0.2.0
type SyncRequires struct {
Capabilities []string `json:"capabilities,omitempty" yaml:"capabilities,omitempty"`
}
SyncRequires carries additive capability requirements. Capability names are stable identifiers (e.g. repository-encrypted-s3-v1); they never carry secrets.
type SyncSecretRefs ¶ added in v0.1.6
type SyncSecretRefs struct {
CredentialID string `json:"credential_id,omitempty" yaml:"credential_id,omitempty"`
EncryptionKeyID string `json:"encryption_key_id" yaml:"encryption_key_id"`
}
SyncSecretRefs holds logical credential and encryption key identities. Each device resolves these to its local profile, keychain or secret manager; raw values never enter the declaration.
type SyncWorkspace ¶ added in v0.1.6
type SyncWorkspace struct {
TenantID string `json:"tenant_id,omitempty" yaml:"tenant_id,omitempty"`
AppID string `json:"app_id,omitempty" yaml:"app_id,omitempty"`
WorkspaceID string `json:"workspace_id" yaml:"workspace_id"`
}
SyncWorkspace carries stable namespace fields. The effective remote namespace is derived deterministically; full multi-tenant authorization is a separate server capability and is NOT provided by direct transport.
type UnlockProvider ¶ added in v0.1.6
type UnlockProvider interface {
// Name is the stable provider identifier written into the envelope.
Name() string
// Unlock decrypts the envelope into plaintext values. Plaintext MUST NOT
// escape the caller; it lives only in the unlocking runtime.
Unlock(env SecretEnvelope) (map[string]string, error)
// Lock encrypts a single plaintext value under the given identity, returning
// an entry suitable for SecretEnvelope.Secrets.
Lock(name string, plaintext string, identity string, kind SecretEntryKind) (SecretEntry, error)
}
UnlockProvider resolves a SecretEnvelope to plaintext name→value pairs using a device-local identity. Implementations must fail-closed on any missing or wrong identity and never log plaintext. The interface is provider-neutral so the first version can ship a deterministic fake + env provider and later swap in age/keychain without changing the CLI contract.
func ResolveUnlockProvider ¶ added in v0.1.6
func ResolveUnlockProvider(providerName string) (UnlockProvider, error)
ResolveUnlockProvider selects the provider for an envelope. Unknown providers fail-closed; the caller surfaces a runnable recovery command.