Documentation
¶
Overview ¶
Package config is Ether's owner-editable settings, stored as JSON in the data directory. The web UI edits it through internal/admin; components get a snapshot at construction and subscribe for changes.
Index ¶
- Constants
- Variables
- func ContainsSealed(b []byte) bool
- func DataDirWarning(dataDir string) string
- func DefaultDataDir() string
- func EnsureDataDir(dataDir string) error
- func FieldContext(id int64) string
- func InstallCredentialKey(dir string, key []byte) error
- func IsSealed(stored string) bool
- func KeyFingerprint(key []byte) string
- func LocalJSON(c Config) []byte
- func OpenWith(s FieldSealer, stored string, context ...string) (string, bool, error)
- func ReadCredentialKey(dir string) ([]byte, error)
- func RedactDatabaseURL(raw string) string
- func SealWith(s FieldSealer, plain string, context ...string) (string, error)
- func SharedJSON(c Config) []byte
- type Cache
- type Catalog
- type Config
- type Database
- type Debrid
- type DebridProvider
- type Downloads
- type Duration
- type Extras
- type FieldSealer
- type Indexer
- type IndexerSites
- type LLM
- type LLMProvider
- type Markers
- type Notifier
- type OpenSubtitles
- type Personalize
- type Plex
- type Prowlarr
- type Quality
- type QualityRule
- type RTXVideo
- type Resolver
- type Scope
- type Server
- type SharedBackend
- type SharedDoc
- type SharedOptions
- type Spike
- type Store
- func (s *Store) AttachShared(ctx context.Context, b SharedBackend, o SharedOptions) error
- func (s *Store) Close()
- func (s *Store) Clustered() bool
- func (s *Store) CredentialKey() []byte
- func (s *Store) CredentialsUpgraded() bool
- func (s *Store) Get() Config
- func (s *Store) KeyCreated() bool
- func (s *Store) KeyFingerprint() string
- func (s *Store) OpenField(stored string, context ...string) (string, bool, error)
- func (s *Store) Path() string
- func (s *Store) RefreshShared(ctx context.Context) error
- func (s *Store) Seal(name string, plain []byte) (string, error)
- func (s *Store) SealField(plain string, context ...string) (string, error)
- func (s *Store) SealsFields() bool
- func (s *Store) SetNotifier(n Notifier)
- func (s *Store) SharedVersion() int64
- func (s *Store) Subscribe(fn func(Config))
- func (s *Store) Unseal(name, sealed string) ([]byte, error)
- func (s *Store) Update(fn func(*Config) error) error
- func (s *Store) UpgradeCredentials(ctx context.Context) error
- type Trakt
- type Transcode
- type Updates
- type Usenet
- type UsenetProvider
Constants ¶
const ( IndexerKindNewznab = "newznab" IndexerKindCardigann = "cardigann" )
Indexer kinds (Indexer.Kind).
const ( DatabaseSQLite = "sqlite" DatabasePostgres = "postgres" )
const ( DebridRealDebrid = "realdebrid" DebridTorBox = "torbox" DebridAllDebrid = "alldebrid" DebridPremiumize = "premiumize" )
Debrid kinds (DebridProvider.Kind).
const ( LLMAPIOpenAI = "openai" // POST {base}/chat/completions, GET {base}/models LLMAPIAnthropic = "anthropic" // POST {base}/messages, GET {base}/models // LLMAPIGoogle chats through Gemini's OpenAI-compatible surface // ({base}/openai/chat/completions) and lists models natively. LLMAPIGoogle = "google" )
LLM wire protocols.
const ( // node changes the shared settings. SharedTopic = "config:changed" // ResyncTopic is the event bus's synthetic "you may have missed // events" notice after a reconnect; the store re-reads its version. ResyncTopic = "bus:resync" DefaultSharedPoll = 30 * time.Second )
const CredentialKeyFile = ".credentials-key"
const DatabaseURLEnv = "ETHER_DATABASE_URL"
DatabaseURLEnv overrides Database for this process: a postgres:// (or postgresql://) URL selects PostgreSQL with that URL. It lets container and service deployments keep credentials out of config.json.
const LLMProviderCustom = "custom"
LLMProviderCustom is any OpenAI-compatible endpoint the owner supplies.
Variables ¶
var ( // stored version is not the expected one. ErrSharedConflict = errors.New("config: shared settings were changed by another node") // ErrKeyMismatch means this node's credential key is not the cluster's. ErrKeyMismatch = errors.New("config: this node's credential key does not match the cluster's") ErrSharedMissing = errors.New("config: the cluster database holds no shared settings") )
var DebridKinds = []string{DebridRealDebrid, DebridTorBox, DebridAllDebrid, DebridPremiumize}
DebridKinds lists the supported debrid services.
var ErrSealedWithoutKey = errors.New("config: sealed value but no credential key")
ErrSealedWithoutKey is returned when a sealed field is read without a sealer.
var LLMProviders = []LLMProvider{ {ID: "anthropic", Label: "Anthropic — Claude", API: LLMAPIAnthropic, BaseURL: "https://api.anthropic.com/v1", NeedsKey: true, KeyHint: "sk-ant-…"}, {ID: "openai", Label: "OpenAI — GPT", API: LLMAPIOpenAI, BaseURL: "https://api.openai.com/v1", NeedsKey: true, KeyHint: "sk-…"}, {ID: "google", Label: "Google — Gemini", API: LLMAPIGoogle, BaseURL: "https://generativelanguage.googleapis.com/v1beta", NeedsKey: true, KeyHint: "AIza…"}, {ID: "openrouter", Label: "OpenRouter — any model, one key", API: LLMAPIOpenAI, BaseURL: "https://openrouter.ai/api/v1", NeedsKey: true, KeyHint: "sk-or-…", ModelHint: "Models are vendor/model slugs, e.g. openai/gpt-4o."}, {ID: "groq", Label: "Groq — fast inference", API: LLMAPIOpenAI, BaseURL: "https://api.groq.com/openai/v1", NeedsKey: true, KeyHint: "gsk_…"}, {ID: "xai", Label: "xAI — Grok", API: LLMAPIOpenAI, BaseURL: "https://api.x.ai/v1", NeedsKey: true, KeyHint: "xai-…"}, {ID: "deepseek", Label: "DeepSeek", API: LLMAPIOpenAI, BaseURL: "https://api.deepseek.com", NeedsKey: true, KeyHint: "sk-…"}, {ID: "mistral", Label: "Mistral", API: LLMAPIOpenAI, BaseURL: "https://api.mistral.ai/v1", NeedsKey: true, KeyHint: "Your Mistral API key"}, {ID: "ollama", Label: "Ollama — on your network", API: LLMAPIOpenAI, BaseURL: "http://localhost:11434/v1", Local: true, KeyHint: "Not needed"}, {ID: "lmstudio", Label: "LM Studio — on your network", API: LLMAPIOpenAI, BaseURL: "http://localhost:1234/v1", Local: true, KeyHint: "Not needed"}, {ID: LLMProviderCustom, Label: "Custom (OpenAI-compatible)", API: LLMAPIOpenAI, Local: true, RequiresBaseURL: true, KeyHint: "Your provider API key", ModelHint: "Any OpenAI-compatible endpoint, such as Together, Fireworks, vLLM or llama.cpp."}, }
LLMProviders lists the supported services, hosted first.
Functions ¶
func ContainsSealed ¶ added in v0.2.4
ContainsSealed reports whether b holds a sealed value of any format, so that it needs the data directory's credential key to be read.
func DataDirWarning ¶ added in v0.2.4
DataDirWarning returns a warning when the data directory or the credential key can be read by other local accounts, or "" (always on Windows, where the installer and service install manage ACLs). It never changes permissions: an existing directory keeps the mode its owner or a package postinstall chose. The message names modes, not paths.
func DefaultDataDir ¶
func DefaultDataDir() string
DefaultDataDir is where Ether keeps config, database and cache. On Windows that is %ProgramData%\Ether: the server runs as a LocalSystem service, so a per-user directory would depend on who installed it.
func EnsureDataDir ¶ added in v0.2.4
EnsureDataDir creates dataDir owner-only (0700) when it does not exist: it holds config.json, the credential key and ether.db. Parents are made as usual, and a directory that already exists keeps the mode its owner or a package postinstall gave it.
func FieldContext ¶ added in v0.2.4
FieldContext joins an int64 row identity for a field context.
func InstallCredentialKey ¶
InstallCredentialKey writes key as dir's credential key. It refuses to replace an existing key file: a node joining a cluster moves its old configuration aside first.
func KeyFingerprint ¶
KeyFingerprint identifies a credential key without revealing it. Cluster nodes compare fingerprints to prove they hold the same key.
func OpenWith ¶ added in v0.2.4
OpenWith is s.OpenField; with a nil s, a sealed value is ErrSealedWithoutKey and anything else is returned as is.
func ReadCredentialKey ¶
ReadCredentialKey returns dir's credential key, or os.ErrNotExist.
func RedactDatabaseURL ¶
RedactDatabaseURL masks the password in a connection URL.
func SealWith ¶ added in v0.2.4
func SealWith(s FieldSealer, plain string, context ...string) (string, error)
SealWith is s.SealField, or plain unchanged when s is nil (components built without a credential key, such as in tests).
func SharedJSON ¶
SharedJSON is the cluster-shared part of c as JSON (unsealed).
Types ¶
type Cache ¶
type Cache struct {
Storage string `json:"storage,omitempty"` // local (default) or shared; restart required
PreloadEnabled bool `json:"preloadEnabled,omitempty"`
PreloadBudgetBytes int64 `json:"preloadBudgetBytes,omitempty"`
PreloadMaxTitles int `json:"preloadMaxTitles,omitempty"`
PreloadRateMiB int `json:"preloadRateMiB,omitempty"`
PreloadDuringPlayback bool `json:"preloadDuringPlayback,omitempty"`
PreloadMinFreeBytes int64 `json:"preloadMinFreeBytes,omitempty"`
PreloadSource string `json:"preloadSource,omitempty"`
PreloadContent string `json:"preloadContent,omitempty"` // mixed (default), movies, tv
PreloadHouseholdWeight *int `json:"preloadHouseholdWeight,omitempty"` // 0–100; nil = 65
PreloadHouseholdScope string `json:"preloadHouseholdScope,omitempty"` // home (default), all
PreloadEpisodesAhead int `json:"preloadEpisodesAhead,omitempty"` // per-show shelf window; 0 = 3
PreloadHistoryDays int `json:"preloadHistoryDays,omitempty"` // 0 = 90
// Mode: empty/demand retains fetched bytes; adaptive favours replayed titles;
// off bypasses persistent media writes. Zero budget remains unlimited.
Mode string `json:"mode,omitempty"`
RetentionDays int `json:"retentionDays,omitempty"`
ReplayBoostHours int `json:"replayBoostHours,omitempty"`
DisableSpeculation bool `json:"disableSpeculation,omitempty"`
DetailWarmup string `json:"detailWarmup,omitempty"` // empty/on or off
DetailBytes int64 `json:"detailBytes,omitempty"` // zero preserves the 2 MiB default
Dir string `json:"dir"`
BudgetBytes int64 `json:"budgetBytes"`
// Prefetch
PrefetchNextEpisode bool `json:"prefetchNextEpisode"`
PrefetchBytes int64 `json:"prefetchBytes"` // opening bytes pulled ahead
KeepInProgressWarm bool `json:"keepInProgressWarm"`
NextEpisodeAt float64 `json:"nextEpisodeAt"` // fraction of current episode, e.g. 0.8
// InProgressPinMaxAge: an in-progress title nobody has watched for this
// long stops pinning its release in the cache (0 = default 30 days).
InProgressPinMaxAge Duration `json:"inProgressPinMaxAge,omitempty"`
// InProgressPinPercent caps the share of BudgetBytes that in-progress
// pins may hold; the least recently watched titles lose their pin first
// (0 = default 50, max 100).
InProgressPinPercent int `json:"inProgressPinPercent,omitempty"`
}
type Catalog ¶
type Catalog struct {
TMDBKey string `json:"tmdbKey"` // owner override; empty uses the bundled key
Language string `json:"language"` // TMDB language, e.g. "en-US"
Region string `json:"region"`
MinPopularity float64 `json:"minPopularity"`
MinVotes int `json:"minVotes"`
IncludeAdult bool `json:"includeAdult"`
RefreshInterval Duration `json:"refreshInterval"`
}
type Config ¶
type Config struct {
// SharedCache is the cluster-wide NAS policy. Mount paths stay in Cache.
Server Server `json:"server"`
Plex Plex `json:"plex"`
Usenet Usenet `json:"usenet"`
Indexers []Indexer `json:"indexers"`
Cache Cache `json:"cache"`
Catalog Catalog `json:"catalog"`
Resolver Resolver `json:"resolver"`
Quality Quality `json:"quality"`
Transcode Transcode `json:"transcode"`
Trakt Trakt `json:"trakt"`
OpenSubtitles OpenSubtitles `json:"openSubtitles"`
LLM LLM `json:"llm"`
Updates Updates `json:"updates"`
Spike Spike `json:"spike"`
Downloads Downloads `json:"downloads"`
Markers Markers `json:"markers"`
Personalize Personalize `json:"personalize"`
Extras Extras `json:"extras"`
Debrid Debrid `json:"debrid"`
// Database is node-local storage selection (SQLite or PostgreSQL).
Database Database `json:"database"`
IndexerSites IndexerSites `json:"indexerSites"`
SetupComplete bool `json:"setupComplete"`
}
func MergeShared ¶
MergeShared returns local with every shared setting taken from the shared JSON document (as produced by SharedJSON). Shared settings the document lacks (written by an older version) take their defaults; node-local keys in the document are ignored.
func Read ¶ added in v0.2.4
Read loads dataDir's settings without creating or rewriting anything: no directory, no credential key and no re-sealed config.json, unlike Open. It is for other processes (the tray) that only look at the server's settings and must not race its writes. Sealed credentials need the existing credential key.
func (Config) EffectiveCache ¶ added in v0.2.4
EffectiveCache combines the shared pool policy with this node's mount path.
func (*Config) SetCachePolicy ¶ added in v0.2.4
SetCachePolicy updates the shared policy only when this node joins the NAS pool.
type Database ¶
type Database struct {
// Driver is "sqlite" (default) or "postgres".
Driver string `json:"driver,omitempty"`
// URL is the PostgreSQL connection URL, e.g.
// postgres://ether:secret@db.lan:5432/ether?sslmode=verify-full
URL string `json:"url,omitempty"`
// DeferMigrations makes this node wait, at startup, instead of applying
// new schema migrations to the shared database while another live node
// runs a version of Ether that does not know them. Off by default:
// migrations are additive, so older nodes keep working on a newer
// schema and a rolling update never stalls on it.
DeferMigrations bool `json:"deferMigrations,omitempty"`
}
Database is this node's storage backend. It is node-local: a standalone server keeps SQLite in its data directory; a cluster node points at the owner-supplied PostgreSQL database. The URL carries credentials, so it is sealed in config.json like other secrets and must only ever be logged through Redacted.
func (Database) Effective ¶
Effective applies the ETHER_DATABASE_URL override (read through getenv) and normalises the driver name.
func (Database) IsPostgres ¶
IsPostgres reports whether these (effective) settings select PostgreSQL.
type Debrid ¶
type Debrid struct {
Providers []DebridProvider `json:"providers"`
// CachedOnly accepts a torrent only when a provider can serve it
// instantly. When false, an uncached torrent is left on the account to
// download and the release is skipped for now; playback never waits.
CachedOnly bool `json:"cachedOnly"`
// RemoveUncached deletes torrents Ether added that turned out not to be
// cached, keeping the owner's account clean (CachedOnly only).
RemoveUncached bool `json:"removeUncached"`
// ChunkBytes caps one HTTP range request (0 = 8 MiB). Requests start
// small for a fast first byte and grow towards this size.
ChunkBytes int64 `json:"chunkBytes,omitempty"`
// ReadAheadChunks fixes the sequential read-ahead window in chunks
// (0 = adaptive to measured throughput; negative disables read-ahead).
ReadAheadChunks int `json:"readAheadChunks,omitempty"`
// MaxConnections caps concurrent range requests per open file (0 = 4).
MaxConnections int `json:"maxConnections,omitempty"`
}
Debrid configures owner-supplied debrid accounts. Torrent releases from Torznab indexers are added to a debrid service that already holds them and streamed over HTTPS range requests; nothing is downloaded locally.
func (Debrid) EnabledProviders ¶
EnabledProviders reports whether any provider is enabled with a key.
type DebridProvider ¶
type DebridProvider struct {
ID string `json:"id"`
Name string `json:"name"`
Kind string `json:"kind"` // realdebrid, torbox, alldebrid, premiumize
APIKey string `json:"apiKey"`
Priority int `json:"priority"` // lower is tried first
Enabled bool `json:"enabled"`
}
DebridProvider is one debrid account.
type Downloads ¶
type Downloads struct {
Enabled bool `json:"enabled"`
// AllowFriends lets shared (non-owner) users download. Off by
// default, matching PMS sharing where sync is a separate grant; an
// AccessPolicy AllowDownloads decision overrides this per user.
AllowFriends bool `json:"allowFriends"`
// MaxConcurrent bounds simultaneous remuxes server-wide (default 2),
// MaxConcurrentPerUser per viewer (default 1).
MaxConcurrent int `json:"maxConcurrent"`
MaxConcurrentPerUser int `json:"maxConcurrentPerUser"`
// DiskBudgetBytes bounds the temporary remuxed files on disk at any
// time (default 20 GiB). 0 uses the default.
DiskBudgetBytes int64 `json:"diskBudgetBytes"`
// Expiry is how long a finished download stays fetchable before its
// temp file is deleted (default 24h).
Expiry Duration `json:"expiry"`
// MaxHeight caps the download profile's resolution (default 1080).
MaxHeight int `json:"maxHeight"`
}
Downloads controls Plex offline downloads (allowSync + the Download Queue API). A download resolves a compact mobile-native release and remuxes it (stream copy) to a temporary MP4; nothing is stored permanently. Enabled by default for the owner: it is bounded by the disk budget, the concurrency caps and the normal stream limits, and the owner can turn it off entirely here.
type Duration ¶
Duration marshals as a Go duration string ("6h").
func (Duration) MarshalJSON ¶
func (*Duration) UnmarshalJSON ¶
type Extras ¶
type Extras struct {
// Enabled turns extras (and Cinema Trailers) on.
Enabled bool `json:"enabled"`
// ResolverCommand is the path (or PATH name) of a yt-dlp-compatible
// tool that prints a direct media URL for a video page. Empty disables
// the command source. Ether ships no resolver binary.
ResolverCommand string `json:"resolverCommand,omitempty"`
// ResolverArgs is the fixed argument template passed to
// ResolverCommand. The placeholders {url}, {site}, {key} and {id} are
// substituted; the command always runs without a shell. Empty uses a
// yt-dlp-compatible default ("-g -f b[ext=mp4]/b --no-playlist {url}").
ResolverArgs []string `json:"resolverArgs,omitempty"`
// ResolverTimeout bounds one resolver invocation (default 20s).
ResolverTimeout Duration `json:"resolverTimeout,omitempty"`
// PrewarmBytes is how much of the feature to pre-buffer while a
// trailer plays (clamped to 32–64 MiB, default 48 MiB).
PrewarmBytes int64 `json:"prewarmBytes,omitempty"`
}
Extras controls trailers and other clips attached to titles (Plex "Extras" and Cinema Trailers). TMDB only provides YouTube/Vimeo keys, which Plex apps cannot play, so a playable source must be configured by the owner as a yt-dlp-compatible resolver command. Off by default; with no working command Ether advertises no extras at all.
func (Extras) PrewarmBytesOr ¶
PrewarmBytesOr returns the trailer pre-buffer size, clamped to 32–64 MiB.
type FieldSealer ¶ added in v0.2.4
type FieldSealer interface {
SealField(plain string, context ...string) (string, error)
OpenField(stored string, context ...string) (plain string, legacy bool, err error)
}
FieldSealer protects secrets stored outside config.json (*Store implements it).
type Indexer ¶
type Indexer struct {
ID string `json:"id"`
Name string `json:"name"`
URL string `json:"url"` // base URL; /api is appended
APIKey string `json:"apiKey"`
DailyLimit int `json:"dailyLimit"` // optional personal API cap per UTC day; 0 = automatic service limits
DailyGrabs int `json:"dailyGrabs"` // optional personal NZB cap per UTC day; 0 = automatic service limits
Priority int `json:"priority"`
Enabled bool `json:"enabled"`
// Categories override; empty uses Newznab defaults (2000 movies, 5000 TV).
MovieCategories []int `json:"movieCategories"`
TVCategories []int `json:"tvCategories"`
// Protocol is "usenet" (Newznab, the default when empty) or "torrent"
// (Torznab; results stream through a debrid provider).
Protocol string `json:"protocol,omitempty"`
// Kind selects how the indexer is searched: "" or IndexerKindNewznab
// for a Newznab/Torznab API at URL, IndexerKindCardigann for a built-in
// site run from an owner-installed definition. For a built-in site URL
// is the site address and Protocol follows the definition.
Kind string `json:"kind,omitempty"`
// Definition is the installed definition id of a built-in site.
Definition string `json:"definition,omitempty"`
// Settings are the definition's setting values by name.
Settings map[string]string `json:"settings,omitempty"`
// SecretSettings are the definition's password-type settings. They are
// sealed at rest and redacted in API responses like API keys.
SecretSettings map[string]string `json:"secretSettings,omitempty"`
}
type IndexerSites ¶
type IndexerSites struct {
// DefinitionsURL is an optional source of definitions the owner trusts
// and has the right to use: a GitHub contents API URL, a GitHub folder
// or raw folder URL, or a plain list of definition URLs. Empty = none;
// definitions are then added only by upload.
DefinitionsURL string `json:"definitionsUrl,omitempty"`
// FlareSolverrURL is an optional FlareSolverr service used to pass
// Cloudflare browser challenges, e.g. http://localhost:8191.
FlareSolverrURL string `json:"flareSolverrUrl,omitempty"`
}
IndexerSites configures built-in indexer sites: owner-installed definitions in the Cardigann YAML format, run by internal/cardigann. Ether bundles no definitions and no definitions source.
type LLM ¶
type LLM struct {
Enabled bool `json:"enabled"` // off by default
// Provider is an LLMProviders ID; empty means LLMProviderCustom.
Provider string `json:"provider"`
// BaseURL overrides the provider's endpoint for local servers and is
// required for a custom OpenAI-compatible endpoint, e.g.
// http://localhost:11434/v1. Hosted providers ignore it.
BaseURL string `json:"baseUrl"`
APIKey string `json:"apiKey"`
Model string `json:"model"`
}
func (LLM) Endpoint ¶ added in v0.2.2
func (l LLM) Endpoint() (LLMProvider, string)
Endpoint resolves the provider and the base URL requests go to: the owner's BaseURL for local and custom servers when set, otherwise the provider default. An unknown provider resolves to an empty base URL.
type LLMProvider ¶ added in v0.2.2
type LLMProvider struct {
ID string `json:"id"`
Label string `json:"label"`
// API is the wire protocol (LLMAPIOpenAI, LLMAPIAnthropic, LLMAPIGoogle).
API string `json:"api"`
// BaseURL is the default endpoint; empty for LLMProviderCustom.
BaseURL string `json:"baseUrl"`
// NeedsKey is set for hosted services that reject anonymous calls.
NeedsKey bool `json:"needsKey"`
// Local servers run on the owner's network, so their endpoint is
// editable (BaseURL is only the usual default).
Local bool `json:"local"`
// RequiresBaseURL is set when the owner must supply the endpoint.
RequiresBaseURL bool `json:"requiresBaseUrl"`
// KeyHint is the API-key placeholder; ModelHint explains model IDs.
KeyHint string `json:"keyHint,omitempty"`
ModelHint string `json:"modelHint,omitempty"`
}
LLMProvider is one language-model service the owner can pick. The hosted entries mirror @braedonsaunders/appkit-ai's AI_PROVIDER_SPECS (same IDs, labels and endpoints) so Ether and AppKit apps configure providers the same way; Ollama and LM Studio are Ether's local additions.
func LookupLLMProvider ¶ added in v0.2.2
func LookupLLMProvider(id string) (LLMProvider, bool)
LookupLLMProvider finds a provider by ID; empty means LLMProviderCustom.
type Notifier ¶
type Notifier interface {
Publish(ctx context.Context, topic string, payload []byte) error
Subscribe(topic string, fn func([]byte)) (unsubscribe func())
}
Notifier is the cluster event bus as the config store uses it. A nil Notifier means no cross-node push; polling still picks changes up.
type OpenSubtitles ¶
type Personalize ¶
type Personalize struct {
// PostPlay ranks the post-play "hook queue" per viewer instead of
// returning generic related rows (#30). The ranked endpoint itself is
// cheap; the switch exists for owners who prefer plain PMS behaviour.
PostPlay bool `json:"postPlay"`
// PostPlayPrebuffer pre-warms and pre-buffers the #1 post-play pick in
// the last minutes of a movie or finale (#30).
PostPlayPrebuffer bool `json:"postPlayPrebuffer"`
// MicroReasons decorates single-item detail pages with the viewer's
// decision header ("96% Match · … · Ends at 10:45 PM") (#31).
MicroReasons bool `json:"microReasons"`
// SessionIntent adapts /hubs to what the viewer is browsing tonight
// (detail opens that did not turn into playback) (#32).
SessionIntent bool `json:"sessionIntent"`
// ClockFormat is the finish-time style of the decision header:
// "12h", "24h", or "" to derive it from Catalog.Language.
ClockFormat string `json:"clockFormat"`
}
Personalize holds the owner's switches for the Netflix-style per-viewer hooks (roadmap items #30–#32). All are on by default (see Defaults); a config written before this section existed keeps those defaults because loading unmarshals over them.
type Plex ¶
type Plex struct {
CompatibilityVersion string `json:"compatibilityVersion,omitempty"`
LatestCompatibilityVersion string `json:"latestCompatibilityVersion,omitempty"`
DisableVersionTracking bool `json:"disableVersionTracking"`
// Token is the owner's plex.tv token from sign-in; the server token.
Token string `json:"token"`
ClientIdentifier string `json:"clientIdentifier"` // X-Plex-Client-Identifier Ether uses towards plex.tv
OwnerPlexID int64 `json:"ownerPlexId"`
OwnerUsername string `json:"ownerUsername"`
Claimed bool `json:"claimed"`
// Remote access
RemoteAccess bool `json:"remoteAccess"`
UseUPnP bool `json:"useUpnp"`
PublicPort int `json:"publicPort"` // manual port forward; 0 = same as Server.Port
PublicHost string `json:"publicHost"` // override detected WAN IP
CustomURLs []string `json:"customUrls"`
// TLS certificate for *.plex.direct (or a self-signed fallback).
CertFile string `json:"certFile"`
KeyFile string `json:"keyFile"`
CertHash string `json:"certHash"` // plex.direct subdomain hash
SecureConnections string `json:"secureConnections"` // "required", "preferred", "disabled"
}
type Prowlarr ¶
type Prowlarr struct {
URL string `json:"url"`
APIKey string `json:"apiKey"`
DailyLimit int `json:"dailyLimit"`
DailyGrabs int `json:"dailyGrabs"`
Priority int `json:"priority"`
}
Prowlarr is an owner-managed source of Newznab indexers. Discovery is explicit and does not search or download from the indexers.
type Quality ¶
type Quality struct {
// Profiles by name. Rules pick a profile per client.
Profiles []core.QualityProfile `json:"profiles"`
Rules []QualityRule `json:"rules"`
Default string `json:"default"`
Remote string `json:"remote"` // profile for remote clients, "" = same as default
}
func DefaultQuality ¶
func DefaultQuality() Quality
type QualityRule ¶
type QualityRule struct {
Product string `json:"product,omitempty"` // substring of X-Plex-Product
Platform string `json:"platform,omitempty"` // substring of X-Plex-Platform
UserID int64 `json:"userId,omitempty"`
Profile string `json:"profile"`
}
QualityRule selects a profile when all non-empty matchers match.
type RTXVideo ¶ added in v0.2.4
type RTXVideo struct {
Mode string `json:"mode"` // off, auto (LAN), on (also remote)
ExecutablePath string `json:"executablePath"` // optional NVEncC64.exe, otherwise PATH
SourceMaxHeight int `json:"sourceMaxHeight"` // default 720
TargetMaxHeight int `json:"targetMaxHeight"` // default 1080, maximum 2160
Quality int `json:"quality"` // default 2, NGX quality 1..4 or streaming 21/23 (Ampere+); 0 uses default
MaxConcurrent int `json:"maxConcurrent"` // default 1, maximum 8
}
RTXVideo controls the optional, owner-installed NVEncC RTX Video backend. Empty mode is off. Paths are node-local and changes require a restart. Numeric zero values use the documented defaults; invalid values disable enhancement rather than silently substituting a non-AI scaler.
type Resolver ¶
type Resolver struct {
HealthRace int `json:"healthRace"` // 0 = ranked checks; >0 = first healthy of bounded concurrent candidates
OptimisticStart bool `json:"optimisticStart"` // return top NZB before background health check
CustomFormats []core.CustomFormat `json:"customFormats,omitempty"`
CustomFormatScores map[string]int `json:"customFormatScores,omitempty"`
MinCustomFormatScore *int `json:"minCustomFormatScore,omitempty"`
AllowCam bool `json:"allowCam"` // owner setting, off by default
HealthSamples int `json:"healthSamples"`
SearchCacheTTL Duration `json:"searchCacheTtl"`
MaxCandidates int `json:"maxCandidates"`
BlockedGroups []string `json:"blockedGroups"`
BlockedTerms []string `json:"blockedTerms"`
// SourcePreference orders releases of equivalent quality by source:
// "auto" (or empty) puts the source that measurably starts faster
// first, and cached debrid first for popular titles until both
// sources have measurements; "usenet" or "debrid" always prefer one.
// Quality is never traded for speed (see resolver/routing.go).
SourcePreference string `json:"sourcePreference,omitempty"`
// SourceScoreMargin is how far below the leader's score a release of
// the same quality tier may be and still be moved first for its
// source (0 = 25).
SourceScoreMargin float64 `json:"sourceScoreMargin,omitempty"`
}
type Scope ¶
type Scope int
Scope says where a setting lives when nodes form a cluster.
A standalone server keeps every setting in config.json. In a cluster the shared part lives in the database (one document every node reads) and the node-local part stays in each node's config.json. The classification is expressed over the JSON keys of Config so that it can split and merge the documents without reflection; TestScopeClassifiesEveryField fails when a field is added without being classified here.
type Server ¶
type Server struct {
// Port for the Plex API and web UI. PMS uses 32400.
Port int `json:"port"`
BindAddress string `json:"bindAddress"`
FriendlyName string `json:"friendlyName"`
// MachineIdentifier is generated on first run and never changes.
MachineIdentifier string `json:"machineIdentifier"`
LogLevel string `json:"logLevel"`
// DefaultStreamLimit applies to users without their own limit.
DefaultStreamLimit int `json:"defaultStreamLimit"`
MaxStreams int `json:"maxStreams"` // server-wide cap, 0 = derived from Usenet connections
// ClusterWeight is this node's weight when a cluster divides
// account-wide budgets (Usenet provider connections) among live nodes;
// a node with weight 2 gets twice the share of a node with weight 1.
// 0 means 1. Node-local; ignored on a standalone server.
ClusterWeight int `json:"clusterWeight,omitempty"`
}
type SharedBackend ¶
type SharedBackend interface {
// has been stored yet.
LoadShared(ctx context.Context) (doc SharedDoc, found bool, err error)
// nothing stored yet) and returns the new version, or
// ErrSharedConflict.
SaveShared(ctx context.Context, doc SharedDoc, expectVersion int64) (int64, error)
SharedVersion(ctx context.Context) (int64, error)
}
SharedBackend stores the shared settings document (implemented over the database by internal/clusterconfig).
type SharedOptions ¶
type SharedOptions struct {
// SetNotifier. Nil: polling only.
Notifier Notifier
// DefaultSharedPoll; negative disables polling and the watcher).
PollInterval time.Duration
Timeout time.Duration
// NoSeed makes AttachShared fail with ErrSharedMissing instead of
// seeding an empty database from this node's settings.
}
SharedOptions configure AttachShared.
type Spike ¶
Spike serves local files as a library so the Plex front end can be verified against official apps without Usenet.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store loads, saves and broadcasts the config.
func (*Store) AttachShared ¶
func (s *Store) AttachShared(ctx context.Context, b SharedBackend, o SharedOptions) error
AttachShared switches the store to cluster mode: it loads the shared settings from b (seeding b from this node's settings when it holds none, unless NoSeed) and overlays them on the node-local ones, refusing with ErrKeyMismatch when the cluster was sealed with another credential key. Until ctx ends it then follows changes made by other nodes.
func (*Store) Close ¶
func (s *Store) Close()
Close stops following other nodes' changes (the watcher started by AttachShared and the notifier subscriptions) and waits for an in-flight refresh to finish. The store keeps working; it no longer refreshes.
func (*Store) CredentialKey ¶
CredentialKey returns a copy of this node's credential key. Only the cluster join-token issuer needs it; never log or store it.
func (*Store) CredentialsUpgraded ¶ added in v0.2.4
CredentialsUpgraded reports whether this node completed UpgradeCredentials (config.json is written in v2).
func (*Store) KeyCreated ¶ added in v0.2.4
KeyCreated reports whether Open generated this data directory's credential key, i.e. nothing stored before this start was sealed with it. Database rows sealed under another key mean the key file was lost.
func (*Store) KeyFingerprint ¶
KeyFingerprint identifies this node's credential key.
func (*Store) OpenField ¶ added in v0.2.4
OpenField reverses SealField for the same context. A value stored unsealed (written before the upgrade) is returned as is with legacy set, so the caller can re-seal it. A sealed value that does not open — wrong key, damaged, or moved to another context — is an error.
func (*Store) RefreshShared ¶
RefreshShared applies the stored shared settings if another node changed them, notifying subscribers. It is a no-op on a standalone server.
func (*Store) SealField ¶ added in v0.2.4
SealField protects plain for storage, bound to context. Empty values stay empty. Until the store seals fields (SealsFields) it returns plain unchanged.
func (*Store) SealsFields ¶ added in v0.2.4
SealsFields reports whether SealField seals (database fields and shared settings are written in v2), which can precede CredentialsUpgraded on a cluster node that loaded settings another node upgraded.
func (*Store) SetNotifier ¶
SetNotifier connects (or, with nil, disconnects) the cluster event bus. It may be called before or after AttachShared.
func (*Store) SharedVersion ¶
SharedVersion is the version of the shared settings this node holds (0 on a standalone server).
func (*Store) Update ¶
Update applies fn to a copy of the config, saves it and notifies subscribers.
On a cluster node the shared part is written to the database with an optimistic version check; when another node changed it first, fn is applied again to the fresh settings, so fn must only mutate its argument.
func (*Store) UpgradeCredentials ¶ added in v0.2.4
UpgradeCredentials switches the store to v2 credentials (see above): config.json is rewritten in v2 and, on a cluster node, the shared settings are re-sealed in v2 unless another node already did. It is idempotent and resumable: config.json is rewritten atomically when the store switches (a configuration opened in v2 already has), and the shared settings are checked on every call, so a call interrupted between the two steps completes on the next.
type Transcode ¶
type Transcode struct {
RTXVideo RTXVideo `json:"rtxVideo"`
FFmpegPath string `json:"ffmpegPath"` // empty = bundled, then PATH
FFprobePath string `json:"ffprobePath"`
HWAccel string `json:"hwAccel"` // "auto", "none", "videotoolbox", "nvenc", "qsv", "vaapi"
VAAPIDevice string `json:"vaapiDevice"`
TempDir string `json:"tempDir"`
MaxTranscodes int `json:"maxTranscodes"`
RemoteMaxBitrateKbps int `json:"remoteMaxBitrateKbps"`
// Adaptive HLS engine (additive). Numeric zero values select the
// transcode package defaults; see Defaults for the shipped values.
FastStart bool `json:"fastStart"` // short, quick opening segments
FastStartSegments int `json:"fastStartSegments"` // opening segments in the ramp (default 2)
FastStartSegment Duration `json:"fastStartSegment"` // ramp segment length (default 2s)
FastStartMaxHeight int `json:"fastStartMaxHeight"` // ramp encode height cap (default 720)
ABRLadder bool `json:"abrLadder"` // multi-rung master playlist / MPD
VBVReservoir bool `json:"vbvReservoir"` // constant-quality + generous VBV for capped transcodes
VBVBufferSeconds int `json:"vbvBufferSeconds"` // VBV buffer depth in seconds of maxrate (default 8)
SpikeGovernor bool `json:"spikeGovernor"` // clamp VBR spikes in copy streams to measured goodput
SpikeMarginPercent int `json:"spikeMarginPercent"` // spike threshold over goodput, percent (default 125)
}
type Updates ¶
type Updates struct {
Channel string `json:"channel"` // "stable", "beta"
AutoUpdate bool `json:"autoUpdate"`
FeedURL string `json:"feedUrl"`
// MaintenanceStart/End bound the local-time window ("HH:MM") in which
// a downloaded update may be installed automatically. The window may
// wrap midnight; equal values mean "any time".
MaintenanceStart string `json:"maintenanceStart"`
MaintenanceEnd string `json:"maintenanceEnd"`
// MaxDrain bounds how long a clustered node about to install an update
// waits for its own playback sessions to end (it admits no new ones
// meanwhile) before installing anyway inside the window. 0 means 30
// minutes. Ignored on a standalone server, which waits for idle.
MaxDrain Duration `json:"maxDrain,omitempty"`
}
type Usenet ¶
type Usenet struct {
Providers []UsenetProvider `json:"providers"`
// MinIdle is how many connections per primary (non-backfill) provider
// are kept warm (dialled, authenticated, DATE-pinged) at all times so
// Play never pays a cold handshake. 0 = default (2); negative = off
// (connections are dialled lazily and expire when idle). A provider's
// own MinIdle overrides this.
MinIdle int `json:"minIdle"`
}
type UsenetProvider ¶
type UsenetProvider struct {
ID string `json:"id"`
Name string `json:"name"`
Host string `json:"host"`
Port int `json:"port"`
TLS bool `json:"tls"`
Username string `json:"username"`
Password string `json:"password"`
Connections int `json:"connections"`
// Priority: lower is tried first. Block accounts set Backfill.
Priority int `json:"priority"`
Backfill bool `json:"backfill"` // only used for articles missing elsewhere
Enabled bool `json:"enabled"`
// MinIdle overrides Usenet.MinIdle for this provider: 0 = inherit the
// global setting, positive = that many warm connections, negative =
// no warm floor for this provider. Backfill providers are never kept
// warm regardless.
MinIdle int `json:"minIdle,omitempty"`
}