NetOSField accessMenu
Signed in user unavailable
Documentation/Admin & Operations
Download PDF

Xiber NetOS — Admin & Operations Manual

Development setup, services, configuration, migrations, roles, and troubleshooting.


Tech Stack

ComponentTechnologyNotes
DatabasePostgreSQL 16PostGIS (geospatial), pgvector (embeddings)
BackendFastAPI + SQLAlchemy 2.0Async via asyncpg, Pydantic v2 validation
MigrationsAlembicBaseline + infrastructure + agreements + audit/feedback versions
FrontendNext.js 14 + React 18TypeScript 5.5, TailwindCSS, App Router
MapsMapLibre GLVector tile rendering
SchedulingIn-process asyncio loopWeekly renewal report + activity digest run inside the API; no Celery app/worker yet (Redis provisioned but dormant)
MCPPython FastMCP22 token-aware read/context tools for XOS agents; write tools and gateway registration pending
Dev DeploymentDocker ComposeHot-reload for both API and web

Prerequisites

  • Docker Engine 24+
  • Docker Compose v1 (the standalone docker-compose binary, e.g. 1.29.2). The production NetOS server runs Compose v1; the Compose v2 plugin (docker compose, no hyphen) is not installed for the root/sudo context on that host, so all server operations use the hyphenated docker-compose command. For consistency with the server, every command in this guide uses the hyphenated docker-compose (v1) form.
  • 4 GB RAM minimum
  • Ports 3000, 5432, 6379, 8000 available
  • (Optional) Python 3.12+ and Node.js 20+ for local development outside Docker

Repository Layout

circuitos/
├── apps/
│   ├── api/                FastAPI backend
│   │   ├── app/
│   │   │   ├── main.py         Entry point
│   │   │   ├── api/v1/         Route handlers
│   │   │   ├── core/           Auth, config
│   │   │   ├── db/             Session management
│   │   │   ├── models/         SQLAlchemy entities + enums
│   │   │   ├── schemas/        Pydantic request/response models
│   │   │   └── services/       Business logic (import, extraction, scheduler)
│   │   ├── alembic/            Migration versions
│   │   ├── scripts/            Seed data, utilities
│   │   ├── pyproject.toml      Python dependencies
│   │   └── Dockerfile
│   ├── web/                Next.js frontend
│   │   ├── app/                App Router pages
│   │   ├── components/         React components
│   │   ├── lib/                API client, utilities
│   │   ├── package.json
│   │   └── Dockerfile
│   └── mcp/                MCP server skeleton
│       ├── app/server.py
│       └── pyproject.toml
├── infra/
│   └── docker/
│       ├── docker-compose.yml          Local dev stack
│       ├── docker-compose.public.yml   Production profile (SSO + tunnel)
│       ├── nginx/                      Public reverse proxy config
│       └── postgres/                   Custom PostgreSQL Dockerfile
├── docs/                   Documentation (you are here)
├── examples/               Sample CSV data
└── .env.example            Environment variable template

Local Development

Start Everything

cd infra/docker
docker-compose up --build

Run Migrations

docker-compose exec api alembic upgrade head

Seed Sample Data

docker-compose exec -T -e PYTHONPATH=/app api python scripts/seed_sample_data.py

Creates: 4 carriers (Lumen, Zayo, Cogent, Hurricane Electric), 10 endpoints with coordinates, 8 circuits with contracts, and lifecycle events. The script is idempotent — safe to run multiple times.

Restart Services

docker-compose restart api web

Rebuild After Dependency Changes

docker-compose up --build api web

View Logs

docker-compose logs -f api        # API logs
docker-compose logs -f web        # Frontend logs
docker-compose logs -f postgres   # Database logs

Services

ServiceURLDocker Name
Web UIhttp://localhost:3000web
APIhttp://localhost:8000api
API Docs (Swagger)http://localhost:8000/docsapi
API Healthhttp://localhost:8000/healthzapi
PostgreSQLlocalhost:5432postgres
Redislocalhost:6379redis

