clusterconfig

package
v0.3.5 Latest Latest
Warning

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

Go to latest
Published: Nov 7, 2026 License: GPL-3.0 Imports: 23 Imported by: 0

README

Deployment enrollment and local drain control

Standalone SQLite and historical full-server startup are unchanged when ETHER_DEPLOYMENT_FILE is absent. Deployment mode uses PostgreSQL authority, including with an external database. A bundled singleton PostgreSQL is NOT DB HA.

ETHER_DEPLOYMENT_FILE names a mounted node-local JSON document (not a shared config section). Initial supported runtime sets are full-server (empty roles or all five roles) and transcode-only, subject to runtime composition acceptance. Other role sets remain rejected until their actual service graph is integrated.

Example document (all Secret values are separate files):

{
  "bootstrap": true,
  "roles": ["transcode"],
  "internalAddress": "https://ether-transcode-0.ether-private:32401",
  "privateListen": ":32401",
  "probeListen": ":32402",
  "databaseURLFile": "/run/ether-secrets/database-url",
  "clusterKeyFile": "/run/ether-secrets/cluster-key",
  "caFile": "/run/ether-issuer/ca.crt",
  "caKeyFile": "/run/ether-issuer/ca.key",
  "rootsFile": "/run/ether-issuer/roots.pem",
  "capacity": 2,
  "clockBoundKnown": true,
  "clockSkewMillis": 1000,
  "tempDir": "/scratch/transcode",
  "cacheDir": "/scratch/cache",
  "drainSeconds": 120
}

Private and probe ports default to 32401/32402. capacity (1..4096) is explicit; clock bound must be explicitly known and skew 0..60000ms. Deployment operators must maintain that pairwise bound between source, database and encoder clocks. resourceScope optionally names a shared physical encoder budget; otherwise the persistent node ID supplies the scope. Old boot claims retain capacity on that scope until authenticated retirement proof. Capacity is admission accounting, not proof that detected hardware can encode a requested format.

Startup holds the data-directory lock, opens/migrates PostgreSQL, takes a shared provisioning lock, then seeds or verifies shared settings. bootstrap:false requires existing shared settings. Multiple bootstrap pods with the SAME provisioned encryption key serialize and adopt the first committed identity. An existing cluster with a different key fails closed. Cluster key is exactly 32 RAW bytes, not base64 text (Kubernetes Secret data is already decoded on disk). Never put DB passwords, encryption keys, CA keys or join tokens in Helm values, command arguments or logs. File paths may be stored in deployment configuration. The deployment Secret database URL takes precedence over legacy URL environment overrides in deployment mode.

Every replica needs its own retained PVC/data directory. It generates and retains its own node.json; NEVER distribute or clone that file. Repeated startup with matching local key/config is idempotent, including after interruption between local writes or after shared seeding. Mismatched local keys and existing config without its key require explicit offline recovery; no automatic overwrite or adoption occurs. An existing configured standalone server requires explicit ether cluster init migration. Trusted provisioning does not consume join tokens: it can recover a blank replica after a legacy token was consumed before its local files were installed. Never reuse a single-use token across replicas.

The cluster encryption key and database credential already grant access to the trusted cluster state. Specialists are NOT a sandbox for untrusted machines. Issuer custody follows that trust model: a separately generated Ed25519 CA cert and PKCS8 private key live in a separately administered Secret, restricted to trusted Ether service accounts. Never derive signing keys from encryption keys. The issuer cert is directly trusted (no intermediate chain). Each process mints an ephemeral Ed25519 leaf for its EXISTING membership Incarnation and configured DNS/IP host, valid twelve hours, refreshed hourly. No extra TLS boot ID is minted.

For CA rotation first distribute an overlapping old+new explicit roots.pem, then swap issuer key/cert, then remove the old root after all members have rotated. Secret contents are reopened and checked on rotation; node policy and identities are immutable until restart. Invalid rotation fails closed and does not publish new capabilities. Encryption key rotation is separate, requires coordinated credential resealing/restart, and never happens by replacing local settings from arbitrary mounted bytes. DB password rotation requires application restart.

