Configuration - HULKs/labello GitHub Wiki

Server configuration

Labello reads its server configuration from labello.server.toml in the current working directory. Set LABELLO_CONFIG to use a different path. If the selected file does not exist, the server creates its parent directories and writes the default configuration before starting.

The tracked labello.server.example.toml contains every supported setting. Copy it when you want to prepare a configuration before the first start:

cp labello.server.example.toml labello.server.toml

labello.server.toml is ignored by Git because it can contain credentials. Keep production secrets in the environment or another secret-management system.

Configuration file

The complete example lists supported server fields. Top-level settings and [developmentAuth] are required; unknown fields are rejected. OAuth, import, and prelabel sections are optional, but require their complete settings when present. Individual import, preview, and export limits use their documented defaults when omitted. Environment overrides apply after file loading.

Top-Level settings

Setting Default Description
bind "127.0.0.1:8080" Socket address on which the API listens. It must parse as an IP address and port, such as 127.0.0.1:8080 or [::1]:8080. LABELLO_BIND overrides it.
datasetsRoot "datasets" Filesystem directory containing all datasets and server authentication state. Relative paths are resolved from the server process working directory. LABELLO_DATASETS_ROOT overrides it.
bootstrapAdmins ["admin"] Internal user IDs allowed to create datasets. This does not replace per-dataset role checks. GitHub users have IDs such as github_123456.
browserOrigins Local Trunk origins Exact browser origins allowed to make credentialed cross-origin API requests. At least one origin is required.
sessionCookieSecure false Whether session cookies receive the Secure attribute. Set this to true when the browser reaches the API through HTTPS.

Browser origins

Each browserOrigins entry must be an http or https origin with a host and optional port. Paths, credentials, queries, fragments, wildcards, and empty lists are rejected. For example:

browserOrigins = ["https://label.example.com"]

Use the exact hostname seen by the browser. localhost and 127.0.0.1 are different origins and different cookie hosts.

Authenticated unsafe requests require the session-bound token returned as csrfToken by the login and GET /me responses. Send it in x-csrf-token. Browser mutations must also carry an Origin that exactly matches browserOrigins; token-bearing native clients may omit Origin. Local development login always requires a configured browser origin.

Credentialed CORS preflights allow content-type, x-csrf-token, idempotency-key, upload-offset, upload-length, and digest for current and planned mutation protocols.

Bootstrap administrators

bootstrapAdmins grants only the server-level ability to create a dataset. Dataset access remains controlled by the annotator, reviewer, and data-admin roles stored with each dataset. Keep at least one reachable account in the list when dataset creation is required.

Local development login

The [developmentAuth] section is required.

Setting Default Description
developmentAuth.localAdminLogin true Enables one-click session login as the first configured bootstrap administrator. It requires a loopback bind address and a valid bootstrap administrator.

Local administrator login is intended only for a trusted local environment. Disable it for any internet-facing deployment:

[developmentAuth]
localAdminLogin = false

GitHub OAuth

GitHub OAuth is disabled when [githubOauth] is absent. To configure it in the file, uncomment the entire section and replace every placeholder:

[githubOauth]
clientId = "your-github-client-id"
clientSecret = "your-github-client-secret"
redirectUri = "https://api.example.com/auth/github/callback"
Setting Default Description
githubOauth.clientId None Client ID from the GitHub OAuth App.
githubOauth.clientSecret None Client secret from the GitHub OAuth App. Do not commit a real value.
githubOauth.redirectUri None API callback URI registered with GitHub, ending in /auth/github/callback.

The browser application's public URL belongs in the GitHub OAuth App's homepage field. The callback must point to the API, not the browser client. Keep the browser and callback hostnames consistent throughout local cookie flows.

redirectUri must be an absolute HTTP(S) URL without user information, query, or fragment. Its path must end exactly in /auth/github/callback. An optional deployment prefix consists of nonempty slash-separated segments containing only ASCII letters, digits, -, _, ., or ~. Dot segments . and .., repeated slashes, backslashes, whitespace, cookie-attribute delimiters, and all percent encoding are rejected at startup. Encoded paths are rejected rather than decoded or normalized, so the configured public path is the cookie path's authority.