Server Deployment

The current demo deployment runs on the Xiber NetOS server.

ServiceURL
Public UIhttps://circuitos.xiberian.net
Direct UIhttp://127.0.0.1:3010 on the VM
Protected internal APIhttp://10.10.67.118:8010
Direct API Docshttp://10.10.67.118:8010/docs

Server path:

/home/stephen/netos

Server compose file:

infra/docker/docker-compose.server.yml

The server runs Docker Compose v1 (docker-compose 1.29.2). Always use the hyphenated docker-compose binary with the -p netos project name and the server compose file:

docker-compose -p netos -f docker-compose.server.yml ps
docker-compose -p netos -f docker-compose.server.yml logs -f api
docker-compose -p netos -f docker-compose.server.yml logs -f web

Containers on this project are named netos_<service>_1 — e.g. netos_api_1, netos_web_1, netos_postgres_1, netos_redis_1.

Compose v1 recreate bug (KeyError: 'ContainerConfig')

Compose v1 (1.29.x) frequently fails when asked to recreate an already-running service in place, aborting with:

ERROR: for netos_api_1  'ContainerConfig'
KeyError: 'ContainerConfig'

The safe pattern the checked-in deploy scripts use is to stop and remove the old container first, then bring it up fresh:

cd ~/netos/infra/docker
docker-compose -p netos -f docker-compose.server.yml build api web

# Remove the existing containers BEFORE `up` to avoid the ContainerConfig bug
docker stop netos_api_1 netos_web_1 || true
docker rm   netos_api_1 netos_web_1 || true

docker-compose -p netos -f docker-compose.server.yml up -d api web

Do this per service you are recreating. docker-compose ... up -d <svc> on a service whose container was already stopped+removed creates it cleanly; up -d on a live container is what triggers the bug.

The server web service runs next start from a built image. Do not mount apps/web or .next over the production container; only docs/ is mounted read-only at /app/content/docs.

Releases that include migrations

When a release ships new Alembic migrations, apply them with a one-off container BEFORE swapping the running `api` container, so the schema is already at head when the new code starts:

cd ~/netos/infra/docker
docker-compose -p netos -f docker-compose.server.yml build api
docker-compose -p netos -f docker-compose.server.yml run --rm api alembic upgrade head
# then recreate api (and web) using the stop+rm+up sequence above

Rollback

Tag the currently-good images before deploying so you can revert quickly if a new build misbehaves:

# Before deploy: retag the known-good images
docker tag netos_api:latest netos_api:rollback
docker tag netos_web:latest netos_web:rollback

# To roll back: point :latest back at the saved images and recreate
docker tag netos_api:rollback netos_api:latest
docker tag netos_web:rollback netos_web:latest
docker stop netos_api_1 netos_web_1 || true
docker rm   netos_api_1 netos_web_1 || true
docker-compose -p netos -f docker-compose.server.yml up -d api web

Note that a rollback only reverts application code/images, not the database. If the failed release ran migrations, plan the corresponding downgrade separately.

For fast frontend iteration, run a separate local preview service instead of putting the public web container into development mode:

cd ~/netos
scripts/start-web-preview.sh

The preview service runs next dev with the web source bind-mounted. It is available locally at http://127.0.0.1:3011/preview and through the protected public proxy at https://netos.xiberian.net/preview. When a set of changes is ready for the public app, promote it with:

cd ~/netos
scripts/deploy-prod-web.sh

Run migrations on the server:

cd ~/netos/infra/docker
docker-compose -p netos -f docker-compose.server.yml run --rm api alembic upgrade head

Configuration

RF System Standards

RF system standards are maintained from the Admin page. They define the selectable defaults used when creating RF Links.

