diff --git a/.env.example b/.env.example index b339e15..0c62165 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,6 @@ -ORBITALWARD_ENV=development -ORBITALWARD_SECRET_KEY=change-me -DATABASE_URL=postgresql+psycopg://orbitalward:orbitalward@postgres:5432/orbitalward +ORBITWARD_ENV=development +ORBITWARD_SECRET_KEY=change-me +DATABASE_URL=postgresql+psycopg://orbitward:orbitward@postgres:5432/orbitward REDIS_URL=redis://redis:6379/0 FRONTEND_URL=http://localhost:5173 BACKEND_URL=http://localhost:8000 diff --git a/AGENTS.md b/AGENTS.md index c61c43d..f98f310 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,6 @@ -# OrbitalWard Agent Notes +# OrbitWard Agent Notes -OrbitalWard should be built incrementally. Keep the first release focused on a secure monitoring appliance with guided setup, website monitoring, alerts, and notifications. +OrbitWard should be built incrementally. Keep the first release focused on a secure monitoring appliance with guided setup, website monitoring, alerts, and notifications. ## Product Guardrails diff --git a/LICENSE b/LICENSE index 668210d..fd3efe7 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2026 OrbitalWard contributors +Copyright (c) 2026 OrbitWard contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 03909b1..32ddbc7 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -# OrbitalWard +# OrbitWard Beautiful, self-hosted infrastructure monitoring without the enterprise-tool headache. -OrbitalWard is an open-source monitoring appliance for homelabs, small businesses, and internal IT teams. The first target is a secure and attractive monitoring foundation with guided setup, website checks, alerts, notifications, and clean dashboards. It is not trying to be a full Zabbix or LibreNMS replacement in the first release. +OrbitWard is an open-source monitoring appliance for homelabs, small businesses, and internal IT teams. The first target is a secure and attractive monitoring foundation with guided setup, website checks, alerts, notifications, and clean dashboards. It is not trying to be a full Zabbix or LibreNMS replacement in the first release. ## Current Status @@ -57,7 +57,7 @@ Default local admin credentials come from `.env`: - `INITIAL_ADMIN_EMAIL=admin@example.com` - `INITIAL_ADMIN_PASSWORD=change-me` -Change these values before using OrbitalWard anywhere beyond local development. +Change these values before using OrbitWard anywhere beyond local development. ## Project Structure diff --git a/backend/alembic.ini b/backend/alembic.ini index 6d59d28..ad19032 100644 --- a/backend/alembic.ini +++ b/backend/alembic.ini @@ -2,7 +2,7 @@ script_location = alembic prepend_sys_path = . -sqlalchemy.url = postgresql+psycopg://orbitalward:orbitalward@postgres:5432/orbitalward +sqlalchemy.url = postgresql+psycopg://orbitward:orbitward@postgres:5432/orbitward [loggers] keys = root,sqlalchemy,alembic diff --git a/backend/alembic/versions/20260522_0001_initial_schema.py b/backend/alembic/versions/20260522_0001_initial_schema.py index 64897f6..f7c8c0c 100644 --- a/backend/alembic/versions/20260522_0001_initial_schema.py +++ b/backend/alembic/versions/20260522_0001_initial_schema.py @@ -1,4 +1,4 @@ -"""Initial OrbitalWard schema. +"""Initial OrbitWard schema. Revision ID: 20260522_0001 Revises: diff --git a/backend/app/__init__.py b/backend/app/__init__.py index f399ca0..79435b7 100644 --- a/backend/app/__init__.py +++ b/backend/app/__init__.py @@ -1 +1 @@ -"""OrbitalWard backend package.""" +"""OrbitWard backend package.""" diff --git a/backend/app/api/health.py b/backend/app/api/health.py index 5aaafd9..f23ad6b 100644 --- a/backend/app/api/health.py +++ b/backend/app/api/health.py @@ -5,4 +5,4 @@ router = APIRouter(tags=["health"]) @router.get("/health") def health() -> dict[str, str]: - return {"status": "ok", "service": "orbitalward-backend"} + return {"status": "ok", "service": "orbitward-backend"} diff --git a/backend/app/api/notifications.py b/backend/app/api/notifications.py index 4866741..1cf4fe7 100644 --- a/backend/app/api/notifications.py +++ b/backend/app/api/notifications.py @@ -14,7 +14,7 @@ router = APIRouter(prefix="/notifications/channels", tags=["notifications"]) def _channel_to_read(channel: NotificationChannel) -> NotificationChannelRead: settings = dict(channel.settings or {}) - settings.setdefault("username", "OrbitalWard") + settings.setdefault("username", "OrbitWard") return NotificationChannelRead( id=channel.id, name=channel.name, @@ -40,7 +40,7 @@ def create_channel( db: Session = Depends(get_db), ) -> NotificationChannelRead: channel_settings = dict(payload.settings or {}) - channel_settings.setdefault("username", "OrbitalWard") + channel_settings.setdefault("username", "OrbitWard") channel = NotificationChannel( name=payload.name, channel_type=payload.channel_type, @@ -70,8 +70,8 @@ def test_channel( response = httpx.post( url, json={ - "username": (channel.settings or {}).get("username") or "OrbitalWard", - "text": f"OrbitalWard test notification for {channel.name}", + "username": (channel.settings or {}).get("username") or "OrbitWard", + "text": f"OrbitWard test notification for {channel.name}", }, timeout=10, ) diff --git a/backend/app/auth/security.py b/backend/app/auth/security.py index e3d714b..c9c3691 100644 --- a/backend/app/auth/security.py +++ b/backend/app/auth/security.py @@ -20,12 +20,12 @@ def verify_password(plain_password: str, hashed_password: str) -> bool: def create_access_token(subject: str) -> str: expires_at = datetime.now(UTC) + timedelta(minutes=settings.access_token_expire_minutes) payload = {"sub": subject, "exp": expires_at} - return jwt.encode(payload, settings.orbitalward_secret_key, algorithm=ALGORITHM) + return jwt.encode(payload, settings.orbitward_secret_key, algorithm=ALGORITHM) def decode_access_token(token: str) -> str | None: try: - payload = jwt.decode(token, settings.orbitalward_secret_key, algorithms=[ALGORITHM]) + payload = jwt.decode(token, settings.orbitward_secret_key, algorithms=[ALGORITHM]) return payload.get("sub") except JWTError: return None diff --git a/backend/app/core/config.py b/backend/app/core/config.py index 70b125d..01729af 100644 --- a/backend/app/core/config.py +++ b/backend/app/core/config.py @@ -1,15 +1,19 @@ from functools import lru_cache -from pydantic import AnyHttpUrl, Field +from pydantic import AliasChoices, AnyHttpUrl, Field from pydantic_settings import BaseSettings, SettingsConfigDict class Settings(BaseSettings): model_config = SettingsConfigDict(env_file=".env", extra="ignore") - orbitalward_env: str = "development" - orbitalward_secret_key: str = Field(default="change-me", min_length=8) - database_url: str = "postgresql+psycopg://orbitalward:orbitalward@postgres:5432/orbitalward" + orbitward_env: str = Field(default="development", validation_alias=AliasChoices("ORBITWARD_ENV", "ORBITALWARD_ENV")) + orbitward_secret_key: str = Field( + default="change-me", + min_length=8, + validation_alias=AliasChoices("ORBITWARD_SECRET_KEY", "ORBITALWARD_SECRET_KEY"), + ) + database_url: str = "postgresql+psycopg://orbitward:orbitward@postgres:5432/orbitward" redis_url: str = "redis://redis:6379/0" frontend_url: AnyHttpUrl | str = "http://localhost:5173" backend_url: AnyHttpUrl | str = "http://localhost:8000" diff --git a/backend/app/core/secrets.py b/backend/app/core/secrets.py index faf668d..92cd7a0 100644 --- a/backend/app/core/secrets.py +++ b/backend/app/core/secrets.py @@ -7,7 +7,7 @@ from app.core.config import settings def _fernet() -> Fernet: - digest = hashlib.sha256(settings.orbitalward_secret_key.encode("utf-8")).digest() + digest = hashlib.sha256(settings.orbitward_secret_key.encode("utf-8")).digest() return Fernet(base64.urlsafe_b64encode(digest)) diff --git a/backend/app/main.py b/backend/app/main.py index d08a4e0..a7a719a 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -25,7 +25,7 @@ async def lifespan(_: FastAPI) -> AsyncIterator[None]: app = FastAPI( - title="OrbitalWard API", + title="OrbitWard API", version="0.1.0", description="Self-hosted infrastructure monitoring API", lifespan=lifespan, diff --git a/backend/pyproject.toml b/backend/pyproject.toml index a1ec77d..254cea3 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -1,7 +1,7 @@ [project] -name = "orbitalward-backend" +name = "orbitward-backend" version = "0.1.0" -description = "OrbitalWard FastAPI backend" +description = "OrbitWard FastAPI backend" requires-python = ">=3.12" dependencies = [ "alembic>=1.13.3", diff --git a/backend/tests/test_notifications.py b/backend/tests/test_notifications.py index 4e50e97..4595a4d 100644 --- a/backend/tests/test_notifications.py +++ b/backend/tests/test_notifications.py @@ -11,8 +11,8 @@ def test_notification_channel_does_not_return_saved_secret(client: TestClient, d json={ "name": "Operations Webhook", "channel_type": "generic_webhook", - "settings": {"username": "OrbitalWard"}, - "secret": "https://hooks.example.test/orbitalward", + "settings": {"username": "OrbitWard"}, + "secret": "https://hooks.example.test/orbitward", "is_enabled": True, }, ) @@ -25,8 +25,8 @@ def test_notification_channel_does_not_return_saved_secret(client: TestClient, d channel = db_session.get(NotificationChannel, body["id"]) assert channel is not None - assert channel.encrypted_secret != "https://hooks.example.test/orbitalward" - assert decrypt_secret(channel.encrypted_secret) == "https://hooks.example.test/orbitalward" + assert channel.encrypted_secret != "https://hooks.example.test/orbitward" + assert decrypt_secret(channel.encrypted_secret) == "https://hooks.example.test/orbitward" list_response = client.get("/notifications/channels") assert list_response.status_code == 200 @@ -42,7 +42,7 @@ def test_notification_channel_update_without_secret_preserves_existing_secret(cl json={ "name": "Mattermost", "channel_type": "mattermost", - "settings": {"username": "OrbitalWard"}, + "settings": {"username": "OrbitWard"}, "secret": "https://hooks.example.test/mattermost", "is_enabled": True, }, @@ -54,7 +54,7 @@ def test_notification_channel_update_without_secret_preserves_existing_secret(cl f"/notifications/channels/{channel_id}", json={ "name": "Mattermost Alerts", - "settings": {"username": "OrbitalWard Alerts"}, + "settings": {"username": "OrbitWard Alerts"}, "is_enabled": False, }, ) @@ -62,7 +62,7 @@ def test_notification_channel_update_without_secret_preserves_existing_secret(cl assert update_response.status_code == 200 body = update_response.json() assert body["name"] == "Mattermost Alerts" - assert body["settings"]["username"] == "OrbitalWard Alerts" + assert body["settings"]["username"] == "OrbitWard Alerts" assert body["is_enabled"] is False assert body["has_secret"] is True assert "secret" not in body diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 2a6350d..8bda8ec 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -1,14 +1,16 @@ +name: orbitward + services: postgres: image: postgres:16-alpine environment: - POSTGRES_USER: orbitalward - POSTGRES_PASSWORD: orbitalward - POSTGRES_DB: orbitalward + POSTGRES_USER: orbitward + POSTGRES_PASSWORD: orbitward + POSTGRES_DB: orbitward volumes: - postgres-dev-data:/var/lib/postgresql/data healthcheck: - test: ["CMD-SHELL", "pg_isready -U orbitalward -d orbitalward"] + test: ["CMD-SHELL", "pg_isready -U orbitward -d orbitward"] interval: 10s timeout: 5s retries: 5 diff --git a/docker-compose.yml b/docker-compose.yml index 067d26a..c69de60 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,14 +1,16 @@ +name: orbitward + services: postgres: image: postgres:16-alpine environment: - POSTGRES_USER: orbitalward - POSTGRES_PASSWORD: orbitalward - POSTGRES_DB: orbitalward + POSTGRES_USER: orbitward + POSTGRES_PASSWORD: orbitward + POSTGRES_DB: orbitward volumes: - postgres-data:/var/lib/postgresql/data healthcheck: - test: ["CMD-SHELL", "pg_isready -U orbitalward -d orbitalward"] + test: ["CMD-SHELL", "pg_isready -U orbitward -d orbitward"] interval: 10s timeout: 5s retries: 5 diff --git a/docs/agent-handoff.md b/docs/agent-handoff.md index 2928552..2293dd5 100644 --- a/docs/agent-handoff.md +++ b/docs/agent-handoff.md @@ -4,9 +4,9 @@ Last updated: 2026-05-26 ## Current Identity -- Product name: OrbitalWard -- Local repository path: `/home/ksmith/projects/OrbitalWard` -- Git remote: `https://git.firebugit.com/ksmith/OrbitalWard.git` +- Product name: OrbitWard +- Local repository path: `/home/ksmith/projects/OrbitalWard` until the working directory is moved +- Git remote: `https://git.firebugit.com/ksmith/OrbitWard.git` - Main branch: `main` - Latest pushed commit: check `origin/main` with `git log -1 --oneline origin/main` @@ -15,14 +15,14 @@ The project was previously named InfraPulse. Do not reintroduce the old name in ## Gitea Access - Gitea API base: `https://git.firebugit.com/api/v1` -- Repository API path: `/repos/ksmith/OrbitalWard` +- Repository API path: `/repos/ksmith/OrbitWard` - Access token file: `/home/ksmith/.codex_security/gitea_token` Never print the token value. Read it only inside commands that call the Gitea API. ## Current Product State -OrbitalWard is a secure monitoring appliance focused on the v0.1 vertical slice: +OrbitWard is a secure monitoring appliance focused on the v0.1 vertical slice: - Authenticated FastAPI backend with SQLAlchemy, Alembic, Pydantic, and JWT auth. - React, TypeScript, Vite, and Tailwind frontend. @@ -55,18 +55,18 @@ Recent Docker checks: Earlier rename and monitor work also verified: - `docker compose -f docker-compose.dev.yml up -d --build` -- Backend health returned `{"status":"ok","service":"orbitalward-backend"}`. +- Backend health returned `{"status":"ok","service":"orbitward-backend"}`. - Direct worker probes for TCP and ICMP ping checks passed inside the Docker network. - API probe created and deleted one ping monitor and one TCP monitor successfully. -The final Compose project uses `orbitalward-*` containers, images, network, and volumes. +The final Compose project uses `orbitward-*` containers, images, network, and volumes. ## Important Implementation Notes -- `ORBITALWARD_SECRET_KEY` is the encryption/JWT secret environment variable. -- `DATABASE_URL` now defaults to the `orbitalward` database/user in Compose. -- The frontend local storage key is `orbitalward_token`. -- Notification default username is `OrbitalWard`. +- `ORBITWARD_SECRET_KEY` is the encryption/JWT secret environment variable. +- `DATABASE_URL` now defaults to the `orbitward` database/user in Compose. +- The frontend local storage key is `orbitward_token`. +- Notification default username is `OrbitWard`. - The TLS expiry check lives in `worker/app/collectors/website.py` and is enabled per monitor through JSON config fields: - `check_tls_expiry` - `tls_warning_days` @@ -75,11 +75,11 @@ The final Compose project uses `orbitalward-*` containers, images, network, and Use the Gitea API with the token file above. Useful endpoints: -- List issues: `GET /repos/ksmith/OrbitalWard/issues?state=all` -- Create issue: `POST /repos/ksmith/OrbitalWard/issues` -- Update issue: `PATCH /repos/ksmith/OrbitalWard/issues/{index}` -- List milestones: `GET /repos/ksmith/OrbitalWard/milestones` -- List labels: `GET /repos/ksmith/OrbitalWard/labels` +- List issues: `GET /repos/ksmith/OrbitWard/issues?state=all` +- Create issue: `POST /repos/ksmith/OrbitWard/issues` +- Update issue: `PATCH /repos/ksmith/OrbitWard/issues/{index}` +- List milestones: `GET /repos/ksmith/OrbitWard/milestones` +- List labels: `GET /repos/ksmith/OrbitWard/labels` Issue source docs: diff --git a/docs/alerting-design.md b/docs/alerting-design.md index 95ae0c0..cd861ef 100644 --- a/docs/alerting-design.md +++ b/docs/alerting-design.md @@ -26,4 +26,4 @@ Initial channels: - Zoom Team Chat incoming webhook - Generic webhook -Alert messages should be human-readable and include asset, check, status, duration, timestamps, and a link back to OrbitalWard. +Alert messages should be human-readable and include asset, check, status, duration, timestamps, and a link back to OrbitWard. diff --git a/docs/architecture.md b/docs/architecture.md index 0ab5688..0887884 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -1,6 +1,6 @@ # Architecture -OrbitalWard is a monorepo with four main areas: +OrbitWard is a monorepo with four main areas: - `backend`: FastAPI service exposing REST endpoints and owning database access. - `worker`: Background scheduler and collectors for checks and alert evaluation. diff --git a/docs/discovery-design.md b/docs/discovery-design.md index 49e39ae..9835d56 100644 --- a/docs/discovery-design.md +++ b/docs/discovery-design.md @@ -1,6 +1,6 @@ # Discovery Design -Guided discovery is a core OrbitalWard workflow. +Guided discovery is a core OrbitWard workflow. ```text Add target @@ -16,7 +16,7 @@ Create monitors and optional alert rules ## Monitor vs Alert Separation -OrbitalWard must allow monitoring without alerting. Every discovered item should eventually support separate choices: +OrbitWard must allow monitoring without alerting. Every discovered item should eventually support separate choices: - Collect metric - Graph metric diff --git a/docs/gitea-issues.md b/docs/gitea-issues.md index 908738e..59eddfd 100644 --- a/docs/gitea-issues.md +++ b/docs/gitea-issues.md @@ -58,7 +58,7 @@ 44. Show and graph SNMP interface throughput 45. Build asset detail UI for monitors, metrics, and context 46. Refine metric-only monitor status semantics -47. Rename product from OrbitalWard to OrbitWard +47. Rename product to OrbitWard ## Current Implementation Snapshot diff --git a/docs/plugin-design.md b/docs/plugin-design.md index 5b92e76..0abab38 100644 --- a/docs/plugin-design.md +++ b/docs/plugin-design.md @@ -1,11 +1,11 @@ # Plugin Design -Plugins will let OrbitalWard add collectors and discovery logic without hard-coding every integration into the core API. +Plugins will let OrbitWard add collectors and discovery logic without hard-coding every integration into the core API. Target shape: ```python -class OrbitalWardPlugin: +class OrbitWardPlugin: name: str display_name: str diff --git a/docs/progress.md b/docs/progress.md index b4b24cf..429f188 100644 --- a/docs/progress.md +++ b/docs/progress.md @@ -1,10 +1,10 @@ -# OrbitalWard Progress +# OrbitWard Progress Last updated: 2026-05-26 ## Current State -OrbitalWard has a working Docker Compose development stack with PostgreSQL, Redis, FastAPI backend, Python worker, and React/Vite frontend. +OrbitWard has a working Docker Compose development stack with PostgreSQL, Redis, FastAPI backend, Python worker, and React/Vite frontend. Implemented foundation: @@ -35,7 +35,7 @@ Implemented notification slice: - Create, edit, test, and delete notification channels from the UI. - Generic webhook, Mattermost, and Zoom Team Chat channel types. -- Webhook URLs encrypted at rest using `ORBITALWARD_SECRET_KEY`. +- Webhook URLs encrypted at rest using `ORBITWARD_SECRET_KEY`. - Saved webhook URLs are not returned to the UI. - Configurable post username per notification channel. - Worker sends incident open and recovery notifications. @@ -148,4 +148,4 @@ Default local login comes from `.env`: - `INITIAL_ADMIN_EMAIL=admin@example.com` - `INITIAL_ADMIN_PASSWORD=change-me` -Change these values before using OrbitalWard outside local development. +Change these values before using OrbitWard outside local development. diff --git a/docs/security.md b/docs/security.md index 0ecb9df..81edf17 100644 --- a/docs/security.md +++ b/docs/security.md @@ -1,6 +1,6 @@ # Security -OrbitalWard must be secure from the beginning because it will store infrastructure credentials. +OrbitWard must be secure from the beginning because it will store infrastructure credentials. ## Authentication @@ -19,7 +19,7 @@ Credential records are modeled separately from monitors and assets. Secret field Rules: -- Use `ORBITALWARD_SECRET_KEY` from the environment. +- Use `ORBITWARD_SECRET_KEY` from the environment. - Never log secrets. - Mask saved secrets in the UI. - Audit credential create, update, and delete events. diff --git a/docs/vision.md b/docs/vision.md index d78e4ea..1d6c056 100644 --- a/docs/vision.md +++ b/docs/vision.md @@ -1,12 +1,12 @@ -# OrbitalWard Vision +# OrbitWard Vision -OrbitalWard is a secure, self-hosted monitoring platform for homelabs, small businesses, and internal IT teams. +OrbitWard is a secure, self-hosted monitoring platform for homelabs, small businesses, and internal IT teams. The v0.1 product should feel like a polished appliance, not a pile of raw monitoring configuration. Users should be guided through adding targets, testing connections, discovering useful items, choosing what to monitor, and separately choosing what should alert. ## Design Philosophy -OrbitalWard exposes intent, not implementation details. +OrbitWard exposes intent, not implementation details. Raw SNMP OIDs, probe internals, and collector details belong behind friendly profiles and advanced tools. The normal UI should say things like "Port 5 outbound traffic", "Graph this port", and "Alert if port goes down". diff --git a/frontend/index.html b/frontend/index.html index de63a13..46fa01d 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -3,7 +3,7 @@
-