The OAuth flow cookie uses the callback's parent path for both creation and expiry. For example, callbacks at /auth/github/callback, /api/auth/github/callback, and /labello/api/auth/github/callback use /auth/github, /api/auth/github, and /labello/api/auth/github, respectively. The proxy may strip the deployment prefix before forwarding to the API; browsers match cookies against the public path before that forwarding happens. Session cookies retain Path=/. The flow cookie retains its ten-minute lifetime, HttpOnly and configured Secure/SameSite attributes. See the rollout instructions when replacing a proxy cookie-path workaround.

Environment variables

The server first loads or creates the TOML file, then applies environment overrides.

Variable Effect
LABELLO_CONFIG Selects the configuration file path. Defaults to labello.server.toml.
LABELLO_DATASETS_ROOT Overrides datasetsRoot.
LABELLO_BIND Overrides bind.
GITHUB_CLIENT_ID Overrides githubOauth.clientId when all three GITHUB_* variables are present.
GITHUB_CLIENT_SECRET Overrides githubOauth.clientSecret when all three GITHUB_* variables are present.
GITHUB_REDIRECT_URI Overrides githubOauth.redirectUri when all three GITHUB_* variables are present.
RUST_LOG Sets the tracing filter. This is not a TOML setting.
LABELLO_LOG_FORMAT Selects text or json logs. Defaults to text and is not a TOML setting.

All three GITHUB_* variables must be present for the environment to enable or replace GitHub OAuth. A partial set is ignored. See operations.md for logging and redaction requirements.

Browser runtime configuration

The Axum server and browser distribution are deployed separately. The WASM application therefore cannot safely read labello.server.toml: it must know the API address before making its first API request, and the server file may contain OAuth credentials.

The tracked labello.client.example.json contains every supported browser field with its default value:

{
  "apiBaseUrl": null
}

Copy it to the public runtime file before setting a deployment-specific API URL:

cp apps/labello-wasm/labello.client.example.json \
  apps/labello-wasm/labello.client.json

"apiBaseUrl": null is equivalent to omitting the field and selects the hostname-derived port 8080 fallback. Set it to an absolute URL to configure a deployment:

{
  "apiBaseUrl": "https://api.example.com"
}

The runtime apps/labello-wasm/labello.client.json is ignored by Git. When it exists, a Trunk post-build hook copies it into the distribution root. When it is absent, the build still succeeds and the client uses the hostname-derived fallback. Operators may replace the deployed copy without rebuilding the WASM bundle. A browser reload is required after a deployed-file change; restart trunk serve after changing the source runtime file.

The client resolves its API URL in this order:

  1. The api URL query parameter, when present.
  2. apiBaseUrl from labello.client.json, when present.
  3. http(s)://<browser-host>:8080.

apiBaseUrl must be an absolute HTTP or HTTPS URL with a host. It must not contain credentials, a query, or a fragment. A non-root path prefix is supported only when it ends in /, for example https://example.com/labello-api/. Unknown fields, malformed JSON, files over 16 KiB, and invalid configured URLs stop browser startup with a bounded error that does not repeat the URL. An HTTP 404 means the runtime file is absent and uses the legacy fallback. Other fetch failures stop startup.

The HTML bootstrap starts the runtime-file fetch alongside WASM loading, without browser caching on each page load. Rust validates that response before starting API requests. It is a public artifact and must never contain OAuth credentials, cookies, tokens, or other secrets. Static hosting must return a real 404 for an absent labello.client.json, rather than rewriting that path to index.html.

Dataset import

Dataset import is disabled when [import] is absent. Enabling it exposes the four version-one YOLO detection, YOLO pose, COCO instances, and COCO keypoints profiles only when the filesystem also provides Linux beneath-open and atomic no-replace publication guarantees. Startup fails if configured server roots do not exist, overlap the datasets root, overlap each other, or use duplicate or unsafe IDs.

