Homelab App Discovery
Automatic identification of API endpoints and authentication mechanisms for self-hosted
apps registered through the Homelab Scanner. The system uses two complementary strategies:
label-based static discovery (authoritative) and runtime probe-based detection
(optional best-effort).
Overview
The discovery subsystem enriches scanned homelab apps with capability, endpoint, and
authentication metadata, enabling the Hub to present discovered capabilities and the
health checker to probe API endpoints directly.
docker-compose labels runtime HTTP probes
\ /
\ /
App Scanning (scanner.go)
|
ParseLabels() + ProbeEngine
|
App.Capabilities[] field
|
hub.AutoBind() / LogDiscovered()
|
hub.EndpointHealthChecker
Flowbot is assumed to run on the host. Probes only target published TCP ports
(Host / localhost + host port). Labels must use URLs that the Flowbot process can
reach. One compose file binds one capability.
Strategy 1: Label-Based Discovery (Static)
Define the capability, endpoint, and auth mechanism directly in your
docker-compose.yaml using the flowbot.* label convention. Labels are parsed
during filesystem scanning with zero runtime cost and take precedence over probes
when label_priority is true.
Label Convention
| Label | Required | Description | Example |
|---|---|---|---|
flowbot.capability |
Yes | Capability type the app provides | karakeep |
flowbot.backend |
No | Deprecated; ignored | — |
flowbot.endpoint.base |
No | Base URL reachable from the Flowbot process | http://127.0.0.1:3000/api/v1 |
flowbot.endpoint.health |
No | Health check path (relative to base) | /health |
flowbot.endpoint.health_ttl |
No | Health check cache TTL (duration string) | 30s |
flowbot.auth.type |
No | Auth type: api_token, basic, oauth2, oidc, none |
api_token |
flowbot.auth.header |
No | HTTP header name for auth | Authorization |
flowbot.auth.prefix |
No | Header value prefix | Bearer |
flowbot.auth.token_key |
No | Environment variable name holding the token | LW_API_KEY |
flowbot.auth.token_source |
No | Where to read the token: env, file, config |
env |
flowbot.endpoint.base must be reachable by the Flowbot process (typically
http://127.0.0.1:<published-port>/...). Do not use container DNS names unless you
know that name resolves from where Flowbot runs.
Supported Capabilities
| Label Value | Capability Type | Typical Backend |
|---|---|---|
karakeep |
Bookmark | karakeep |
archive |
Archive | archivebox |
miniflux |
Reader (RSS) | miniflux |
kanboard |
Kanban | kanboard |
finance |
Finance | fireflyiii |
infra |
Infrastructure | — |
shell_history |
Shell History | atuin |
Legacy domain labels (bookmark, reader, kanban, note, memo, forge) map to
the canonical provider IDs above and log a deprecation warning.
Example
# docker-compose.yaml
services:
linkwarden:
image: ghcr.io/linkwarden/linkwarden:latest
container_name: linkwarden
ports:
- "3000:3000"
labels:
flowbot.capability: "karakeep"
flowbot.endpoint.base: "http://127.0.0.1:3000/api/v1"
flowbot.endpoint.health: "/health"
flowbot.endpoint.health_ttl: "30s"
flowbot.auth.type: "api_token"
flowbot.auth.header: "Authorization"
flowbot.auth.prefix: "Bearer"
flowbot.auth.token_key: "LW_API_KEY"
flowbot.auth.token_source: "env"
With this label configuration, the scanner automatically:
- Registers
karakeepas the discovered capability for this app. - Records the base URL, health path, and auth metadata.
- The hub logs the discovery and exposes it through
/hub/capabilitiesresponses. - The health checker probes
http://127.0.0.1:3000/api/v1/healthon each check cycle.
Strategy 2: Runtime Probe Engine (Dynamic)
When labels are absent or incomplete, an optional HTTP probe engine can discover
endpoints and auth mechanisms by making requests to host-published ports on
running containers. Probes are best-effort; they do not replace labels.
What It Detects
| Discovery Target | Method |
|---|---|
| API endpoint reachability | HTTP GET to each published TCP port (http and https) |
| Health endpoint | Probes common paths: /health, /healthz, /api/health, /api/v1/health, /ping, /status |
| Authentication mechanism | Analyses HTTP 401/403 responses and WWW-Authenticate headers |
| OIDC support | Probes /.well-known/openid-configuration |
| Service fingerprint | Matches known API paths against the fingerprint database |
Auth Detection Logic
| Response | Detected Auth |
|---|---|
401 with WWW-Authenticate: Bearer |
oauth2 |
401 with WWW-Authenticate: Basic |
basic |
401/403 with no WWW-Authenticate |
api_token |
200 at /.well-known/openid-configuration |
oidc |
| 200 at base URL | none |
| Other | unknown |
Fingerprint Database
The probe engine matches known self-hosted services by path reachability only.
When a detection path responds successfully, the capability type is inferred.
| Service | Capability | Detection Path |
|---|---|---|
| LinkWarden | karakeep | /api/v1/health |
| ArchiveBox | archive | /admin |
| Miniflux | miniflux | /v1/healthcheck |
| Kanboard | kanboard | /jsonrpc.php |
| Firefly III | finance | /api/v1/about |
Probe Behavior
- Probes only run when
homelab.discovery.probe_enabledistrue. - Non-running apps are skipped (probes require an active container).
- Only TCP ports with a published host port are probed; unpublished container ports are skipped.
- Both HTTP and HTTPS are attempted for each published port.
- A configurable concurrency limit controls simultaneous probes.
- Each probe respects a configurable per-request timeout.
- Probes do not follow redirects (to avoid false positives on login pages).
- All probe requests include
User-Agent: Flowbot-Homelab-Probe/1.0.
Configuration
Add the discovery block under homelab in your flowbot.yaml:
homelab:
root: "/home/user/homelab"
apps_dir: "/home/user/homelab/apps"
compose_file: "docker-compose.yaml"
runtime:
mode: "docker_socket"
docker_socket: "unix:///var/run/docker.sock"
allowlist: []
permissions:
status: true
logs: true
start: false
stop: false
restart: false
pull: false
update: false
exec: false
discovery:
probe_enabled: true
probe_timeout: "5s"
probe_concurrency: 4
fingerprint_enabled: true
label_priority: true
Discovery Configuration Reference
| Field | Type | Default | Description |
|---|---|---|---|
probe_enabled |
bool | false |
Enable runtime HTTP probing of published ports |
probe_timeout |
duration | "5s" |
Per-request timeout for probe HTTP calls |
probe_concurrency |
int | 4 |
Maximum number of parallel probe goroutines |
fingerprint_enabled |
bool | true |
Enable service fingerprint matching by path |
label_priority |
bool | true |
When true, label-derived data takes precedence |
How Discovery Feeds Into the Hub
Scanning Phase
Scanner.Scan()readsapps/*/docker-compose.yamlfiles.ParseLabels()extracts at most oneAppCapabilityfrom labels.- If discovery probes are enabled,
ProbeEngine.ProbeAll()enriches apps with runtime data. - Results are merged into
App.Capabilities[]and persisted to PostgreSQL viahub_store.
Capability Awareness
hub.AutoBind()reads homelab registry apps and returns a list of discovered bindings
with their registration status in the hub.hub.LogDiscovered()logs discovered capabilities that are not yet configured as
full hub descriptors.- The
/hub/capabilitiesAPI endpoint exposes binding information. - The homelab app listing at
/hub/appsincludescapabilitiesin each app response.
Health Check Integration
EndpointHealthCheckerprobes discovered health URLs on each health check cycle.- Results are included in the
/hub/healthresponse alongside registered hub descriptors. - Capabilities already registered in the hub are excluded to avoid duplicate entries.
- The overall health status degrades if any endpoint health check fails.
Design Decisions
Rationale for the host-side discovery contracts:
.agents/notes/implemented/simplification/2026-09-11-homelab-discovery-host-contracts.md.
| Contract | Behaviour |
|---|---|
| Host-published ports only | Probes use Host or localhost plus the published host port; unpublished ports are skipped |
| Path-only fingerprints | Matching uses reachable detection paths only |
| One compose = one capability | ParseLabels accepts a single flowbot.capability per compose file |
Reachable endpoint.base |
Label URLs must be reachable from the Flowbot process |
| Labels beat probes | When label_priority is true, label data wins over probe enrichment |
Flat flowbot.* labels |
Compose label convention for static discovery |
| Probe engine nil when disabled | Callers check for nil instead of an empty engine |
Homelab cap strings, not hub.CapabilityType |
Avoids an import cycle between homelab and hub |
| TCP only; no redirects | UDP skipped; redirects not followed |
| Auth header names via labels | Probes may infer api_token from 401/403 without WWW-Authenticate, but not the exact header name |
Adding New Service Fingerprints
To add fingerprint support for a new service, edit
pkg/homelab/probe/fingerprints.go:
{
Capability: "your-capability",
Provider: "your-provider",
Paths: []string{"/api/v1/health"},
}
Each reachable path contributes to the confidence score. When the cumulative score
exceeds zero, the engine emits a match.
Testing
# Label parsing tests
go test -run TestParseLabels ./pkg/homelab/
# Auth detector tests
go test ./pkg/homelab/probe/ -run TestAuthDetector
# Auto-binding tests
go test ./pkg/hub/ -run TestAutoBind
# Full homelab suite
go test ./pkg/homelab/...
Image digest inspection and homelab.image.update_available live in homelab-image-updates.md.