{"id":51518860,"url":"https://github.com/the-last-devops/vantage","last_synced_at":"2026-07-08T14:00:39.339Z","repository":{"id":366526765,"uuid":"1274587543","full_name":"The-Last-Devops/vantage","owner":"The-Last-Devops","description":"Self-hosted DevOps control plane — monitor, alert \u0026 operate servers, clusters, services \u0026 cloud. Single Rust binary.","archived":false,"fork":false,"pushed_at":"2026-07-08T05:02:55.000Z","size":1560,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-07-08T05:05:32.006Z","etag":null,"topics":["alerting","axum","control-plane","devops","kubernetes","monitoring","observability","rust","self-hosted","vue"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/The-Last-Devops.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":"docs/ROADMAP.md","authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-19T17:10:10.000Z","updated_at":"2026-07-08T05:02:55.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/The-Last-Devops/vantage","commit_stats":null,"previous_names":["the-last-devops/last-monitor","the-last-devops/vantage"],"tags_count":21,"template":false,"template_full_name":null,"purl":"pkg:github/The-Last-Devops/vantage","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/The-Last-Devops%2Fvantage","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/The-Last-Devops%2Fvantage/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/The-Last-Devops%2Fvantage/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/The-Last-Devops%2Fvantage/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/The-Last-Devops","download_url":"https://codeload.github.com/The-Last-Devops/vantage/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/The-Last-Devops%2Fvantage/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35266857,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-08T02:00:06.796Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["alerting","axum","control-plane","devops","kubernetes","monitoring","observability","rust","self-hosted","vue"],"created_at":"2026-07-08T14:00:29.717Z","updated_at":"2026-07-08T14:00:39.325Z","avatar_url":"https://github.com/The-Last-Devops.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003c!-- NAMING: the product display name is \"Vantage\" (Title Case) everywhere in\n     the UI and docs. The slug \"vantage\" is only for repo / package / image\n     names. Do not show \"vantage\" as a user-facing label. --\u003e\n\u003cdiv align=\"center\"\u003e\n\n\u003cimg src=\"assets/banner.svg\" alt=\"Vantage\" width=\"760\"\u003e\n\n# Vantage\n\n**Lightweight, self-hosted DevOps control plane — written in Rust.**\n\nOne place to **monitor, alert and operate** your infrastructure — servers, clusters,\nservices and cloud. Host metrics (an agent on every server) with a NewRelic-style fleet\noverview, SSH/shell exec to act on a host, multi-user workspaces and RBAC — all served\nfrom a **single Rust binary** that embeds the web UI. No Node, no `node_modules` at runtime.\n\n\u003c/div\u003e\n\n---\n\n## Why\n\n- **One small binary.** The hub serves the JSON API **and** the web UI (a Vue SPA embedded\n  into the binary). The whole repo is ~0.25 MB on GitHub; the agent and hub share one Rust\n  workspace.\n- **Push-based agents.** Agents reach out to the hub, so they work behind NAT/firewalls.\n  One reusable **API key** can enroll a whole fleet (e.g. a Kubernetes DaemonSet) — hosts\n  auto-register by hostname.\n- **Time-series done right.** Metrics live in PostgreSQL + **TimescaleDB** hypertables, kept\n  separate from the config database so the time-series store can be scaled independently.\n\n## Features\n\n**Fleet overview** — every host on one chart per metric (CPU / Memory / Disk / Network).\nHover a host to isolate its line across all charts, click to pin (multi-select), drag to\nzoom a time range — selection and zoom window are kept in the URL (shareable). A powerful\nsearch box filters both charts and tables: `web*`, `cpu\u003e50`, `ws:production`, `kind:docker`.\n\n**Host metrics** (agent) — overall CPU plus a **CPU breakdown** (user / system / iowait /\nsteal on Linux via `/proc/stat`; user / system on macOS via mach), **load average** (1m / 5m\n/ 15m), memory, swap, disk usage, **disk I/O**, network throughput, uptime, temperature\nsensors, NVIDIA **GPU** (usage / VRAM / power), and **per-container Docker stats**.\n\n**Systems view** — nodes, Docker hosts (expand to their containers) and Kubernetes clusters\n(expand to their nodes), with a workspace column, sortable columns, multi-select + bulk\ndelete, and an **Add system** wizard (binary / Docker / Compose / k8s DaemonSet).\n\n**Per-system detail** — uPlot charts with a synced cursor, drag-to-zoom, interactive legends\nand live updates (sub-hour ranges refresh every second). Charts always span the selected\nwindow, leaving blank space when data is sparse.\n\n**Services (uptime monitors)** — Uptime-Kuma-style checks: HTTP/HTTPS (status ranges,\nkeyword match, headers/body/auth, redirects), TCP, Ping, DNS, TLS-cert expiry, **Push**\n(passive), and database probes — **PostgreSQL, MySQL, MongoDB, Redis, RabbitMQ**. Full\nedit form, retries/flap guard, upside-down mode, and a **last request/response debug**\npanel (with copy) for the most recent success and failure. A `Down` view lists only what's\ncurrently failing.\n\n**Alerting** — wire a source (host or service) → a condition → one or more **notify\nchannels**. 17 channel types (Telegram, Slack, Discord, Mattermost, Teams, Google Chat,\nMatrix, ntfy, Pushover, Gotify, Bark, PagerDuty, Opsgenie, Twilio SMS, SMTP email, generic\nwebhook, Apprise) with a one-click test; fire on monitor-down or a host condition (offline,\nCPU/memory/load), with an optional **re-notify cadence** while still firing. Channels are a\nshared resource any workspace can attach. An **Events** feed records every fire/recover with\ndurations.\n\n**Needs attention** — triage view that surfaces only abnormal hosts (down / high\nCPU / memory / disk / disk-I/O), with per-workspace thresholds.\n\n**API \u0026 automation** — a token-authed JSON API (**personal access tokens** under Settings)\nand an **embedded MCP server** (`POST /mcp`) so AI assistants (Claude, etc.) can read and\noperate the monitor with your RBAC. See [docs/API.md](docs/API.md).\n\n**Admin \u0026 data** — a human-readable **audit log** (action + affected object), an **About**\npage (version + update check), **data retention** tiers (TimescaleDB continuous aggregates +\nretention policies), and **backup / restore** — download/upload or scheduled to\nS3-compatible storage.\n\n**Multi-tenant** — workspaces (k8s-style names), workspace-scoped RBAC plus a system\n`admin`, opaque revocable cookie sessions (argon2), and a first-run wizard to create\nthe admin account. Reusable API keys enroll agents; deleting a key de-registers its hosts.\n\nThe sidebar groups everything into **Infrastructure**, **Services**, **Alert** and\n**Settings** — click a parent to jump to its first page, hover the arrow to reveal its\nsub-pages.\n\n## Architecture\n\n\u003cp align=\"center\"\u003e\u003cimg src=\"assets/architecture.svg\" alt=\"Vantage architecture\" width=\"900\"\u003e\u003c/p\u003e\n\nAgents **push** to the hub (`POST /pub/ingest`, per-server token) so they work behind\nNAT/firewalls — the hub never connects back. The hub is a **single Rust binary** that also\n**probes** your services and embeds the Vue SPA. Storage is **two separate PostgreSQL\ndatabases** (config vs time-series on TimescaleDB), related only by IDs at the application\nlayer — **never JOINed** — so the time-series store can scale or relocate independently. The\nagent ↔ hub wire types live in `crates/shared`. See [CLAUDE.md](CLAUDE.md) for the full design.\n\n## Quick start (Docker Compose)\n\n```bash\ngit clone \u003crepo\u003e \u0026\u0026 cd vantage\nbash scripts/frontend.sh build        # build the embedded Vue SPA → frontend/dist (first run installs deps)\ndocker compose up -d --build          # Postgres/TimescaleDB + hub (:8080) + Adminer (:8088) + a bundled agent\n```\n\nOpen **http://localhost:8080**. On first run you create the admin account (or set\n`ADMIN_EMAIL` / `ADMIN_PASSWORD`). A bundled agent reports the Docker host out of the box.\n\n\u003e Want sizeable test data? `bash scripts/sim-agents.sh` spins up many simulated\n\u003e node / docker / k8s hosts pushing realistic metrics.\n\nFor **production** — Docker with published images, or **Kubernetes** via the Helm chart —\nsee the [deploy guide](deploy/README.md).\n\n## Security\n\nSecurity is the top design constraint. What protects a Vantage deployment:\n\n- **Authentication** — opaque, revocable DB-backed **sessions** (httpOnly cookie; argon2id\n  passwords). Programmatic callers use **PATs** (`Authorization: Bearer \u003ctoken\u003e`); agents\n  authenticate per-request with a per-server `x-api-key`. **No open registration** — the first\n  admin is bootstrapped from `ADMIN_EMAIL`/`ADMIN_PASSWORD`, then admins provision users.\n- **Two-factor auth** (opt-in, per user — **Settings → Security**): an **authenticator app\n  (TOTP)** — scan a QR with Google Authenticator / 1Password / Authy — and/or **passkeys\n  (WebAuthn)** (Touch ID / Windows Hello / a security key). Sign-in then requires the second\n  factor; one-time backup codes are issued. See [docs/auth-2fa-passkey.md](docs/auth-2fa-passkey.md).\n- **Login throttle** — repeated failures lock the account for an escalating cooldown, so\n  passwords and 2FA codes can't be brute-forced online.\n- **Workspace-scoped RBAC** — `owner` / `editor` / `viewer` per workspace, plus a system\n  `admin`. Shell/exec into a host additionally requires the `can_exec` capability.\n- **Put the hub behind an auth gate.** Vantage authenticates every request, but you should\n  still front it with **nginx basic-auth**, **Cloudflare Zero Trust**, or a **VPN** so the\n  login page + `/api` aren't open to the internet — **allowing `/pub/*` through** for agents.\n  Set `PUBLIC_URL`, then **Settings → Security → Public exposure → Check now** verifies it.\n  See [docs/exposure.md](docs/exposure.md).\n\n### SSH key encryption (`EXEC_APP_SECRET`)\n\nUsers' stored SSH private keys are protected with **envelope encryption**. Each user has\none random **master key** that seals their keys; the master key is itself wrapped in two\nlayers:\n\n1. **inner — the user's password** (argon2id). The hub can unwrap a master key only while\n   the user supplies their password (at login / console step-up). Changing a password only\n   re-wraps the master key, so the user's keys keep working — no bulk re-encryption.\n2. **outer — the application secret** `EXEC_APP_SECRET` (kept in env / a secrets manager,\n   **never in the database**). A database leak therefore can't unwrap anything, even with a\n   guessed password.\n\nSet a high-entropy secret in production (omit it in dev → only the password layer applies,\nand the hub logs a warning):\n\n```bash\nEXEC_APP_SECRET=\"$(openssl rand -base64 32)\"   # 32 random bytes; store it safely!\n```\n\n\u003e ⚠️ **Back up `EXEC_APP_SECRET`.** If you lose it, every user's master key (and thus their\n\u003e SSH keys) becomes undecryptable. Keep it in a secrets manager, not only in the DB host.\n\n### Rotating the application secret\n\nRotation re-wraps only the **outer** layer of each master key — no user passwords are\nneeded. Run the hub binary's one-shot subcommand with both the new and old secret set:\n\n```bash\nEXEC_APP_SECRET=\"\u003cNEW secret\u003e\" \\\nEXEC_APP_SECRET_OLD=\"\u003cprevious secret\u003e\" \\\nCONFIG_DATABASE_URL=… DATA_DATABASE_URL=… \\\n  vantage-hub rotate-app-secret          # or: bash scripts/rotate-app-secret.sh\n```\n\nIt re-wraps every user to the new secret and prints how many were updated. Once it succeeds,\ndrop `EXEC_APP_SECRET_OLD` and restart the hub with only the new `EXEC_APP_SECRET`. (The same\ncommand also **enables** the secret for the first time on an existing deployment.)\n\n\u003e An **admin password reset** can't recover a user's keys (the old password is unknown), so it\n\u003e mints a fresh master key and drops that user's stored SSH keys — they re-add them. A user\n\u003e changing **their own** password (with the old one) keeps their keys.\n\n## Adding servers\n\nIn the UI: **Add system** → pick Node / Docker / Kubernetes → copy the install snippet. The\nAPI key is managed for you. Run the agent anywhere; hosts appear automatically.\n\n```bash\n# Docker (reports host metrics via shared workspaces + mounts)\ndocker run -d --restart=unless-stopped --pid=host \\\n  -e HUB_URL=https://hub.example.com -e API_KEY=\u003capi-key\u003e -e DISK_PATH=/host \\\n  -v /:/host:ro -v /var/run/docker.sock:/var/run/docker.sock:ro \\\n  ghcr.io/\u003cowner\u003e/vantage-agent:latest\n```\n\nA **Helm chart** for the hub and a DaemonSet manifest for agents live in [deploy/](deploy/).\n\n## Environment variables\n\n### Hub (`vantage-hub`)\n\n| Variable | Required | Default | Meaning |\n|---|---|---|---|\n| `CONFIG_DATABASE_URL` | ✅ | — | Postgres URL for the **config** DB (users, workspaces, RBAC, server/monitor config, alert rules, status pages). |\n| `DATA_DATABASE_URL` | ✅ | — | Postgres **+ TimescaleDB** URL for the **data** DB (metrics \u0026 heartbeat hypertables). May point at the same instance early on. |\n| `ADMIN_EMAIL` / `ADMIN_PASSWORD` | — | — | Bootstrap the first admin on startup if that user doesn't exist. After first login, provision users in the UI. |\n| `EXEC_APP_SECRET` | — (prod: yes) | — | Application secret that wraps the **outer** layer of every user's SSH-key master key. **Set a high-entropy value in production** and back it up. Omitted → password-only protection + a startup warning. See [Security](#security). |\n| `EXEC_APP_SECRET_OLD` | — | — | The previous `EXEC_APP_SECRET`, set **only during a rotation** so old rows can be re-wrapped (`vantage-hub rotate-app-secret`). |\n| `PUBLIC_URL` | — | auto-detected from the request | The hub's externally-reachable base URL (e.g. `https://vantage.example.com`), used by the public-exposure self-check. Normally **auto-detected** from the request's `X-Forwarded-Proto`/`-Host` (or `Host`); set this only to override. |\n| `WEBAUTHN_RP_ID` | — | derived from request `Origin` | Passkey relying-party ID — the **registrable domain** (e.g. `vantage.example.com`). **Optional**: left unset, it's derived per request from the browser's `Origin`, so passkeys work on whatever domain serves the hub. Set it (with `WEBAUTHN_ORIGIN`) only to pin one canonical RP. |\n| `WEBAUTHN_ORIGIN` | — | derived from request `Origin` | Passkey origin(s) — full scheme+host the SPA is served from. **Optional** (see `WEBAUTHN_RP_ID`). Comma-separate to allow several (e.g. dev `http://localhost:5173,http://localhost:8080`). |\n| `BIND_ADDR` | — | `0.0.0.0:8080` | Listen address. |\n| `INSECURE_COOKIES` | — | `0` | Set `1` to drop the `Secure` flag on the session cookie (local **http** dev only). |\n| `EGRESS_POLICY` | — | (allow private) | Set `strict` to also block private (RFC1918/ULA) outbound targets for probes / notify / backup (SSRF hardening). |\n| `LOCAL_API_KEY` | — | — | If set, auto-creates a `default` workspace + a `local` server enrolled with this key (lets the bundled compose agent report out of the box). |\n| `AUTO_UPDATE` | — | — | On the `:auto-update` channel under k8s, opt the hub into self-update. |\n| `RUST_LOG` | — | `info,sqlx=warn` | Log filter (`tracing` / `env_filter`). |\n\n\u003e `GIT_SHA` / `VANTAGE_CHANNEL` are **build-time** args (set by CI, baked via `build.rs`) for the About page + auto-update channel — not runtime config.\n\n### Agent (`vantage-agent`)\n\n| Variable | Required | Default | Meaning |\n|---|---|---|---|\n| `HUB_URL` | ✅ | — | Base URL of the hub the agent pushes to (e.g. `https://vantage.example.com`). |\n| `API_KEY` | ✅ | — | The per-server enrollment key (sent as `x-api-key`). The hub maps it to a workspace; hosts auto-register. |\n| `INTERVAL` | — | hub-controlled | Optional push-cadence override (seconds). Normally the hub returns the next interval. |\n| `ALLOW_SHELL` | — | **on** | The reverse SSH tunnel for the browser console. Set `0`/`false`/`no`/`off` to disable shell access from this host. |\n| `HOSTNAME_OVERRIDE` | — | system hostname | The name this host reports as. |\n| `AGENT_KIND` | — | auto | Force the agent kind (`node` / `docker` / `kubernetes`) instead of auto-detecting. |\n| `CLUSTER` | — | — | Cluster label for grouping (Kubernetes). |\n| `DISK_PATH` | — | `/` | Filesystem path to report disk usage for (use `/host` when mounting the host root into a container). |\n| `NODE_NAME` | — | (k8s downward API) | The Kubernetes node name when running as a DaemonSet. |\n| `AUTO_UPDATE` | — | — | Opt the agent into self-update on the auto-update channel. |\n\n## Development\n\n```bash\ncargo build                  # whole workspace (hub + agent + shared)\ncargo test                   # unit tests\ncargo clippy --all-targets   # lint\ncargo fmt                    # format\n\nbash scripts/frontend.sh dev # Vite dev server on :5173 (HMR; proxies the API to :8080)\nbash scripts/frontend.sh build  # produce frontend/dist embedded by the hub\nHUB_URL=http://localhost:8080 API_KEY=\u003ckey\u003e cargo run -p vantage-agent   # run an agent\n```\n\nDuring UI work use the Vite dev server (**:5173**) — it hot-reloads and is immune to hub\nrebuilds. The hub serves the built SPA at **:8080**.\n\n**Stack:** Rust + **Axum** (hub), **sysinfo + bollard** (agent), **sqlx** (runtime queries),\n**PostgreSQL + TimescaleDB**, **Vue 3 + Vite + uPlot + Tailwind** SPA embedded via\n`rust-embed`.\n\n## Documentation\n\n- [docs/](docs/README.md) — documentation index.\n- [docs/API.md](docs/API.md) — HTTP API + MCP server reference (auth, endpoints, tools).\n- [deploy/README.md](deploy/README.md) — install \u0026 deploy (Docker, Kubernetes/Helm, agents).\n- [CLAUDE.md](CLAUDE.md) — architecture \u0026 engineering conventions.\n- [CHANGELOG.md](CHANGELOG.md) — per-release changes.\n\n## Roadmap\n\nService monitors (12 types), **multi-channel alerting** + events feed, the audit log,\nTimescaleDB rollups + tunable retention, **backup/restore (S3, scheduled)**, and a\n**token-authed API + embedded MCP server** are all **shipped**. Planned next: **web\nSSH/terminal** into hosts and an **adaptive report interval** (realtime only while a host\nis being viewed). See [docs/ROADMAP.md](docs/ROADMAP.md).\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthe-last-devops%2Fvantage","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fthe-last-devops%2Fvantage","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthe-last-devops%2Fvantage/lists"}