The tracked example shows enabled = true, retainRawSource = false, failedRetentionHours = 24, and successfulMetadataRetentionDays = 30 when import is configured. These are explicit settings, not defaults for a missing section. It also declares one [import.serverRoots](/HULKs/labello/wiki/import.serverRoots) entry. For a browser-upload-only deployment, omit that array entry and set serverRoots = [] inside [import].

Setting Description
import.enabled Enables import capability probing and all supported profiles.
import.serverRoots Optional list of server-side source roots. Browser folder import remains independent of this list.
import.retainRawSource Retains copied/uploaded raw source after successful publication when true.
import.failedRetentionHours Retention period for failed or cancelled job metadata.
import.successfulMetadataRetentionDays Retention period for successful job metadata.
import.serverRoots[].id Safe opaque ID advertised to clients; paths are never advertised.
import.serverRoots[].path Existing source directory outside and non-overlapping with datasetsRoot.
import.serverRoots[].allowedOwners Bootstrap administrator user IDs allowed to see and select this root. An empty list allows any bootstrap administrator. Invalid user IDs fail startup.

The two retention values configure storage cleanup policy, but the production server does not currently schedule that cleanup. Startup recovery separately expires abandoned non-protected jobs; it does not provide periodic cleanup of retained failed, cancelled, or successful metadata. See Dataset Import operations.

Import limits

The optional [import.limits] section controls every limit enforced by the storage import service. Omit the section to retain all storage defaults, or set only the fields that need to differ. Byte limits are literal bytes; the values below correspond to the binary-size defaults used by storage.

Setting Default Enforced limit
import.limits.concurrentBuildJobs 1 Concurrent preflight/build jobs for the server.
import.limits.imageValidationWorkers 8 Maximum concurrent YOLO image decoders; configurable up to 32.
import.limits.decodedImageMemoryBytes 5_368_709_120 (5 GiB) Aggregate image-validation memory reservation shared by concurrent preflights. Reservations include encoded bytes, worst-case decoded output, and an extra decoded canvas for GIF validation.
import.limits.concurrentBrowserUploadJobs 2 Concurrent browser upload jobs for the server.
import.limits.activeReservationsPerOwner 2 Active destination reservations per owner.
import.limits.browserSourceFiles 25_000 Files registered by one browser source.
import.limits.browserSourceBytes 21_474_836_480 (20 GiB) Total bytes registered by one browser source.
import.limits.serverSourceFiles 50_000 Files copied from one server-directory source.
import.limits.totalSourceBytes 107_374_182_400 (100 GiB) Total bytes in any source.
import.limits.selectedImages 10_000 Images selected by preflight.
import.limits.singleSourceFileBytes 4_294_967_296 (4 GiB) Bytes in one source file.
import.limits.descriptorBytes 16_777_216 (16 MiB) Bytes read from one dataset descriptor.
import.limits.uploadChunkBytes 8_388_608 (8 MiB) Bytes accepted in one browser upload chunk.
import.limits.sourcePathBytes 1_024 Bytes in one normalized relative source path.
import.limits.sourcePathDepth 32 Components in one relative source path.
import.limits.sourceComponentBytes 255 Bytes in one source path component.
import.limits.selectedCategories 100 Categories selected by preflight.
import.limits.selectedTasks 200 Tasks generated by an import plan.
import.limits.coverageEntries 2_000_000 Image-task coverage entries generated by an import plan.
import.limits.annotationsTotal 1_000_000 Annotations generated by an import.
import.limits.annotationsPerImage 10_000 Annotations generated for one image.
import.limits.generatedFileBytesPerImage 67_108_864 (64 MiB) Bytes in a generated event log or state file for one image.
import.limits.keypointsPerSkeleton 512 Keypoints in one skeleton category.
import.limits.yoloLineBytes 1_048_576 (1 MiB) Bytes in one YOLO annotation line.
import.limits.yoloColumns 4_096 Columns in one YOLO annotation line.
import.limits.structuredDataNesting 64 JSON or YAML nesting depth.
import.limits.decodedImagePixels 50_000_000 Decoded pixels in one image.
import.limits.decodedImageBytes 536_870_912 (512 MiB) Decoded image memory used by image validation.
import.limits.stagedBytes 268_435_456_000 (250 GiB) Source, spool, and generated output bytes staged by one import.
import.limits.diagnosticExamplesPerCode 100 Stored diagnostic examples for each diagnostic code.