FieldPurpose
NameSystem label used in generated RF Link names and equipment defaults
DescriptionShort operating note or intended use case
Frequency UsedHuman-readable spectrum band, such as CBRS / 5 GHz or 11 GHz licensed
Capacity MbpsDefault capacity applied to new RF Links
CostDefault CAPEX applied to new RF Links
TypePTP or PtMP
RoleStandalone, PtMP Base/Sector, or PtMP Subscriber
Compatible BasesFor PtMP subscriber radios, the base/sector systems that can serve that subscriber
ActiveControls whether the system appears as a selectable option for new RF Links

Use PtMP Base/Sector for equipment installed on an infrastructure asset as a PtMP node, such as Tarana BN, Siklu Terragraph DN, or Cambium ePMP AP. Use PtMP Subscriber for customer-side radios, such as Tarana RN/RNv, Siklu T280/T265, or Cambium Force 4600c. When a user creates a PtMP RF Link from a selected PtMP source node, NetOS shows only subscriber radios whose compatible base list includes that source node's base system.

RF Links no longer require a manually entered link name. NetOS generates the display name from the selected A facility, Z facility or customer endpoint, and RF system/equipment. Links between two infrastructure assets are classified as backhaul. Links from an infrastructure asset to a customer endpoint are classified as customer endlinks.

Environment Variables

The API reads its configuration from environment variables via apps/api/app/core/config.py (Settings). Names below are the environment-variable form (Settings maps them case-insensitively). Defaults are the application defaults from Settings; the server compose file (docker-compose.server.yml) overrides some of them (noted where it matters). Copy .env.example to .env for local dev; the production stack is configured through docker-compose.server.yml and infra/docker/.env.public.

Database and Redis

VariableDefaultDescription
DATABASE_URLpostgresql+asyncpg://circuitos:circuitos@postgres:5432/circuitosAsync (asyncpg) database connection string. Also used to derive a sync (+psycopg) URL for Alembic.
REDIS_URLredis://redis:6379/0Redis connection (reserved as a future Celery broker; no worker app exists yet, so Redis is provisioned but dormant).

Authentication and proxy trust (see Authorization & RBAC)

VariableDefaultDescription
ALLOW_DEV_AUTHfalseEnables the header-based dev-auth shim (x-user-email / x-user-role). Must be `false` in production. Server compose defaults this to false.
TRUST_PROXY_AUTH_HEADERSfalseTrust SSO identity headers (e.g. X-Auth-Request-Email) forwarded by the reverse proxy. Server compose defaults this to true.
TRUSTED_AUTH_PROXY_CIDRS127.0.0.1/32,172.16.0.0/12Comma-separated CIDRs whose client IP is allowed to supply trusted identity headers. Requests from outside these ranges cannot spoof identity headers.
DEV_AUTH_DEFAULT_ROLEexecLegacy identity role used by the dev shim when x-user-role is absent.
DEV_AUTH_DEFAULT_EMAILdev@xiber.comFallback identity email under dev auth. Note: this address is on the bootstrap Super Admin allowlist when dev auth is on — another reason dev auth must be off in production.
RBAC_BOOTSTRAP_SUPER_ADMIN_EMAILS*(empty)*Comma-separated emails auto-provisioned as Super Admin on first login. Falls back to ADMIN_EMAIL when unset.
ADMIN_EMAILadmin@xiber.comSuper Admin bootstrap fallback and default admin notification recipient.
CORS_ALLOWED_ORIGINShttp://localhost:3000Comma-separated allowed browser origins. Server compose sets https://netos.xiberian.net,https://circuitos.xiberian.net.
ENTRA_TENANT_IDcommonEntra tenant (placeholder; live SSO is handled by oauth2-proxy — see Public Hostname & SSO).
ENTRA_CLIENT_IDdev-client-idEntra app client ID (placeholder; not used for in-app token validation today).
JWT_AUDIENCEapi://circuitosExpected JWT audience claim (reserved for future in-app JWT validation).
OBJECT_STORE_BUCKETcircuitos-devObject-store bucket name (reserved).