Probe listener contains only GET /livez, /startupz, /readyz, /admissionz. Liveness has no DB dependency. Startup means real compatible services started; readiness describes existing-work service availability. Admission additionally rejects a draining node. Private headless DNS may publish not-ready addresses; placement still requires verified current membership, exact boot, role, protocol and admission. Probes never carry media, provider credentials or drain controls.

ether cluster drain --data-dir /config --wait 120s (--timeout alias) is local control authenticated by the data-directory owner's filesystem permissions. It atomically writes a random operation and exact current boot to drain.request, then waits for matching drain.status with proven zero pending obligations. Files must be regular 0600 files; symlinks and group/world-accessible files are rejected. A stale PVC request/ack cannot drain or acknowledge a new boot. Timeout returns nonzero, retaining all unresolved ownership/capacity. No public HTTP drain route exists. Use explicit drain-before-delete and StatefulSet OnDelete for planned upgrades; preStop is only a bounded fallback, not a promise of seamless replacement. SIGTERM stops new admission and preserves private existing-media handlers, membership and lease reconciliation during drain. Source cleanup runs separately from heartbeat/expiry and may block in backend Close: a deadline/network silence never proves remote encoder or source retirement.

If SIGTERM reaches drainSeconds with unresolved source/encoder obligations, Ether reports the unresolved drain and KEEPS membership, private handlers and cleanup running until proof arrives or an external supervisor terminates the process. Arbitrary backend Close is not cancellable. CLI drain times out nonzero without stopping the runtime. Kubernetes terminationGracePeriod may ultimately kill the process; that does not retire its durable reservations. Planned upgrades must require a successful explicit drain before deletion. Set the external grace period above the intended drain window, allowing control and shutdown overhead.

The supported deployment topology uses full servers as frontend/source/delivery coordinators and transcode-only nodes as remote execution targets. Full servers keep their historical local encoder but are EXCLUDED from private transcode placement: local legacy sessions do not yet participate in the durable placement budget, so admitting private encodes there could double-book hardware. This does not disable their existing local capabilities. Transcode-only nodes use a separate fixed-plan encoder manager with adaptive ladders/governor/enhancement and automatic source mutation disabled. Unified local+private physical accounting is required before full servers can become remote encoding targets.

ledgerCapacity is distinct from concurrent encoder capacity and boot-local history retention. It defaults to 1000000, accepts 4096..10000000, and must cover concurrent/history capacity. Every deployment replica must use the SAME value. Placement migration 2 stores that fleet ceiling. Initial startup seeds or verifies it under the serialized placement lock. Every new-generation Reserve checks policy in the SAME transaction as allocation; conflicting coordinator settings fail closed. Existing assignment replay/renewal/retirement remain available on a stale coordinator so correcting policy never destroys ownership. Drain old pre-policy candidate binaries before introducing this policy; they predate this enforcement.

Changing JSON alone cannot silently raise the ceiling. The trusted operator runs ether cluster ledger-quota --data-dir /config --expect 1000000 --capacity 2000000, then rolls matching ledgerCapacity:2000000 settings across the fleet. This is an explicit monotonic compare-and-swap, never a decrease or table cleanup. Stale coordinators stop NEW allocations until their configured ceiling matches the new policy. /admissionz includes a ledger object with capacity/used/retired/exhausted/ matches; exhaustion returns 503 for new admissions and logs the quota transition, while /readyz and existing work remain available. A larger ceiling is bounded operational mitigation, not safe history compaction or unlimited admission. Retired generation floors remain permanent; never delete them to manufacture capacity.

Role boundary and public routing

The staged isolated graphs construct these concrete dependencies. Source, server and delivery singleton activation remains a separate acceptance guard; construction checks alone do not establish shipping playback or replacement continuity.

Graph Local services Public responsibilities
Historical full server Existing providers, source manager, frontends and local encoder Existing public APIs and representations
Source singleton Providers, resolver, cache, native/ffprobe metadata probing and source manager None; authenticated private source operations only
Transcode singleton Dedicated fixed-plan encoder and private input gateway None; authenticated private encoding only
Server singleton Catalogue, identity, owner console, protocol frontends and remote playback adapters Browse/control and existing protocol routes that combine admission or authentication with response bytes
Delivery singleton Identity/policy readers and indexed authenticated representation forwarding Bounded GET/HEAD representation routes only

