HomelabSec is a local homelab security inventory and monitoring system.
Current release version: 0.2.0
It currently supports:
- LAN discovery with Nmap
- ingest of Nmap XML into Postgres
- asset fingerprinting
- local AI classification via Ollama
- fingerprint history
- change persistence
- daily reporting
This repo is at the stage where it can:
- discover LAN assets
- ingest network scan results
- build per-asset fingerprints
- classify assets using a custom Ollama model
- persist changes into the
changestable - generate a
/report/dailysummary
- Linux host
- Docker
- Docker Compose
- Git
- Curl
- Ollama running locally on the host
- An installed classifier model such as
homelabsec-classifier
Copy .env.example to .env and change secrets before exposing the stack beyond a local trusted environment.
Important variables:
POSTGRES_DB=homelabsec
POSTGRES_USER=homelabsec
POSTGRES_PASSWORD=change-me
OLLAMA_URL=http://host.containers.internal:11434
OLLAMA_HOST_URL=http://localhost:11434
OLLAMA_MODEL=homelabsec-classifier
OLLAMA_TIMEOUT_SECONDS=120
CLASSIFICATION_FALLBACK_ROLE=unknown
CLASSIFICATION_FALLBACK_CONFIDENCE=0.10
OBSERVATIONS_LIST_LIMIT=200
FINGERPRINTS_LIST_LIMIT=200
NOTABLE_ASSET_LIMIT=20
ADMIN_STALE_SCAN_MINUTES=90
AUTH_SESSION_DAYS=7
DEFAULT_ADMIN_USERNAME=admin
DEFAULT_ADMIN_PASSWORD=change-me-now
DEFAULT_ADMIN_DISPLAY_NAME=Administrator
LOG_LEVEL=INFO
SCHEDULER_API_BASE=http://127.0.0.1:8088
SCHEDULER_METRICS_PORT=9100compose/compose.yaml now reads Postgres credentials from env variables instead of embedding them directly in the file.
You can install directly from GitHub with:
curl -fsSL https://raw.githubusercontent.com/Twix166/homelabsec/main/install.sh | bashThe installer now:
- clones or updates the repository
- treats repo-root
.envas the source of truth and resyncscompose/.env - validates Ollama connectivity and model availability
- starts the stack with
--build - waits for
/health - validates schema readiness through
/report/summary
If install exits successfully, Ollama was reachable, the configured model existed, and the API answered both a basic health check and a schema-dependent query.
Installer-related Ollama variables:
OLLAMA_HOST_URL=http://localhost:11434
OLLAMA_MODEL=homelabsec-classifier
SKIP_OLLAMA_VALIDATION=falseOLLAMA_HOST_URL is used by the installer on the host. OLLAMA_URL is used by the brain container and defaults to http://host.containers.internal:11434.
Installer env behavior:
- edit the repo-root
.env - the installer resyncs
compose/.envonly when needed - installer-driven compose commands explicitly load
../.env, so they do not depend on whichever env file happens to be in the current working directory
Installer guarantees on success:
- the repo is present at the requested branch
.envexistscompose/.envmatches.env- Ollama was reachable unless validation was explicitly skipped
- the configured model existed unless validation was explicitly skipped
- Postgres started
- migrations ran
brain,scheduler, andfrontendstarted/healthresponded/report/summaryreturned a schema-dependent payload/versionreports the application release version
Still manual:
- choosing production-grade secrets
- deciding whether the deployment is LAN-only or exposed behind a proxy
- configuring TLS and authentication if you expose the stack beyond a trusted admin network
- setting host firewall policy
- creating and testing a recurring backup plan
- installing or updating custom Ollama models beyond the installer’s existence check
HomelabSec now includes scripted Postgres backup and restore helpers for compose-based deployments.
Backup:
./scripts/backup_db.shRestore:
./scripts/restore_db.sh /path/to/backup.sqlBoth scripts target the compose-managed postgres service by default and can be pointed at another compose file or project with environment variables:
COMPOSE_FILE=/path/to/compose.yaml
COMPOSE_PROJECT_NAME=homelabsec-test
POSTGRES_DB=homelabsec
POSTGRES_USER=homelabsecThe repo also includes automated integration coverage that performs a disposable backup and restore round trip, so this path is verified rather than only documented.
HomelabSec now uses semantic versioning.
Current version source of truth:
Runtime visibility:
GET /versionreturns the current application version/admin/statusincludes the current application version
Version bump workflow:
- update
brain/VERSION - run tests
- commit the change
- create a matching Git tag such as
v0.1.0
To print the main local access URLs without manually inspecting compose files:
./scripts/show_access_urls.shA read-only web dashboard is available from the frontend service on port 8080.
Start the stack from compose/compose.yaml:
cd compose
docker compose up -d --buildThe default compose project name is homelabsec, so the stack appears under that name instead of the directory-derived default.
On a fresh Postgres volume, the database schema is initialized automatically from brain/init.sql.
For ongoing schema changes, HomelabSec now uses versioned SQL migrations from brain/migrations/ executed by brain/migrate.py. The compose stack runs the one-shot migrate service before starting brain, so existing deployments can be upgraded without recreating the Postgres volume.
The init.sql bootstrap remains in place for brand-new Postgres volumes, but it is no longer the only schema path.
Migration authoring workflow:
- add a new versioned SQL file in
brain/migrations/ - run
python3 brain/render_init_sql.py --write - run tests
- commit both the new migration and the regenerated
brain/init.sql
If brain/init.sql drifts from the migration set, python3 brain/render_init_sql.py --check fails and the automated tests catch it.
Then open:
http://localhost:8080
The frontend proxies API requests internally to the brain service, so no backend changes are required.
The summary cards on the dashboard are clickable. Selecting Total assets, Observations, Fingerprints, or 24h changes opens a detail list for that category in the dashboard.
The dashboard also includes an Admin status panel. It shows API status, scheduler freshness, summary counts, and quick links to the main operator surfaces.
The asset inventory now uses a single unified table. Assets identified as notable by the daily report are tagged inline, and the table includes a client-side filter to switch between All assets and Most notable.
The inventory also supports confidence-color filters and sortable columns, and each asset row links to a dedicated detail page with exposed services, learned lookup data, and a host rescan action.
The web UI now has an application login page, profile menu, profile editor, and a separate admin console. The profile menu appears at the top-right of the main pages and includes Edit profile, Sign out, and Admin console when the logged-in user has admin rights.
The admin console is split into three areas:
- data enrichment: enable or disable enrichment modules such as MAC brand lookup and LLM categorisation
- raw data sources: manage source-level switches for ingest and discovery paths
- user management: create users and manage admin versus operator access
The default admin account is bootstrapped from environment variables if no users exist yet:
DEFAULT_ADMIN_USERNAMEDEFAULT_ADMIN_PASSWORDDEFAULT_ADMIN_DISPLAY_NAME
Change those defaults before exposing the deployment beyond a trusted environment.
HomelabSec now includes an optional lynis-runner microservice for host-audit enrichment.
Important design constraint: Lynis audits the system it runs on. It is not a native remote scanner like Nmap. Because of that, HomelabSec integrates Lynis by orchestrating execution on the target host over SSH.
The current flow is:
- configure an SSH-capable target for an asset from the asset detail page
- click
Lynis auditon that asset - HomelabSec queues the job
- the
lynis-runnermicroservice claims the job - the runner connects to the target host over SSH
- if
lynisis not already present, the runner installs or updates it under the remote user home directory at~/.local/share/homelabsec/lynisfrom the official CISOfy GitHub repository - the runner executes
lynis audit system --quickwith a dedicated audit timeout so longer scans do not fail at the SSH command timeout boundary - the parsed summary is stored and shown back in the Lynis modal
The runner currently assumes:
- a Unix-like target host reachable over SSH
- password-based SSH authentication
- optional
sudoon the target when deeper audit coverage is needed gitis available on the target if Lynis must be installed from the official repository
Relevant Lynis runner settings:
LYNIS_SSH_TIMEOUT_SECONDS: SSH connect and short command timeoutLYNIS_AUDIT_TIMEOUT_SECONDS: longer timeout for the actuallynis audit systemrun
The upstream source used for Lynis installation is:
- official repository:
https://github.com/CISOfy/lynis
This was chosen because it is the official upstream, open source, free to use, and actively maintained by CISOfy.
The compose stack now includes healthchecks for postgres, brain, scheduler, and frontend. brain waits for Postgres readiness, and the dependent services wait for the API health endpoint before starting.
The brain service is now built from brain/Dockerfile with pinned Python dependencies in brain/requirements.txt instead of installing packages dynamically at container startup.
HomelabSec is currently designed for trusted local-network use.
Supported deployment assumptions:
- the stack runs on infrastructure you control
- the dashboard and API are reachable only from your LAN, VPN, or another trusted admin network
- Postgres is not exposed directly to untrusted clients
- Ollama is reachable only from the local host or a trusted internal network path
Current non-goals for the default compose deployment:
- multi-user access control
- internet exposure without an additional reverse proxy and auth layer
- direct public access to the API or dashboard
If you keep the default compose ports published on a host with broader network exposure, you should treat that as an unsupported deployment shape until you add the controls below.
HomelabSec now ships an optional edge-based basic-auth and TLS deployment path, but it still does not include application-level user management, SSO, or per-user authorization.
If you need access beyond a trusted LAN, put it behind a reverse proxy and add all of the following:
- TLS termination with a real certificate
- authentication in front of both the dashboard and the API
- IP allowlisting or VPN access if possible
- restricted exposure of Postgres so it is never reachable from the public internet
Practical deployment pattern:
- publish
frontendandbrainonly to localhost or a private interface - terminate TLS in Caddy, Nginx, Traefik, or another reverse proxy
- require SSO, basic auth, or forward-auth at the proxy layer
- avoid exposing
schedulerorpostgresat all
If the stack must remain LAN-only, the simplest safe option is to keep the current compose deployment and limit host-level firewall access to your admin subnet.
HomelabSec now includes an optional secure edge overlay for broader deployments.
Start it with:
./run_secure_edge.shFor stronger auth instead of basic auth:
./run_secure_edge.sh --oidcThe launcher chooses ports in this order:
8081/844318081/18443- the next free pair starting at
20081/20443, after an interactive confirmation
If you prefer to choose ports yourself, you can still start the overlay directly:
cd compose
docker compose -f compose.yaml -f compose.exposed.yaml up -d --buildOIDC-authenticated edge:
cd compose
docker compose -f compose.yaml -f compose.exposed.yaml -f compose.oidc.yaml up -d --buildThe secure overlay:
- removes direct host exposure of the dashboard container
- keeps
brainbound to localhost on the host side - publishes an authenticated TLS edge in front of both the dashboard and API
- supports either self-signed or operator-provided certificates
Relevant variables:
EDGE_AUTH_USERNAME=admin
EDGE_AUTH_PASSWORD=change-me-now
EDGE_AUTH_MODE=basic
EDGE_SERVER_NAME=localhost
EDGE_TLS_MODE=self_signed
EDGE_HTTP_PORT=8081
EDGE_HTTPS_PORT=8443OIDC variables for the stronger-auth overlay:
EDGE_OIDC_PROVIDER=oidc
EDGE_OIDC_ISSUER_URL=https://your-idp.example.com/application/o/homelabsec/
EDGE_OIDC_CLIENT_ID=homelabsec
EDGE_OIDC_CLIENT_SECRET=replace-me
EDGE_OIDC_COOKIE_SECRET=replace-with-32-byte-base64-secret
EDGE_OIDC_REDIRECT_URL=https://localhost:8443/oauth2/callback
EDGE_OIDC_EMAIL_DOMAINS=*
EDGE_OIDC_SCOPE=openid email profile
EDGE_OIDC_WHITELIST_DOMAINS=OIDC operator notes:
./run_secure_edge.sh --oidcnow validates the required OIDC variables before starting compose- if
EDGE_OIDC_REDIRECT_URLis unset, the launcher fills it automatically ashttps://localhost:<chosen-https-port>/oauth2/callback EDGE_OIDC_ISSUER_URLmust usehttps://for real deployments, withhttp://localhostallowed only for local test IdPsEDGE_OIDC_COOKIE_SECRETmust be set and at least 16 characters long
Reference provider setup pattern:
- Create an OIDC client in your identity provider with redirect URI
https://localhost:8443/oauth2/callback - Set
EDGE_OIDC_ISSUER_URLto the provider issuer URL - Set
EDGE_OIDC_CLIENT_IDandEDGE_OIDC_CLIENT_SECRETfrom that client - Set
EDGE_OIDC_COOKIE_SECRETto a strong random secret - Start the edge with
./run_secure_edge.sh --oidc
For local validation only, you can point the issuer at a localhost-hosted IdP and let the launcher fill the callback URL for the chosen HTTPS port.
TLS modes:
EDGE_TLS_MODE=self_signedgenerates a self-signed certificate automatically on first startEDGE_TLS_MODE=providedexpects certificate files atedge/certs/tls.crtandedge/certs/tls.key
The default secure overlay covers reverse proxying, basic auth, and TLS termination.
The OIDC overlay adds stronger proxy-layer auth with oauth2-proxy, while preserving the current app and API shape. It still does not provide in-app per-user roles or API token management.
Compose now accepts:
OLLAMA_URL=http://host.containers.internal:11434
OLLAMA_MODEL=homelabsec-classifier
OLLAMA_TIMEOUT_SECONDS=120
CLASSIFICATION_FALLBACK_ROLE=unknown
CLASSIFICATION_FALLBACK_CONFIDENCE=0.10During install, HomelabSec validates host-side Ollama access through OLLAMA_HOST_URL and confirms the configured OLLAMA_MODEL exists via the Ollama tags API.
If Ollama is unreachable or returns an invalid transport response, the classification endpoints now fail with 502 instead of a generic 500. If the model returns non-JSON content, the API keeps the existing soft-fallback behavior and stores an unknown classification with raw_model_output included in the response.
Classification is now lookup-first and LLM-second. HomelabSec learns a reusable classification signature from prior LLM classifications and stores the learned role plus confidence in classification_lookup. That learned table is then checked before calling Ollama again, which makes repeated scans and similar newly discovered hosts much faster. Low-confidence learned entries are visible through the API and are good candidates for deeper investigation such as SSH-based inspection.
MAC brand enrichment is also available in the asset inventory and asset detail views. HomelabSec resolves the brand in this order:
- use the
vendorvalue observed directly in the Nmap XML when it is present - otherwise fall back to an offline OUI lookup using
pymanuf
pymanuf was chosen because it is MIT-licensed, works locally without depending on a hosted API at runtime, and is actively maintained with regular releases. That makes it a better fit than an ad hoc third-party lookup service for a self-hosted deployment.
HomelabSec now supports optional Fingerbank-based device enrichment. The classification order is:
- learned local classification lookup
- Fingerbank passive evidence match
- Ollama fallback
- default fallback role
Fingerbank enrichment is driven by passive evidence collected into network_observations and then normalized into a deterministic evidence object. The current passive collectors are:
- DHCP: captures MAC, IP, hostname, DHCP Option 55 fingerprint, and DHCP Option 60 vendor class
- mDNS: captures advertised local services and hostnames
- SSDP: captures UPnP
SERVER,USER-AGENT, andLOCATIONstrings
Relevant environment variables:
FINGERBANK_ENABLED=true
FINGERBANK_API_KEY=
FINGERBANK_BASE_URL=https://api.fingerbank.org
FINGERBANK_TIMEOUT_SECONDS=10
FINGERBANK_MIN_SCORE_ACCEPT=51
FINGERBANK_MIN_SCORE_AUTO_ACCEPT=76
COLLECTORS_ENABLED=true
COLLECTOR_INTERFACE=any
COLLECTOR_DHCP_ENABLED=true
COLLECTOR_MDNS_ENABLED=true
COLLECTOR_SSDP_ENABLED=trueFingerbank calls are cached by deterministic evidence_hash, including no-match results, so the same passive signature does not repeatedly call the external API.
Important deployment requirement: passive packet capture in Docker is not the same as passive capture on the host LAN. For production collector use, the brain service needs host-network visibility plus packet-capture capabilities. In Compose terms that means:
network_mode: host
cap_add:
- NET_RAW
- NET_ADMINWithout host networking, the collectors still fail soft and the main app continues to run, but the packet capture layer may not see the real LAN traffic you expect.
The Fingerbank API documentation for evidence interrogation is here:
HomelabSec keeps Fingerbank identity and internal role mapping separate. Fingerbank returns the device identity and score; HomelabSec then maps that identity into your internal role taxonomy via fingerbank_role_mappings.
Migration files live in brain/migrations/ and are applied in filename order.
To run migrations manually:
cd compose
docker compose run --rm migrateThe migration runner records applied versions in the schema_migrations table.
The durable data for HomelabSec lives in Postgres. At minimum, back up:
- the Postgres database contents
- your
.env - any local model/runtime configuration needed to reach Ollama
Example logical backup:
docker compose -f compose/compose.yaml exec -T postgres \
pg_dump -U "$POSTGRES_USER" "$POSTGRES_DB" > homelabsec-$(date +%F).sqlExample restore into a fresh stack:
cat homelabsec-YYYY-MM-DD.sql | docker compose -f compose/compose.yaml exec -T postgres \
psql -U "$POSTGRES_USER" "$POSTGRES_DB"Operational notes:
- run restores only after the target schema is migrated to the expected version
- test restores periodically on a disposable instance instead of trusting backups blindly
- named Docker volumes are not a backup strategy by themselves
If you prefer volume-level backups instead of logical dumps, document and test that process separately for your host environment.
The test strategy is tracked in TEST_PLAN.md. It defines the unit, integration, regression, and pre-UAT checks needed to make changes safely as the product evolves.
The first automated test slice covers pure functions in brain/app.py and does not require a running database or Ollama instance.
Install the test dependency:
python3 -m pip install -r requirements-dev.txtRun the unit tests:
python3 -m pytestRun only the integration tests:
python3 -m pytest tests/integrationThe integration suite starts an isolated Postgres test container on port 55432, loads the schema from brain/init.sql, and runs the FastAPI app in-process. Ollama is mocked in the classification integration tests so the results stay deterministic.
Integration coverage also locks down:
- invalid and missing ingest XML paths
- missing-asset
404behavior on classification and change detection 502handling when Ollama is unreachable- idempotent change persistence for the same fingerprint transition
Run the regression tests for the frontend-backed API contract:
python3 -m pytest tests/regressionRun the compose smoke test:
python3 -m pytest tests/smokeThe smoke suite starts the full compose stack with temporary remapped host ports chosen at runtime, waits for healthchecks to pass, verifies the API and frontend respond, and then tears the stack down.
The scheduler now waits for API readiness at startup, retries API calls, logs per-job failures without crashing the loop, and supports optional immediate discovery with:
STARTUP_DISCOVERY=trueStartup discovery decision:
- default behavior remains
STARTUP_DISCOVERY=false - this avoids kicking off a network scan immediately on every container restart
- use
STARTUP_DISCOVERY=trueif you explicitly want boot-time discovery after maintenance windows or host reboots - periodic discovery still starts on the configured interval either way
Additional scheduler tuning variables:
SCHEDULER_API_BASE=http://127.0.0.1:8088
SCHEDULER_METRICS_PORT=9100
API_RETRY_ATTEMPTS=5
API_RETRY_DELAY_SECONDS=5
STARTUP_API_TIMEOUT_SECONDS=120network_mode: "host" is intentionally retained for the scheduler.
Current rationale:
- the scheduler runs
nmap -sS, which relies on raw-socket scanning semantics tied closely to the network namespace it runs in - the target is a LAN subnet, so scanning from the host namespace preserves the expected source address and routing behavior
- the scheduler currently reaches the API through host loopback via
SCHEDULER_API_BASE, which defaults tohttp://127.0.0.1:8088
This means the scheduler is currently Linux-host oriented. If host networking is removed in the future, that change should be treated as a networking redesign, not a compose cleanup, because scan behavior and API reachability would both change.
Operational assumptions:
nmap -sSrequires raw-socket privileges inside the scheduler container- the scheduler should run only on trusted local infrastructure
- host-network mode should be reviewed again only if the scan model changes, for example moving away from SYN scans or offloading discovery to the host
Current observability is intentionally minimal. The stack relies on container logs plus compose healthchecks.
brain, scheduler, and migrate now emit structured JSON log lines to stdout so compose logs are easier to filter and ship elsewhere.
brain and scheduler now expose Prometheus-style metrics:
brain:http://brain:8088/metricsfrom the compose network, orhttp://127.0.0.1:8088/metricson the hostscheduler:http://host.containers.internal:9100/metricsfrom other containers, orhttp://127.0.0.1:9100/metricson the host
Start the monitoring overlay with:
cd compose
docker compose -f compose.yaml -f compose.monitoring.yaml up -d --buildThis adds:
- Prometheus on
127.0.0.1:9090 - Alertmanager on
127.0.0.1:9093 - Grafana on
127.0.0.1:3001
The monitoring overlay now also includes:
- a provisioned
HomelabSec OverviewGrafana dashboard - Alertmanager for routed notifications
- Prometheus alert rules for brain target availability
- Prometheus alert rules for scheduler target availability
- Prometheus alert rules for elevated brain 5xx rate and sustained high average latency
- Prometheus alert rules for scheduler job failures and stale discovery runs
- a
HomelabSecWatchdogalert that always fires so alert routing can be tested safely
Relevant variables:
PROMETHEUS_HOST_PORT=9090
ALERTMANAGER_HOST_PORT=9093
ALERTMANAGER_DEFAULT_RECEIVER=null
ALERTMANAGER_WEBHOOK_URL=
ALERTMANAGER_VALIDATION_WEBHOOK_URL=
ALERTMANAGER_EMAIL_TO=
ALERTMANAGER_EMAIL_FROM=
ALERTMANAGER_SMARTHOST=
ALERTMANAGER_SMTP_AUTH_USERNAME=
ALERTMANAGER_SMTP_AUTH_PASSWORD=
ALERTMANAGER_SMTP_REQUIRE_TLS=true
GRAFANA_HOST_PORT=3001
GRAFANA_ADMIN_USER=admin
GRAFANA_ADMIN_PASSWORD=change-me-now
SCHEDULER_METRICS_PORT=9100
LOG_LEVEL=INFOGrafana is provisioned with a default Prometheus datasource and the HomelabSec Overview dashboard.
Alert routing now runs through Alertmanager. The monitoring overlay supports three receiver modes:
ALERTMANAGER_DEFAULT_RECEIVER=nullALERTMANAGER_DEFAULT_RECEIVER=webhookALERTMANAGER_DEFAULT_RECEIVER=email
Webhook example:
ALERTMANAGER_DEFAULT_RECEIVER=webhook
ALERTMANAGER_WEBHOOK_URL=https://example.internal/alertsEmail example:
ALERTMANAGER_DEFAULT_RECEIVER=email
ALERTMANAGER_EMAIL_TO=ops@example.com
ALERTMANAGER_EMAIL_FROM=homelabsec@example.com
ALERTMANAGER_SMARTHOST=smtp.example.com:587
ALERTMANAGER_SMTP_AUTH_USERNAME=homelabsec@example.com
ALERTMANAGER_SMTP_AUTH_PASSWORD=replace-me
ALERTMANAGER_SMTP_REQUIRE_TLS=trueThe default null receiver keeps the overlay safe to start even before notification settings are configured. The HomelabSecWatchdog alert exists so routing can be validated once a real receiver is configured.
Validate webhook delivery with a disposable receiver:
# 1. Point Alertmanager's validation-only route at the temporary receiver.
export ALERTMANAGER_VALIDATION_WEBHOOK_URL=http://host.containers.internal:19094/homelabsec-alert-validation
cd compose
ALERTMANAGER_VALIDATION_WEBHOOK_URL="$ALERTMANAGER_VALIDATION_WEBHOOK_URL" \
docker compose -f compose.yaml -f compose.monitoring.yaml up -d --build alertmanager prometheus
cd ..
# 2. Start the temporary receiver and inject a synthetic validation alert.
python3 scripts/validate_alert_delivery.py \
--alertmanager-url http://127.0.0.1:9093 \
--listen-port 19094The validation script starts a temporary local webhook, posts a synthetic HomelabSecDeliveryValidation alert to Alertmanager, and waits for Alertmanager to call the temporary webhook back through the validation-only Alertmanager route. A successful run proves the alert path is delivering beyond Prometheus rule evaluation and into an outbound Alertmanager receiver path.
For machine-readable automation:
python3 scripts/validate_alert_delivery.py --alertmanager-url http://127.0.0.1:9093 --listen-port 19094 --jsonUseful operational commands:
docker compose -f compose/compose.yaml ps
docker compose -f compose/compose.yaml logs -f brain
docker compose -f compose/compose.yaml logs -f scheduler
docker compose -f compose/compose.yaml logs -f frontend
docker compose -f compose/compose.yaml logs -f postgresWhat to watch:
brainfor request failures, migration issues, and Ollama-related errorsschedulerfor discovery failures, API retry loops, and scan timingpostgresfor startup or readiness failures- compose health status for service-level regressions
Recommended next-step observability if the project grows:
- centralized log retention outside Docker’s default local buffers
- dashboards and alerting on the exposed metrics
- alerting on repeated scheduler failures or unhealthy services
The API is exposed by the brain service on port 8088.
Classify a single asset:
curl -X POST http://localhost:8088/classify/<asset_id>Example response:
{
"asset_id": "7d0d0a6f-4f7a-4a30-8b56-0f3b0aa9d9ab",
"classification": {
"role": "nas",
"confidence": 0.97
},
"classification_source": "lookup",
"fingerprint": {},
"fingerprint_store": {
"changed": false
},
"raw_model_output": null
}Classify all known assets:
curl -X POST http://localhost:8088/classify_allExample response:
{
"total_assets": 12,
"classified_ok": 12,
"lookup_hits": 9,
"llm_classified": 3,
"errors": 0,
"failed": []
}Inspect learned lookup entries:
curl http://localhost:8088/classification_lookupView one asset in detail:
curl http://localhost:8088/assets/<asset_id>Queue a focused rescan for one asset:
curl -X POST http://localhost:8088/rescan/<asset_id>Example response:
{
"entries": [
{
"lookup_id": "1f8c81f3-7d69-4331-b9e9-b10b21527a0e",
"signature_hash": "4f5b4f4d20b9f5f93a8b8d80c271cd9c5cc6d0b991a8ea7d9db5bdb66f60f1c5",
"signature": {},
"role": "nas",
"confidence": 0.97,
"source": "llm_learned",
"sample_count": 4,
"first_learned_at": "2026-04-09T14:00:00+00:00",
"last_learned_at": "2026-04-09T14:10:00+00:00"
}
]
}