SMTP and notifications (bootstrap defaults; Admin > Notifications overrides these once saved)

VariableDefaultDescription
SMTP_HOST*(unset)*SMTP host. If unset and no Admin setting exists, scheduled email is skipped. Server compose sets mgw.xiberian.net.
SMTP_PORT587587 for STARTTLS or 465 for implicit TLS.
SMTP_USERNAME*(unset)*Optional SMTP username. Server compose sets agents@xiberian.net.
SMTP_PASSWORD*(unset)*Optional SMTP password. Encrypted at rest when saved via Admin.
SMTP_FROMnetos@xiber.comFrom address. Server compose sets NetOS <agents@xiberian.net>; the Xiber gateway requires an @xiberian.net sender for SPF/DKIM/DMARC alignment.
NETOS_PUBLIC_BASE_URLhttps://netos.xiberian.netPublic URL used to generate report links back into NetOS.
NEW_USER_EMAIL_ENABLEDtrueSends a welcome email when an admin creates a new NetOS user.

AI providers

VariableDefaultDescription
AI_PROVIDERdeterministicBootstrap provider until Admin > AI is saved. Supported: openai, minimax, deterministic.
OPENAI_API_KEY*(unset)*Bootstrap API key for the backend assistant; never exposed to the browser.
OPENAI_MODELgpt-5.2Bootstrap model for assistant extraction.
OPENAI_BASE_URLhttps://api.openai.com/v1Bootstrap OpenAI-compatible base URL.
AI_SECRET_KEY*(unset)*Encryption seed for provider API keys stored in the database (Fernet). Gotcha: when unset, the seed is derived from DATABASE_URL, so changing DATABASE_URL (host, credentials, or db name) makes previously stored provider keys undecryptable. Set AI_SECRET_KEY explicitly in any real deployment so stored keys survive database URL changes.

Geocoding

VariableDefaultDescription
GEOCODING_ENABLEDtrueEnables address-to-coordinate lookups.
GEOCODING_PROVIDER_URLhttps://nominatim.openstreetmap.org/searchGeocoding endpoint.
GEOCODING_USER_AGENTXiber NetOS geocoderUser-Agent sent with geocoding requests (Nominatim requires an identifiable UA).

Scheduling (weekly renewal report and activity digest; times are UTC, Monday = 0)

VariableDefaultDescription
RENEWAL_REPORT_ENABLEDtrueToggle for the weekly renewal email scheduler.
RENEWAL_REPORT_WEEKDAY0UTC weekday for the scheduled report.
RENEWAL_REPORT_HOUR8UTC hour after which the report can send.
RENEWAL_REPORT_LOOKAHEAD_DAYS180Renewal decision lookahead window included in the report.
ACTIVITY_DIGEST_ENABLEDtrueToggle for the weekly activity digest scheduler.
ACTIVITY_DIGEST_WEEKDAY0UTC weekday for the digest.
ACTIVITY_DIGEST_HOUR9UTC hour after which the digest can send.
ACTIVITY_DIGEST_LOOKBACK_DAYS7Activity lookback window included in the digest.

Frontend (web container) — these are read by Next.js, not by Settings:

VariableDefaultDescription
API_BASE_URLhttp://api:8000Server-side (SSR) API URL over Docker internal DNS.
NEXT_PUBLIC_API_BASE_URL*(varies)*Browser-side API base. Server/preview set same-origin so browser calls go back through the proxy.

Copy .env.example to .env and adjust as needed. For production SSO, see Public Hostname & SSO.

AI Assistant Provider

The NetOS Assistant uses /api/v1/ai/extract. With no provider configured it uses the deterministic parser. Admin users should normally configure the live provider from Admin > AI, where API keys are encrypted server-side and only the last four characters are shown after save.

Supported AI providers:

  • OpenAI: default base URL https://api.openai.com/v1, using the Responses API with strict JSON output.
  • MiniMax: default base URL https://api.minimax.io/v1, default model MiniMax-M3, using the OpenAI-compatible Chat Completions API for text/image tasks.
  • Fallback parser: deterministic extraction with no external model.

Use Test in Admin > AI after entering a key. The test calls the provider's OpenAI-compatible /models endpoint and verifies that the selected model is advertised before users rely on the provider.

The assistant never writes inventory directly. It returns draft payloads that users apply to forms and save through the normal NetOS APIs. Durable assistant memory is stored in ai_memory_entries and is limited to explicit user or organization preferences/rules, such as lines beginning with "remember..." or "always...".

Site photo analysis uses the same configured provider. Uploaded images are stored under /app/storage and the Docker deployment mounts that path from apps/api/storage so documents survive API container rebuilds. If an image analysis fails because the provider returned an empty or invalid response, users can open the site document gallery and choose Re-run AI after settings are corrected.

The Admin > AI page also includes a Customer Research Connector for Sonar/MCP-style enrichment. Configure the MCP URL, tool name, optional authorization token, and enable the connector. NetOS calls this connector only from the backend, then converts the research result into a normal customer import preview row. Users must still review, edit, select, and commit the staged row through the standard import workflow.

The connector supports either an MCP SSE URL such as http://mcp.xiberian.net:8081/sse?agent_id=netos-customer-research or a direct HTTP endpoint that accepts MCP JSON-RPC tools/call requests. For SSE, NetOS opens the stream, reads the server-provided message endpoint, initializes the MCP session, and then calls the configured tool. The configured tool should accept a query argument and return either structured JSON with draft, citations, warnings, and summary, or text containing that JSON. Browser clients never receive the connector credential. If the MCP server expects Authorization: <token> rather than Authorization: Bearer <token>, paste only the raw token in the API key field; NetOS sends the value exactly as entered.

Use the Test button after entering connector values. The test initializes the MCP session and runs tools/list; it does not perform customer research or create import rows. A passing test means NetOS can reach the MCP server and the configured tool name appears in the advertised tool list.

Email Notifications

Admin users with settings_view / settings_edit can manage delivery from Admin > Notifications. This page controls:

  • SMTP host, port, username, password, and sender
  • public NetOS base URL used in email links
  • welcome emails for newly created users
  • weekly report enablement, day, UTC hour, and lookahead window
  • weekly activity digest enablement, day, UTC hour, and lookback window
  • test email delivery before saving or sending reports

Saved Admin notification settings override environment variables. Environment variables remain useful as bootstrap defaults and for deployments that have not yet saved a database-backed notification setting. SMTP passwords are encrypted server-side and are never returned to the browser; the UI only shows whether a password exists and its last four characters. The Xiber gateway at mgw.xiberian.net refuses plaintext authentication; NetOS uses STARTTLS on port 587 and implicit TLS on port 465.

The Timeline page includes a Weekly Renewal Email Report section. It previews and manually sends an HTML report for circuits and infrastructure agreements whose renewal decision deadline is overdue or within the configured lookahead window. Each report includes:

  • urgency state (watch, active, critical, overdue)
  • agreement/vendor/type/status details
  • MRC, term end, renewal notice, and renewal term
  • links back to the circuit or infrastructure detail
  • links to available agreement documents
  • an HTML timeline-style snapshot for the report items

The API process also runs a lightweight scheduler. When SMTP is configured and the renewal report is enabled, it checks every 30 minutes and sends once on the configured UTC weekday/hour to all active registered users. Sends are recorded in user_activity_logs with event_type=report, so a container restart does not send the same report date twice.

The same scheduler sends the weekly activity digest when enabled. The digest summarizes write events, authorization changes, API-token usage, report sends, and renewal notice counts for the configured lookback window. Admins can also send the digest immediately from Admin > Notifications.

