config

package
v0.17.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 14, 2026 License: Apache-2.0 Imports: 16 Imported by: 0

Documentation

Overview

Package config loads the user-global config (~/.config/notenv/config.toml, not committed) and merges it with the project contract into an effective configuration. A machine may define several named storages; the storage target is machine-only, and the contract contributes just the namespace.

Index

Constants

View Source
const (
	DefaultBase         = "notenv"
	DefaultStorage      = "default" // name of the storage setup creates first
	ModePass            = "passphrase"
	DefaultCacheTTL     = time.Hour // master-key keyring cache
	DefaultBlobCacheTTL = time.Hour // local ciphertext cache (matches key cache; --refresh forces fresh)
)
View Source
const LocalBindingFile = "notenv.local.toml"

LocalBindingFile is the per-checkout, git-ignored file that binds a project to a named storage and pins its namespace. It lives beside the contract and is never committed.

Variables

View Source
var ErrMasterChanged = errors.New("the vault's master key changed unexpectedly: a legitimate rotation on another machine, or a substitution attack. If you have confirmed it is legitimate, run `notenv key trust`; otherwise treat the storage as compromised")

ErrMasterChanged reports that an observed header wraps a different master than the pinned one. The caller distinguishes it from other pin failures because it has a second chance: a chain of signed transitions from the pinned master can prove the change legitimate before the alarm stands.

Functions

func AbsPath added in v0.10.0

func AbsPath(path string) (string, error)

AbsPath expands a leading "~" and makes the path absolute.

func AcceptNamespace added in v0.11.0

func AcceptNamespace(scope, namespace string) error

AcceptNamespace records the acceptance of a namespace at a storage scope.

func CacheScope

func CacheScope(remote, base string) string

CacheScope is the keyring cache key for a storage base. Length-prefixed on the remote so (remote, base) pairs can't alias: a plain "remote:base" join makes ("r","a:b") and ("r:a","b") collide.

func CheckPin added in v0.2.0

func CheckPin(stored Pin, have bool, obsRevision int, obsMasterPub string) (advance bool, err error)

CheckPin compares an observed header (revision, master public key) against the stored pin. It returns advance=true when the pin should move forward (or on first contact), or an actionable error: ErrMasterChanged for an unexpected master (the caller may try signed transitions before alarming), or a rollback error for an older revision.

func DefaultVaultDir added in v0.10.0

func DefaultVaultDir(name string) (string, error)

DefaultVaultDir is where a named local vault lives by default: the platform's data directory, never a repository.

func Dir

func Dir() (string, error)

Dir returns the user config directory, honoring XDG_CONFIG_HOME.

func Exists

func Exists() bool

Exists reports whether a user config file is present (the "is this machine set up" check).

func ForgetScope added in v0.7.0

func ForgetScope(scope string) error

ForgetScope removes a scope's binding, its vault's pin (`notenv key forget`, after a deliberate vault reset), and the namespaces accepted there. The pin survives if another scope still references the vault (the same vault reachable through two storage configurations). Forgetting an unbound scope still drops its namespace acceptances.

func MachineID added in v0.3.0

func MachineID() (string, error)

MachineID returns this machine's stable identifier, creating it on first use. It names and orders the segments this machine writes, so two machines never produce the same segment. It is random, not secret, and lives in local state.

func NamespaceAccepted added in v0.11.0

func NamespaceAccepted(scope, namespace string) (bool, error)

NamespaceAccepted reports whether this user has explicitly accepted addressing a namespace at a storage scope before (--namespace first use).

func NextSeq added in v0.3.0

func NextSeq(scope, namespace string, floor int) (int, error)

NextSeq returns the next strictly-increasing sequence number for (scope, namespace) on this machine, persisting the counter. It orders this machine's segments even when a freshly listed remote is briefly stale, so two of its writes never share a sequence number. The read-modify-write is locked, so two concurrent processes on the machine can't read the same counter and collide.

floor is the high-water this machine has already written to the namespace, as observed in the latest fold. seq.json is disposable per-machine state (a restore that drops it, a storage rename that re-scopes it), and a counter that reset below floor would reissue numbers storage has already folded, which a later fold flags as a replay. Catching up to floor before incrementing keeps every write above what is already recorded, so a lost counter is a non-event.

func Path

func Path() (string, error)

func ReadOnlyEnv added in v0.11.0

func ReadOnlyEnv() bool