Every limit must be greater than zero and fit the server platform's numeric types. Limits advertised through the client capability contract must also fit that contract without truncation. Startup rejects contradictory ceilings: browser bytes or a single file cannot exceed total source bytes; descriptors and upload chunks cannot exceed a single file; path component and depth limits cannot exceed the path byte limit; per-image annotations cannot exceed total annotations; YOLO columns cannot exceed line bytes; generated per-image files cannot exceed staged bytes; and total source bytes cannot exceed staged bytes.

Image validation also requires:

decodedImageMemoryBytes >= singleSourceFileBytes + (2 * decodedImageBytes)

The reservation covers the encoded source file, its worst-case decoded output, and a second decoded canvas required by GIF validation. With the defaults this is exactly 4 GiB + (2 × 512 MiB) = 5 GiB. Startup rejects configurations that overflow while calculating the minimum or whose memory budget is below it with an error naming all three settings.

Server-root capability filtering is fail closed: only roots present in the loaded configuration and authorized for the current bootstrap administrator are advertised. The filesystem path is never returned by the API.

COCO keypoint imports may explicitly pair one instances descriptor with one keypoints descriptor by assigning both the same release, split, and pairingGroup. Descriptor kinds are retained in the committed import manifest. Descriptors without a pairing group remain separate even when their numeric image, category, or annotation IDs match.

Imports reserve and publish destinations under a process-local datasets-root mutation lock shared with normal dataset creation. Run only one server process per datasets root. Import staging under .labello-server/imports is private server state and is never listed as a dataset.

Image preview limits

The optional [previews] section has cacheRoot = ".labello-preview-cache", relative to the process working directory. It must be separate from, and neither an ancestor nor a descendant of, datasetsRoot after resolving existing path components. The production service working directory is /var/lib/labello, so the default cache is outside /var/lib/labello/data backups. The server creates the private cache lazily; one process owns its filesystem lock.

[previews.limits] fields can be overridden independently:

Field Default Accepted bound
maxSourceBytes 67,108,864 (64 MiB) 1–536,870,912
maxPixels 32,000,000 1–100,000,000
maxDecodedBytes 268,435,456 (256 MiB) 1–1,073,741,824
workers 2 1–8
cacheBytes 2,147,483,648 (2 GiB) 1–1,099,511,627,776
cacheEntries 4096 1–16,384

Source bytes, decoded pixel count, decoder allocation budget and decoded output bytes are checked before generation. Resize/encoder work is additionally bounded by source pixels and the fixed output profiles; maxDecodedBytes is a decoder budget, not a total process-RSS cap. The same worker pool and source/decoder limits cover encoded previews, original-detail reads and legacy RGBA fallback. Thumbnail and foreground requests have separate bounded admission queues, each allowing at most 16 * workers waiting or running requests. Admitted requests wait for a worker; only a full admission queue fails promptly. With two or more workers, thumbnails use at most workers - 1 slots so opening an image retains foreground capacity. A single-worker configuration serializes both lanes. Cancelled waiting requests release admission; already-started work retains its permits until it finishes. Identical source/profile requests share one generation. Encoded output is limited to 16 MiB. Cache accounting includes per-entry metadata and reserves space before temporary publication; the zero-byte lock file is not charged.

The service rejects zero/out-of-range limits and unknown settings. Changing limits requires restart. Smaller cache limits trigger eviction on first access. See operations for disposal and recovery.