Server isolation excludes local provider connections, source/cache ownership and encoder construction. It does not promise that the server sends no response bytes. Existing Plex start/start.mkv, direct /library/parts and query subtitle routes retain their URLs and control semantics, including track-selection PUT requests. Their authenticated proxy responses are legitimate frontend work. Silo's viewer/device-authenticated playback routes likewise stay on the API frontend until an explicit equivalent authenticated delivery seam exists.

Mixed-role deployments need same-origin routing to API (full/server) and media (full/delivery) Services. Route only the supported GET/HEAD pure representation namespaces to media: canonical /sessions/media/ and the typed Plex /video/:/transcode/universal/session/{id}/{rung-or-representation}/{resource}. The latter supports bounded playlists, headers, init and TS/M4S/MKV segments; ASS and query subtitle routes remain API. Never route the broad universal or /library/parts prefix to delivery. Jellyfin/Emby /jellyfin/sessions/media/ and /emby/sessions/media/ require the explicit protocol-bound alias adapter, preserving grant, query and original request policy. A generic prefix rewrite without that owner-side surface check is not equivalent. Full-only routing stays unchanged.

Independent source selection prefers currently eligible source-only peers before full-server peers. Once preparation has an operation identity, an uncertain result retains that exact owner and operation until proof; it never retries on another source merely because a connection failed.

The existing owner-authenticated GET /ether/api/cluster/ optionally includes selfRuntime for a deployment process only. nodeId and boot match the same response's captured self; runtimeSampledAt timestamps its local cached atomic state (protocol, started, readyExisting, acceptingNew, draining). quota.sampledAt retains the last successful quota observation time and quota.status its measured ledger status. A failed current quota read closes admission without refreshing the retained observation. Optional resources retains each metric's own at and state; these are resource sample times, not response times. Missing runtime/quota/resources are unknown, not measured zero. This is local process diagnostics, not fleet readiness, placement or retirement proof. Drain can leave readyExisting:true with acceptingNew:false.

The frozen real Plex writer inventory (internal/plex/representation_paths.md) confirms that public DASH uses the API start.mpd response plus typed session {numeric-representation}/header and {number}.m4s URLs. ASS uses the existing API query subtitle routes. Encoder filesystem manifest.mpd and subtitle ASS names are not authored session-namespace URLs. Do not expand the delivery grammar from those filenames. A canonical-HLS-only lab restriction records unproven mixed role playback/capability acceptance, not a demonstrated authored ASS/MPD URL gap.

Source-only nodes require the existing bundled/configured ffprobe for genuine native-incomplete formats (for example MPEG-4 Visual mp4v in MP4). The source manager retains native probing first and uses a probe-only fallback without an encoder, hardware inventory or transcode session manager. It accepts only the source manager's literal-loopback HTTP capability, preserves actual parsed facts, and admits at most two concurrent probes with a 20-second cancellation deadline including queue time and a shared 2 MiB output bound. Caller cancellation and shutdown stop the probe; its slot and service remain owned until the runner's child and output readers join. Close requests cancellation and joins, never substitutes elapsed time for completion. Errors omit capability URLs and raw ffprobe stderr. Genuine failed probes remain estimated and are still rejected by remote encoder preflight; no estimate or capability gate is relaxed.

New runtimes may include optional numeric pendingRuntime, pendingSessions and fixed pendingCategories in protected drain.status. These count outstanding barriers, not distinct playbacks: one preparation can contribute to a runtime barrier and the app session list independently. Categories cover source preparations/lifetimes/callbacks, encoder starts/tokens, inputs, remote preparation states, executions, reopens and placements. A runtime accounting error leaves pending:-1 and omits its unknown runtime/category fields; known app-session counts may remain. Missing fields on older binaries are unknown. The existing exact boot/operation, permissions and 4 KiB bound remain mandatory, and errors never acknowledge quiescence. This cannot retrofit diagnostics or terminal proof into an older running process.

