Operations¶
Use this reference to understand configuration, server lifecycle, backup, and maintenance. For commands to run, start with the setup guide or backup guide.
Configuration¶
Fotobank selects its TOML configuration in this order:
--config <path>FOTOBANK_CONFIG$XDG_CONFIG_HOME/fotobank/config.toml$HOME/.config/fotobank/config.toml./config.toml
fotobank config init writes the embedded canonical example to the selected
path without replacing an existing file. The same example remains in
internal/config/config.example.toml for source readers.
Configuration covers local state, Docbank, NAS artifacts, identity, HTTP, imports, thumbnails, broker, backup, observability, admins, AI, search, and UI feature flags. Defaults and the narrow documented environment overrides are applied before validation. Database-backed application overrides are merged before validating the effective runtime view.
The flash, Docbank, and NAS roots become canonical absolute paths during validation. Invalid enum values, unsafe overlaps, incomplete identity boundaries, non-loopback admin listeners, and impossible retention settings fail before server startup.
fotobank config diagnose is the read-only operational check. It reports the
effective file, environment, and default configuration together with the
availability of SQLite, the Docbank catalog and blob directory, NAS artifacts,
checkout boundary configuration, identity mode, and the initialized backup
repository when scheduling is enabled. It never initializes or migrates SQLite,
opens Docbank through its mutating vault
lifecycle, creates directories, or tests storage by writing a file. A healthy
result therefore establishes readable structure and valid boundaries, not a
full database integrity check or proof that the service account can write.
Startup and shutdown¶
fotobank serve opens the SQLite database and migrations, registers the owner,
opens the embedded Docbank adapter, builds repositories and services, starts
workers, then starts the HTTP listeners.
Readiness reports whether required local dependencies and worker initialization are usable. Liveness is a simple process endpoint and does not turn transient external AI failure into process failure.
Shutdown stops HTTP intake, waits for in-flight handlers, cancels workers, waits for bounded worker completion, closes the Docbank adapter, and finally closes database resources. A worker must not outlive a collaborator it uses.
Backup and restore¶
internal/backup coordinates complete recovery archives containing the Fotobank
catalog and authoritative Docbank content. Repository storage, manifests,
locking, verification, forgetting recovery points, and pruning belong to
Docbank and are exposed through internal/content. Fotobank owns the capture
schedule and which recovery points scheduled retention selects.
backup init, backup create, backup list, backup verify, and
backup restore require an explicit --repo. Creation opens an initialized
repository; only init creates one. Repository listing and verification
run through the authenticated operator API and require configuration, but not
the original vault when the daemon runs in explicit recovery mode. Restore additionally
requires --target and uses a separate empty directory.
internal/backup.CreateArchive snapshots Fotobank SQLite into private temporary
storage during Docbank's mutation freeze and declares it as
application/catalog.sqlite in the same manifest. Preparation first checks
SQLite integrity and the schema_migrations marker using ValidateSnapshot;
empty or unrelated databases are rejected before snapshot creation. SQLite uses
a separate connection without running Fotobank migrations and captures live
WAL state with VACUUM INTO. The temporary snapshot remains
until archive creation returns, then is removed. Content already referenced by
that catalog exists before Docbank pins its state; later content appends do not
invalidate the recovery point. Manual backup create calls
BackupService.Create through the running server's authenticated local operator
interface. The service checks the configured stub principal and captures the
whole deployment using the server's existing vault and catalog path; the CLI
opens neither SQLite nor Docbank. This is a trusted host-operator capability,
not an owner-scoped photo API. The scheduled worker calls the same archive
operation independently. Both paths retain the existing repository locking and
short mutation freeze, so capture runs while the server remains available.
Scheduling is opt-in through [backup].enabled, which defaults to false. When
enabled, backup.repository must explicitly name an initialized repository
with an absolute path; home expansion is supported. backup.interval defaults
to 24 hours and backup.keep_last to 30; both must be positive. Configuration
validation checks values without initializing or opening the repository.
Runtime readiness and diagnostics inspect the configured repository rather
than creating a destination directory.
internal/backup.Worker runs serially and reads the latest persisted
fotobank:scheduled timestamp to decide when capture is due. No scheduled point
means capture is due immediately. Restarts preserve the schedule; a failed
attempt retries after min(interval, 5 minutes). A stored timestamp in the
future makes capture due immediately, so clock rollback cannot postpone it
indefinitely. The server owns the worker's context and waits for it to exit
before closing the vault or catalog.
internal/backup/scheduled_retention.go selects only recovery points tagged
fotobank:scheduled. The tag is reserved and rejected by manual
backup create --tag. After a successful capture, the worker retains the point
just created and the newest remaining scheduled points up to keep_last.
Manual archives are never selected for forgetting. The worker calls
BackupRepository.Forget and then BackupRepository.Prune; it does not delete
repository files itself. Docbank coordinates those operations and preserves
content referenced by retained recovery points.
Capture failure does not run cleanup. Cleanup failure preserves the newly created archive, logs a warning, and records a separate retention failure metric. The next successful capture retries cleanup. Archive-success metrics therefore describe successful publication independently of retention outcomes. Pruning removes unused packs and rewrites sparse packs with less than 50% live indexed bytes. Packs at or above that threshold can retain unused bytes; pruning does not rewrite every partially used pack.
Complete archives include all owners and hidden media. They do not include
configuration files, provider credentials, disposable artifacts, or checkout
files that have not been committed. Repository verification checks the stored
catalog bytes, not Fotobank relationship semantics. The archive integration
test restores both databases and resolves a catalog file through
contentresolver, checking node, version, digest, size, and original bytes.
backup restore [snapshot-id] --repo ... --target ... uses
the recovery-only ArchiveRestoreService, which calls
internal/backup.RestoreArchive and the repository-only content restore API.
The CLI requires an already-running recovery daemon and sends the repository,
snapshot ID, and target through the documented Huma operation. The source database
selection is captured from the daemon's configuration and environment at startup,
not from the requesting client's environment.
It never opens or creates the original vault or catalog. Configuration loading
permits absent source storage but retains the configured aliases and validated
roots, plus the database directory (including FOTOBANK_DB_PATH) and configured
backup repository, as protected roots for Docbank's target validation.
The target must be separate and empty; the CLI does not expose overwrite.
config.ArchiveRestorePaths permits dangling source database symlinks, including
parent-directory links, without opening or recreating them. It checks configured
aliases lexically and supplies resolved destinations to Docbank's filesystem
checks. Ordinary database opening and lifetime-lock resolution remain strict.
After Docbank restores and verifies its snapshot and host files, Fotobank validates the captured SQLite catalog without migrations. It resolves current media-file mappings, applied import receipts, and retained checkout base versions against the restored vault, checking virtual paths against their recorded nodes where retained and comparing version ownership, digests, and sizes and reading each referenced version through verification. The catalog need not name the latest Docbank head: later appends can be included in the same snapshot. A failed check leaves the isolated target for diagnosis and returns an error rather than reporting a usable recovery. Success reports the vault and catalog paths; it does not change the deployment configuration.
Checkout records retain their original working paths. Recovery does not restore working files, relocate checkout roots, or activate the recovered deployment. Operators must review those paths before running its checkout scanner. Archive verification establishes byte integrity, and restore adds the named reference checks; neither claims whole-application metadata validation.
Observability¶
Fotobank uses structured slog logging with configurable text or JSON output,
level, and source locations. Request middleware attaches request and principal
context. Logs prefer opaque IDs and avoid source paths or media payloads.
The optional admin listener is loopback or Unix-socket only. It serves readiness, Prometheus metrics, and optional pprof endpoints. Metrics cover HTTP, imports, thumbnail and AI queues, search, shares, backups, and worker outcomes. Metrics use bounded labels; media IDs, paths, prompts, and unbounded error text do not become labels.
Maintenance¶
The durable content-operation ledger, rather than a scan of NAS paths, is the source for Docbank recovery and orphan reporting. A pending operation means the cross-database import has not yet recorded a receipt; a conflict is terminal until an explicit resolution workflow is invoked. Re-running an import resumes matching pending identities with their recorded IDs and virtual paths.
fotobank content recover asks the daemon to check every registered owner,
using its existing vault and the same lock as imports. The CLI starts a missing
daemon through the shared lifecycle path. When a pending path
already contains the reserved identity, it adopts the Docbank node and version
and finishes the asset. Different authority at the path terminalizes the asset
as a conflict. A missing path remains pending because only the original import
source can supply those bytes; re-running that import retries the stable create.
The command also walks each owner's Docbank media subtree through the bounded
embedded traversal API and reports files with no operation-ledger row. It never
deletes, moves, or overwrites unmatched authority.
Garbage collection of live authoritative content is an explicit maintenance action. Rebuildable caches may be evicted automatically. Archive retention is separately authorized by enabling the backup schedule: it removes only expired scheduled recovery points and unused repository storage after a new archive succeeds. Ordinary reads do not prune either live content or archive storage.
fotobank checkout estimate and checkout create use the running server's
local operator interface. The server resolves selections for its configured
owner, validates destination storage boundaries, and holds the creation lock
through materialization. Their CLI opens neither Docbank nor SQLite. Creation
results preserve the reserved checkout ID and completed-file count on failure;
partial files remain for inspection. Connection loss requires checking the
saved checkout list/status before retrying, not assuming creation did nothing.
fotobank checkout commit <checkout-id> is an explicit writeback operation for
settled tracked edits. It does not import untracked files, apply working-file
deletions, infer renames, or resolve conflicts. It uses the running server's authenticated local
operator interface (internal/operator). --json preserves result counts and
errors, including partial completion. The CLI never opens a second vault for
commits. Operators inspect the durable
state first with fotobank checkout list and fotobank checkout status
<checkout-id>; these daemon queries do not scan or mutate the working
copy. The status view shows the saved selection, entry-state totals, and every
file that is pending, conflicted, missing, or errored.
Those states remain visible in the checkout ledger for their dedicated
lifecycle operations. A primary edit
is not settled in Fotobank until Docbank source metadata for the committed
version is available; the pending checkout entry is the retry record.
Tests and CI¶
Go code uses the sqlite_fts5 tag, CGO, mattn/go-sqlite3, and sqlite-vec.
Tests open fresh migrated temporary databases. Content contract tests use a
real temporary embedded Docbank vault; fakes do not establish storage
behavior.
The normal local tiers are:
make test-shortfor the fast Go suite;make testfor the full Go suite;make lintfor golangci-lint and the Testify helper analyzer;make nilawayfor nilness analysis;make frontend-checkandmake test-e2efor the web product; andprek runfor the commit hooks.
GitHub Actions validates Linux amd64 tests, Windows amd64 CGO/SQLite behavior,
lint, and the shared Testify rules. All jobs use GitHub-hosted runners:
ubuntu-latest for Linux and windows-latest for Windows. Pull requests use
the reusable workflow on main, which tests the pull request commit.
Performance workloads such as 10,000-file import and repeated XMP changes are repeatable benchmarks, not timing assertions in ordinary CI. They report hashing, queue wait, Docbank write, and projection time separately.