Export limits

Linux servers initialize dataset export when [export] is absent. Other platforms advertise export as unavailable. The section uses camelCase fields and rejects unknown fields.

Export size is unlimited by default. Omit maxImages, maxFiles, maxSourceBytes, maxFileBytes, maxDecodedImageBytes, maxArchiveBytes, and maxMetadataBytes to impose no application quota. JSON capabilities report these omitted limits as null. Existing explicitly configured numeric limits continue to apply; remove those entries to remove their quotas. Numeric limits must be positive, and maxFiles, when present, must be at least three. There are no fixed upper ceilings for these settings. Byte values are bytes.

Concurrency and retention remain independently configurable:

[export]
maxConcurrentJobs = 1
maxConcurrentDownloads = 2
maxRetainedJobs = 8
retentionSeconds = 86_400

Accepted concurrency is one through four jobs and one through eight downloads. Retained-job capacity must accommodate configured workers and cannot exceed 64; retention is one second through seven days. These settings control simultaneous work and cleanup, not how many images one export can contain.

Unlimited size is not a disk reservation or a constant-memory guarantee. Image validation decodes one image at a time; dataset metadata, annotation provenance, and ZIP directory entries require memory proportional to the captured content. See export operations for disk planning and recovery. An optional maxMetadataBytes also bounds source configuration, image-index and per-image event-log reads, as well as generated metadata.

Dataset assignment balance

Dataset administrators configure overlapping-box visibility in Administration > Automation > Overlapping boxes. The dataset setting defaults to 0.9 when absent:

[boundingBoxVisibility]
iouThreshold = 0.9

The threshold must be finite and within [0, 1]. Boxes compete only within the same image and class, across that image's workflows. A box is hidden when its IoU with a retained box is strictly greater than the threshold. Set 1 to show all boxes. Current exact-version approval wins over an unreviewed box, which wins over a rejected box; stable annotation IDs break ties. Superseded decisions and approvals of older versions do not rank the current box.

This filters annotation, review, dataset inspection, and migration guide views. It preserves stored annotations, versions, deletion flags, image membership, statistics, and export content. Moving or deleting a retained box or changing the threshold can reveal a previously hidden box. Reload other open clients to pick up a changed setting. Saving configuration updates the current client's views and releases its prepared assignments. Stale review submissions fail without partial writes and require a fresh assignment.

Pending migration targets with hidden guide boxes are skipped. Existing skeletons and their review targets remain. Changing visibility does not rewrite completed history or automatically reopen completed tasks. See workflow policy for the shared selection and replay contract.

Dataset administrators also configure preloadQueueSize in Administration > Automation. It is a top-level integer in labello.dataset.toml, defaults to two when absent, and accepts 1 through 200 upcoming assignments. The current image is additional. The setting applies to both annotation and review; the old browser queueSize URL parameter is ignored. Live clients reconcile the target through their existing availability refresh, normally within 30 seconds. Shrinking releases surplus prepared assignments and preserves current work.

Queue depth is a target, not a reservation of memory or balance capacity. Larger queues use more browser memory and bandwidth and hold work away from other workers. Each prepared assignment has a 30-minute lease; expired entries are discarded and released instead of being promoted. Decoded pixels alone can use 6.25 MiB per square 1280-pixel preview, or 625 MiB for 100 entries, before image state and transient allocations. Choose smaller queues on constrained devices. The 200-entry ceiling bounds pixels alone to about 1.22 GiB; it is not a recommended default. WebAssembly can retain its peak allocated memory for reuse after entries are removed. Workspace navigation releases unused reservations with at most eight release requests in flight. Closing the tab instead leaves server reservations to expire under the normal lease policy.

Assignment balance belongs to each versioned labello.dataset.toml, not the server configuration above. Data administrators edit it through the Administration Automation view or the dataset administration API.

This example enforces an absolute window of five completed images:

[imbalance]
enforce = true
maxDifference = 5

