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
- Variables
- func Attach(ctx context.Context, cfg *config.Store, db *sql.DB, o config.SharedOptions) error
- func CheckSetup(ctx context.Context, s SetupRequest) error
- func ConsumeJoinToken(ctx context.Context, db *sql.DB, t JoinToken, usedBy string, now time.Time) error
- func CreateJoinToken(ctx context.Context, db *sql.DB, databaseURL string, key []byte, ...) (JoinToken, TokenInfo, error)
- func Handler(o HandlerOptions) http.Handler
- func Init(ctx context.Context, o InitOptions) error
- func NewToken(ctx context.Context, db *sql.DB, cfg *config.Store, databaseURL string, ...) (JoinToken, TokenInfo, error)
- func Provision(ctx context.Context, o ProvisionOptions) error
- func PruneExpiredJoinTokens(ctx context.Context, db *sql.DB, now time.Time) error
- func ReadSecretFile(path string, limit int64) ([]byte, error)
- func RequestDrain(ctx context.Context, dir string) error
- func RevokeJoinToken(ctx context.Context, db *sql.DB, id string) (bool, error)
- func SetupError(err error) string
- func WriteDrainControl(dir, name string, d DrainControl) error
- type Deployment
- type DrainControl
- type DrainPendingCategories
- type HandlerOptions
- type InitOptions
- type JoinOptions
- type JoinResult
- type JoinToken
- type NodeControlRecord
- type NodeControls
- func (s *NodeControls) CompareAndSwap(ctx context.Context, node, boot string, version int64, doc []byte) error
- func (s *NodeControls) Initialize(ctx context.Context, node, boot string, doc []byte) error
- func (s *NodeControls) Load(ctx context.Context, node, boot string) (NodeControlRecord, error)
- func (s *NodeControls) RequestMatches(ctx context.Context, node, boot, id string, payload []byte) (bool, error)
- func (s *NodeControls) Submit(ctx context.Context, node, boot, id string, payload, doc []byte, version int64) (bool, error)
- type ProvisionOptions
- type Sealer
- type Secrets
- type SetupRequest
- type Shared
- type TokenInfo
Constants ¶
const DefaultJoinTokenTTL = time.Hour
DefaultJoinTokenTTL is how long a join token stays valid.
const DrainRequestFile = "drain.request"
const DrainStatusFile = "drain.status"
const JoinTokenPrefix = "ether-join-v1:"
JoinTokenPrefix starts every join token.
const TokenWarning = "" /* 374-byte string literal not displayed */
TokenWarning is shown with every new token.
Variables ¶
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") )
var ErrNodeControlConflict = errors.New("node control conflict")
Functions ¶
func Attach ¶
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 ¶
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 ¶
ReadSecretFile bounds input and deliberately omits filenames and bytes from errors. Each call reopens the path so Kubernetes projected-volume swaps work.
func RequestDrain ¶
RequestDrain blocks until exact-operation quiescence or ctx expiry. It never treats stale status, node disappearance or a deadline as retirement proof.
func RevokeJoinToken ¶
RevokeJoinToken deletes an unused token; it reports whether one existed.
func SetupError ¶ added in v0.2.4
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 ¶
ParseJoinToken decodes a token string.
type NodeControlRecord ¶
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 (*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 ¶
NewSecrets returns a blob store sealing values with sealer.
func (*Secrets) Get ¶
Get returns the blob stored under name and its version; version 0 means nothing is stored.
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 (*Shared) LoadShared ¶
LoadShared implements config.SharedBackend.
func (*Shared) SaveShared ¶
SaveShared 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 ¶
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.