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
- Variables
- func AbsPath(path string) (string, error)
- func AcceptNamespace(scope, namespace string) error
- func CacheScope(remote, base string) string
- func CheckPin(stored Pin, have bool, obsRevision int, obsMasterPub string) (advance bool, err error)
- func DefaultVaultDir(name string) (string, error)
- func Dir() (string, error)
- func Exists() bool
- func ForgetScope(scope string) error
- func MachineID() (string, error)
- func NamespaceAccepted(scope, namespace string) (bool, error)
- func NextSeq(scope, namespace string, floor int) (int, error)
- func Path() (string, error)
- func ReadOnlyEnv() bool
- func RemoveStorage(name string) (existed bool, err error)
- func ScopeVault(scope string) (vaultID string, bound bool, err error)
- func SetDefault(name string) error
- func UpsertStorage(name string, entry StorageEntry, makeDefault bool) (string, error)
- func ValidStorageName(name string) bool
- func WriteLocalBinding(dir string, b LocalBinding) (string, error)
- func WritePin(scope, vaultID string, p Pin) error
- type Effective
- type LocalBinding
- type NamespaceDecision
- type Pin
- type StorageEntry
- type User
Constants ¶
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) )
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 ¶
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 AcceptNamespace ¶ added in v0.11.0
AcceptNamespace records the acceptance of a namespace at a storage scope.
func CacheScope ¶
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
DefaultVaultDir is where a named local vault lives by default: the platform's data directory, never a repository.
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
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
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
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
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 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
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
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
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
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.
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 ¶
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
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
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
Local reports whether the storage is a local vault directory.
func (Effective) Scope ¶ added in v0.10.0
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
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.
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 ¶
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
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
StorageNames returns the configured storage names, sorted.