Documentation

Overview

Package clusterconfig keeps the settings and identity material that the nodes of an Ether cluster share in the owner's PostgreSQL database:

  • the shared settings document (Shared, a config.SharedBackend),
  • sealed identity files such as the plex.direct certificate (Secrets),
  • one-time join tokens (jointoken.go),

and the bootstrap steps that turn a standalone server into the first cluster node (Init) and add further nodes (Join). Everything stored is sealed with the cluster's credential key, which travels only inside join tokens and never sits in the database in the clear.

The tables exist on SQLite too (migrations are dialect-neutral) but a standalone server never uses them.

Index

Constants

View Source
const DefaultJoinTokenTTL = time.Hour

DefaultJoinTokenTTL is how long a join token stays valid.

View Source
const DrainRequestFile = "drain.request"
View Source
const DrainStatusFile = "drain.status"
View Source
const JoinTokenPrefix = "ether-join-v1:"

JoinTokenPrefix starts every join token.

View Source
const TokenWarning = "" /* 374-byte string literal not displayed */

TokenWarning is shown with every new token.

Variables

View Source
var (
	// ErrTokenInvalid: the token is malformed or unknown to the cluster.
	ErrTokenInvalid = errors.New("clusterconfig: join token is not valid for this cluster")
	// ErrTokenUsed: the token was already used to join a node.
	ErrTokenUsed = errors.New("clusterconfig: join token has already been used")
	// ErrTokenExpired: the token's validity window has passed.
	ErrTokenExpired = errors.New("clusterconfig: join token has expired")
)
View Source
var ErrNodeControlConflict = errors.New("node control conflict")

Functions

func Attach

func Attach(ctx context.Context, cfg *config.Store, db *sql.DB, o config.SharedOptions) error

Attach switches cfg to the cluster's shared settings in db, seeding them from cfg when the database holds none (the first node of a cluster). It is the boot step between opening the database and building components; see config.Store.AttachShared for the options.

func CheckSetup added in v0.2.4

func CheckSetup(ctx context.Context, s SetupRequest) error

CheckSetup checks connectivity and eligibility without migrations, writes or consuming a join token. Apply repeats the authoritative checks after shutdown.

func ConsumeJoinToken

func ConsumeJoinToken(ctx context.Context, db *sql.DB, t JoinToken, usedBy string, now time.Time) error

ConsumeJoinToken marks the token used by usedBy, atomically: of any number of concurrent attempts exactly one succeeds. A used, expired or unknown token fails with ErrTokenUsed, ErrTokenExpired or ErrTokenInvalid.

func CreateJoinToken

func CreateJoinToken(ctx context.Context, db *sql.DB, databaseURL string, key []byte, ttl time.Duration, now time.Time) (JoinToken, TokenInfo, error)

CreateJoinToken stores a new one-time token (only its hash) valid for ttl and returns it. databaseURL is the URL the joining node will use and key the cluster credential key; both travel only inside the token.

func Handler

func Handler(o HandlerOptions) http.Handler

Handler is the owner API mounted at /ether/api/cluster:

GET    /settings             shared-settings status and join tokens
POST   /join-tokens          create a one-time join token {ttlMinutes?, databaseUrl?}
DELETE /join-tokens/{id}     revoke an unused token

The admin package authenticates the owner; token creation additionally requires setup to be complete, so the unauthenticated LAN access the wizard allows before then can never mint a token.

func Init

func Init(ctx context.Context, o InitOptions) error

Init turns a stopped standalone server into the first node of a cluster: it copies every table of the local SQLite database into the empty PostgreSQL database (a logical export/import, as backups use), seeds the shared settings from config.json and points config.json at PostgreSQL. It refuses a database that already holds data.

func NewToken

func NewToken(ctx context.Context, db *sql.DB, cfg *config.Store, databaseURL string, ttl time.Duration) (JoinToken, TokenInfo, error)

NewToken creates a join token for the cluster whose database is db, embedding databaseURL and cfg's credential key.

func Provision

func Provision(ctx context.Context, o ProvisionOptions) error