ReadOnlyEnv reports whether NOTENV_READONLY marks this whole process read-only: the env-shaped sibling of a storage entry's read_only, for wrapping an agent without touching the machine config. Any value but "" and "0" counts.

func RemoveStorage added in v0.17.0

func RemoveStorage(name string) (existed bool, err error)

RemoveStorage deletes a named storage from the machine config. If it was the default, the default is reassigned to the sole remaining storage or cleared. Reports whether the storage existed; removing an absent one is not an error (idempotent teardown).

func ScopeVault added in v0.7.0

func ScopeVault(scope string) (vaultID string, bound bool, err error)

ScopeVault returns the vault ID previously seen at a storage scope (bound=false if this machine has never pinned anything there).

func SetDefault added in v0.2.0

func SetDefault(name string) error

SetDefault changes which storage is the default.

func UpsertStorage added in v0.2.0

func UpsertStorage(name string, entry StorageEntry, makeDefault bool) (string, error)

UpsertStorage adds or replaces a named storage and writes the config. The storage becomes the default if it is the first one, if no default is set, or if makeDefault is true. Returns the config path.

func ValidStorageName added in v0.2.0

func ValidStorageName(name string) bool

ValidStorageName reports whether name is usable as a storage name.

func WriteLocalBinding added in v0.2.0

func WriteLocalBinding(dir string, b LocalBinding) (string, error)

WriteLocalBinding records the binding for the project in dir and returns the file path. The caller is responsible for git-ignoring it.

func WritePin added in v0.2.0

func WritePin(scope, vaultID string, p Pin) error

WritePin records the pin for a vault and binds the scope it was seen at.

Types

type Effective

type Effective struct {
	StorageName  string // the resolved storage name
	Path         string // local vault directory (absolute); empty for remote storages
	Remote       string // rclone remote name; empty for local storages
	Base         string // path within the remote
	Versioned    bool   // remote retains versions on overwrite
	ReadOnly     bool   // policy: refuse mutating commands against this storage
	Namespace    string
	Mode         string        // crypto mode
	CacheTTL     time.Duration // master-key cache TTL; <= 0 disables caching
	BlobCacheTTL time.Duration // local ciphertext cache TTL; <= 0 disables
}

Effective is the merged result of the selected storage + contract.

func Resolve

func Resolve(u *User, f *contract.File, contractDir, storageName string) (Effective, error)

Resolve selects a storage (storageName empty means auto: default or sole) and combines it with the contract's namespace. Storage target is machine config only; the contract contributes the namespace (it cannot redirect where this machine reads/writes; see contract.Parse).

func ResolveNamespace added in v0.11.0

func ResolveNamespace(u *User, storageName, namespace string) (Effective, error)

ResolveNamespace is Resolve without a project: an explicitly named namespace (--namespace) combined with a selected storage. The vault is addressed directly: no contract, no checkout, no cwd.

func ResolveStorage added in v0.10.0

func ResolveStorage(u *User, explicit string) (Effective, error)

ResolveStorage selects and normalizes a storage without a project contract (storage-wide commands: the key family, vault copy).

func (Effective) Local added in v0.10.0

func (e Effective) Local() bool

Local reports whether the storage is a local vault directory.

func (Effective) Scope added in v0.10.0

func (e Effective) Scope() string

Scope returns the storage's local-state scope (key cache, pins, seq counters). Local storages scope on ":local" plus the absolute path: a ":" cannot appear in an rclone remote name, so a local scope can never collide with a remote's, however the remote is named.

type LocalBinding added in v0.5.0

type LocalBinding struct {
	Storage   string `toml:"storage"`
	Namespace string `toml:"namespace"`
}

LocalBinding is what a checkout has locally agreed to: which configured storage it uses (empty means "resolve from the machine config") and which namespace it reads. The namespace pin is a security boundary, not a convenience: the committed contract chooses the namespace, so without a local pin a cloned repository could silently point a checkout at any other project's secrets in the bound vault.

func ReadLocalBinding added in v0.2.0

func ReadLocalBinding(dir string) (LocalBinding, error)

ReadLocalBinding returns the project's local binding in dir. A missing file is a zero binding, not an error.

type NamespaceDecision added in v0.5.0

type NamespaceDecision int

NamespaceDecision is CheckNamespacePin's verdict on using a contract's namespace in a checkout.