Docker Network

  • Server-side web calls (Next.js SSR) use API_BASE_URL=http://api:8000 (Docker internal DNS)
  • Browser-side calls use the UI host origin.
  • Machine clients and MCP connectors should use http://10.10.67.118:8010

with Authorization: Bearer ntos_live_.... The API is also bound to 127.0.0.1:8010 for local VM checks and is not bound to the public NIC.

  • The VM has persistent netplan routes for 10.10.75.0/24 and 10.10.43.0/24

via 10.10.67.1 dev ens19. These keep replies to internal API clients on the protected LAN path instead of returning through the public ens18 default route.


Authentication & RBAC

Authentication (who you are) and authorization (what you may do) are separate. Microsoft SSO / Entra proves identity; NetOS decides authorization with its own database-driven roles, permissions, and field-level filtering. The authoritative reference is Authorization & RBAC; this section is the operator summary.

Identity: production vs. dev auth

Two mutually exclusive identity paths, both selected in apps/api/app/core/config.py:

  • Production (proxy-trusted headers). With TRUST_PROXY_AUTH_HEADERS=true, the API trusts SSO identity headers (X-Auth-Request-Email, X-Forwarded-Email, CF-Access-Authenticated-User-Email, X-Ms-Client-Principal-Name, etc.) — but only when the request's client IP is inside `TRUSTED_AUTH_PROXY_CIDRS`. This is how browser SSO flows through the reverse proxy. See Public Hostname & SSO.
  • Dev auth (header shim). With ALLOW_DEV_AUTH=true, the API accepts x-user-email / x-user-role with no identity verification:

``bash curl -H "x-user-role: exec" http://localhost:8000/api/v1/circuits

When no email header is present it falls back to dev@xiber.com, which auto-bootstraps as Super Admin. For that reason `ALLOW_DEV_AUTH` must be `false` in production. The server compose file already defaults it to false.

If neither path yields an identity, the API returns 401.

Roles

Real authorization uses the DB-seeded RBAC roles — Super Admin, Executive, Finance Admin, Project Manager, Network Engineer, Field Technician, Read-Only User — each composed of granular permissions. See Authorization & RBAC for the full model, field-level financial masking, and API tokens.

The lowercase identity roles below are the legacy `user.role` values and the values accepted by the dev-auth x-user-role header. They gate a few coarse write checks but are not the primary authorization mechanism:

Legacy roleDescriptionWrite Access
execExecutiveYes
financeFinance teamYes
network_engNetwork engineeringYes
operationsOperationsYes
pmProject managementYes
salesSalesNo (not in the write-role set)
read_onlyView onlyNo
agentMCP/XOS agentLimited

Database

Custom PostgreSQL Image

Built from pgvector/pgvector:pg16 with PostGIS installed.

Dockerfile: infra/docker/postgres/Dockerfile

Core Tables

TablePurpose
usersUser identity and role
carriersCarrier/provider companies
service_type_catalogEditable circuit service type catalog
rf_system_catalogEditable RF equipment/system presets
customersCommercial and MFC customer sites used by circuits, RF Links, and attribution
endpointsPhysical locations with PostGIS geometry
circuitsWholesale circuit records
rf_linksWireless backhaul and customer endlink records
rf_link_documentsFCC licenses and supporting RF Link documents
infrastructure_assetsTowers, rooftops, data centers, POPs, carrier hotels, offices, and aggregation sites
infrastructure_documentsSite agreements, floorplans, invoices, and supporting infrastructure documents
subtended_linksExplicit infrastructure/customer/circuit/RF attribution hierarchy
electrical_servicesElectrical utility service accounts attached to infrastructure or customer sites
electrical_service_documentsBills, invoices, agreements, meter photos, and supporting electrical service documents
contractsContract terms, renewal windows, ETF
customer_attributionsRevenue attribution (future Sonar link)
lifecycle_eventsTimeline events (order, install, outage, etc.)
invoicesCarrier invoices
invoice_line_itemsInvoice detail lines
renewal_decisionsRenewal state machine records
outage_eventsOutage tracking
sla_creditsSLA credit recovery records
import_jobsCSV/XLSX import metadata
import_staging_rowsStaged import rows before commit
user_activity_logsRequest/write audit activity
feedback_requestsBug and feature requests
feedback_commentsProgress comments on bug/feature requests

Uploaded document files are persisted outside the API container at apps/api/storage, mounted into the container as /app/storage. Do not remove this mount in production; otherwise database document records can outlive the uploaded files after a container rebuild.

Materialized View

  • circuit_financial_mv — pre-computed financial rollups for dashboard performance

Documentation PDFs

Markdown documentation is the source of truth. Downloadable PDFs are generated from the Markdown files and published from apps/web/public/docs.

Run the exporter after material documentation changes:

python3 scripts/build_doc_pdfs.py

Generated files include:

PDFPublic URL
Complete bundle/docs/netos-documentation.pdf
User Manual/docs/user-manual.pdf
Admin & Operations/docs/admin-operations.pdf
Data & Import Guide/docs/data-import-guide.pdf
API & Integrations/docs/api-integrations.pdf
Public Hostname & SSO/docs/public-hostname-sso.pdf
Authorization & RBAC/docs/authorization-rbac.pdf
Infrastructure Attribution/docs/infrastructure-attribution.pdf
Roadmap & Known Gaps/docs/roadmap-known-gaps.pdf

The exporter embeds public documentation images such as the infrastructure attribution and waterfall diagrams.

Extensions

  • PostGIS — geometry columns for endpoint coordinates, GeoJSON queries
  • pgvector — vector embeddings (installed, not yet used)

Migrations

Migration Files

Located in apps/api/alembic/versions/, numbered sequentially (00010054 and growing). Always deploy schema with alembic upgrade head rather than tracking individual files. Notable milestones:

MigrationDescription
0001_foundation_schema.pyCore tables, enums, constraints
0002_infrastructure_assets.pyInfrastructure asset tables
0004_audit_feedback.pyUser activity logs and feedback requests
00130015, 0026, 0054Subtended-link attribution, history, and redundancy
0016, 0035, 0037, 0053RF links, PtMP nodes, RF system roles, endpoint customers
00170025, 0036, 0039Customers, sites/services, revenue, commission, MFC fields
0021Electrical services
0030, 0031RBAC authorization tables and audit columns
0033, 0034, 0038AI assistant memory, provider settings, research connector
0040, 0052Notification settings and digest records
00430051Vendor quotes, pricing options, escalators, line items, scope docs
0046API access tokens

Run docker-compose -p netos -f docker-compose.server.yml run --rm api alembic current to see the applied revision.

Commands

Apply all migrations:

# Via Docker
docker-compose exec api alembic upgrade head

# Or standalone
cd apps/api
alembic upgrade head

Check current version:

docker-compose exec api alembic current

Create a new migration:

docker-compose exec api alembic revision --autogenerate -m "description"

Review auto-generated migrations before applying — they may need manual adjustments for PostGIS columns or custom types.


Seed Data

Script: apps/api/scripts/seed_sample_data.py

What it creates:

EntityCountExamples
Carriers4Lumen, Zayo, Cogent, Hurricane Electric
Endpoints10With coordinates for map rendering
Circuits8Various service types and statuses
Contracts8With term dates and renewal windows
Lifecycle Events~15Orders, installs, bandwidth changes

The script uses upsert logic on natural keys — safe to run repeatedly without creating duplicates.


Audit Trail

Audit data is available in the Admin view at /admin.

What Is Logged

Event TypeExamples
Request activityAPI path, method, user, role, IP address, response status
Record writesCreate/update circuit, infrastructure, provider
Bulk actionsPer-record entries for bulk circuit and infrastructure updates/deletes
FeedbackBug/feature request creation, status/priority changes, progress comments

Timestamps are stored in UTC in PostgreSQL and displayed in the UI as Eastern time with the timezone label.

Soft Deletes

Circuit and infrastructure bulk deletes set deleted_at. They do not physically remove rows. Normal list/map/dashboard views filter out soft-deleted rows.


Bug & Feature Queue

The Admin view includes a feedback form and queue.

FieldValues
Prioritylow, normal, high, urgent
Statusnew, triaged, planned, in_progress, done, declined

Admins can add progress comments to each request. Public comments attempt to email the requester when SMTP is configured. SMTP failures are non-blocking, so feedback updates still save if email is unavailable.

SMTP configuration is normally managed from Admin > Notifications. The variables below are bootstrap fallbacks before Admin settings are saved:

VariableDescription
SMTP_HOSTSMTP server hostname
SMTP_PORTSMTP port, default 587
SMTP_USERNAMEOptional SMTP username
SMTP_PASSWORDOptional SMTP password
SMTP_FROMFrom address, default netos@xiber.com
ADMIN_EMAILAdmin notification recipient

Verification Commands

Check API Health

curl -fsS http://localhost:8000/healthz
# → {"status": "ok"}

Check Map Data

curl -fsS http://localhost:8000/api/v1/circuits/map | python3 -m json.tool | head -20

Check Dashboard

curl -fsS http://localhost:8000/api/v1/dashboard/summary | python3 -m json.tool | head -20

API Tests and Lint (pytest + httpx)

The API has an integration test harness under apps/api/tests/ (pytest, pytest-asyncio, and an in-process httpx client against the ASGI app). Run it from the API venv:

cd apps/api
.venv/bin/pytest tests/ -v
.venv/bin/ruff check app tests

Key facts for operators:

  • Tests run against a dedicated `circuitos_test` database, never the live circuitos database. The default TEST_DATABASE_URL is postgresql+asyncpg://circuitos:circuitos@127.0.0.1:55432/circuitos_test (override via the TEST_DATABASE_URL env var).
  • conftest.py hard-refuses any target database whose name does not end in _test, and it auto-creates the test database and runs migrations on first use.
  • The harness forces ALLOW_DEV_AUTH=true; with no identity headers, requests resolve to dev@xiber.com (auto-bootstrapped Super Admin), so tests exercise authorized paths by default.

Compile Check (Python)

python3 -m compileall apps/api/app apps/api/scripts apps/mcp/app

Web Gates (Next.js)

cd apps/web
npm run typecheck   # tsc --noEmit
npm run lint        # next lint
npm run build       # next build

Troubleshooting

Map Doesn't Load

CheckSolution
API reachable?curl http://localhost:8000/api/v1/circuits/map
CORS?Ensure http://localhost:3000 is in allowed origins
Data exists?Run seed script, verify circuits have endpoint coordinates
Browser cache?Hard refresh (Cmd+Shift+R / Ctrl+Shift+R)
Containers running?docker-compose ps — restart api and web if needed

Frontend 500 on Server-Rendered Pages

  • Verify API_BASE_URL=http://api:8000 is set in Docker Compose
  • Verify the api container is running: docker-compose logs api
  • Check for Python exceptions in API logs

No Sample Circuits Showing

  • Run the seed script (see above)
  • Verify: curl http://localhost:8000/api/v1/circuits returns data
  • Check migrations: docker-compose exec api alembic current

PostGIS Extension Errors

  • Rebuild the custom PostgreSQL image: docker-compose build postgres
  • Verify infra/docker/postgres/Dockerfile is referenced in docker-compose.yml
  • Check: docker-compose exec postgres psql -U circuitos -c "SELECT PostGIS_Version();"

Container Won't Start

docker-compose logs <service>     # Check for errors
docker-compose down -v            # Nuclear option: remove volumes and rebuild
docker-compose up --build

Warning: down -v destroys all data. Re-run migrations and seed script after.