Provision is restartable after every local write and after DB initialization. Possession of the database credential AND credential key is the enrollment authority. It never consumes join tokens or replaces an existing identity/key.

func PruneExpiredJoinTokens

func PruneExpiredJoinTokens(ctx context.Context, db *sql.DB, now time.Time) error

PruneExpiredJoinTokens removes at most 256 expired records per owner read or creation. Backlogs drain across calls; expiry validation never depends on cleanup. A pruned credential remains invalid, though its historical expiry/used reason is no longer retained. No goroutine or periodic sweep is started.

func ReadSecretFile

func ReadSecretFile(path string, limit int64) ([]byte, error)

ReadSecretFile bounds input and deliberately omits filenames and bytes from errors. Each call reopens the path so Kubernetes projected-volume swaps work.

func RequestDrain

func RequestDrain(ctx context.Context, dir string) error

RequestDrain blocks until exact-operation quiescence or ctx expiry. It never treats stale status, node disappearance or a deadline as retirement proof.

func RevokeJoinToken

func RevokeJoinToken(ctx context.Context, db *sql.DB, id string) (bool, error)

RevokeJoinToken deletes an unused token; it reports whether one existed.

func SetupError added in v0.2.4

func SetupError(err error) string

SetupError never includes connection strings or join-token material.

func WriteDrainControl

func WriteDrainControl(dir, name string, d DrainControl) error

Types

type Deployment

type Deployment struct {
	EncoderRPCDeadlineMillis  int64            `json:"encoderRPCDeadlineMillis,omitempty"`
	EncoderFailureGraceMillis int64            `json:"encoderFailureGraceMillis,omitempty"`
	EncoderSegmentWaitMillis  int64            `json:"encoderSegmentWaitMillis,omitempty"`
	LedgerCapacity            int              `json:"ledgerCapacity,omitempty"`
	TempDir                   string           `json:"tempDir,omitempty"`
	CacheDir                  string           `json:"cacheDir,omitempty"`
	DrainSeconds              int              `json:"drainSeconds,omitempty"`
	Capacity                  int              `json:"capacity"`
	ClockSkewMillis           int64            `json:"clockSkewMillis"`
	ClockBoundKnown           bool             `json:"clockBoundKnown"`
	ResourceScope             string           `json:"resourceScope,omitempty"`
	Bootstrap                 bool             `json:"bootstrap"`
	Roles                     config.NodeRoles `json:"roles"`
	InternalAddress           string           `json:"internalAddress"`
	PrivateListen             string           `json:"privateListen"`
	ProbeListen               string           `json:"probeListen"`
	DatabaseURLFile           string           `json:"databaseURLFile"`
	ClusterKeyFile            string           `json:"clusterKeyFile"`
	CAFile                    string           `json:"caFile"`
	CAKeyFile                 string           `json:"caKeyFile"`
	RootsFile                 string           `json:"rootsFile"`
	CgroupV2Path              string           `json:"cgroupV2Path,omitempty"`
}

Deployment contains paths and node-local policy only. Secret CONTENTS must reside in separate mounted files. This document is never a shared setting.

func ReadDeployment

func ReadDeployment(path string) (Deployment, error)

func (Deployment) ProvisionOptions

func (d Deployment) ProvisionOptions(dataDir string) (ProvisionOptions, error)

type DrainControl

type DrainControl struct {
	Boot      string `json:"boot"`
	Operation string `json:"operation"`
	State     string `json:"state"`
	Pending   int    `json:"pending"`
	// Optional numeric barriers, not distinct playback counts. Missing is unknown.
	PendingRuntime    *int                    `json:"pendingRuntime,omitempty"`
	PendingSessions   *int                    `json:"pendingSessions,omitempty"`
	PendingCategories *DrainPendingCategories `json:"pendingCategories,omitempty"`
}

DrainControl is node-local. Boot and Operation must match the current process and request; an old PVC request/ack never controls a new process incarnation.

func ReadDrainControl

func ReadDrainControl(dir, name string) (DrainControl, error)

type DrainPendingCategories