maxDifference is a non-negative 64-bit integer; zero is valid. Ratio-based configuration is not supported. Legacy ratio settings must be replaced before startup. For example, replace:

[imbalance]
maxRatio = 2.0
enforce = true

with an explicitly chosen maxDifference. Labello rejects maxRatio, tagged policy objects, negative values, and values larger than an unsigned 64-bit integer rather than guessing an absolute window. Omitting imbalance, or setting enforce = false, disables assignment blocking. See Assignment for count, denominator, peer, zero-count, and exact-boundary semantics.

Item queue history and Overview backlog

Administrators configure these settings in Automation. In labello.dataset.toml:

[workflowQueue]
historyDepth = 5
# maxPendingOverviews = 20

historyDepth accepts 0 through 100. It bounds backward navigation and the retained forward reservation window, in addition to the current item. maxPendingOverviews is optional and must be positive when present. It counts distinct images awaiting Overview, reserving capacity for active object work on new images. Already started images can finish their prerequisite objects even at the limit. Omission disables the limit. Objects and Overview share one task's image-based completion balance.

Reviewer corrections

Dataset task review configuration retains the legacy allowReviewerCorrections field for file and wire compatibility. Both false and true allow corrections in approval review; the administration UI no longer offers the switch. Every rejection requires substantive reviewer changes and creates a fresh review round. Each current-round object and the final Overview require one approval, including guided migration. Different reviewers may approve different items. Objects exclude the final annotation author; Overview excludes anyone who contributed annotation or review work to that image. Viewing or saving an empty proposal does not count as a contribution. A recorded self-review exception is allowed only when the user has no independent work available in any review queue in the dataset. Historical requiredReviews values are normalized by the dataset review-policy upgrade. Revisiting one's own independently approved Overview retains its original eligibility when the history target and captured fingerprints remain valid. Other available review work does not block that revisit. New reviews and history originally admitted by fallback retain the dataset-wide exclusion checks.

Production guidance

Use TLS, secure session cookies, exact browser origins, and external secret injection. Disable local admin login. Run one server per datasets root and back up that root, including authentication state. Follow deployment for the supported guest layout and operations for backup and recovery.

Prelabel models and limits

Enable [prelabel] with an operator-managed modelsRoot to support server-managed detection and pose models. Omission disables model inspection, generation and management. Add the section to labello.server.toml or the file selected by LABELLO_CONFIG, restart the server, and reload the web app. When omitted, Automation and workflow settings show "Prelabeling is disabled by server configuration." Annotators never have model selection or generation controls. Existing dataset model configurations and workflow bindings are preserved.

timeoutSeconds defaults to 120 and accepts 1 through 300. [prelabel.limits] fields are optional and use the values in the server example. Model supply, supported tensor shapes, processing, Linux worker limits and retention are defined in Model prelabels.

Optional [prelabel.runtime] settings onnxLibrary and webgpuLibrary select operator-owned native ONNX Runtime and WebGPU plugin libraries. Server inference tries CUDA, WebGPU, then CPU; missing native runtime libraries retain the included Tract CPU fallback. Runtime/provider failures do not disable the whole feature. The model check and inference share the configured worker concurrency limit. See Model prelabels for dependencies, resource bounds and the overall timeout budget.

Optional [prelabel.workers] controls retained inference processes:

Field Default Range
maxWorkers 4 1–8
idleTimeoutSeconds 120 1–3600
threadsPerWorker 1 1–16

Workers are scoped to dataset/account or dataset generation run. Each caches two compiled models. maxWorkers bounds live processes, including idle workers and ones being reaped; limits.maxConcurrentInferences separately bounds active inference and model checks. Native runtime thread counts use threadsPerWorker; the included Tract backend is single-threaded. Idle processes expire within the configured interval plus at most 30 seconds, or earlier under capacity pressure. Interactive requests have priority between batch items. Up to 64 wait for admission for at most 30 seconds; saturation returns busy; managed preparation reports its pending or failed state.