Documentation
¶
Overview ¶
Package config loads and validates bridge.yaml (library roots, listen address, TLS paths, scan interval).
Relative paths in the config file resolve against the config file's own directory, matching how most Unix tools handle config-relative paths.
Index ¶
- Constants
- func IsInQuietHours(startMin, endMin, now int) bool
- func ParseQuietHours(s string) (startMin, endMin int, err error)
- func ValidateCustomEndpoints(in []string) (kept []string, warnings []error)
- type BackupConfig
- type Config
- type LibraryWatchConfig
- type TailscaleConfig
- type TailscaleMode
- type UpdateConfig
- type UpscaleConfig
Constants ¶
const ( DefaultListenAddress = ":7788" DefaultAdminAddress = "127.0.0.1:7789" DefaultDataDir = "./data" // DefaultScanIntervalSec is 6h, not 1h. A 50k-track library on // mechanical NAS with the prior 1h cadence spun the disks every // hour preventing idle spindown — operator-facing wear hazard. // Operators with quiet libraries should set this higher; admin // console exposes on-demand triggers for ad-hoc rescans. // fsnotify integration would let us drop this further but has // cross-platform pitfalls (Windows path semantics, recursive // watch fan-out on large libraries) and deserves its own design // pass (PR #N). DefaultScanIntervalSec = 21600 DefaultLibraryName = "1-bit Bridge" DefaultBackupIntervalHours = 24 DefaultBackupKeep = 7 // DefaultLibraryWatchDebounceSeconds is the per-directory // event coalesce window when fsnotify-based watching is on. // 10 seconds matches the documented default and is long // enough that a large-folder copy doesn't trigger a scan- // per-file storm. DefaultLibraryWatchDebounceSeconds = 10 // DefaultUpscaleQueueCap bounds the pending-job channel of the // long-lived transcode worker pool inside `bridge serve`. 5000 // covers 2-3 average user libraries; smaller deployments stay // well under the cap, the user-spam-the-button case bounces // against a clean 503 instead of exhausting memory. DefaultUpscaleQueueCap = 5000 )
Defaults applied when a field is absent or zero-valued.
Variables ¶
This section is empty.
Functions ¶
func IsInQuietHours ¶ added in v0.1.1
IsInQuietHours returns true when minute-of-day `now` falls inside [startMin, endMin]. Handles midnight-wrap windows (start > end = the window crosses midnight, so the "inside" is [start, 1440) ∪ [0, end]).
func ParseQuietHours ¶ added in v0.1.1
ParseQuietHours parses a "HH:MM-HH:MM" window into start and end minute-of-day values (0-1439 each). Returns an error for malformed input. The window may wrap midnight (start > end means "from start until midnight, then midnight until end").
Used by Validate to catch bad config at load time, and by the updater's auto-install scheduler to decide whether the current wall-clock minute is inside the window.
func ValidateCustomEndpoints ¶ added in v0.1.2
ValidateCustomEndpoints filters the input to entries that parse as absolute HTTPS URLs with a non-empty host. Returns (kept, warnings) where `warnings` is one error per dropped entry. Used by Validate() to scrub the persisted list and by the admin patch handler to surface per-entry errors back to the operator.
Why HTTPS-only: iOS clients won't speak plain-HTTP to the bridge (ATS rejects it before our pinning runs even on a local-network allowlisted host), and the bridge itself only listens TLS. Allowing HTTP entries would just produce a confusing "advertised but unreachable" row in the Devices panel.
Types ¶
type BackupConfig ¶ added in v0.1.1
type BackupConfig struct {
// IntervalHours is the cadence for automatic snapshots.
// nil/omitted → DefaultBackupIntervalHours.
// 0 → disabled.
// >0 → cadence in hours (negative values rejected at Validate).
IntervalHours *int `yaml:"intervalHours,omitempty"`
// Keep is the maximum number of snapshots to retain after a
// rotation. Older snapshots beyond this count are deleted on
// each periodic snapshot. Zero or negative disables pruning;
// the operator manages backup-disk usage by hand in that case.
// Defaults to DefaultBackupKeep when the entire backup section
// is omitted; an operator who wants no pruning sets `keep: -1`
// (a negative value won't trip Validate).
Keep int `yaml:"keep,omitempty"`
}
BackupConfig configures the periodic state-snapshot ticker that `bridge serve` runs alongside the manifest scanner. The CLI `bridge backup` / `bridge restore` work regardless of this section — these knobs only govern the in-process automatic schedule.
Default state (section absent): IntervalHours=24, Keep=7 (one snapshot per day, a week retained). Setting `intervalHours: 0` explicitly disables the periodic ticker; the operator can still snapshot on-demand via the CLI or the admin console. To preserve the "omitted vs explicit-zero" distinction across YAML round-trips, `IntervalHours` is a `*int` — nil means "absent, use default", a pointer to 0 means "explicitly disabled".
func (BackupConfig) EffectiveIntervalHours ¶ added in v0.1.1
func (b BackupConfig) EffectiveIntervalHours() int
EffectiveIntervalHours resolves the runtime cadence from the pointer-typed config field. Caller code uses this rather than dereferencing IntervalHours directly so the nil-vs-zero contract stays in one place.
func (BackupConfig) EffectiveKeep ¶ added in v0.1.1
func (b BackupConfig) EffectiveKeep() int
EffectiveKeep returns the rotation count to apply on each periodic snapshot. Mirrors EffectiveIntervalHours so call sites have one helper per field.
type Config ¶
type Config struct {
LibraryRoots []string `yaml:"libraryRoots"`
ListenAddress string `yaml:"listenAddress"`
AdminAddress string `yaml:"adminAddress,omitempty"`
DataDir string `yaml:"dataDir"`
TLSCertPath string `yaml:"tlsCertPath,omitempty"`
TLSKeyPath string `yaml:"tlsKeyPath,omitempty"`
ScanIntervalSec int `yaml:"scanIntervalSec"`
LibraryName string `yaml:"libraryName"`
// CustomEndpoints lets operators advertise URLs that aren't
// discoverable from the host's interface table — e.g. a reverse
// proxy, a port-forwarded WAN URL, or a non-default Tailscale
// MagicDNS that the auto-detector didn't pick up. Each entry is
// a complete URL ("https://my-bridge.example.com:7788") and is
// surfaced to iOS via /v1/health and the admin endpoints panel.
//
// Validation: each entry must parse as an absolute URL with
// scheme="https" — entries failing validation are dropped at
// load with a `.warn` log and don't fail the whole config.
//
// Operators add entries via the admin console's Settings page;
// hand-edits to bridge.yaml work too. Used as input to the cert
// SAN gather (PR feat/tls-broader-sans) so that adding a new
// custom DNS hostname here, then rotating the cert, makes that
// URL TLS-handshake-compatible from iOS.
CustomEndpoints []string `yaml:"customEndpoints,omitempty"`
Update UpdateConfig `yaml:"update,omitempty"`
Backup BackupConfig `yaml:"backup,omitempty"`
LibraryWatch LibraryWatchConfig `yaml:"libraryWatch,omitempty"`
Upscale UpscaleConfig `yaml:"upscale,omitempty"`
Tailscale TailscaleConfig `yaml:"tailscale,omitempty"`
}
Config mirrors the on-disk bridge.yaml shape. See config/bridge.yaml.example.
func Load ¶
Load parses a bridge.yaml file, fills defaults, resolves relative paths against the config file's directory, and validates. A returned *Config is ready to hand to downstream subsystems.
func (*Config) Save ¶
Save atomically writes c as YAML to path (temp file + rename). Parent directory must exist. Comments and fields unknown to this schema are not preserved — callers that want to keep hand-authored comments should not use Save. `bridge init` and admin-console edits are the intended callers.
func (*Config) ScanInterval ¶
ScanInterval returns scanIntervalSec as a time.Duration.
type LibraryWatchConfig ¶ added in v0.1.1
type LibraryWatchConfig struct {
// Enabled is the master toggle. Default false.
Enabled bool `yaml:"enabled,omitempty"`
// DebounceSeconds is the per-directory event coalesce window.
// 10 seconds is the documented default — long enough that a
// large-folder copy doesn't trigger a scan-per-file storm,
// short enough that the perceived "instant" feel survives.
// Zero or omitted → DefaultLibraryWatchDebounceSeconds.
DebounceSeconds int `yaml:"debounceSeconds,omitempty"`
}
LibraryWatchConfig governs the optional fsnotify-based instant-update watcher. Off by default — the periodic scan (ScanIntervalSec) remains the safety net in either case. Power users with local-disk libraries flip this on to get Roon/Plex-style "drop a file in, it appears" responsiveness; NAS / spinning-disk users keep the periodic-only path so a flapping server can't trigger a thrash of incremental scans.
Linux deployments with very large libraries should also raise `fs.inotify.max_user_watches` — `bridge doctor` warns when the kernel limit looks too low for the configured roots.
func (LibraryWatchConfig) EffectiveDebounceSeconds ¶ added in v0.1.1
func (l LibraryWatchConfig) EffectiveDebounceSeconds() int
EffectiveDebounceSeconds resolves the runtime debounce window from the YAML field. Centralised so the watcher and the doctor check can't disagree.
type TailscaleConfig ¶ added in v0.1.2
type TailscaleConfig struct {
// Mode selects the integration: "cli" (default), "tsnet", or
// "disabled". Empty value falls back to "cli" at load time.
Mode string `yaml:"mode,omitempty"`
// AuthKey is the Tailscale auth key used by tsnet on first run.
//
// Precedence (matches Tailscale's standard idiom):
// 1. TS_AUTHKEY environment variable (preferred — keeps
// secrets out of yaml on disk)
// 2. This field (fallback for ops who can't set env vars)
// 3. Empty → triggers interactive OAuth (`bridge tsnet auth`
// prints an AuthURL the operator visits in a browser)
//
// Unused once tsnet has persisted state.
AuthKey string `yaml:"authKey,omitempty"`
// Hostname is the magic-DNS hostname tsnet will register with.
// Empty falls back to the bridge's deviceName / library name.
Hostname string `yaml:"hostname,omitempty"`
}
TailscaleConfig selects how the bridge integrates with Tailscale. `cli` (default) preserves the historical CLI-shell-out flow: `tailscale status` for endpoint detection + `tailscale cert` for LE-on-magic-DNS cert minting. The CLI path requires the operator's host to have `tailscaled` running and `tailscale` in $PATH, and ships LE cert/key files to disk for the SNI cert switcher to read.
`tsnet` makes the bridge its own tailnet node via the embedded `tailscale.com/tsnet` library — no external daemon needed, no on-disk LE cert dance (tsnet's ListenTLS terminates LE in-process). State (machine identity + control-plane keys) persists under `<dataDir>/tailscale/`. First-time auth uses an interactive browser flow; subsequent boots load persisted state. Headless deploys can pre-seed the state by setting AuthKey or the TS_AUTHKEY environment variable.
`disabled` skips both — useful for LAN-only deployments where the operator doesn't want tailscale code paths exercised.
Default is `cli` to preserve back-compat. Operators opt into `tsnet` per-device; the default may be flipped in a future release after the tsnet path soaks.
func (TailscaleConfig) EffectiveMode ¶ added in v0.1.2
func (t TailscaleConfig) EffectiveMode() (TailscaleMode, error)
EffectiveMode resolves the configured mode, applying the "cli" default for empty values. Returns one of the three known modes or an error when the yaml carries something unrecognised — the safer shape than silently falling through to the default on a typo (e.g. `mode: tnset` would otherwise look like it worked).
type TailscaleMode ¶ added in v0.1.2
type TailscaleMode string
TailscaleMode is the typed representation of TailscaleConfig.Mode.
const ( TailscaleModeCLI TailscaleMode = "cli" TailscaleModeTsnet TailscaleMode = "tsnet" TailscaleModeDisabled TailscaleMode = "disabled" )
type UpdateConfig ¶ added in v0.1.1
type UpdateConfig struct {
// AutoInstall enables the poll-loop's automatic install attempt.
// Off by default — the operator must opt in. Even when on, the
// stream-active gate refuses if any /v1/download is in flight,
// and quiet-hours (when set) restrict to the configured window.
AutoInstall bool `yaml:"autoInstall,omitempty"`
// QuietHours restricts auto-install to a daily window in
// "HH:MM-HH:MM" form using the server's local time. Empty means
// "any time". The window may wrap midnight (e.g.
// "23:00-06:00") — see config_test.go for the wrap-around test
// matrix. Any-clock format that fails to parse rejects at
// Validate time so a bad config doesn't silently disable the
// auto-installer.
QuietHours string `yaml:"quietHours,omitempty"`
// CheckIntervalHours overrides the default poll cadence (6 h).
// Operator-tunable for installations on metered or rate-limited
// uplinks. Values below 1h are clamped at runtime by the updater.
// Zero = use the package default.
CheckIntervalHours int `yaml:"checkIntervalHours,omitempty"`
}
UpdateConfig configures the Phase C opt-in auto-installer. The safeties from Phase B (stream-active gate, signature verification, rollback marker) ALWAYS apply — these toggles only decide whether auto-install is attempted at all and within what time window.
Default state: AutoInstall=false (operator-triggered only). The admin Settings UI exposes these for hand-edit; YAML-direct edits are also supported.
type UpscaleConfig ¶ added in v0.1.2
type UpscaleConfig struct {
// Enabled is the master toggle. Default false.
Enabled bool `yaml:"enabled,omitempty"`
// Workers is the size of the long-lived transcode worker pool
// instantiated by `bridge serve` when Enabled. Zero (the default)
// resolves to min(NumCPU-1, 4) at startup. Operators with beefy
// hosts can raise; small-box deployments (Pi) should leave at
// the default to avoid starving downloads / pairing requests.
Workers int `yaml:"workers,omitempty"`
// QueueCap is the hard cap on the worker pool's pending-job
// channel. POST /v1/upscale enqueues are non-blocking and drop
// to a 503 `queue_full` response when the channel can't accept
// more — protects against a user spamming "Generate" on their
// 50k-track library exhausting memory or wedging the HTTP path.
// Zero (the default) resolves to DefaultUpscaleQueueCap.
QueueCap int `yaml:"queueCap,omitempty"`
// TargetRate selects the resampler output rate. "auto" (the
// default) picks 176400 Hz for 44.1-family sources and 192000
// Hz for 48-family sources. Explicit numeric values override
// the auto pick (e.g. "352800" for DSD-friendly DACs that
// prefer the next octave up). Sources at or above the chosen
// rate are skipped — never downsample.
TargetRate string `yaml:"targetRate,omitempty"`
// TargetBits is the output bit depth. 24 (the default) is the
// sweet spot for FLAC: lower ceiling than 32-bit float but
// 96 dB above the audible noise floor and well below any
// realistic dither quantisation noise. 32-bit-int output is
// supported for completeness but rarely a sonic improvement.
TargetBits int `yaml:"targetBits,omitempty"`
}
UpscaleConfig governs the optional offline PCM-upscaling feature introduced in v1.2. Disabled by default — operators must explicitly opt in via `upscale.enabled: true`. When disabled:
- the `bridge upscale` CLI exits with a friendly "feature is disabled" error.
- the `POST /v1/upscale` endpoint returns 503 `upscale_disabled`.
- the manifest provider does not splice `Variants` into Track responses even if `track_variants` rows exist on disk.
- `/v1/health` reports `upscaleEnabled: false`.
When enabled, additional safety: `bridge serve` runs an `exec.LookPath("sox")` probe at startup. Missing `sox` logs an error and overrides Enabled to false in-memory — feature gracefully degrades, the rest of the server keeps running.
`track_variants` table is created unconditionally (additive schema, no harm in empty); only the read/write paths are gated. This means a user can: enable → run `bridge upscale` → variants populate → disable → variants disappear from manifest → re-enable → variants reappear without re-conversion.
func (UpscaleConfig) EffectiveQueueCap ¶ added in v0.1.2
func (u UpscaleConfig) EffectiveQueueCap() int
EffectiveQueueCap resolves the pending-job channel cap, defaulting to DefaultUpscaleQueueCap when the YAML field is zero.
func (UpscaleConfig) EffectiveTargetBits ¶ added in v0.1.2
func (u UpscaleConfig) EffectiveTargetBits() int
EffectiveTargetBits resolves the output bit depth. Defaults to 24.
func (UpscaleConfig) EffectiveTargetRate ¶ added in v0.1.2
func (u UpscaleConfig) EffectiveTargetRate() string
EffectiveTargetRate returns the YAML field's literal value when set, or "auto" (the default) when empty. Validation happens at the call site in the transcode package, where the source rate is in scope.
func (UpscaleConfig) EffectiveWorkers ¶ added in v0.1.2
func (u UpscaleConfig) EffectiveWorkers() int
EffectiveWorkers resolves the runtime worker count from the YAML field, applying the min(NumCPU-1, 4) default at zero. Centralised so the CLI and the serve-side pool can't disagree on the floor.