type DrainPendingCategories struct {
	SourcePreparations   int `json:"sourcePreparations"`
	PreparationLifetimes int `json:"preparationLifetimes"`
	PreparationReceipts  int `json:"preparationReceipts"`
	SourceLifetimes      int `json:"sourceLifetimes"`
	SourceCallbacks      int `json:"sourceCallbacks"`
	EncoderStarts        int `json:"encoderStarts"`
	EncoderTokens        int `json:"encoderTokens"`
	Inputs               int `json:"inputs"`
	RemotePreparing      int `json:"remotePreparing"`
	RemoteLive           int `json:"remoteLive"`
	RemoteRetiring       int `json:"remoteRetiring"`
	Executions           int `json:"executions"`
	Reopens              int `json:"reopens"`
	Placements           int `json:"placements"`
}

DrainPendingCategories is a closed, identifier-free inventory of obligations. Each category may count a barrier for the same playback independently.

type HandlerOptions

type HandlerOptions struct {
	// DB is the cluster database; nil or SQLite means clustering is off.
	DB *sql.DB
	// Clustered reports whether this node runs against PostgreSQL.
	Clustered bool
	Config    *config.Store
	// DatabaseURL is the URL placed in new tokens (the node's own).
	DatabaseURL func() string
}

HandlerOptions configure the owner API.

type InitOptions

type InitOptions struct {
	// DataDir is the standalone server's data directory (config.json,
	// .credentials-key and ether.db).
	DataDir string
	// DatabaseURL is the empty PostgreSQL database the cluster will use.
	DatabaseURL string
	Logger      *slog.Logger
}

InitOptions configure Init.

type JoinOptions

type JoinOptions struct {
	// DataDir is the new node's data directory.
	DataDir string
	// Token is the join token string from an existing node.
	Token string
	// DatabaseURL overrides the URL in the token (e.g. the database is
	// reached under another name from this node).
	DatabaseURL string
	// Force moves an already configured server's settings aside.
	Force bool
	// NodeName is recorded as the token's user (default: host name).
	NodeName string
	Logger   *slog.Logger
	Now      func() time.Time
}

JoinOptions configure Join.

type JoinResult

type JoinResult struct {
	// MovedAside lists files renamed out of the way.
	MovedAside []string
	// MachineIdentifier is the cluster's server identity.
	MachineIdentifier string
}

JoinResult reports what Join did.

func Join

func Join(ctx context.Context, o JoinOptions) (JoinResult, error)

Join makes DataDir a node of the cluster the token belongs to: it checks the token's credential key against the cluster, consumes the token, then installs the key and writes a config.json holding node-local defaults (or this server's existing node-local settings) and the database URL. The node then boots with the cluster's shared settings.

type JoinToken

type JoinToken struct {
	DatabaseURL string `json:"d"`
	ID          string `json:"i"`
	Secret      string `json:"s"`
	Key         []byte `json:"k"`
}

JoinToken is what a join token string carries. It is a secret: it holds the database URL (with its password) and the cluster credential key.

func ParseJoinToken

func ParseJoinToken(s string) (JoinToken, error)

ParseJoinToken decodes a token string.

func (JoinToken) String

func (t JoinToken) String() string

String encodes the token as "ether-join-v1:" + base64url(JSON).

type NodeControlRecord

type NodeControlRecord struct {
	Version  int64
	Document []byte
}

NodeControlRecord contains only the typed, nonsecret nodecontrol document. Persistence belongs here; app wiring must not query these tables.

type NodeControls

type NodeControls struct {
	// contains filtered or unexported fields
}

func NewNodeControls

func NewNodeControls(db *sql.DB) *NodeControls

func (*NodeControls) CompareAndSwap

func (s *NodeControls) CompareAndSwap(ctx context.Context, node, boot string, version int64, doc []byte) error

CompareAndSwap orders requester writes and target observations. A DB revision is independent of the user-visible requested/applied command versions.

func (*NodeControls) Initialize

func (s *NodeControls) Initialize(ctx context.Context, node, boot string, doc []byte) error

func (*NodeControls) Load

func (s *NodeControls) Load(ctx context.Context, node, boot string) (NodeControlRecord, error)

func (*NodeControls) RequestMatches