const (
	// NamespaceOK: the checkout already pinned this namespace; proceed.
	NamespaceOK NamespaceDecision = iota
	// NamespacePin: first use and the namespace is just the directory's name
	// (the obvious default); pin it without ceremony.
	NamespacePin
	// NamespaceConfirm: first use of an explicitly chosen namespace; pinning it
	// is a decision the user should see (confirm interactively, warn in CI).
	NamespaceConfirm
)

func CheckNamespacePin added in v0.5.0

func CheckNamespacePin(b LocalBinding, resolved, derived string) (NamespaceDecision, error)

CheckNamespacePin decides whether a checkout may use the contract's resolved namespace. derived is the namespace the directory name would yield. A pinned checkout whose contract now names a different namespace is refused: the committed contract selects which secrets reach a child process, so changing it underneath an existing checkout is either an attack or a rename the user must explicitly re-accept (`notenv init` re-pins).

type Pin added in v0.2.0

type Pin struct {
	Revision  int    `json:"revision"`
	MasterPub string `json:"master_pub"`
	SignPub   string `json:"sign_pub"`
}

Pin is a per-vault rollback anchor: the highest header revision this machine has seen, plus the master's encryption and signing public keys it expects. It is not secret (the threat is storage write, not local read), so it lives in a plain local file.

Pins are keyed by the vault's own ID, not by where the vault happens to be stored, so trust survives relocating a vault to another remote or base. A separate scope → vault-ID binding records which vault each storage location held: without it, substituting a header with a freshly minted vault ID would sidestep the pin entirely (no pin under the new ID, trust on first use). A bound scope whose header claims a different vault ID (or no header at all) is therefore an alarm, never a fresh start.

func ReadPin added in v0.2.0

func ReadPin(vaultID string) (p Pin, have bool, err error)

ReadPin returns the stored pin for a vault (have=false if none).

type StorageEntry added in v0.2.0

type StorageEntry struct {
	// Path is a local vault directory (pure-Go backend, no rclone).
	Path   string `toml:"path"`
	Remote string `toml:"remote"`
	Base   string `toml:"base"`
	// Versioned: the remote retains old object versions on overwrite
	// (B2 does natively), so skip the ~3s server-side .prev backup copy.
	Versioned bool `toml:"versioned"`
	// ReadOnly refuses every mutating command against this storage. It is
	// policy, not crypto: it constrains cooperating clients (an honest agent
	// doing something destructive by accident), it does not contain
	// adversaries: anyone who can decrypt can forge writes with their own
	// tooling. Enforced read-only comes from the storage credential itself
	// (e.g. a read-only B2 application key behind the rclone remote).
	ReadOnly bool `toml:"read_only"`
	// CacheTTL bounds local ciphertext-cache lifetime for this storage
	// (Go duration; "0" disables). Default 1h.
	CacheTTL string `toml:"cache_ttl"`
}

StorageEntry is one named storage target: either a local vault directory (Path) or an rclone remote (Remote/Base/Versioned): the populated field is the storage's type, and exactly one of the two must be set.

type User

type User struct {
	Default string                  `toml:"default"`
	Storage map[string]StorageEntry `toml:"storage"`
	Crypto  struct {
		Mode string `toml:"mode"`
		// CacheTTL is how long the master-key cache may hold the key
		// (Go duration string; "0" disables caching). Default: 1h.
		CacheTTL string `toml:"cache_ttl"`
	} `toml:"crypto"`
}

User is the per-machine config. Storage targets are keyed by name so one machine can drive several vaults; Default names the one used when a project has no local binding.

func LoadUser

func LoadUser() (*User, error)

LoadUser reads the user config. A missing file is not an error: it returns a zero-value config (callers surface the "no storage" error later).

func (*User) MasterCacheTTL added in v0.2.0

func (u *User) MasterCacheTTL() (time.Duration, error)

MasterCacheTTL is the master-key cache lifetime (crypto.cache_ttl; default 1h, "0" disables caching).

func (*User) SelectStorage added in v0.2.0

func (u *User) SelectStorage(explicit string) (string, StorageEntry, error)

SelectStorage picks the storage to use, in precedence order: an explicit name (from --storage or a project's local binding) → the configured default → the sole storage when only one exists. It returns the resolved name and entry, or an actionable error.

func (*User) StorageNames added in v0.2.0

func (u *User) StorageNames() []string

StorageNames returns the configured storage names, sorted.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL