Documentation
¶
Index ¶
- Variables
- func AddDownloadClient(ctx context.Context, e DownloadClientEntry) error
- func AddIndexer(ctx context.Context, e IndexerEntry) error
- func AddMediaServer(ctx context.Context, e MediaServerEntry) error
- func AddOIDCProvider(ctx context.Context, p OIDCConfig) error
- func AddQualityProfile(ctx context.Context, e QualityProfileEntry) error
- func DeleteDownloadClient(ctx context.Context, name string) error
- func DeleteIndexer(ctx context.Context, name string) error
- func DeleteMediaServer(ctx context.Context, name string) error
- func DeleteOIDCProvider(ctx context.Context, name string) error
- func DeleteQualityProfile(ctx context.Context, name string) error
- func DumpDefaults(w io.Writer) error
- func EnsurePlexClientID(ctx context.Context) error
- func LoadReader(r io.Reader) error
- func PublicURL() string
- func ResetForTest()
- func SecretValue(inline, file string) string
- func Update(ctx context.Context, fn func(*Config) error) error
- func UpdateDownloadClient(ctx context.Context, name string, p DownloadClientPatch) error
- func UpdateIndexer(ctx context.Context, name string, p IndexerPatch) error
- func UpdateMediaServer(ctx context.Context, name string, p MediaServerPatch) error
- func UpdateOIDCProvider(ctx context.Context, name string, patch OIDCProviderPatch) error
- func UpdateQualityProfile(ctx context.Context, name string, p QualityProfilePatch) error
- type AppLog
- type AuthConfig
- type AuthPatch
- type Config
- type DownloadClientEntry
- type DownloadClientPatch
- type EventsConfig
- type HTTPLog
- type IndexerEntry
- type IndexerPatch
- type LibraryConfig
- type LockoutConfig
- type LogConfig
- type LogRotate
- type MediaServerConfig
- type MediaServerEntry
- type MediaServerPatch
- type MetadataConfig
- type OIDCConfig
- type OIDCProviderPatch
- type OTelConfig
- type QualityProfileEntry
- type QualityProfilePatch
- type ScheduleConfig
- type SeedAdminCfg
- type ServerConfig
Constants ¶
This section is empty.
Variables ¶
var ( ErrOIDCProviderExists = errors.New("oidc provider name already exists") ErrOIDCProviderNotFound = errors.New("oidc provider not found") ErrOIDCDiscoveryFailed = errors.New("oidc discovery failed") // ErrSecretFileManaged is returned when a UI/API edit tries to set a secret // inline while that field is sourced from a *_file path. The file is the // source of truth — change it (or git), not the UI. ErrSecretFileManaged = errors.New( "secret is file-managed; edit the file, not the UI", ) )
Named errors returned by the mutate layer. Handlers branch on these to decide between 404 / 409 / 422 responses.
var ( ErrDownloadClientExists = errors.New("download client name already exists") ErrDownloadClientNotFound = errors.New("download client not found") )
var ( ErrIndexerExists = errors.New("indexer name already exists") ErrIndexerNotFound = errors.New("indexer not found") )
var ( ErrMediaServerExists = errors.New("media server name already exists") ErrMediaServerNotFound = errors.New("media server not found") )
var ( ErrQualityProfileExists = errors.New( "quality profile name already exists", ) ErrQualityProfileNotFound = errors.New("quality profile not found") ErrQualityProfileInUseAsDefault = errors.New( "quality profile is the configured default", ) )
var ( // ErrNoPath means the config was loaded from a reader (not a file path) so // Update has no file to write back to. ErrNoPath = errors.New("config has no backing file path") // ErrReadOnly means the config is declared read_only, so Update refuses // every write-back. Declarative/GitOps deploys mount config read-only and // change it through git, not the UI; runtime-generated state // (plex_client_id, session secret) is surfaced to the operator instead of // persisted here. ErrReadOnly = errors.New("config is read-only") )
Functions ¶
func AddDownloadClient ¶
func AddDownloadClient(ctx context.Context, e DownloadClientEntry) error
func AddIndexer ¶
func AddIndexer(ctx context.Context, e IndexerEntry) error
func AddMediaServer ¶
func AddMediaServer(ctx context.Context, e MediaServerEntry) error
func AddOIDCProvider ¶
func AddOIDCProvider(ctx context.Context, p OIDCConfig) error
AddOIDCProvider probes the issuer's discovery document, then appends the provider to the auth.oidc list. Returns ErrOIDCProviderExists if the name is already in use or ErrOIDCDiscoveryFailed if the issuer is unreachable.
Discovery runs against the caller's ctx (with a 5s timeout) so slow remotes can't hold the config lock — Update is called only after the probe returns.
func AddQualityProfile ¶
func AddQualityProfile(ctx context.Context, e QualityProfileEntry) error
func DeleteOIDCProvider ¶
DeleteOIDCProvider removes the named provider from auth.oidc. Returns ErrOIDCProviderNotFound if no provider carries that name.
func DumpDefaults ¶
DumpDefaults writes the default configuration as YAML to w. Output is a ready-to-use config file; running `config validate` on it should succeed once data_dir exists on disk.
func EnsurePlexClientID ¶
EnsurePlexClientID generates and persists a Plex client identifier when a Plex server is already configured but none exists yet (e.g. on boot from a config that lists a Plex server). No-op — and no disk write — when no Plex server is present or an id already exists.
func LoadReader ¶
LoadReader reads YAML config from r, overlays env vars, validates, and stores the resulting Config in the singleton (access via Get).
func PublicURL ¶
func PublicURL() string
PublicURL returns the public base URL used for OIDC redirect URIs, invite links and admin-visible display. Priority: STREAMLINE_PUBLIC_URL env var (not part of the config schema — read directly) then http://host:port from the config. Should be overridden to an https URL when running behind a reverse proxy.
func SecretValue ¶
SecretValue returns the effective secret for an inline/file pair: the cached contents of file when a *_file path is set, otherwise the inline value. The pair is validated mutually exclusive (excluded_with), so at most one is ever set. Use this instead of reading the inline field directly so file-backed secrets resolve.
func Update ¶
Update deep-clones the current config, runs fn, validates the result, writes it atomically to the backing file, then swaps the singleton. Returns ErrNoPath if no file path was captured at Load time. Update holds mu.Lock across the full clone/validate/write/swap sequence. This blocks concurrent Get readers for the duration of disk I/O; acceptable because Update is expected to fire only on admin-triggered settings changes.
func UpdateDownloadClient ¶
func UpdateDownloadClient( ctx context.Context, name string, p DownloadClientPatch, ) error
func UpdateIndexer ¶
func UpdateIndexer(ctx context.Context, name string, p IndexerPatch) error
func UpdateMediaServer ¶
func UpdateMediaServer(ctx context.Context, name string, p MediaServerPatch) error
func UpdateOIDCProvider ¶
func UpdateOIDCProvider( ctx context.Context, name string, patch OIDCProviderPatch, ) error
UpdateOIDCProvider merges the patch into the named provider. An empty/nil ClientSecret is treated as "keep existing" so the UI doesn't have to re-surface the secret just to let the admin change the issuer. When Issuer is patched the new URL is probed before the Update is written.
func UpdateQualityProfile ¶
func UpdateQualityProfile( ctx context.Context, name string, p QualityProfilePatch, ) error
Types ¶
type AppLog ¶
type AppLog struct {
Enabled bool `koanf:"enabled"`
Level string `koanf:"level" validate:"required,oneof=debug info warn error"`
Format string `koanf:"format" validate:"required,oneof=text json"`
Output string `koanf:"output" validate:"required"`
Rotate LogRotate `koanf:"rotate" validate:"required"`
}
type AuthConfig ¶
type AuthConfig struct {
Mode string `koanf:"mode" validate:"required,oneof=full trusted-network disabled"`
TrustedNetworks []string `koanf:"trusted_networks" validate:"dive,cidr"`
TrustedRole string `koanf:"trusted_role" validate:"required,oneof=admin member request_only"`
SessionSecret string `koanf:"session_secret" validate:"excluded_with=SessionSecretFile"`
SessionSecretFile string `koanf:"session_secret_file" validate:"omitempty,excluded_with=SessionSecret,filepath"`
SessionTTL string `koanf:"session_ttl" validate:"required"`
RegistrationMode string `koanf:"registration_mode" validate:"required,oneof=disabled open invite"`
OIDCDefaultRole string `koanf:"oidc_default_role" validate:"required,oneof=admin member request_only"`
SeedAdmin SeedAdminCfg `koanf:"seed_admin"`
OIDC []OIDCConfig `koanf:"oidc" validate:"dive"`
Lockout LockoutConfig `koanf:"lockout" validate:"required"`
}
func UpdateAuth ¶
func UpdateAuth(ctx context.Context, patch AuthPatch) (AuthConfig, error)
UpdateAuth validates the patch, merges it into the auth section, and persists via Update. Returns the resulting AuthConfig on success so callers can echo the new state back to the client without a second Get.
type AuthPatch ¶
AuthPatch carries optional field updates to the auth section. Nil fields are left untouched so callers only need to populate keys the user actually changed.
type Config ¶
type Config struct {
Server ServerConfig `koanf:"server" validate:"required"`
DataDir string `koanf:"data_dir" validate:"required,dir"`
// ReadOnly rejects every config.Update write-back with ErrReadOnly. For
// declarative/GitOps deploys where config is mounted read-only and changes
// flow through git, not the UI.
ReadOnly bool `koanf:"read_only"`
Auth AuthConfig `koanf:"auth" validate:"required"`
Library LibraryConfig `koanf:"library" validate:"required"`
Schedule ScheduleConfig `koanf:"schedules" validate:"required"`
Metadata MetadataConfig `koanf:"metadata"`
Log LogConfig `koanf:"log" validate:"required"`
OTel OTelConfig `koanf:"otel"`
MediaServer MediaServerConfig `koanf:"media_server"`
Events EventsConfig `koanf:"events" validate:"required"`
DownloadClients []DownloadClientEntry `koanf:"download_clients" validate:"unique=Name,dive"`
Indexers []IndexerEntry `koanf:"indexers" validate:"unique=Name,dive"`
QualityProfiles []QualityProfileEntry `koanf:"quality_profiles" validate:"unique=Name,dive"`
QualityDefaultProfile string `koanf:"quality_default_profile"`
}
func Get ¶
func Get() *Config
Get returns a pointer to the current singleton config. Callers must treat the returned value as read-only; use Update to mutate.
func (Config) DatabasePath ¶
DatabasePath is the SQLite database location, derived from DataDir.
type DownloadClientEntry ¶
type DownloadClientEntry struct {
Name string `koanf:"name" validate:"required"`
ClientType string `koanf:"client_type" validate:"required,oneof=qbittorrent transmission deluge"`
Host string `koanf:"host" validate:"required"`
Port uint16 `koanf:"port" validate:"required,port"`
AuthMethod string `koanf:"auth_method" validate:"required,oneof=password api_key"`
Username string `koanf:"username"`
Password string `koanf:"password" validate:"excluded_with=PasswordFile"`
PasswordFile string `koanf:"password_file" validate:"omitempty,excluded_with=Password,filepath"`
APIKey string `koanf:"api_key" validate:"excluded_with=APIKeyFile"`
APIKeyFile string `koanf:"api_key_file" validate:"omitempty,excluded_with=APIKey,filepath"`
UseSSL bool `koanf:"use_ssl"`
Priority uint8 `koanf:"priority"`
Enabled bool `koanf:"enabled"`
}
func EnabledDownloadClients ¶
func EnabledDownloadClients() []DownloadClientEntry
EnabledDownloadClients returns every enabled download client. The adoption pass scans each for untracked managed-category torrents.
func FindDownloadClient ¶
func FindDownloadClient(name string) (DownloadClientEntry, bool)
func PickDownloadClient ¶
func PickDownloadClient() (DownloadClientEntry, bool)
PickDownloadClient returns the highest-priority enabled download client.
type DownloadClientPatch ¶
type DownloadClientPatch struct {
ClientType *string
Host *string
Port *uint16
AuthMethod *string
Username *string
Password *string
APIKey *string
UseSSL *bool
Priority *uint8
Enabled *bool
}
DownloadClientPatch carries optional field updates. Blank Password/APIKey preserve the existing secret.
type EventsConfig ¶
type EventsConfig struct {
Retention string `koanf:"retention" validate:"required"`
}
EventsConfig governs the MovieEvent retention window. Old rows are deleted by the cleanup job after Retention.
type IndexerEntry ¶
type IndexerEntry struct {
Name string `koanf:"name" validate:"required"`
Host string `koanf:"host" validate:"required"`
Port uint16 `koanf:"port" validate:"required,port"`
Path string `koanf:"path"`
UseSSL bool `koanf:"use_ssl"`
APIKey string `koanf:"api_key" validate:"required_without=APIKeyFile,excluded_with=APIKeyFile"`
APIKeyFile string `koanf:"api_key_file" validate:"omitempty,excluded_with=APIKey,filepath"`
Protocol string `koanf:"protocol" validate:"required,oneof=torznab prowlarr"`
Priority uint8 `koanf:"priority"`
Enabled bool `koanf:"enabled"`
}
func EnabledIndexers ¶
func EnabledIndexers() []IndexerEntry
func FindIndexer ¶
func FindIndexer(name string) (IndexerEntry, bool)
type IndexerPatch ¶
type IndexerPatch struct {
Host *string
Port *uint16
Path *string
UseSSL *bool
APIKey *string
Protocol *string
Priority *uint8
Enabled *bool
}
IndexerPatch carries optional field updates. A blank APIKey preserves the existing key.
type LibraryConfig ¶
type LibraryConfig struct {
MoviePath string `koanf:"movie_path" validate:"required"`
MovieNaming string `koanf:"movie_naming" validate:"required"`
SeriesPath string `koanf:"series_path" validate:"required"`
SeriesNaming string `koanf:"series_naming" validate:"required"`
// DownloadPath is the host-side directory where streamline reads
// completed torrents from. qBittorrent (or any other client) decides
// its own save location; this only tells the importer where to look,
// per-torrent content lives at <DownloadPath>/<torrent.Name>. The
// directory is not validated at boot — it may be a bind-mount that
// comes up after streamline; the importer surfaces a clear stat error
// if the path is wrong.
DownloadPath string `koanf:"download_path" validate:"required"`
ImportMode string `koanf:"import_mode" validate:"required,oneof=hardlink copy move"`
NoMatchCooldown string `koanf:"no_match_cooldown" validate:"required"`
MaxGrabFailures uint8 `koanf:"max_grab_failures" validate:"required,min=1"`
KeepTorrentSeeding bool `koanf:"keep_torrent_seeding"`
ImportMaxAttempts uint8 `koanf:"import_max_attempts" validate:"required,min=1"`
AllowedDownloadRoots []string `koanf:"allowed_download_roots"`
// DriftGraceTicks is the number of consecutive drift_check ticks a
// MediaFile may be absent from disk before its row is deleted and the
// owning movie reverts to "wanted". Bounded to give operators a knob
// for noisy mounts without unbounded patience.
DriftGraceTicks uint8 `koanf:"drift_grace_ticks" validate:"required,min=1,max=20"`
}
type LockoutConfig ¶
type LockoutConfig struct {
Threshold uint8 `koanf:"threshold" validate:"required,min=1,max=255"`
Window string `koanf:"window" validate:"required"`
Duration string `koanf:"duration" validate:"required"`
}
LockoutConfig governs the per-account login-failure lockout. Threshold is the failed-attempt count that locks the account; Window is the sliding window over which failures accumulate; Duration is how long the lockout holds before auto-expiry.
type MediaServerConfig ¶
type MediaServerConfig struct {
PlexClientID string `koanf:"plex_client_id"`
Servers []MediaServerEntry `koanf:"servers" validate:"unique=Name,dive"`
}
MediaServerConfig holds media-server integration identifiers. PlexClientID is generated and persisted the first time a Plex server is configured (see EnsurePlexClientID); required by the Plex PIN OAuth flow as the X-Plex-Client-Identifier header value.
type MediaServerEntry ¶
type MediaServerEntry struct {
Name string `koanf:"name" validate:"required"`
ServerType string `koanf:"server_type" validate:"required,oneof=plex jellyfin emby"`
Host string `koanf:"host" validate:"required"`
APIKey string `koanf:"api_key" validate:"excluded_with=APIKeyFile"`
APIKeyFile string `koanf:"api_key_file" validate:"omitempty,excluded_with=APIKey,filepath"`
Enabled bool `koanf:"enabled"`
// LibrarySection is the Plex movie-library section key (play-on hint).
LibrarySection *string `koanf:"library_section"`
// LibrarySectionTV is the Plex TV-library section key (play-on hint).
LibrarySectionTV *string `koanf:"library_section_tv"`
}
MediaServerEntry is one media-server integration. Secrets (api_key) are stored plaintext in YAML, consistent with auth.oidc[].client_secret.
func EnabledMediaServers ¶
func EnabledMediaServers() []MediaServerEntry
func FindMediaServer ¶
func FindMediaServer(name string) (MediaServerEntry, bool)
type MediaServerPatch ¶
type MediaServerPatch struct {
ServerType *string
Host *string
APIKey *string
Enabled *bool
LibrarySection *string
}
MediaServerPatch carries optional field updates. A blank APIKey preserves the existing token.
type MetadataConfig ¶
type MetadataConfig struct {
TMDBAPIKey string `koanf:"tmdb_api_key" validate:"excluded_with=TMDBAPIKeyFile"`
TMDBAPIKeyFile string `koanf:"tmdb_api_key_file" validate:"omitempty,excluded_with=TMDBAPIKey,filepath"`
TVDBAPIKey string `koanf:"tvdb_api_key" validate:"excluded_with=TVDBAPIKeyFile"`
TVDBAPIKeyFile string `koanf:"tvdb_api_key_file" validate:"omitempty,excluded_with=TVDBAPIKey,filepath"`
Language string `koanf:"language" validate:"omitempty,bcp47_language_tag"`
TMDBRegion string `koanf:"tmdb_region" validate:"omitempty,len=2,uppercase"`
}
type OIDCConfig ¶
type OIDCConfig struct {
Name string `koanf:"name" validate:"required"`
Issuer string `koanf:"issuer" validate:"required,url"`
ClientID string `koanf:"client_id" validate:"required"`
ClientSecret string `koanf:"client_secret" validate:"required_without=ClientSecretFile,excluded_with=ClientSecretFile"`
// ClientSecretFile, when set, is read into ClientSecret after validation.
// Mutually exclusive with ClientSecret (set exactly one). Lets a GitOps
// deploy mount the secret from a k8s Secret instead of inlining it.
ClientSecretFile string `koanf:"client_secret_file" validate:"omitempty,excluded_with=ClientSecret,filepath"`
// RoleClaim is the ID-token claim (e.g. "groups", "roles") consulted for
// claim-based role mapping; RoleMapping maps a claim value to a Streamline
// role. When both are set and a value matches, the mapped role is
// authoritative — applied at signup and re-synced on every login (the
// highest-privilege role wins if several values map). Leave empty to give
// OIDC users auth.oidc_default_role.
RoleClaim string `koanf:"role_claim"`
RoleMapping map[string]string `koanf:"role_mapping" validate:"omitempty,dive,oneof=admin member request_only"`
}
type OIDCProviderPatch ¶
OIDCProviderPatch carries optional field updates to a single OIDC provider. A nil ClientSecret (or empty string) preserves the existing secret — the UI never shows the current value, so blank means "unchanged."
type OTelConfig ¶
type OTelConfig struct {
Endpoint string `koanf:"endpoint"`
}
type QualityProfileEntry ¶
type QualityProfileEntry struct {
Name string `koanf:"name" validate:"required"`
PreferredResolution string `koanf:"preferred_resolution" validate:"required,oneof=720p 1080p 2160p"`
MinResolution string `koanf:"min_resolution" validate:"required,oneof=720p 1080p 2160p"`
UpgradeAllowed bool `koanf:"upgrade_allowed"`
}
func ResolveQualityProfile ¶
func ResolveQualityProfile(name string) (QualityProfileEntry, bool)
ResolveQualityProfile returns the profile named by name, falling back to QualityDefaultProfile when name is empty or unknown. ok is false only when no profiles are configured at all.
type QualityProfilePatch ¶
type QualityProfilePatch struct {
PreferredResolution *string
MinResolution *string
UpgradeAllowed *bool
}
QualityProfilePatch carries optional field updates.
type ScheduleConfig ¶
type ScheduleConfig struct {
RSSSync string `koanf:"rss_sync" validate:"required"`
MetadataRefresh string `koanf:"metadata_refresh" validate:"required"`
DownloadMonitor string `koanf:"download_monitor" validate:"required"`
MissingSearch string `koanf:"missing_search" validate:"required"`
Cleanup string `koanf:"cleanup" validate:"required"`
ImportScan string `koanf:"import_scan" validate:"required"`
OrphanScan string `koanf:"orphan_scan" validate:"required"`
DriftCheck string `koanf:"drift_check" validate:"required"`
}