func (s *NodeControls) RequestMatches(ctx context.Context, node, boot, id string, payload []byte) (bool, error)

RequestMatches permits retrying a completed request without running it again.

func (*NodeControls) Submit

func (s *NodeControls) Submit(ctx context.Context, node, boot, id string, payload, doc []byte, version int64) (bool, error)

Submit atomically deduplicates a request and replaces the current document. Reusing an ID for a different payload always fails, including after completion.

type ProvisionOptions

type ProvisionOptions struct {
	MigrationGate   store.MigrationGate
	TempDir         string
	CacheDir        string
	DataDir         string
	DatabaseURL     string
	Key             []byte
	Bootstrap       bool
	Roles           config.NodeRoles
	InternalAddress string
}

ProvisionOptions describes an explicitly trusted deployment. Key is the raw 32-byte cluster credential key supplied through a mounted Secret, not a token. The caller must exclusively lock DataDir for the complete operation.

type Sealer

type Sealer interface {
	Seal(name string, plain []byte) (string, error)
	Unseal(name, sealed string) ([]byte, error)
}

Sealer encrypts values with the cluster credential key (*config.Store).

type Secrets

type Secrets struct {
	// contains filtered or unexported fields
}

Secrets stores named, sealed blobs (identity files) shared by all nodes. Each name has a version that increases on every write.

func NewSecrets

func NewSecrets(db *sql.DB, sealer Sealer) *Secrets

NewSecrets returns a blob store sealing values with sealer.

func (*Secrets) Get

func (s *Secrets) Get(ctx context.Context, name string) ([]byte, int64, error)

Get returns the blob stored under name and its version; version 0 means nothing is stored.

func (*Secrets) Put

func (s *Secrets) Put(ctx context.Context, name string, data []byte) (int64, error)

Put stores data under name and returns the new version.

func (*Secrets) Version

func (s *Secrets) Version(ctx context.Context, name string) (int64, error)

Version is the stored version of name (0 when absent).

type SetupRequest added in v0.2.4

type SetupRequest struct {
	Mode        string `json:"mode"`
	DatabaseURL string `json:"databaseUrl"`
	Token       string `json:"token"`
	Replace     bool   `json:"replace"`
}

SetupRequest carries secrets only in memory until the stopped server applies it.

func (SetupRequest) URL added in v0.2.4

func (s SetupRequest) URL() string

func (SetupRequest) Validate added in v0.2.4

func (s SetupRequest) Validate() error

type Shared

type Shared struct {
	// contains filtered or unexported fields
}

Shared stores the shared settings document in clusterconfig_shared (one row, versioned for optimistic concurrency).

func NewShared

func NewShared(db *sql.DB) *Shared

NewShared returns the database-backed config.SharedBackend.

func (*Shared) LoadShared

func (s *Shared) LoadShared(ctx context.Context) (config.SharedDoc, bool, error)

LoadShared implements config.SharedBackend.

func (*Shared) SaveShared

func (s *Shared) SaveShared(ctx context.Context, d config.SharedDoc, expect int64) (int64, error)

SaveShared implements config.SharedBackend.

func (*Shared) SharedVersion

func (s *Shared) SharedVersion(ctx context.Context) (int64, error)

SharedVersion implements config.SharedBackend.

type TokenInfo

type TokenInfo struct {
	ID        string    `json:"id"`
	CreatedAt time.Time `json:"createdAt"`
	ExpiresAt time.Time `json:"expiresAt"`
	UsedAt    time.Time `json:"usedAt,omitzero"`
	UsedBy    string    `json:"usedBy,omitempty"`
}

TokenInfo describes a stored token (never its secret).

func ActiveJoinTokens

func ActiveJoinTokens(ctx context.Context, db *sql.DB, now time.Time) ([]TokenInfo, error)

ActiveJoinTokens performs bounded maintenance and returns only unused tokens valid at now, without credential material. UI clients must also filter expiry as time passes after the response.

func ListJoinTokens

func ListJoinTokens(ctx context.Context, db *sql.DB) ([]TokenInfo, error)

ListJoinTokens returns stored tokens, newest first.

Jump to

Keyboard shortcuts

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