Documentation
¶
Overview ¶
Package settingsio: cross-instance user export/import.
Users are exported by username (the only key that is portable across instances; ids are uuid-per-instance). On import, users that already exist on the target are left untouched -- the operator's existing setup wins over the envelope. Users absent on the target are recreated so that downstream rows (api tokens, user preferences) can attribute back to them without a fallback.
This file lives alongside tokens.go and libraries.go to keep the per-domain export/import helpers physically together; the dispatch from Service.Export / Service.Import is in export.go.
Index ¶
- Constants
- Variables
- type APITokenExport
- type ConnectionExport
- type Envelope
- type ImportOptions
- type ImportResult
- type LibraryExport
- type Payload
- type PriorityExport
- type RuleExport
- type ScraperConfigExport
- type Service
- func (s *Service) Export(ctx context.Context, passphrase string) (*Envelope, error)
- func (s *Service) Import(ctx context.Context, env *Envelope, passphrase string) (*ImportResult, error)
- func (s *Service) ImportWithOptions(ctx context.Context, env *Envelope, passphrase string, opts ImportOptions) (*ImportResult, error)
- func (s *Service) WithRuleService(rs *rule.Service) *Service
- func (s *Service) WithScraperService(ss *scraper.Service) *Service
- type UserExport
- type UserPrefsExport
Constants ¶
const CurrentEnvelopeVersion = "1.3"
CurrentEnvelopeVersion is the version emitted by Export. Bump whenever the Payload schema changes in a way that older binaries cannot safely round-trip.
- "1.0": original format (settings, connections, platform profiles, webhooks, provider keys, priorities)
- "1.1": adds rules, scraper_configs, user_preferences, plaintext summary
- "1.2": adds libraries (connection refs remapped by type+url) and api_tokens (token_hash + metadata only; never plaintext)
- "1.3": adds users block so cross-instance restore can recreate absent owners before remapping api_tokens / user_preferences (#1283). The password_hash inside Users is a bcrypt digest -- never plaintext -- and only crosses the wire inside the passphrase-encrypted envelope.
Variables ¶
var ErrUnsupportedVersion = errors.New("unsupported export format version")
ErrUnsupportedVersion is returned by Import when the envelope's Version field is not one of the formats this binary knows how to read.
var ErrWrongPassphrase = errors.New("incorrect passphrase or corrupted backup file")
ErrWrongPassphrase is returned by Import when the AES-GCM tag verification fails, meaning the supplied passphrase does not match the one used during export. Callers may use errors.Is to distinguish this case from other import errors and surface a human-friendly hint.
Functions ¶
This section is empty.
Types ¶
type APITokenExport ¶ added in v1.0.5
type APITokenExport struct {
Name string `json:"name"`
TokenHash string `json:"token_hash"`
Scopes string `json:"scopes"`
Username string `json:"username"`
CreatedAt string `json:"created_at"`
LastUsedAt string `json:"last_used_at,omitempty"`
RevokedAt string `json:"revoked_at,omitempty"`
Status string `json:"status"`
}
APITokenExport carries the persisted API-token row in a form portable across instances. The plaintext token is never persisted in the DB, so this struct cannot expose it; only the stored hash crosses the wire, and only inside the passphrase-encrypted envelope. Username replaces user_id because user IDs are generated per-instance (same remap pattern as user_preferences).
type ConnectionExport ¶
type ConnectionExport struct {
Name string `json:"name"`
Type string `json:"type"`
URL string `json:"url"`
APIKey string `json:"api_key"`
Enabled bool `json:"enabled"`
FeatureLibraryImport bool `json:"feature_library_import"`
FeatureNFOWrite bool `json:"feature_nfo_write"`
FeatureImageWrite bool `json:"feature_image_write"`
}
ConnectionExport is a connection with its API key decrypted for export.
type Envelope ¶
type Envelope struct {
Version string `json:"version"`
AppVersion string `json:"app_version"`
CreatedAt string `json:"created_at"`
Salt string `json:"salt"` // base64-encoded PBKDF2 salt
Data string `json:"data"` // base64-encoded nonce+ciphertext
Summary *ImportResult `json:"summary,omitempty"`
}
Envelope is the outer JSON wrapper for an exported settings file. Summary is plaintext metadata -- section counts do not reveal secrets (which remain inside the encrypted Data blob) but give the user a sanity-check that the export matches their expectations.
type ImportOptions ¶ added in v1.0.5
type ImportOptions struct {
// AdminFallbackTokens, when true, attributes api_tokens whose original
// username remains absent on the target (after the envelope's Users
// block has been applied) to ImportingAdminUserID. Each reassignment
// increments ImportResult.OwnershipReassigned so the audit is visible
// to the operator.
//
// This is opt-in because silent ownership reassignment surprises
// operators who rely on the historical "skip unknown owner" semantics
// for cross-environment exports (e.g. prod -> staging clones).
AdminFallbackTokens bool
// ImportingAdminUserID is the user_id to attribute reassigned tokens to
// when AdminFallbackTokens is true. The HTTP handler resolves it from
// the authenticated session before calling ImportWithOptions.
ImportingAdminUserID string
}
ImportOptions controls optional behaviors at import time. The zero value reproduces the historical behavior: tokens whose owning username is absent on the target are skipped (their count surfaces via APITokensSkipped) and no automatic ownership reassignment occurs.
type ImportResult ¶
type ImportResult struct {
Settings int `json:"settings"`
Connections int `json:"connections"`
Profiles int `json:"platform_profiles"`
Webhooks int `json:"webhooks"`
ProviderKeys int `json:"provider_keys"`
Priorities int `json:"priorities"`
Rules int `json:"rules"`
ScraperConfigs int `json:"scraper_configs"`
UserPreferences int `json:"user_preferences"`
Libraries int `json:"libraries"`
LibrariesSkipped int `json:"libraries_skipped,omitempty"`
APITokens int `json:"api_tokens"`
APITokensSkipped int `json:"api_tokens_skipped,omitempty"`
// UsersImported counts user rows recreated from the envelope on import
// because they were absent on the target instance (#1283). Users that
// already existed on the target are NOT counted -- their rows are left
// untouched so the operator's local setup wins over the envelope.
UsersImported int `json:"users_imported,omitempty"`
// OwnershipReassigned counts api_tokens whose original owner is absent
// on the target AND who were attributed to the importing admin via the
// admin-fallback opt-in. This is a deliberate ownership change and is
// surfaced in the result so it cannot be silent (#1283).
OwnershipReassigned int `json:"ownership_reassigned,omitempty"`
}
ImportResult summarizes what was imported. Skip counters track rows that were intentionally omitted (e.g. a library whose connection is missing on the target, or a token whose owning user is absent); they let callers surface a per-domain "imported / skipped" breakdown without reparsing logs.
type LibraryExport ¶ added in v1.0.5
type LibraryExport struct {
Name string `json:"name"`
Path string `json:"path"`
Type string `json:"type"` // "regular" | "classical"
Source string `json:"source"` // "manual" | "emby" | "jellyfin" | "lidarr"
ConnectionType string `json:"connection_type,omitempty"` // for remap on import
ConnectionURL string `json:"connection_url,omitempty"` // for remap on import
ExternalID string `json:"external_id,omitempty"`
FSWatch int `json:"fs_watch"`
FSPollInterval int `json:"fs_poll_interval"`
NFOLockData bool `json:"nfo_lock_data,omitempty"`
}
LibraryExport carries the persistent configuration of a single library row. Connection IDs are not exported because they are generated per-instance; instead the owning connection's (type, url) is carried alongside so the import step can remap to the local connection_id. Runtime-detected shared-FS state (status / evidence / peer ids) is intentionally omitted -- the target instance must re-detect on its own scan to avoid carrying stale cluster geometry across the boundary.
type Payload ¶
type Payload struct {
Settings map[string]string `json:"settings"`
Connections []ConnectionExport `json:"connections"`
PlatformProfiles []platform.Profile `json:"platform_profiles"`
Webhooks []webhook.Webhook `json:"webhooks"`
ProviderKeys map[string]string `json:"provider_keys"`
ProviderPriorities []PriorityExport `json:"provider_priorities"`
Rules []RuleExport `json:"rules,omitempty"`
ScraperConfigs []ScraperConfigExport `json:"scraper_configs,omitempty"`
UserPreferences []UserPrefsExport `json:"user_preferences,omitempty"`
Libraries []LibraryExport `json:"libraries,omitempty"`
APITokens []APITokenExport `json:"api_tokens,omitempty"`
Users []UserExport `json:"users,omitempty"`
}
Payload is the decrypted inner content of an export.
type PriorityExport ¶
type PriorityExport struct {
Field string `json:"field"`
Providers []provider.ProviderName `json:"providers"`
Disabled []provider.ProviderName `json:"disabled,omitempty"`
}
PriorityExport holds a field's provider priority list.
type RuleExport ¶ added in v0.9.6
type RuleExport struct {
ID string `json:"id"`
Enabled bool `json:"enabled"`
AutomationMode string `json:"automation_mode"`
Config rule.RuleConfig `json:"config"`
}
RuleExport holds the mutable configuration of a single rule. Only enabled, automation_mode, and config are exported; immutable fields (name, description, category) are not overwritten on import because they are defined by the application binary, not by the user.
type ScraperConfigExport ¶ added in v0.9.6
type ScraperConfigExport struct {
Scope string `json:"scope"`
Config scraper.ScraperConfig `json:"config"`
Overrides *scraper.Overrides `json:"overrides,omitempty"`
}
ScraperConfigExport holds one scope's scraper configuration and its override set. The scope string identifies global ("global") or a connection-scoped entry.
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
Service handles settings export and import.
func NewService ¶
func NewService( db *sql.DB, ps *provider.SettingsService, cs *connection.Service, ps2 *platform.Service, ws *webhook.Service, ) *Service
NewService creates a settings export/import service.
func (*Service) Export ¶
Export collects all settings data, encrypts it with the given passphrase, and returns an Envelope. The passphrase is used with PBKDF2 to derive an AES-256-GCM key, making exports portable across instances.
func (*Service) Import ¶
func (s *Service) Import(ctx context.Context, env *Envelope, passphrase string) (*ImportResult, error)
Import decrypts and applies settings from an Envelope using the given passphrase. The passphrase must match the one used during export.
This is a thin wrapper around ImportWithOptions that preserves the historical default (no admin-fallback for token ownership). New callers that need to opt into admin-fallback should call ImportWithOptions directly.
func (*Service) ImportWithOptions ¶ added in v1.0.5
func (s *Service) ImportWithOptions(ctx context.Context, env *Envelope, passphrase string, opts ImportOptions) (*ImportResult, error)
ImportWithOptions decrypts and applies settings from an Envelope, honoring the supplied ImportOptions. See ImportOptions for the available knobs.
func (*Service) WithRuleService ¶ added in v0.9.6
WithRuleService attaches a rule service so rule configuration is included in export/import. If not set, rule config is silently skipped.
type UserExport ¶ added in v1.0.5
type UserExport struct {
Username string `json:"username"`
DisplayName string `json:"display_name,omitempty"`
PasswordHash string `json:"password_hash,omitempty"`
Role string `json:"role"`
AuthProvider string `json:"auth_provider,omitempty"`
ProviderID string `json:"provider_id,omitempty"`
IsActive bool `json:"is_active"`
IsProtected bool `json:"is_protected,omitempty"`
CreatedAt string `json:"created_at"`
}
UserExport carries a single user row in a form portable across instances. Internal ids (id, invited_by) are intentionally omitted because they are regenerated per-instance; downstream rows (api_tokens, user_preferences) remap by username instead.
PasswordHash is preserved for local-auth users so credentials survive a restore. The hash is a bcrypt digest, never plaintext, and it only crosses the wire inside the passphrase-encrypted envelope. Federated-only users have an empty hash (their schema row already stores ”).
type UserPrefsExport ¶ added in v0.9.6
type UserPrefsExport struct {
Username string `json:"username"`
Preferences map[string]string `json:"preferences"`
}
UserPrefsExport holds the full preference map for a single user, identified by username rather than internal ID so the export is portable across instances.