Documentation
¶
Overview ¶
Package admin serves the local-only web console for operating a running bridge instance — adding/removing library roots, pairing/revoking client devices, and surfacing scan + uptime stats.
Trust model: the admin listener binds a loopback address (default 127.0.0.1:7789). Anyone on the host already has read access to the token store and sqlite DB, so adding an auth layer on top would be theatre. Loopback binding is enforced in two places — config.validateLoopbackAddress at load time and a RemoteAddr check in the Handler as a belt-and-braces runtime guard so a future misconfiguration (e.g. forgetting to bind the listener to 127.0.0.1) still refuses LAN traffic.
Mutations (add root, pair device, revoke, settings edit) go through a single mutex on the Server so two operators hitting the UI simultaneously can't interleave a config.Save against each other. In practice the admin surface is single-user, so the mutex is a correctness guard rather than a performance concern.
Index ¶
- Variables
- type AdminBatchCoordinator
- type AdminBatchInsufficientDiskSpace
- type AdminBatchRow
- type AdminBatchSubmitResult
- type AdminBatchThroughput
- type AdminVariantDeleteRequest
- type AdminVariantDeleteResponse
- type AdminVariantDeleter
- type Deps
- type Server
- type TailscaleProvider
- type TailscaleStatus
- type UpdateProvider
- type UpdateStatus
- type UpscalePoolStats
Constants ¶
This section is empty.
Variables ¶
var ( ErrUpdateNoUpdate = errors.New("no update available") ErrUpdateActiveSessions = errors.New("active downloads in flight") ErrUpdateNotSupported = errors.New("self-install not supported on this platform") ErrUpdatePathNotWritable = errors.New("binary path not writable") )
Sentinel errors for the install / rollback paths. Defined in admin/ rather than re-exported from internal/updater so the wire shape lives entirely in this package — handlers_api.go classifies via errors.Is, not string-substring (which was the original implementation; PR #42 review flagged the fragility).
The adapter in cmd/bridge/main.go translates internal/updater's equivalent errors to these via fmt.Errorf("%w: %s", ErrXxx, ...) so the admin handler can switch on errors.Is without reaching into the updater package.
ErrAdminVariantDeleterUnavailable is the sentinel error the `DELETE /api/upscale/variants` handler matches against to emit 503 — wired to wrap `api.ErrVariantDeleteUnavailable` in the cmd/bridge adapter so the admin package stays decoupled from the api package's error definitions.
Functions ¶
This section is empty.
Types ¶
type AdminBatchCoordinator ¶ added in v0.1.3
type AdminBatchCoordinator interface {
Submit(ctx context.Context, libraryRelPath string, targetRate, targetBits int) (AdminBatchSubmitResult, error)
Cancel(idHex string) error
ListBatches(limit int) ([]AdminBatchRow, error)
Throughput() AdminBatchThroughput
}
AdminBatchCoordinator is the admin-side interface the Library Inspector's "Upscale this folder" button + the Jobs page consume. Implemented by an adapter in cmd/bridge/main.go around transcode.Coordinator — same UpscaleEnqueuer / UpscaleStats decoupling pattern. The methods mirror `api.BatchCoordinator` but use admin-package wire shapes (`AdminBatchRow`, etc.) so the admin package stays free of internal/api.
`Submit` takes a `context.Context` so the HTTP request's cancellation propagates through to the coordinator and any downstream listings the coordinator does internally (manifest projection walk). Per Gemini high on PR #202.
type AdminBatchInsufficientDiskSpace ¶ added in v0.1.3
type AdminBatchInsufficientDiskSpace struct {
ProjectedBytes int64
RequiredBytes int64
AvailableBytes int64
}
AdminBatchInsufficientDiskSpace is the typed-error shape returned when the coordinator's pre-flight refuses a submit.
func (*AdminBatchInsufficientDiskSpace) Error ¶ added in v0.1.3
func (e *AdminBatchInsufficientDiskSpace) Error() string
type AdminBatchRow ¶ added in v0.1.3
type AdminBatchRow struct {
ID string `json:"id"`
Path string `json:"path"`
TargetRate int `json:"targetRate"`
TargetBits int `json:"targetBits"`
Status string `json:"status"`
TotalFiles int `json:"totalFiles"`
ProcessedFiles int `json:"processedFiles"`
FailedFiles int `json:"failedFiles"`
Error string `json:"error,omitempty"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
AdminBatchRow is the per-row wire shape returned by the admin list endpoint (Jobs page consumer).
`CreatedAt` / `UpdatedAt` are `time.Time` (RFC 3339-encoded on the wire) rather than int64 nanoseconds. JavaScript's Number type can't safely represent 64-bit ns timestamps — values above 2^53 round, causing the Jobs page to render a date a few hundred milliseconds off. The string form parses safely via `new Date(...)`. Per Gemini high on PR #202.
type AdminBatchSubmitResult ¶ added in v0.1.3
type AdminBatchSubmitResult struct {
BatchID string `json:"batchID"`
Path string `json:"path"`
TargetRate int `json:"targetRate"`
TargetBits int `json:"targetBits"`
TotalFiles int `json:"totalFiles"`
AlreadyCovered int `json:"alreadyCovered"`
ProjectedSizeBytes int64 `json:"projectedSizeBytes"`
AvailableBytes int64 `json:"availableBytes"`
EnqueuedCount int `json:"enqueuedCount"`
}
AdminBatchSubmitResult is the wire shape returned by the admin Submit endpoint. Mirrors transcode.SubmitResult + api.BatchSubmitResult field-for-field but lives here so the admin templates can JSON-decode without importing either of those packages.
type AdminBatchThroughput ¶ added in v0.1.3
type AdminBatchThroughput struct {
JobsPerHour float64 `json:"jobsPerHour"`
EtaSeconds float64 `json:"etaSeconds"`
Samples int `json:"samples"`
}
AdminBatchThroughput is the wire shape returned by the throughput proxy. Mirrors transcode.ThroughputSnapshot.
type AdminVariantDeleteRequest ¶ added in v0.1.3
AdminVariantDeleteRequest is the parsed-and-validated input to the admin handler's call into the deleter. Exactly one of `All` / `Prefix` / `Path` is set on any valid request; the handler short-circuits the unscoped form behind a typed-phrase confirmation in the UI (matches the `bridge artwork --gc` `--confirm` CLI convention) plus the existing `?confirm=true` query gate for direct callers.
type AdminVariantDeleteResponse ¶ added in v0.1.3
type AdminVariantDeleteResponse struct {
DeletedCount int `json:"deletedCount"`
FreedBytes int64 `json:"freedBytes"`
DeletedPaths []string `json:"deletedPaths"`
}
AdminVariantDeleteResponse is the wire shape returned on success. Same field set as the public endpoint's response so the admin UI can render "deleted N variants, freed Y bytes" directly from this body.
type AdminVariantDeleter ¶ added in v0.1.3
type AdminVariantDeleter interface {
Delete(ctx context.Context, req AdminVariantDeleteRequest) (AdminVariantDeleteResponse, error)
}
AdminVariantDeleter is the admin-side interface the `DELETE /api/upscale/variants` handler consumes. The single `Delete` method takes a parsed admin-package request and returns a parsed admin-package response — the adapter in cmd/bridge/main.go translates to/from the `api` package's `VariantDeleteRequest` / `VariantDeleteResponse` shapes so the admin package stays free of `internal/api` (same decoupling pattern as `AdminBatchCoordinator`).
Errors:
- `ErrAdminVariantDeleterUnavailable` (sentinel) → handler emits 503 service_unavailable. Distinguishes "feature off on this bridge" from "feature on but listing failed".
- Any other error → handler emits 500 internal, with the error surface text in the response body.
type Deps ¶
type Deps struct {
CfgHolder *config.RuntimeConfig // live config snapshot holder
CfgPath string // path to bridge.yaml, for Config.Save()
Auth *auth.Store // token list / mint / revoke
Manifest *manifest.Store
Scanner *manifest.Scanner
Resolver *bridgefs.Resolver
// Fingerprint is the TLS cert SHA-256 in colon-hex form. Shown in the
// pairing modal and the dashboard.
Fingerprint string
// StartedAt is used to render uptime. Typically time.Now().UTC() at
// the moment the serve command completes its init.
StartedAt time.Time
// Restart is called when the operator clicks "Restart now" on a
// restart-required settings edit. Nil means os.Exit(0) — fine when
// running under launchd/systemd which will relaunch.
Restart func()
// ScanCtx is the parent context for admin-triggered scans. serveCmd
// should pass the same context it passes to scanner.RunPeriodic so a
// shutdown cancels any admin-triggered scan along with the periodic
// one. Nil defaults to context.Background() — only acceptable for
// tests that don't care about goroutine cleanup.
ScanCtx context.Context
// Updater is the optional read-side of the update poller. Wired via
// an adapter in cmd/bridge/main.go so this package doesn't import
// internal/updater. Nil-safe — when absent, the dashboard's update
// tile shows "not configured" and the /api/updates endpoint
// returns the same fallback shape.
Updater UpdateProvider
// BackupSources is the resolved set of state-file paths the
// admin's "Snapshot now" button hands to `backup.Snapshot`.
// Injected from `cmd/bridge/main.go` via the same
// `buildBackupSources` helper the CLI uses, so paths can't drift
// between the two surfaces (CodeRabbit + Gemini both flagged the
// prior local helper as a divergence risk on PR #44). Zero-value
// is treated as "feature not wired" and the API endpoints return
// 503 — fine for tests that don't construct the admin server
// with backup wiring.
BackupSources backup.Sources
// Tailscale is the read+refresh side of the Tailscale HTTPS
// auto-pilot. Nil-safe — when absent, the dashboard's Tailscale
// tile shows "not configured" and the /api/tailscale endpoints
// return the same fallback shape. Wired via an adapter in
// cmd/bridge/main.go so this package doesn't import
// internal/tailscale or the cmd/bridge auto-pilot type.
Tailscale TailscaleProvider
// Pairing backs the admin-approval pairing flow. Optional — when
// nil, /api/pairing returns an empty list and the approve / decline
// handlers reply 503 (so a misconfigured deployment surfaces a
// distinct error rather than silently dropping operator clicks).
// The iOS-facing /v1/pairing/* endpoints are gated by the api
// package's own pairing wiring; both sides receive the same Store
// from cmd/bridge/main.go.
Pairing *pairing.Store
// UpscalePrecheck probes whether the upscale feature can run on
// this host (sox on PATH, --version returns within 2 s). Wired
// to `transcode.PrecheckSox` via a closure in cmd/bridge/main.go
// so this package doesn't import internal/transcode (matches the
// MBIDProbe / UpdateProvider decoupling pattern). Nil-safe — when
// absent the Settings response omits the `upscaleSoxAvailable`
// field and the UI hides the warning banner.
UpscalePrecheck func() error
// UpscaleStats returns a snapshot of the long-lived
// transcode pool's counters (workers, queue length, in-
// flight jobs, lifetime totals). Wired via a closure in
// cmd/bridge/main.go so the admin package stays decoupled
// from internal/transcode. The closure returns nil when
// the feature is off (Pool isn't instantiated); the admin
// endpoint then omits the `pool` field instead of
// surfacing zero-padded clutter ("0/0 queue, 0 inflight"
// would suggest the pool exists but is idle, which is
// semantically wrong).
UpscaleStats func() *UpscalePoolStats
// ProjectedSize estimates the on-disk size of a FLAC
// variant produced from (sourceSize, sourceRate, sourceBits)
// at (targetRate, targetBits). Wired to
// `transcode.ProjectedSize` (with `DefaultCompressionFactor`
// baked in) via a closure in cmd/bridge/main.go so the admin
// package doesn't import internal/transcode. Mirrors the
// UpscaleStats / UpscalePrecheck decoupling pattern.
//
// Nil when upscale is disabled — the projection endpoint
// surfaces a clean 503 in that case.
ProjectedSize func(sourceSize int64, sourceRate, sourceBits, targetRate, targetBits int) int64
// AvailableDiskSpace probes free bytes on the volume holding
// `dir`. Wired to `transcode.AvailableDiskSpace`. Nil-safe
// alongside ProjectedSize (both wired together when upscale
// is enabled, both nil when disabled).
AvailableDiskSpace func(dir string) (int64, error)
// BatchCoordinator is the v1.3 admin Library Inspector's gateway
// to the transcode.Coordinator. Wired to a closure-based adapter
// in cmd/bridge/main.go (same decoupling pattern as
// UpscaleStats / UpscaleEnqueuer). Nil-safe — when absent the
// admin Library Inspector renders the folder tree but the
// "Upscale this folder" trigger surfaces a 503.
BatchCoordinator AdminBatchCoordinator
// VariantDeleter is the admin-side gateway to the same
// list/unlink/DeleteVariant/SSE-publish pipeline that powers
// the public `DELETE /v1/upscale/variants` endpoint. Wired in
// cmd/bridge to an adapter around `api.Server.RunVariantDelete`
// so the admin console and the iOS app go through exactly one
// code path on the way out — no risk of drift between the two
// destructive surfaces. Nil-safe: when absent (upscale disabled
// on this bridge OR pre-feature build) the admin handler
// surfaces 503 service_unavailable, matching the
// `BatchCoordinator == nil` shape on the same page.
VariantDeleter AdminVariantDeleter
// IsSupervised reports whether the current process is running
// under launchd / systemd / Windows SCM — i.e. whether
// `os.Exit(0)` will trigger an automatic relaunch. Threaded
// through to `settingsResponse.IsSupervised` so the admin UI
// can show "Restart now" (auto-relaunch promised) versus
// "Stop now (manual restart required)" (operator must run the
// service back up themselves). Wired in `cmd/bridge/main.go`
// from `supervision.IsSupervised()`. Defaults to false in
// test harnesses, which yields the conservative — never-
// promise-relaunch — UI wording, never the lying one.
IsSupervised bool
}
Deps bundles the runtime state the admin console reads and mutates. All fields are required unless marked optional.
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server owns the admin listener + mux. One per process.
func New ¶
New constructs an admin Server. Call Handler to get the http.Handler for ListenAndServe, or Serve to run a background listener with graceful shutdown.
type TailscaleProvider ¶ added in v0.1.2
type TailscaleProvider interface {
Status() TailscaleStatus
RefreshNow(ctx context.Context) TailscaleStatus
}
TailscaleProvider is the read+refresh side of the Tailscale auto-pilot the admin tile reads. The adapter in cmd/bridge/main.go wraps the process-scoped autopilot so the wire shape lives entirely in this package — keeps internal/admin decoupled from cmd/bridge's implementation choices (background renewer cadence, mint trigger strings, etc.).
type TailscaleStatus ¶ added in v0.1.2
type TailscaleStatus struct {
CLIAvailable bool `json:"cliAvailable"`
NodeName string `json:"nodeName,omitempty"`
MagicDNSName string `json:"magicDNSName,omitempty"`
HTTPSCertsEnabled bool `json:"httpsCertsEnabled"`
CertPresent bool `json:"certPresent"`
CertNotAfter *time.Time `json:"certNotAfter,omitempty"`
CertPath string `json:"certPath,omitempty"`
MagicDNSURL string `json:"magicDNSURL,omitempty"`
LastError string `json:"lastError,omitempty"`
LastChecked *time.Time `json:"lastChecked,omitempty"`
}
TailscaleStatus is the JSON shape /api/tailscale/status returns. Mirrors `cmd/bridge/tailscaleStatus` but lives here so the admin package compiles without importing cmd/bridge.
Optional time fields are pointers (Qodo on PR #102): a non-pointer `time.Time` with `json:",omitempty"` still serialises the zero value `"0001-01-01T00:00:00Z"` because `omitempty` doesn't recognise time-zero. Pointer form honours `omitempty` correctly. Matches the `tokenRow.ExpiresAt *time.Time` precedent.
`MagicDNSURL` is the operator-facing bridge URL on the magic-DNS endpoint, including the configured listen port (NOT a hard-coded `:7788` — operators using non-default `cfg.ListenAddress` need the right URL surfaced for manual recovery, CodeRabbit on PR #102).
type UpdateProvider ¶ added in v0.1.1
type UpdateProvider interface {
Status() UpdateStatus
CheckNow(ctx context.Context) UpdateStatus
Install(ctx context.Context, force bool) (UpdateStatus, error)
Rollback(force bool) error
}
UpdateProvider is the read-side of the updater used by the admin console. Implemented by the adapter in cmd/bridge/main.go around internal/updater.Updater. CheckNow takes a context so a slow GitHub response can be cancelled if the operator's browser disconnects.
Install and Rollback return errors the admin handler classifies via errors.Is against the package-level sentinels below (ErrNoUpdate / ErrActiveSessions / ErrInstallNotSupported / ErrPathNotWritable). The adapter is responsible for mapping internal/updater's typed errors onto these admin-facing sentinels so this package stays decoupled from internal/updater's API.
type UpdateStatus ¶ added in v0.1.1
type UpdateStatus struct {
CurrentVersion string `json:"currentVersion"`
LatestVersion string `json:"latestVersion,omitempty"`
UpdateAvailable bool `json:"updateAvailable"`
ReleaseNotesURL string `json:"releaseNotesURL,omitempty"`
Channel string `json:"channel"`
LastCheck time.Time `json:"lastCheck,omitempty"`
LastError string `json:"lastError,omitempty"`
MinClientVersion string `json:"minClientVersion,omitempty"`
CanInstall bool `json:"canInstall"`
// DeferredReason is the most-recent gate-refusal explanation
// from the auto-installer. Empty when the previous cycle
// either installed the candidate, found no candidate, or
// hadn't yet polled. Currently the only populated reason is
// the MinClientVersion compat gate ("would orphan device(s):
// X"); future gates can extend the same field. Surfaced in
// the dashboard as a yellow "held update" card.
DeferredReason string `json:"deferredReason,omitempty"`
}
UpdateStatus is the wire shape /api/updates returns. Decoupled from internal/updater so the admin package compiles without importing it.
CanInstall is the platform-capability flag the dashboard template uses to gate the "Install & restart" button. False on Windows (and any future platform where the swap path is unimplemented) so the operator never sees a button that returns 501. The adapter in cmd/bridge/main.go fills this from runtime.GOOS at construction time — capability is fixed for the lifetime of the process.
type UpscalePoolStats ¶ added in v0.1.2
type UpscalePoolStats struct {
Workers int `json:"workers"`
QueueCap int `json:"queueCap"`
QueueLen int `json:"queueLen"`
Inflight int `json:"inflight"`
Enqueued uint64 `json:"enqueued"`
Done uint64 `json:"done"`
Failed uint64 `json:"failed"`
}
UpscalePoolStats mirrors `transcode.PoolStats` field-for- field but lives here so the admin package compiles without importing internal/transcode. The wiring closure in cmd/bridge/main.go translates between the two value types.
Source Files
¶
- admin.go
- handlers_api.go
- handlers_backups.go
- handlers_cert.go
- handlers_events.go
- handlers_library_browse.go
- handlers_library_inspector.go
- handlers_library_search.go
- handlers_pages.go
- handlers_pairing.go
- handlers_tailscale.go
- handlers_token_lifecycle.go
- handlers_upscale_delete.go
- handlers_variants_dir.go
- pairing.go