# Introduction > ServerBee is a lightweight, modern VPS monitoring system built with Rust and React. URL: https://docs.serverbee.app/en/docs ServerBee is a lightweight, self-hosted VPS monitoring probe system designed for individuals and small teams. Built with a full Rust stack and a React frontend, it pairs high performance with a very small resource footprint. ## What Is ServerBee [#what-is-serverbee] ServerBee has two core components: * **Server** -- the control center you run on a management host. It serves the web dashboard, stores data, evaluates alerts, runs background jobs, and exposes the REST API. * **Agent** -- a lightweight collector you install on each monitored host. It gathers system metrics and reports them in real time, and (when authorized) runs probes, terminal, file, and Docker operations. Agents keep a persistent WebSocket connection to the server for real-time push. All data is stored in SQLite, so there is no external database to install. ## Key Features [#key-features] ### Real-time monitoring and custom dashboards [#real-time-monitoring-and-custom-dashboards] View the live status of every server through a WebSocket-driven dashboard. Metrics include CPU, memory, disk, network, load average, temperature, GPU, disk I/O, and traffic, arranged across multiple dashboards and widget layouts. ### Alerts and service monitors [#alerts-and-service-monitors] A flexible alert engine supports resource thresholds, traffic cycles, network quality, offline, expiry, and IP-change events, with debounce logic and maintenance suppression. Service monitors cover SSL certificate, DNS, HTTP keyword, TCP, and WHOIS checks, and deliver notifications through Webhook, Telegram, Bark, Email, APNs, and other channels. ### Web terminal, remote tasks, and file manager [#web-terminal-remote-tasks-and-file-manager] Open a browser-based PTY shell on any server, run one-shot commands, or schedule cron tasks with retries. The file manager provides controlled remote browse, read, edit, upload, and download operations with path sandboxing and audit logs. ### Ping, network quality, and traceroute [#ping-network-quality-and-traceroute] Run ICMP, TCP, and HTTP probes from multiple agents to measure target reachability, latency, and packet loss, with charts, CSV export, and preset network targets. The network detail page also offers traceroute for troubleshooting. ### Docker management [#docker-management] When the Docker capability is enabled, view the container list, live stats, lifecycle events, log streams, networks, and volumes, and perform container actions. ### Public status page, themes, and branding [#public-status-page-themes-and-branding] Publish a single public status page at `/status`—no authentication required—covering a selected server scope, live metrics, 90-day uptime timelines, availability thresholds, incidents, and maintenance windows. Appearance settings support preset and custom OKLCH themes plus white-label title, logo, favicon, and footer text. ### VPS cost insights [#vps-cost-insights] After you record each server's price and billing cycle, ServerBee surfaces the monthly-equivalent cost, burn rate, per-resource unit costs, and remaining days in the servers list, the dashboard server card, and a per-server insights panel — plus objective cost advisories (idle burn, offline while paying, expired billing, low uptime). ### OAuth and mobile support [#oauth-and-mobile-support] Sign in via GitHub, Google, or any generic OIDC provider. Mobile sessions, device pairing, and push notifications are supported for the iOS client. ### Tested and CI-enforced [#tested-and-ci-enforced] The project ships with more than 3,800 automated tests, including integration suites that drive the real Axum router over HTTP and WebSocket through a mock-agent harness. The main CI workflow compares the changed files with the base commit and runs zero-warning Clippy, the Rust test jobs, and the frontend and installer checks only for the areas a change touches. See [Testing & Quality](/en/docs/testing). ## Tech Stack [#tech-stack] | Component | Technology | | ---------- | ------------------------------------------------------------------------------------------- | | Server | Rust (Axum 0.8, sea-orm, tokio, SQLite) | | Agent | Rust (sysinfo, tokio-tungstenite) | | Frontend | React 19, Vite 7, TanStack Router, TanStack Query, Bklit charts, shadcn/ui, Tailwind CSS v4 | | Protocol | WebSocket (JSON text messages; terminal data base64-encoded) | | Database | SQLite (WAL mode) | | Deployment | Single binary / Docker / install script | ### Why Rust [#why-rust] * **High performance** -- the agent uses \~4.3 MB at cold start and \~27 MB in steady state (systemd cgroup); a production server typically runs 140–170 MB with `MALLOC_ARENA_MAX=2` set. See [Resource Usage](/en/docs/resource-usage). * **Reliability** -- Rust's memory safety eliminates a whole class of common runtime errors. * **Single-binary deployment** -- the server embeds the frontend assets via `rust-embed`, so you ship one executable. * **Zero dependencies** -- no MySQL or Redis to install; SQLite is embedded in the binary. ## How It Works [#how-it-works] ServerBee follows a hub-and-spoke architecture: 1. The **Server** is the central node: it serves the web UI and REST API, manages all WebSocket connections, and runs background jobs. 2. **Agents** run on each monitored VPS, collecting metrics and reporting back every few seconds. 3. The **Frontend** is a React SPA embedded into the server binary, so there is nothing extra to deploy. All agent-to-server communication uses WebSocket with JSON-encoded messages. Terminal sessions reuse the same JSON text protocol, with the raw PTY byte stream carried in a base64-encoded `data` field. ## Next Steps [#next-steps] # Quick Install > Install the ServerBee server and agents in minutes with the one-line install script. URL: https://docs.serverbee.app/en/docs/quick-start The recommended way to deploy ServerBee is the `deploy/install.sh` one-line script. It handles the tedious parts for you — architecture detection, binary downloads, and service registration — so it works out of the box: * **Interactive wizard** — run it with no arguments to pick the language, component, and install method through prompts * **Architecture aware** — detects amd64/arm64 and pulls the matching Release binary * **Service integration** — creates and starts a systemd or OpenRC service, and enables it on boot * **Binary or Docker** — switch with `--method`; Docker is recommended for the server, binary for agents * **Automatic HTTPS** — pass `--domain` when installing the server and the script sets up Caddy and issues a certificate * **Unified management CLI** — installs a `serverbee` command for upgrades, restarts, config changes, and uninstalls The guide below walks through deployment from two angles: the **Server** and the **Client (monitored node / Agent)**. This site tracks the repository's `main` branch. A published release can lag behind these pages. The default `auto` channel installs the newest stable release when one exists and otherwise falls back to the newest prerelease; upgrades reuse the policy saved at installation. Use `--channel stable` or `--channel beta` only to opt into one track explicitly. For a repeatable deployment, pin `--version vX.Y.Z` and compare that release's changelog before using a newly documented feature. ## Choose a deployment method [#choose-a-deployment-method] | Method | Best for | Persistence and operations | | ---------------------------------------------------- | -------------------------------------------- | -------------------------------------------------------------------------------------------------------- | | Binary (the non-interactive default) | Small VPSes and the shortest path | Files live under `/opt/serverbee`; systemd or OpenRC manages the service; `serverbee upgrade` updates it | | Docker (recommended when you already operate Docker) | Container isolation and image-based upgrades | Named volume stores data; Compose manages the container; back up both the volume and config | | Railway | Managed public HTTPS | Attach a `/data` volume and use Railway logs for first-run credentials | The commands below choose `--method binary` explicitly so their files, logs, and service commands are predictable. Add `--method docker` when you want the Docker lifecycle. ## Prerequisites [#prerequisites] * A Linux host (amd64 / arm64); the script must run as root (it checks and errors out otherwise). Use `sudo` as a regular user; if you are already root (e.g. logged in as root or inside a container) you can drop the `sudo` from the commands * The binary method needs systemd or OpenRC; the Docker method needs Docker 20.10+ and Compose V2 * **Direct IP / HTTP:** allow inbound TCP `9527` * **Domain / HTTPS:** point an `A`/`AAAA` record at the host, allow inbound TCP `80` and `443`, and make sure no non-Caddy service already listens on those ports. Do not expose `9527` publicly; Caddy connects to its loopback binding *** ## Server [#server] The server is the central node: it receives metrics from agents, stores them, and serves the web dashboard. ### Step 1: Install the server [#step-1-install-the-server] There are two quick ways to install the server — pick one based on whether you have a domain. #### Option A: Install with an IP (plain HTTP) [#option-a-install-with-an-ip-plain-http] Fastest to get going; use this when you don't have a domain. You'll reach it at `http://:9527`: ```bash curl -fsSL https://raw.githubusercontent.com/ZingerLittleBee/ServerBee/main/deploy/install.sh | sudo sh -s -- server --method binary -y ``` The script writes `auth.secure_cookie = false` so browser login works over plain HTTP. This path does not encrypt browser, Agent, terminal, file, or command traffic; use it only on a trusted network or for initial evaluation, and prefer HTTPS for production. #### Option B: Install with a domain (automatic HTTPS) [#option-b-install-with-a-domain-automatic-https] If you already have a domain whose DNS `A` record points to this server, add `--domain` and the script sets up Caddy and issues an HTTPS certificate for you. You'll reach it at `https://your-domain`: ```bash curl -fsSL https://raw.githubusercontent.com/ZingerLittleBee/ServerBee/main/deploy/install.sh | sudo sh -s -- server \ --method binary \ --domain monitor.example.com \ --email admin@example.com \ -y ``` The script first checks that the domain resolves to this server; if it doesn't resolve or points to a different IP, installation stops and tells you which DNS record to add. `--email` is for Let's Encrypt notices and can be omitted. To add a domain to an already-installed server, run `sudo serverbee domain setup --domain monitor.example.com --email admin@example.com`. You can also run it with no arguments for an interactive wizard that prompts for language, binary vs Docker, and whether to configure a domain: `curl -fsSL https://raw.githubusercontent.com/ZingerLittleBee/ServerBee/main/deploy/install.sh | sudo sh`. Either way, the script automatically: For the binary method shown above, the script detects the architecture, downloads and verifies the matching release, creates `/opt/serverbee/{bin,etc,data}`, registers a systemd or OpenRC service, and installs the management CLI at `/usr/local/bin/serverbee`. Docker mode instead generates Compose and configuration files under `/opt/serverbee` and persists data in a named volume. If Docker is available, running the server in Docker is recommended — upgrades and isolation are easier. Just add `--method docker`: `... | sudo sh -s -- server --method docker -y`. ### Step 2: Get the first-run admin password [#step-2-get-the-first-run-admin-password] On its first start the server creates an admin account with a random password and prints it to the logs exactly once: ```bash sudo journalctl -u serverbee-server --no-pager | grep -A8 'FIRST-RUN ADMIN CREDENTIALS' | tail -n 9 ``` For the Docker method, retrieve the complete last credential block explicitly: ```bash docker logs serverbee-server 2>&1 | grep -A8 'FIRST-RUN ADMIN CREDENTIALS' | tail -n 9 ``` The password is shown only once — copy it before logs rotate. First login forces a password change, so do that before exposing the server to the internet. ### Step 3: First login [#step-3-first-login] 1. Open `http://:9527` in your browser (or `https://your-domain` for the domain method) 2. Log in with the default account `admin` and the random password from the logs 3. Complete the forced password change; you can pick a new username while you're at it If you installed with an IP over plain HTTP and later want to switch to a domain with HTTPS: run `sudo serverbee domain setup --domain monitor.example.com --email admin@example.com`, set `auth.secure_cookie` back to `true` in `/opt/serverbee/etc/server.toml` and restart the server, then update the `server_url` of any connected agents to the new `https://` address. ### Manage the server [#manage-the-server] After installation, use the `serverbee` command to manage the instance: ```bash sudo serverbee status # Status of all components sudo serverbee upgrade -y # Upgrade to the latest version sudo serverbee restart # Restart services sudo serverbee config # View current config sudo serverbee config set # Change config sudo serverbee uninstall server --purge # Uninstall and wipe data ``` `uninstall --purge` permanently deletes the Server database, configuration, and managed Docker volume. Create and verify a backup first; omit `--purge` when you want to preserve data for recovery or reinstallation. *** ## Client (Agent) [#client-agent] The agent is a lightweight probe running on each monitored machine; it reports CPU, memory, disk, and network metrics back to the server. ### Step 1: Add the Server and receive its enrollment offer [#step-1-add-the-server-and-receive-its-enrollment-offer] Sign in to the dashboard as an admin, go to **Servers → Add Server**, enter the machine profile, and submit it. ServerBee atomically creates the pending Server and its bound enrollment offer, then shows the one-time code and install command. The code is **single-use** and **short-lived** (10 minute default expiry), and is only needed the first time an agent registers. Generate a fresh one for each new agent. ### Step 2: Install and start the agent [#step-2-install-and-start-the-agent] On the machine you want to monitor, run the install script and pass the server URL and enrollment code as arguments: ```bash curl -fsSL https://raw.githubusercontent.com/ZingerLittleBee/ServerBee/main/deploy/install.sh | sudo sh -s -- agent \ --server-url http://your-server-ip:9527 \ --enrollment-code YOUR_ONE_TIME_CODE ``` The script detects the architecture, downloads the binary, writes the config, registers a systemd service, and brings the agent up. Install agents via the binary method — that's the only way to collect full host metrics. Running the agent in Docker is not recommended. On its first start, the Agent generates and persists its run token before claiming the offer. The Server stores only the hash and returns the existing `server_id`. Later starts use that persisted token and no longer need the code. If the token is lost, start graceful or emergency re-enrollment from that Server's detail page. ### Step 3: Verify the connection [#step-3-verify-the-connection] Back in the dashboard, the new server shows up online within seconds and live metrics start flowing. You can also check from the agent machine: ```bash sudo serverbee status sudo journalctl -u serverbee-agent -n 80 --no-pager ``` ### Manage the agent [#manage-the-agent] ```bash sudo serverbee status # Agent status sudo serverbee upgrade -y # Upgrade the agent sudo serverbee restart # Restart the agent sudo serverbee uninstall agent # Uninstall the agent ``` For collector, logging, and other tunable options, see [Agent Configuration](/en/docs/agent) and the [full configuration reference](/en/docs/configuration). *** ## Accessing by IP vs Domain [#accessing-by-ip-vs-domain] ServerBee works with a direct server IP or behind a domain and reverse proxy. What extra setup you need depends on whether the browser ends up on plain HTTP or HTTPS. | Access Mode | Browser URL | Server Cookie Setting | Agent `server_url` | Extra Setup | | --------------------- | ----------------------------- | --------------------------------------------------------------------- | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | Direct IP, plain HTTP | `http://your-server-ip:9527` | `auth.secure_cookie = false` or `SERVERBEE_AUTH__SECURE_COOKIE=false` | `http://your-server-ip:9527` | Open port `9527` in your firewall | | Domain with HTTPS | `https://monitor.example.com` | `auth.secure_cookie = true` or `SERVERBEE_AUTH__SECURE_COOKIE=true` | `https://monitor.example.com` | Point DNS at the server IP; allow inbound 80/443; keep 9527 private; the script sets up Caddy (or configure Nginx/Traefik yourself) | *** ## Alternative: Deploy the server manually with Docker Compose [#alternative-deploy-the-server-manually-with-docker-compose] If you'd rather manage the server with your own Compose file, create a `docker-compose.yml`: ```yaml title="docker-compose.yml" services: serverbee-server: image: ghcr.io/zingerlittlebee/serverbee-server:1.0.0-beta.6 container_name: serverbee-server ports: - "9527:9527" volumes: - serverbee-data:/data environment: - SERVERBEE_AUTH__SECURE_COOKIE=false restart: unless-stopped volumes: serverbee-data: ``` Start it and read the first-run admin password: ```bash docker compose up -d docker compose logs serverbee-server ``` The example disables the `Secure` cookie flag because this setup uses plain HTTP; once you're on HTTPS, set `SERVERBEE_AUTH__SECURE_COOKIE=true`. Agents should still be onboarded via the `install.sh agent` binary method. # Troubleshooting > Diagnose common installation, login, TLS, enrollment, and upgrade failures. URL: https://docs.serverbee.app/en/docs/troubleshooting Start with the symptom below. Run commands on the affected host and avoid posting tokens, enrollment codes, API keys, or the first-run password in an issue. ## First checks [#first-checks] ```bash sudo serverbee status sudo ss -ltnp | grep -E ':(80|443|9527)[[:space:]]' ``` Then inspect the logs for your deployment: | Deployment | Command | | -------------- | -------------------------------------------------------- | | systemd Server | `sudo journalctl -u serverbee-server -n 100 --no-pager` | | systemd Agent | `sudo journalctl -u serverbee-agent -n 100 --no-pager` | | OpenRC | `sudo tail -n 100 /var/log/serverbee-{server,agent}.log` | | Docker Server | `docker logs --tail 100 serverbee-server` | | Docker Agent | `docker logs --tail 100 serverbee-agent` | ## First-run password is missing [#first-run-password-is-missing] The random password is printed once, only when the first user is created. Read the complete last credentials block: ```bash sudo journalctl -u serverbee-server --no-pager \ | grep -A8 'FIRST-RUN ADMIN CREDENTIALS' | tail -n 9 docker logs serverbee-server 2>&1 \ | grep -A8 'FIRST-RUN ADMIN CREDENTIALS' | tail -n 9 ``` If a user already exists, restarting does not generate another password. Use the supported account recovery/admin path for that installation; do not delete the database to force first-run behavior. ## Login loops or the cookie is not stored [#login-loops-or-the-cookie-is-not-stored] Keep the public URL and cookie mode aligned: * Plain `http://IP:9527` requires `auth.secure_cookie = false`. * HTTPS requires `auth.secure_cookie = true`, and the proxy must forward `Host`, `X-Forwarded-Proto`, and WebSocket upgrades. After changing `/opt/serverbee/etc/server.toml`, run `sudo serverbee restart server`. Clear only the ServerBee site's cookies and retry. Do not disable Secure cookies on an HTTPS production deployment. ## Port, DNS, or certificate setup fails [#port-dns-or-certificate-setup-fails] * Direct-IP mode needs inbound TCP `9527`. * Managed domain mode needs inbound TCP `80` and `443`, and no non-Caddy listener on either port. Keep `9527` private. * Confirm the domain resolves to this host with `getent ahosts monitor.example.com`. * Inspect Caddy with `sudo caddy validate --config /etc/caddy/Caddyfile` and `sudo journalctl -u caddy -n 100 --no-pager`. Fix DNS/firewall/listener conflicts first, then rerun `sudo serverbee domain setup --domain monitor.example.com --email admin@example.com -y`. ## Agent stays offline or enrollment fails [#agent-stays-offline-or-enrollment-fails] 1. Confirm the Agent's `server_url` exactly matches the browser's reachable HTTP/HTTPS origin. 2. Check the Agent log for `expired`, `consumed`, `replaced`, rate-limit, TLS, or connection errors. 3. Enrollment offers are bound, single-use, and expire after 10 minutes by default. Replace the exact outstanding offer on the existing Server when necessary. 4. Before claim, correct values locally: ```bash sudo serverbee config set server_url https://monitor.example.com -y sudo serverbee config set enrollment_code NEW_ONE_TIME_CODE -y ``` Do not rerun `install agent` over a managed Agent. After a successful claim, use graceful or emergency re-enrollment from the Server detail page instead of changing the consumed code. ## The selected release channel is unexpected [#the-selected-release-channel-is-unexpected] ```bash sudo serverbee status sudo serverbee upgrade --channel stable -y sudo serverbee upgrade --channel beta -y ``` The default `auto` policy prefers a suffix-free stable release and falls back to a prerelease only when no stable release exists. The installer saves `auto`, `stable`, or `beta` per component and reuses it for upgrades; either explicit command above replaces the saved policy after a successful upgrade. Selection follows the SemVer suffix rather than GitHub's potentially incorrect prerelease flag. For deterministic recovery, pass `--version vX.Y.Z` and verify that exact release's assets/checksums. After an upgrade, confirm the service/container is healthy, its restart count is stable, `/healthz` succeeds, and the UI/About version matches the intended release. ## Getting help [#getting-help] Include the deployment method, OS/init system, expected and actual version, redacted config keys, the exact command, exit status, and relevant log lines. State whether `/healthz` works and whether the failure affects Server, Agent, browser, or reverse proxy. Never include credentials or tokens. # Server Setup > Install, configure, and run the ServerBee server. URL: https://docs.serverbee.app/en/docs/server The ServerBee server is the central component that hosts the web dashboard, manages agent connections, stores monitoring data, and evaluates alert rules. It is a single Rust binary with the React frontend embedded via `rust-embed`. ## Installation [#installation] ### Install script (recommended) [#install-script-recommended] The easiest path is the one-line install script. In binary mode it detects the architecture, downloads the binary, generates the config, registers and starts a systemd or OpenRC service, and symlinks the `serverbee` management CLI. The server supports two quick installs: ```bash # Option A: install with an IP (plain HTTP), reachable at http://:9527 curl -fsSL https://raw.githubusercontent.com/ZingerLittleBee/ServerBee/main/deploy/install.sh | sudo sh -s -- server --method binary -y # Option B: install with a domain (automatic Caddy + HTTPS), reachable at https://your-domain curl -fsSL https://raw.githubusercontent.com/ZingerLittleBee/ServerBee/main/deploy/install.sh | sudo sh -s -- server \ --method binary \ --domain monitor.example.com --email admin@example.com -y ``` Script layout: binary in `/opt/serverbee/bin/`, config in `/opt/serverbee/etc/server.toml`, data in `/opt/serverbee/data/`, and the management CLI symlinked to `/usr/local/bin/serverbee`. See [Quick Install](/en/docs/quick-start) for the full walkthrough. ### Pre-built Binary (manual) [#pre-built-binary-manual] Download the latest release for your platform from the [releases page](https://github.com/ZingerLittleBee/ServerBee/releases), then run it: ```bash chmod +x serverbee-server ./serverbee-server ``` ### Docker [#docker] ```bash docker run -d \ --name serverbee-server \ -p 9527:9527 \ -v serverbee-data:/data \ -e MALLOC_ARENA_MAX=2 \ ghcr.io/zingerlittlebee/serverbee-server:1.0.0-beta.6 ``` ### Build from Source [#build-from-source] ```bash # Build the frontend first cd apps/web && bun install && bun run build && cd ../.. # Build the server binary cargo build --release -p serverbee-server ``` ## Configuration File [#configuration-file] The server reads configuration from TOML files in the following order, with later sources overriding earlier ones: 1. `/etc/serverbee/server.toml` (system-wide) 2. `server.toml` (working directory) 3. Environment variables with `SERVERBEE_` prefix When deployed via the install script, the config lives at `/opt/serverbee/etc/server.toml` and data at `/opt/serverbee/data/` (the script writes these paths explicitly, overriding the built-in defaults below). `/etc/serverbee` and `/var/lib/serverbee` are the legacy layout, only seen on older installs; the script migrates them automatically. Here is a `server.toml` showing the most common options and their defaults. See the [Configuration Reference](/en/docs/configuration) for all available options. ```toml [server] listen = "0.0.0.0:9527" # Address and port to listen on data_dir = "./data" # Directory for database and other data files trusted_proxies = [] # Defaults to private/loopback CIDRs; set to [] to disable [database] path = "serverbee.db" # Database filename (relative to data_dir) max_connections = 10 # Maximum SQLite connection pool size [auth] session_ttl = 86400 # Session lifetime in seconds (default: 24 hours) max_servers = 0 # Soft limit for newly enrolled servers (0 = unlimited) secure_cookie = true # Set Secure flag on session cookies (disable for HTTP-only dev) [retention] records_days = 7 # Raw metric records retention (days) records_hourly_days = 90 # Hourly aggregated records retention (days) gpu_records_days = 7 # GPU metric records retention (days) ping_records_days = 7 # Ping probe records retention (days) audit_logs_days = 180 # Audit log retention (days) network_probe_days = 7 # Network probe records retention (days) network_probe_hourly_days = 90 # Hourly network probe aggregates retention (days) traffic_hourly_days = 7 # Traffic hourly records retention (days) traffic_daily_days = 400 # Traffic daily records retention (days) task_results_days = 7 # Task results retention (days) docker_events_days = 7 # Docker event records retention (days) service_monitor_days = 30 # Service monitor records retention (days) [rate_limit] login_max = 5 # Max login attempts per window register_max = 10 # Max agent registration attempts per window (Railway overrides to 3) [scheduler] timezone = "UTC" # Timezone for daily traffic aggregation (e.g. Asia/Shanghai) [log] level = "info" # Log level: trace, debug, info, warn, error file = "" # Log file path (empty = stdout only) [upgrade] release_base_url = "https://github.com/ZingerLittleBee/ServerBee/releases" # Base URL for agent upgrades [geoip] mmdb_path = "" # Path to MaxMind GeoLite2-City.mmdb (non-empty enables GeoIP) [oauth] base_url = "" # Public URL of your ServerBee instance allow_registration = false # Allow new user creation on first OAuth login [oauth.github] client_id = "" client_secret = "" [oauth.google] client_id = "" client_secret = "" [oauth.oidc] issuer_url = "" client_id = "" client_secret = "" scopes = ["openid", "email", "profile"] ``` ## Environment Variables [#environment-variables] Every configuration option can be set via environment variables using the `SERVERBEE_` prefix and `__` (double underscore) as a nested key separator. Environment variables take precedence over TOML file values. Examples: ```bash # server.listen export SERVERBEE_SERVER__LISTEN="0.0.0.0:9527" # retention.records_days export SERVERBEE_RETENTION__RECORDS_DAYS=14 # oauth.github.client_id export SERVERBEE_OAUTH__GITHUB__CLIENT_ID="your-github-client-id" # geoip.mmdb_path (non-empty path enables GeoIP) export SERVERBEE_GEOIP__MMDB_PATH="/path/to/GeoLite2-City.mmdb" ``` ## Database [#database] ServerBee uses SQLite for all persistent storage. The database file is created automatically in the configured `data_dir` when the server starts for the first time. Key SQLite pragmas are set automatically: | Pragma | Value | Purpose | | -------------- | ------ | ---------------------------------------- | | `journal_mode` | WAL | Better concurrent read performance | | `synchronous` | NORMAL | Good balance of safety and speed | | `busy_timeout` | 5000ms | Wait up to 5 seconds for locked database | | `foreign_keys` | ON | Enforce referential integrity | Database migrations run automatically on startup. No manual schema management is required. ## Initial Admin Account [#initial-admin-account] On first startup, if no users exist in the database, the server automatically creates an admin account. There is no username/password environment variable: the password is always randomly generated and printed once to the server/container logs as a highlighted credentials banner. ``` ======================================== ServerBee initial admin credentials Username: admin Password: aB3xK9mP2qR5 ======================================== ``` Capture this password from the logs. On first login you are required to change it, and you may optionally choose a different username at that time. The generated password is shown only once in the logs. Record it before the logs rotate, and complete the forced password change on first login before exposing the server to the internet. ## Agent Enrollment [#agent-enrollment] Adding a Server is one idempotent onboarding operation. `POST /api/servers` requires an `onboarding_request_id` and atomically creates the Server profile, tags, default probes, Agent Authority event, and one bound enrollment offer. Retrying the same request ID and input returns the existing Server. A replay never returns the plaintext code again, but it may identify the current outstanding offer so an administrator can replace that exact offer. Enrollment offers are server-bound, single-use, and short-lived (10 minutes by default). Their terminal outcomes are `consumed`, `revoked`, `replaced`, or `expired`; a terminal offer can never become outstanding again. The plaintext code is returned only when an offer is created or replaced. Before claiming an offer, the Agent generates and persists its own run token. It sends that proposed token with `POST /api/agent/register`; the Server stores only its hash and returns only the `server_id`. If the HTTP result is ambiguous, the Agent tries the WebSocket with its staged token before attempting another claim. The Server's `agent_authority.status` is either `claimed` or `unclaimed` and is independent of whether the Agent is currently online. The Agent Authority state and its outstanding offer are available from the Server detail APIs and UI. You can set `auth.max_servers` to cap new Server onboarding, and use **Clean up unconnected** to remove never-initialized offline placeholders. ## Re-enrollment and Revocation [#re-enrollment-and-revocation] Use the Server detail page's **Agent re-enrollment** action when a claimed Agent must receive a new run token: 1. **Graceful** keeps the current authority valid while issuing an enrollment offer. The currently connected Agent continues running until the new claim succeeds. 2. **Emergency** revokes the current authority immediately, fences the old connection, and issues an enrollment offer in the same transition. Only one offer may be outstanding for a Server. Replacing or revoking it always targets the exact visible offer ID, so a stale browser cannot overwrite a newer offer. Revoking Agent Authority is a separate destructive action: it invalidates the current run token and disconnects the Agent, but deliberately does not issue a new offer. Agent Authority transitions are recorded as secret-free events. Their history remains available after the Server record is deleted. ## GeoIP Setup [#geoip-setup] To enable geographic location detection for your agents: 1. Download the free [MaxMind GeoLite2-City](https://dev.maxmind.com/geoip/geolite2-free-geolocation-data) MMDB database (requires a free MaxMind account) 2. Place the `GeoLite2-City.mmdb` file in an accessible location 3. Enable GeoIP in your configuration: ```toml [geoip] mmdb_path = "/path/to/GeoLite2-City.mmdb" ``` The server will resolve each agent's IP address to a region and country code when it connects. ## OAuth Setup [#oauth-setup] ServerBee supports third-party authentication through GitHub, Google, and any OpenID Connect provider. ### Prerequisites [#prerequisites] Set `oauth.base_url` to the public URL of your ServerBee instance. This is used to construct callback URLs: ```toml [oauth] base_url = "https://serverbee.example.com" allow_registration = true # Set to true to create accounts on first OAuth login ``` ### GitHub [#github] 1. Go to [GitHub Developer Settings](https://github.com/settings/developers) and create a new OAuth App 2. Set the callback URL to `https://serverbee.example.com/api/auth/oauth/github/callback` 3. Add credentials to the config: ```toml [oauth.github] client_id = "your-client-id" client_secret = "your-client-secret" ``` ### Google [#google] 1. Create OAuth credentials in the [Google Cloud Console](https://console.cloud.google.com/apis/credentials) 2. Set the callback URL to `https://serverbee.example.com/api/auth/oauth/google/callback` 3. Add credentials to the config: ```toml [oauth.google] client_id = "your-client-id" client_secret = "your-client-secret" ``` ### Generic OIDC [#generic-oidc] For any OpenID Connect provider (e.g., Keycloak, Authentik, Authelia): ```toml [oauth.oidc] issuer_url = "https://auth.example.com/realms/main" client_id = "serverbee" client_secret = "your-client-secret" scopes = ["openid", "email", "profile"] ``` ## Reverse Proxy [#reverse-proxy] If you don't want to configure a reverse proxy by hand, the install script can do it for you: add `--domain monitor.example.com --email admin@example.com` at install time, or run `sudo serverbee domain setup --domain monitor.example.com --email admin@example.com` on an already-installed server. The script verifies DNS, installs Caddy, writes the Caddyfile, issues an HTTPS certificate, and sets `auth.secure_cookie` to `true`. The manual configuration below is only needed if you want to manage the reverse proxy yourself. When running behind a reverse proxy, you must forward WebSocket connections correctly. ### Nginx [#nginx] ```nginx server { listen 443 ssl http2; server_name serverbee.example.com; ssl_certificate /etc/ssl/certs/serverbee.pem; ssl_certificate_key /etc/ssl/private/serverbee.key; location / { proxy_pass http://127.0.0.1:9527; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # WebSocket support proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_read_timeout 86400s; proxy_send_timeout 86400s; } } ``` The long `proxy_read_timeout` and `proxy_send_timeout` values are important for WebSocket connections. Without them, nginx may close idle connections prematurely, causing agents and terminal sessions to disconnect. ### Caddy [#caddy] Caddy handles HTTPS certificates and WebSocket proxying automatically, so the config is minimal: ```txt title="Caddyfile" monitor.example.com { reverse_proxy 127.0.0.1:9527 } ``` Behind a reverse proxy, point agents at the `https://` address: ```toml title="agent.toml" server_url = "https://monitor.example.com" ``` For more reverse proxy configurations including Traefik, see the [Deployment Guide](/en/docs/deployment). ## Background Tasks [#background-tasks] The server runs several background tasks automatically: | Task | Interval | Purpose | | -------------- | -------- | ----------------------------------------------------- | | RecordWriter | 60s | Writes cached agent reports to the database | | OfflineChecker | 10s | Detects agents that stopped reporting (30s threshold) | | Aggregator | Hourly | Aggregates raw records into hourly summaries | | Cleanup | Hourly | Removes expired records based on retention settings | | SessionCleaner | Periodic | Removes expired user sessions | | AlertEvaluator | 60s | Evaluates all enabled alert rules | All tasks start automatically and require no manual configuration. # Agent Setup > Install and configure the ServerBee agent on your monitored servers. URL: https://docs.serverbee.app/en/docs/agent The ServerBee agent is a lightweight Rust binary that runs on each server you want to monitor. It collects system metrics (CPU, memory, disk, network, load, temperature, GPU, disk I/O) and reports them to the central ServerBee server over a persistent WebSocket connection. It also executes probe tasks and remote commands dispatched by the server. ## What the Agent Does [#what-the-agent-does] * Collects system metrics every 3 seconds, including disk I/O throughput on all platforms * Reports metrics to the server over WebSocket * Executes ping probes (ICMP, TCP, HTTP) assigned by the server * Provides a PTY shell for web terminal access * Runs remote commands dispatched from the server * Manages remote file operations (browse, read, write, upload, download) with path sandbox security * Monitors Docker containers when Docker daemon is available (stats, logs, events, networks, volumes) * Supports automatic self-upgrade when the server pushes new versions * Reconnects automatically with exponential backoff if the connection drops ## Installation [#installation] ### Install Script (recommended) [#install-script-recommended] The install script detects the architecture, downloads the binary, generates the config, and registers a systemd service automatically: ```bash curl -fsSL https://raw.githubusercontent.com/ZingerLittleBee/ServerBee/main/deploy/install.sh | sudo sh -s -- agent \ --server-url http://your-server-ip:9527 \ --enrollment-code YOUR_ONE_TIME_CODE ``` Layout: binary in `/opt/serverbee/bin/`, config in `/opt/serverbee/etc/agent.toml`, and the management CLI symlinked to `/usr/local/bin/serverbee`. After installation, manage the agent with the `serverbee` CLI (automatically installed): ```bash sudo serverbee status sudo serverbee upgrade agent -y sudo serverbee restart agent sudo serverbee config agent sudo serverbee uninstall agent -y ``` If the agent is already installed, re-running `install agent` errors out and tells you to use `upgrade` instead — it will not reinstall. To move to a newer version, run `sudo serverbee upgrade agent -y`. ### Pre-built Binary (manual) [#pre-built-binary-manual] Download the agent binary for your platform from the [releases page](https://github.com/ZingerLittleBee/ServerBee/releases): | Platform | File name | | ------------- | ----------------------------------- | | Linux amd64 | `serverbee-agent-linux-amd64` | | Linux arm64 | `serverbee-agent-linux-arm64` | | macOS amd64 | `serverbee-agent-darwin-amd64` | | macOS arm64 | `serverbee-agent-darwin-arm64` | | Windows amd64 | `serverbee-agent-windows-amd64.exe` | ```bash wget https://github.com/ZingerLittleBee/ServerBee/releases/download/v1.0.0-beta.6/serverbee-agent-linux-amd64 chmod +x serverbee-agent-linux-amd64 sudo mv serverbee-agent-linux-amd64 /usr/local/bin/serverbee-agent ``` ### Build from Source [#build-from-source] ```bash git clone https://github.com/ZingerLittleBee/ServerBee.git cd ServerBee cargo build --release -p serverbee-agent # With NVIDIA GPU monitoring (optional) cargo build --release -p serverbee-agent --features gpu ``` The binary is located at `target/release/serverbee-agent`. ### Docker (Not Recommended) [#docker-not-recommended] The Agent executable is a single binary, but a managed installation also persists its configuration/run token under `/opt/serverbee/etc`, service metadata, capability grants under the configured `state_dir` (default `/var/lib/serverbee`), and security state. Use `serverbee uninstall agent`; add `--purge` only when you intend to delete retained credentials and state. If you still prefer Docker, create a persistent config first. Replace both placeholders with the URL and one-time offer shown by **Add Server**: ```bash sudo install -d -m 0700 /opt/serverbee-agent sudo tee /opt/serverbee-agent/agent.toml >/dev/null <<'EOF' server_url = "https://monitor.example.com" enrollment_code = "YOUR_ONE_TIME_CODE" token = "" EOF sudo chmod 0600 /opt/serverbee-agent/agent.toml ``` Then run the privileged container required for host metrics: ```bash docker run -d \ --name serverbee-agent \ --privileged \ --net=host \ --pid=host \ -v /proc:/host/proc:ro \ -v /sys:/host/sys:ro \ -v /etc/os-release:/host/etc/os-release:ro \ -v /opt/serverbee-agent:/etc/serverbee \ --restart unless-stopped \ ghcr.io/zingerlittlebee/serverbee-agent:1.0.0-beta.6 ``` The persistent `/etc/serverbee` mount is mandatory. Before claiming an Enrollment offer, the Agent generates and atomically stores its run token in `agent.toml`. Without it, a recreated container loses the credential that the Server accepted. **Limitations of Docker deployment:** * Requires `--privileged` for full metrics collection * Temperature and GPU monitoring may not work inside containers * Web terminal accesses the container environment, not the host ## Registration Flow [#registration-flow] Agents authenticate with the Server using an Agent-owned **run token**. The Agent generates and persists this secret locally before it claims a one-time Enrollment offer. The Server stores only its hash and never returns the plaintext token. ### Enrollment Code Registration (Recommended) [#enrollment-code-registration-recommended] 1. Sign in as an admin and choose **Add Server**. Server onboarding atomically creates the Server identity and a one-time **Enrollment offer**. Its code is single-use, short-lived (10 minutes by default), and shown only once. 2. Configure the agent with the code, either via environment variables: ```bash SERVERBEE_SERVER_URL=http://your-server-ip:9527 \ SERVERBEE_ENROLLMENT_CODE=YOUR_ONE_TIME_CODE \ serverbee-agent ``` or in the config file: ```toml title="/etc/serverbee/agent.toml" server_url = "http://your-server-ip:9527" enrollment_code = "" # Leave empty on first run; the Agent generates and persists it before claim token = "" ``` 3. Start the agent. On first run (no token), it will: * Generate a high-entropy run token and atomically save it before any claim request * Send `POST /api/agent/register` with the one-time code and `proposed_run_token` * Receive only `server_id`; the Server hashes the proposed token while consuming the offer * Connect via WebSocket using the token for all future sessions -- the enrollment code is no longer needed On subsequent runs (token present), the agent connects directly over WebSocket, sends its static system info, and reports metrics on the interval assigned by the server. If the HTTP result is ambiguous, the Agent first tries WebSocket authentication with the already-staged token. Success proves the claim committed; rejection permits retrying the same code and token. If a code was lost, use the exact Outstanding offer shown on the existing Server to replace it. Replacement is compare-and-swap by offer ID; it never silently replaces an unknown newer offer. ### Correcting a Wrong Enrollment Code [#correcting-a-wrong-enrollment-code] If the Agent was installed with a mistyped enrollment code (or wrong `server_url`) and has not claimed the Server, fix it without reinstalling. Replace the exact Outstanding offer from the Server page when a new code is required, then: ```bash serverbee config set enrollment_code -y # if the server URL was also wrong: serverbee config set server_url http://your-server-ip:9527 -y ``` The `-y` flag restarts the agent so it retries registration immediately. Without `-y` the value is written but the service is not restarted; run `serverbee restart agent` afterwards. Verify the correction with `sudo serverbee status` and `sudo journalctl -u serverbee-agent -n 80 --no-pager`. Do not rerun `serverbee install agent`: the installer intentionally rejects an already managed component. Once the Agent has claimed the Server, the enrollment code is no longer used. Use **Agent re-enrollment** for replacement or reinstall scenarios: Graceful mode preserves current authority until the new claim, while Emergency mode revokes authority and fences the current connection immediately. ## Configuration [#configuration] The agent reads configuration from TOML files in the following order: 1. `/etc/serverbee/agent.toml` (system-wide, preferred) 2. `agent.toml` (working directory) 3. Environment variables with `SERVERBEE_` prefix When deployed via the install script, the config lives at `/opt/serverbee/etc/agent.toml` (`/etc/serverbee` is the legacy layout; the script migrates it automatically). Here is a minimal `agent.toml` for first connection. The [Configuration Reference](/en/docs/configuration) is the canonical list for `file`, `capabilities`, `security`, `ip_change`, `upgrade`, and other groups: ```toml # Required: URL of your ServerBee server server_url = "http://your-server-ip:9527" # Agent-owned run token (generated and staged locally before registration) token = "" # One-time enrollment code for first-time registration (used only if token is empty) enrollment_code = "" [collector] enable_gpu = false # Enable NVIDIA GPU monitoring (requires a GPU-enabled build and NVIDIA NVML) enable_temperature = true # Enable temperature sensor monitoring [log] level = "info" # Log level: trace, debug, info, warn, error file = "" # Log file path (empty = stdout only) ``` | Option | Type | Default | Description | | ------------------------------ | ------ | --------------- | -------------------------------------------------------------------------------------------------------------------- | | `server_url` | string | required | URL of your ServerBee server | | `enrollment_code` | string | `""` | One-time enrollment code, needed only for first registration; consumed on success and unused once a token is present | | `token` | string | Agent-generated | Agent run token, atomically written before the claim request; the Server stores only its hash | | `collector.enable_gpu` | bool | `false` | Enable GPU metric collection | | `collector.enable_temperature` | bool | `true` | Enable temperature collection | | `log.level` | string | `"info"` | Log level | | `log.file` | string | `""` | Log file path (empty = stdout only) | ### Environment Variables [#environment-variables] Like the server, all options support `SERVERBEE_` prefixed environment variables. Use `__` (double underscore) as the nested-field separator: ```bash export SERVERBEE_SERVER_URL="http://your-server-ip:9527" export SERVERBEE_TOKEN="your-agent-token" export SERVERBEE_COLLECTOR__ENABLE_GPU=true ``` The server currently sends a 3-second `report_interval` in its `Welcome` message, and the Agent uses that value for its reporting loop. `collector.interval` and `SERVERBEE_COLLECTOR__INTERVAL` are still accepted for backward compatibility but do not change the active cadence. ## Local Capability Locks [#local-capability-locks] Capability policy is owned entirely by the Agent host and can be changed with local configuration or CLI flags: ```bash serverbee-agent --allow-cap terminal --allow-cap exec serverbee-agent --deny-cap ping_http ``` * The default set is `upgrade`, `ping_icmp`, `ping_tcp`, `ping_http`, `security_events`, `firewall_block`, and `ip_quality` * High-risk capabilities (`terminal`, `exec`, `file`, `docker`) are disabled by default * `--deny-cap` always wins over `--allow-cap` The Server only mirrors the reported set and rejects requests outside it. It has no capability toggle and cannot enable or further configure the Agent's set. See [Capabilities](/en/docs/capabilities). ## GPU Monitoring [#gpu-monitoring] NVIDIA GPU monitoring is disabled by default and requires all three of the following: 1. **Build time** -- compile the agent with the `gpu` feature flag (the prebuilt release binaries do not include it): ```bash cargo build --release -p serverbee-agent --features gpu ``` 2. **Runtime** -- an NVIDIA driver that provides the NVML shared library on the host 3. **Config** -- set `enable_gpu = true`: ```toml [collector] enable_gpu = true ``` When enabled, the agent collects per-GPU metrics for every device: | Metric | Description | | --------------- | ------------------------------ | | Device name | GPU model | | Memory total | Total VRAM | | Memory used | VRAM currently in use | | GPU utilization | Compute utilization percentage | | GPU temperature | Current temperature | These metrics appear in the server dashboard and can be used in alert rules. Only NVIDIA GPUs are supported (via the `nvml-wrapper` library). AMD and Intel GPU support is planned for a future release. The Agent calls NVML through `nvml-wrapper`; it does not run `nvidia-smi`. ## Running as a Systemd Service [#running-as-a-systemd-service] For production deployments, run the agent as a systemd service so it starts automatically on boot. The install script creates this service automatically; to configure it manually, create `/etc/systemd/system/serverbee-agent.service`: ```ini title="/etc/systemd/system/serverbee-agent.service" [Unit] Description=ServerBee Agent After=network-online.target Wants=network-online.target [Service] Type=simple ExecStart=/usr/local/bin/serverbee-agent Restart=always RestartSec=5 User=root WorkingDirectory=/etc/serverbee AmbientCapabilities=CAP_NET_RAW # Optional: limit resource usage MemoryMax=128M CPUQuota=10% [Install] WantedBy=multi-user.target ``` `AmbientCapabilities=CAP_NET_RAW` grants the privilege required for ICMP ping probes; remove the line if you do not need ICMP probing. Then enable and start the service: ```bash sudo systemctl daemon-reload sudo systemctl enable serverbee-agent sudo systemctl start serverbee-agent ``` Check the status and logs: ```bash sudo systemctl status serverbee-agent journalctl -u serverbee-agent -f ``` The agent needs to run as root to access all system metrics (temperature sensors, process lists, etc.) and to open PTY sessions for the web terminal. If you do not need terminal access, you can run it as a non-root user, though some metrics may be unavailable. ## Platform Support [#platform-support] | Platform | Support level | Notes | | ------------------- | ------------- | --------------------------------------------------- | | Linux (amd64/arm64) | Full | Primary target; all features available | | macOS (amd64/arm64) | Full | Suitable for development and testing | | Windows (amd64) | Basic | TCP/UDP connection counts use a different code path | | FreeBSD | Basic | `sysinfo` support for FreeBSD is limited | ## Auto-Update [#auto-update] The server can push upgrade commands to connected agents. When an upgrade is triggered: 1. The server sends an `Upgrade` message with the target version and job ID 2. The agent derives the binary and checksum URLs from its pinned `[upgrade] release_repo_url` 3. The agent verifies the downloaded binary against `sha256sums.txt` 4. A preflight probe executes the candidate and requires it to report the requested embedded version 5. The agent persists an upgrade transaction, copies the current binary to `.bak`, and atomically installs the candidate 6. systemd/OpenRC restarts the service; for a manually launched Agent, the old process launches and supervises the candidate until it becomes healthy 7. The candidate has 90 seconds to reconnect and send `SystemInfo`, then must remain alive through a five-second stability window. If it exits or misses either check, it restores `.bak`, restarts the previous version, and reports the failed upgrade after reconnecting Trigger an upgrade from the server detail page in the dashboard, or via the API: ```bash curl -X POST https://your-server/api/servers/{id}/upgrade \ -H "Cookie: session_token=..." \ -H "Content-Type: application/json" \ -d '{"version": "1.2.0"}' ``` Auto-update requires the agent to have the `upgrade` capability (`CAP_UPGRADE`), which is enabled by default. Capabilities are agent-owned — to turn it off, add `upgrade` to the agent's `[capabilities]` deny list (or pass `--deny-cap upgrade`) on the agent host. See [Capabilities](/en/docs/capabilities). Automatic rollback is enforced by the Agent version that initiates the upgrade. An upgrade started by an older alpha Agent still uses that older Agent's updater; install a rollback-capable version once before relying on this protection for later remote upgrades. ## Connection Behavior [#connection-behavior] The agent maintains a persistent WebSocket connection to the server. If the connection drops: * It reconnects using **exponential backoff** starting at 1 second (1s → 2s → 4s → 8s → 16s → 30s cap) * A random jitter of +/-20% is applied to avoid thundering-herd reconnections * Upon successful reconnection, the backoff resets to 1 second and the agent re-sends its `SystemInfo` * **Heartbeat**: the server pings every 30 seconds and the agent replies with a pong; an agent that stops reporting for more than 30 seconds is marked offline During the initial connection: 1. The server sends a `Welcome` message with the assigned `server_id` and `report_interval` 2. The agent sends its `SystemInfo` (CPU name, cores, arch, OS, kernel, memory, disk, IP addresses, virtualization type, agent version) 3. The server acknowledges with an `Ack` 4. The server syncs any assigned ping tasks 5. The agent begins its periodic metric reporting loop ## Collected Metrics [#collected-metrics] The agent collects the following metrics (sourced from the `sysinfo` library, `/proc` on Linux, and `nvml-wrapper` for GPU) and reports them to the server: | Category | Reported fields | Source | | -------------- | -------------------------------------------------------------------------------- | --------------------------------- | | CPU | `cpu` (usage %), name, cores, arch | `sysinfo::System` | | Memory | `mem_used`, `swap_used` (bytes) | `sysinfo::System` | | Disk | `disk_used` (bytes) | `sysinfo::Disks` | | Network | `net_in_speed` / `net_out_speed`, `net_in_transfer` / `net_out_transfer` (bytes) | `sysinfo::Networks` + delta calc | | Load | `load1` / `load5` / `load15` | `sysinfo::System::load_average()` | | Connections | `tcp_conn` / `udp_conn` | `/proc/net/tcp` (Linux) | | Process | `process_count` | `sysinfo::System::processes()` | | Uptime | `uptime` (seconds) | `sysinfo::System` | | Temperature | `temperature` (°C, optional) | `sysinfo::Components` | | GPU | `gpu` (utilization, VRAM, temperature, optional) | `nvml-wrapper` | | Virtualization | virtualization type | `systemd-detect-virt` / DMI | ## Resource Footprint [#resource-footprint] The Agent has negligible CPU cost (\<1%) and a steady-state memory footprint in the tens of MB. For the full measured Agent and Server CPU/memory/disk/network data, see [Resource Usage](/en/docs/resource-usage). # Configuration Reference > Complete reference for all ServerBee server and agent configuration options. URL: https://docs.serverbee.app/en/docs/configuration ServerBee loads configuration with [Figment](https://github.com/SergioBenitez/Figment), which layers values from multiple sources. This page is a complete reference for every configuration option. ## Configuration Loading Priority [#configuration-loading-priority] Server values are merged in this order, with later sources overriding earlier ones: 1. Built-in defaults 2. `/etc/serverbee/server.toml` 3. `/opt/serverbee/etc/server.toml` (the path used by the install script) 4. `server.toml` in the working directory 5. Environment variables prefixed with `SERVERBEE_` The Agent uses built-in defaults, then `/etc/serverbee/agent.toml`, `agent.toml` in its working directory, and finally `SERVERBEE_` environment variables. The managed service uses `/opt/serverbee/etc` as its working directory, so its third source is `/opt/serverbee/etc/agent.toml`. This lets you override effective settings at runtime without editing the TOML file. Compatibility-only fields, such as the Agent's `collector.interval`, are called out below. ## Environment Variable Mapping [#environment-variable-mapping] Every TOML key maps directly to an environment variable: add the `SERVERBEE_` prefix, uppercase the key, and replace each level of nesting with `__` (double underscore). For example, `auth.secure_cookie` becomes `SERVERBEE_AUTH__SECURE_COOKIE`. ## Developer Workflow Env Vars [#developer-workflow-env-vars] These variables drive local repo tooling and developer workflows. They are not Figment-backed runtime config for the server or agent binaries. | Environment Variable | Used By | Description | | --------------------------------- | ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `SERVERBEE_PROD_URL` | `make db-pull`, `make web-dev-prod` | Production base URL used by the database pull script and the frontend prod-proxy workflow | | `SERVERBEE_PROD_API_KEY` | `make db-pull` | Admin-scoped API key for the production backup API. Do not reuse this for `make web-dev-prod` | | `SERVERBEE_PROD_READONLY_API_KEY` | `make web-dev-prod` | Member-scoped API key injected by the frontend dev proxy for live production browsing | | `ALLOW_WRITES` | `make web-dev-prod` | Local opt-in override. Set to `1` to disable the proxy's read-method-only block. When set, the UI banner changes from the normal read-only warning to a stronger write-enabled warning | These variables are intentionally scoped to local tooling. `ALLOW_WRITES` is not a server feature flag; it is an explicit local override for the frontend prod-proxy workflow only. ### Server Environment Variables [#server-environment-variables] #### Quick Start [#quick-start] There is no admin username/password environment variable. On first start (when no users exist) the server auto-creates an admin account with a randomly generated password and prints it once to the server/container logs as a highlighted credentials banner. Capture it from the logs, then on first login you are required to change this password and may optionally choose a different username. | Environment Variable | Default | Description | | -------------------------- | -------------- | ----------------------- | | `SERVERBEE_SERVER__LISTEN` | `0.0.0.0:9527` | Listen address and port | #### Common [#common] | Environment Variable | Default | Description | | ----------------------------------- | ---------------------- | --------------------------------------------------------------------------------------------- | | `SERVERBEE_SERVER__DATA_DIR` | `./data` | Data directory for database and backups | | `SERVERBEE_AUTH__MAX_SERVERS` | `0` | Maximum servers allowed via enrollment (0 = no limit). Best-effort soft cap | | `SERVERBEE_SERVER__TRUSTED_PROXIES` | private/loopback CIDRs | CIDR list of trusted reverse proxies. Defaults to RFC 1918 + loopback. Set to `[]` to disable | | `SERVERBEE_SCHEDULER__TIMEZONE` | `UTC` | Timezone for daily traffic aggregation (e.g. `Asia/Shanghai`) | | `SERVERBEE_LOG__LEVEL` | `info` | Log level: `trace`, `debug`, `info`, `warn`, `error` | | `SERVERBEE_LOG__FILE` | `""` | Log file path. Empty means stdout only | #### Development Only [#development-only] | Environment Variable | Default | Description | | -------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `SERVERBEE_DEV__DEMO_DATA` | `false` | Reset and seed the local synthetic demo dataset. Only allowed with `SERVERBEE_DATABASE__PATH=dev-demo.db`; creates `admin` / `admin123` and in-memory demo agents for local development | #### OAuth (Optional) [#oauth-optional] | Environment Variable | Default | Description | | ---------------------------------------- | -------------------------------- | ------------------------------------------------------ | | `SERVERBEE_OAUTH__BASE_URL` | `""` | Public server URL for constructing OAuth callback URLs | | `SERVERBEE_OAUTH__ALLOW_REGISTRATION` | `false` | Auto-create user accounts on first OAuth login | | `SERVERBEE_OAUTH__GITHUB__CLIENT_ID` | -- | GitHub OAuth App client ID | | `SERVERBEE_OAUTH__GITHUB__CLIENT_SECRET` | -- | GitHub OAuth App client secret | | `SERVERBEE_OAUTH__GOOGLE__CLIENT_ID` | -- | Google OAuth client ID | | `SERVERBEE_OAUTH__GOOGLE__CLIENT_SECRET` | -- | Google OAuth client secret | | `SERVERBEE_OAUTH__OIDC__ISSUER_URL` | -- | OIDC provider issuer URL | | `SERVERBEE_OAUTH__OIDC__CLIENT_ID` | -- | OIDC client ID | | `SERVERBEE_OAUTH__OIDC__CLIENT_SECRET` | -- | OIDC client secret | | `SERVERBEE_OAUTH__OIDC__SCOPES` | `["openid", "email", "profile"]` | OAuth scopes to request | #### GeoIP (Optional) [#geoip-optional] | Environment Variable | Default | Description | | ---------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `SERVERBEE_GEOIP__MMDB_PATH` | `""` | Path to a MaxMind-compatible MMDB file. Non-empty path enables this custom GeoIP database; otherwise admins can download the DB-IP Lite database from Settings → GeoIP Database | | `SERVERBEE_ASN__MMDB_PATH` | `""` | Path to a DB-IP Lite ASN / MaxMind GeoLite2-ASN MMDB file. Non-empty path enables traceroute ASN enrichment; otherwise admins can download DB-IP Lite ASN from Settings → ASN Database | #### Resend (Email Notifications) [#resend-email-notifications] | Environment Variable | Default | Description | | --------------------------- | ------- | ------------------------------------------------------------------------------------------------------ | | `SERVERBEE_RESEND__API_KEY` | `""` | Resend API key. Required for Email notifications. Sender domain must be verified at resend.com/domains | #### Data Retention (Tuning) [#data-retention-tuning] | Environment Variable | Default | Description | | ------------------------------------------------ | ------- | -------------------------------------------------------- | | `SERVERBEE_RETENTION__RECORDS_DAYS` | `7` | Raw metric records retention in days | | `SERVERBEE_RETENTION__RECORDS_HOURLY_DAYS` | `90` | Hourly aggregated records retention in days | | `SERVERBEE_RETENTION__GPU_RECORDS_DAYS` | `7` | GPU metric records retention in days | | `SERVERBEE_RETENTION__PING_RECORDS_DAYS` | `7` | Ping probe records retention in days | | `SERVERBEE_RETENTION__NETWORK_PROBE_DAYS` | `7` | Raw network probe records retention in days | | `SERVERBEE_RETENTION__NETWORK_PROBE_HOURLY_DAYS` | `90` | Hourly network probe aggregates retention in days | | `SERVERBEE_RETENTION__AUDIT_LOGS_DAYS` | `180` | Audit log retention in days | | `SERVERBEE_RETENTION__TRAFFIC_HOURLY_DAYS` | `7` | Traffic hourly records retention in days | | `SERVERBEE_RETENTION__TRAFFIC_DAILY_DAYS` | `400` | Traffic daily records retention in days | | `SERVERBEE_RETENTION__TASK_RESULTS_DAYS` | `7` | Task results retention in days | | `SERVERBEE_RETENTION__DOCKER_EVENTS_DAYS` | `7` | Docker event records retention in days | | `SERVERBEE_RETENTION__SERVICE_MONITOR_DAYS` | `30` | Service monitor records retention in days | | `SERVERBEE_RETENTION__SECURITY_EVENT_DAYS` | `30` | Security event records retention in days | | `SERVERBEE_RETENTION__IP_QUALITY_EVENT_DAYS` | `90` | IP quality status-change event records retention in days | #### Mobile (Optional) [#mobile-optional] | Environment Variable | Default | Description | | ------------------------------- | --------- | -------------------------------------------------- | | `SERVERBEE_MOBILE__ACCESS_TTL` | `900` | Mobile access token lifetime in seconds (15 min) | | `SERVERBEE_MOBILE__REFRESH_TTL` | `2592000` | Mobile refresh token lifetime in seconds (30 days) | #### Firewall (Optional) [#firewall-optional] Tier-2 guardrail for the [Firewall Blocklist](/en/docs/firewall) feature. CIDRs / IPs listed here are refused by `POST /api/firewall/blocks` even if an administrator tries to insert them. Tier 1 (hard-coded protected ranges: loopback, RFC 1918, link-local, multicast, unspecified) is always enforced. | Environment Variable | Default | Description | | -------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------- | | `SERVERBEE_FIREWALL__ALLOW_LIST` | `[]` | CIDRs / IPs the server refuses to insert into `block_list`. Tier-2 guardrail layered on top of the hard-coded Tier-1 protected ranges | #### IP Quality [#ip-quality] By default, the Server calls [ipapi.is](https://ipapi.is/developers.html) anonymously (100 requests per client IP per UTC day). ServerBee uses the minimal response's proxy, VPN, datacenter, Tor, and abuse flags and derives an IP type, but no numeric risk score. A free account/API key provides 1,000 requests per day and the full response required for the 0–100 score. The Server falls back to [ip-api.com](https://ip-api.com) only when the primary request fails, including HTTP 429, not when a successful response lacks a score. See [IP Quality](/en/docs/ip-quality) for details and the fallback's non-commercial/HTTP warning. | Environment Variable | Default | Description | | ---------------------------------------------- | ------------ | ---------------------------------------------------------------------------------------------------------------- | | `SERVERBEE_IP_QUALITY__RISK_PROVIDER` | `"ipapi_is"` | Primary risk provider. One of: `none`, `ipapi_is`, `ip-api`. | | `SERVERBEE_IP_QUALITY__RISK_PROVIDER_FALLBACK` | `"ip-api"` | Fallback provider triggered on primary failure. Set to `none` to disable. | | `SERVERBEE_IP_QUALITY__IPAPI_IS__API_KEY` | -- | Optional. A free account key provides 1,000 requests per day and the full response required for the 0–100 score. | | `SERVERBEE_IP_QUALITY__IPAPI_IS__ENDPOINT` | `""` | Override for self-hosted mirrors or testing. Empty falls back to the built-in default `https://api.ipapi.is`. | **Migration from older versions:** Earlier releases supported four paid providers (Scamalytics, IPQualityScore, ProxyCheck, AbuseIPDB) configured via `SERVERBEE_IP_QUALITY__{SCAMALYTICS,IPQS,PROXYCHECK,ABUSEIPDB}__*`. These env vars are silently ignored. To restore equivalent functionality, fork or vendor the provider implementation from a tag prior to 2026-05-25. #### Network Probe Anomaly Thresholds [#network-probe-anomaly-thresholds] Average-latency cutoffs used to classify network-probe records returned by `/api/servers/{id}/network-probes/anomalies` and the `anomaly_count` field on the network-probe overview. Records strictly above `very_high_latency_ms` are tagged `very_high_latency`; records above `high_latency_ms` (and at or below the very-high cutoff) are tagged `high_latency`. | Environment Variable | Default | Description | | ----------------------------------------------- | ------- | -------------------------------------------------------------- | | `SERVERBEE_NETWORK_PROBE__HIGH_LATENCY_MS` | `500` | Avg-latency cutoff (ms) for `high_latency` classification | | `SERVERBEE_NETWORK_PROBE__VERY_HIGH_LATENCY_MS` | `800` | Avg-latency cutoff (ms) for `very_high_latency` classification | #### Internal [#internal] > These variables have sensible defaults and rarely need changing. Adjust them only when you have a specific requirement. | Environment Variable | Default | Description | | --------------------------------------- | ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | `SERVERBEE_DATABASE__PATH` | `serverbee.db` | SQLite database file path (relative to data\_dir) | | `SERVERBEE_DATABASE__MAX_CONNECTIONS` | `10` | Maximum database connection pool size | | `SERVERBEE_AUTH__SESSION_TTL` | `86400` | Session token TTL in seconds (default 24h) | | `SERVERBEE_AUTH__SECURE_COOKIE` | `true` | Set the Secure flag on session cookies. Use `false` only when the browser accesses ServerBee over plain HTTP, such as direct IP quick-start installs | | `SERVERBEE_RATE_LIMIT__LOGIN_MAX` | `5` | Max login attempts per IP within 15-minute window | | `SERVERBEE_RATE_LIMIT__REGISTER_MAX` | `10` | Max agent registrations per IP within 15-minute window. Admins can clear an active window from Settings → Rate limits | | `SERVERBEE_UPGRADE__RELEASE_BASE_URL` | `https://github.com/ZingerLittleBee/ServerBee/releases` | Base URL for agent upgrade release assets | | `SERVERBEE_UPGRADE__LATEST_VERSION_URL` | `""` | Optional custom URL for latest version API. If empty, uses GitHub API | | `SERVERBEE_UPGRADE__CHANNEL` | `stable` | Release channel used by the dashboard version check: `stable` or `beta` | | `SERVERBEE_FILE__MAX_UPLOAD_SIZE` | `104857600` | Maximum file upload size in bytes (default 100 MB) | ### Agent Environment Variables [#agent-environment-variables] Agent top-level keys use single underscore. Nested keys use `__` (double underscore). #### Quick Start [#quick-start-1] | Environment Variable | Default | Description | | --------------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `SERVERBEE_SERVER_URL` | -- (required) | Server HTTP base URL (e.g. `http://your-server:9527`). Agent appends API paths automatically | | `SERVERBEE_ENROLLMENT_CODE` | `""` | One-time enrollment offer shown when a Server is added or re-enrollment begins. Single-use and short-lived (default 10 min). Only used when `token` is empty | #### Common [#common-1] | Environment Variable | Default | Description | | ------------------------------------------------ | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `SERVERBEE_COLLECTOR__INTERVAL` | `3` | Compatibility-only field. The Agent currently ignores it and uses the Server's `Welcome.report_interval` value, which is fixed at 3 seconds | | `SERVERBEE_COLLECTOR__ENABLE_GPU` | `false` | Enable NVIDIA GPU monitoring (requires a build with the `gpu` feature and an NVIDIA driver that provides NVML) | | `SERVERBEE_COLLECTOR__ENABLE_TEMPERATURE` | `true` | Enable CPU temperature monitoring | | `SERVERBEE_FILE__ENABLED` | `false` | Enable file management on this agent | | `SERVERBEE_FILE__ROOT_PATHS` | `[]` | Allowed root paths (comma-separated, e.g. `/home,/var/log`). Empty rejects all file operations | | `SERVERBEE_CAPABILITIES__ALLOW` | `[]` | Capability keys to enable on top of the default set (`CAP_DEFAULT`), e.g. `["terminal","file"]`. See [Capabilities](/en/docs/capabilities) | | `SERVERBEE_CAPABILITIES__DENY` | `[]` | Capability keys to strip from the default set. `deny` wins over `allow`. Unknown keys fail at startup | | `SERVERBEE_CAPABILITIES__TEMPORARY_MAX_DURATION` | `24h` | Max duration a single temporary capability grant can last (e.g. `30m`, `2h`, `1d`). See [Capabilities → Temporary grants](/en/docs/capabilities#temporary-grants) | | `SERVERBEE_CAPABILITIES__STATE_DIR` | `/var/lib/serverbee` | Directory where the agent persists `capability_grants.json` (must be writable by the agent) | | `SERVERBEE_IP_CHANGE__ENABLED` | `true` | Enable periodic IP change detection | | `SERVERBEE_LOG__LEVEL` | `info` | Log level: `trace`, `debug`, `info`, `warn`, `error` | | `SERVERBEE_LOG__FILE` | `""` | Log file path. Empty means stdout only | #### Internal [#internal-1] > These variables have sensible defaults and rarely need changing. Adjust them only when you have a specific requirement. | Environment Variable | Default | Description | | --------------------------------------- | ------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `SERVERBEE_TOKEN` | generated when empty | Agent run token. During enrollment the Agent generates and persists one before claim; an explicit environment override is used as provided and is not written to disk | | `SERVERBEE_FILE__MAX_FILE_SIZE` | `1073741824` | Max file size in bytes for read/download (default 1GB) | | `SERVERBEE_FILE__DENY_PATTERNS` | `*.key,*.pem,...` | Glob patterns for files the agent refuses to access | | `SERVERBEE_IP_CHANGE__EXTERNAL_IP_URLS` | `["https://api.ipify.org","https://ifconfig.me/ip","https://icanhazip.com","https://checkip.amazonaws.com"]` | Ordered list of public IP services tried at startup and on every IP-change tick. First success wins. Required for agents behind NAT or in containers where interface enumeration can't see the routable IP. Set to `[]` to skip external lookups for air-gapped deployments | | `SERVERBEE_IP_CHANGE__INTERVAL_SECS` | `300` | IP check interval in seconds (default 5 minutes) | #### Upgrade (Agent) [#upgrade-agent] | Environment Variable | Default | Description | | --------------------------------------------- | ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `SERVERBEE_UPGRADE__RELEASE_REPO_URL` | `https://github.com/ZingerLittleBee/ServerBee/releases` | Pinned release source base URL the Agent downloads upgrades from. Any HTTPS host mirroring the GitHub releases path layout `{base}/download/v{version}/{asset}` and `{base}/download/v{version}/sha256sums.txt` works. Compile-time default is overridable via the `SERVERBEE_RELEASE_REPO` build-time env | | `SERVERBEE_UPGRADE__RELEASE_CERT_SPKI_SHA256` | `""` | Optional TLS certificate SPKI pin for the release host. 64 lowercase hex chars = SHA-256 of the leaf cert SubjectPublicKeyInfo DER. Empty = disabled. If set, the Agent additionally pins the leaf cert SPKI after standard chain validation. Invalid (non-64/non-hex) values are rejected at startup | #### Security (Agent) [#security-agent] Tunes the agent-side security event detectors (SSH login / brute force, port scan). Detection runs entirely on the agent; the server only stores events and evaluates alert rules. Configure per-host since traffic profiles differ. | Environment Variable | Default | Description | | -------------------------------------------------------- | ----------------------------- | -------------------------------------------------------------------------------------------------------------- | | `SERVERBEE_SECURITY__ENABLED` | `true` | Master switch for all security detectors. When `false` the agent emits no `security_event` messages | | `SERVERBEE_SECURITY__SSH__WINDOW_SECONDS` | `60` | Sliding window length (seconds) for SSH brute-force detection | | `SERVERBEE_SECURITY__SSH__FAILED_THRESHOLD` | `10` | Failed SSH attempts within the window that trigger an `ssh_brute_force` event. Queue clears after firing | | `SERVERBEE_SECURITY__PORT_SCAN__ENABLED` | `false` | Enable port-scan detection. Requires `conntrack` CLI installed (Linux) | | `SERVERBEE_SECURITY__PORT_SCAN__WINDOW_SECONDS` | `30` | Sliding window length (seconds) for port-scan detection | | `SERVERBEE_SECURITY__PORT_SCAN__DISTINCT_PORT_THRESHOLD` | `20` | Distinct destination ports hit by a single source IP within the window that trigger a `port_scan` event | | `SERVERBEE_SECURITY__DATA_DIR` | `/var/lib/serverbee/security` | Directory for the persistent `first_seen` store used to mark `ssh_login` events as new (user, IP) combinations | ## Server Configuration (server.toml) [#server-configuration-servertoml] ### `[server]` -- Core Server Settings [#server----core-server-settings] | Key | Type | Default | Description | | ----------------- | --------- | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------- | | `listen` | string | `"0.0.0.0:9527"` | IP address and port the server listens on | | `data_dir` | string | `"./data"` | Directory for database files and other persistent data | | `trusted_proxies` | string\[] | private/loopback CIDRs | CIDR ranges of trusted reverse proxies. Defaults to RFC 1918 + loopback ranges. Set to `[]` to disable X-Forwarded-For extraction | ### `[database]` -- Database Settings [#database----database-settings] | Key | Type | Default | Description | | ----------------- | ------ | ---------------- | ------------------------------------------------ | | `path` | string | `"serverbee.db"` | Database filename (relative to `data_dir`) | | `max_connections` | u32 | `10` | Maximum number of connections in the SQLite pool | ### `[auth]` -- Authentication Settings [#auth----authentication-settings] | Key | Type | Default | Description | | --------------- | ---- | ------- | -------------------------------------------------------------------------------------------------------------- | | `session_ttl` | i64 | `86400` | Session cookie lifetime in seconds (24 hours) | | `max_servers` | u32 | `0` | Maximum servers allowed via enrollment (0 = no limit). Best-effort soft cap | | `secure_cookie` | bool | `true` | Set the `Secure` flag on session cookies. Use `false` only when the browser accesses ServerBee over plain HTTP | ### `[dev]` -- Local Development [#dev----local-development] | Key | Type | Default | Description | | ----------- | ---- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `demo_data` | bool | `false` | Reset and seed the synthetic local development dataset. Guarded to `database.path = "dev-demo.db"` so it cannot accidentally run against normal local or production-copy databases | ### `[retention]` -- Data Retention [#retention----data-retention] | Key | Type | Default | Description | | --------------------------- | ---- | ------- | ---------------------------------------------------------------------- | | `records_days` | u32 | `7` | Days to keep raw metric records | | `records_hourly_days` | u32 | `90` | Days to keep hourly aggregated records | | `gpu_records_days` | u32 | `7` | Days to keep per-GPU metric records | | `ping_records_days` | u32 | `7` | Days to keep ping probe records | | `network_probe_days` | u32 | `7` | Days to keep raw network probe records | | `network_probe_hourly_days` | u32 | `90` | Days to keep hourly aggregated network probe records | | `audit_logs_days` | u32 | `180` | Days to keep audit log entries | | `traffic_hourly_days` | u32 | `7` | Days to keep hourly traffic records | | `traffic_daily_days` | u32 | `400` | Days to keep daily traffic records | | `task_results_days` | u32 | `7` | Days to keep task execution results | | `docker_events_days` | u32 | `7` | Days to keep Docker event records | | `service_monitor_days` | u32 | `30` | Days to keep service monitor check records | | `security_event_days` | u32 | `30` | Days to keep security event records (SSH login/brute force, port scan) | | `ip_quality_event_days` | u32 | `90` | Days to keep IP quality status-change event records | Raw metric records are written every 60 seconds and retained for 7 days by default. The hourly aggregator computes averages so you can keep 90 days of long-term trends without excessive storage. Tune these values to match your disk space and monitoring needs. ### `[scheduler]` -- Scheduler [#scheduler----scheduler] | Key | Type | Default | Description | | ---------- | ------ | ------- | ---------------------------------------------------------------------------------------------------------------------------------- | | `timezone` | string | `"UTC"` | Timezone for daily traffic aggregation and billing cycle computation. Use IANA timezone names (e.g. `Asia/Shanghai`, `US/Eastern`) | ### `[rate_limit]` -- Rate Limiting [#rate_limit----rate-limiting] | Key | Type | Default | Description | | -------------- | ---- | ------- | --------------------------------------------------------------------------------------------------------------------- | | `login_max` | u32 | `5` | Maximum login attempts per rate-limit window | | `register_max` | u32 | `10` | Maximum agent registration attempts per rate-limit window. Admins can clear active windows via Settings → Rate limits | ### `[log]` -- Logging [#log----logging] | Key | Type | Default | Description | | ------- | ------ | -------- | -------------------------------------------------------- | | `level` | string | `"info"` | Log verbosity: `trace`, `debug`, `info`, `warn`, `error` | | `file` | string | `""` | Path to log file. If empty, logs go to stdout only | The log level can also be set via the `RUST_LOG` environment variable, which takes precedence. ### `[geoip]` -- GeoIP Lookup [#geoip----geoip-lookup] | Key | Type | Default | Description | | ----------- | ------ | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `mmdb_path` | string | `""` | Path to a MaxMind-compatible MMDB file. Non-empty path enables this custom GeoIP database; if empty, the UI can download DB-IP Lite into the server data directory | ### `[asn]` -- ASN Lookup (Traceroute) [#asn----asn-lookup-traceroute] | Key | Type | Default | Description | | ----------- | ------ | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `mmdb_path` | string | `""` | Path to a DB-IP Lite ASN / MaxMind GeoLite2-ASN MMDB file. Non-empty path enables this custom ASN database; if empty, the UI can download DB-IP Lite ASN into the server data directory. Used to label each traceroute hop with its autonomous system | ### `[resend]` -- Email Notifications [#resend----email-notifications] | Key | Type | Default | Description | | --------- | ------ | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `api_key` | string | `""` | Resend API key ([resend.com/api-keys](https://resend.com/api-keys)). Required to use the Email notification channel. The `from` address on each email channel must belong to a domain verified at [resend.com/domains](https://resend.com/domains) | ### `[oauth]` -- OAuth / SSO [#oauth----oauth--sso] | Key | Type | Default | Description | | -------------------- | ------ | ------- | --------------------------------------------------------- | | `base_url` | string | `""` | Public URL of your ServerBee instance (for callback URLs) | | `allow_registration` | bool | `false` | Create new user accounts on first OAuth login | ### `[oauth.github]` -- GitHub OAuth [#oauthgithub----github-oauth] | Key | Type | Default | Description | | --------------- | ------ | ------- | ------------------------------ | | `client_id` | string | -- | GitHub OAuth App client ID | | `client_secret` | string | -- | GitHub OAuth App client secret | ### `[oauth.google]` -- Google OAuth [#oauthgoogle----google-oauth] | Key | Type | Default | Description | | --------------- | ------ | ------- | -------------------------- | | `client_id` | string | -- | Google OAuth client ID | | `client_secret` | string | -- | Google OAuth client secret | ### `[oauth.oidc]` -- OpenID Connect [#oauthoidc----openid-connect] | Key | Type | Default | Description | | --------------- | --------- | -------------------------------- | -------------------------------------------------------------- | | `issuer_url` | string | -- | OIDC issuer URL (e.g., `https://auth.example.com/realms/main`) | | `client_id` | string | -- | OIDC client ID | | `client_secret` | string | -- | OIDC client secret | | `scopes` | string\[] | `["openid", "email", "profile"]` | OAuth scopes to request | ### `[upgrade]` -- Agent Upgrade [#upgrade----agent-upgrade] | Key | Type | Default | Description | | -------------------- | ------ | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `release_base_url` | string | `"https://github.com/ZingerLittleBee/ServerBee/releases"` | Base URL for agent upgrade release assets. The server appends `/download/v{version}/` to construct the asset download URL | | `latest_version_url` | string | `""` | Optional custom URL for latest version API. If empty, the server queries GitHub API to determine the latest version. Use this to override with a custom version endpoint | | `channel` | string | `"stable"` | Release channel used by the dashboard version check. Set to `"beta"` to select the newest published GitHub prerelease | ### `[file]` -- File Upload (Server-side) [#file----file-upload-server-side] | Key | Type | Default | Description | | ----------------- | ---- | ----------- | -------------------------------------------------- | | `max_upload_size` | u64 | `104857600` | Maximum file upload size in bytes (default 100 MB) | ### `[mobile]` -- Mobile Authentication [#mobile----mobile-authentication] | Key | Type | Default | Description | | ------------- | ---- | --------- | ------------------------------------------------------------ | | `access_ttl` | i64 | `900` | Mobile access token lifetime in seconds (default 15 minutes) | | `refresh_ttl` | i64 | `2592000` | Mobile refresh token lifetime in seconds (default 30 days) | ### `[push_relay]` -- Verified Mobile Setup [#push_relay----verified-mobile-setup] | Key | Type | Default | Description | | ----- | ------ | ------- | ------------------------------------------------------------------------------------------------------------------------- | | `url` | string | `""` | Public Push Relay HTTPS URL. Empty disables notification registration. Apple signing keys stay exclusively in the Worker. | Set `SERVERBEE_PUSH_RELAY__URL` to the final HTTPS Worker URL. “Verified” here means authenticated Server account, installation and session ownership; there is no App Attest or Relay grant. The app registers its token, APNs environment and per-installation content key with the Server over HTTPS. The Server encrypts content and sends token, environment, event identity, expiry and envelope to the Worker's public `POST /v1/send`. Local Server fixtures may use `http://127.0.0.1:`; the app requires HTTPS and rejects redirects. Worker-only bindings are `APNS_TEAM_ID`, `APNS_KEY_ID`, `APNS_TOPIC`, `APNS_ENVIRONMENTS` and the `APNS_PRIVATE_KEY` secret containing PKCS#8 PEM text. They never belong in Server configuration or the app. `APNS_TOPIC=app.serverbee` matches the official main app; never use the extension identifier `app.serverbee.notifications`. `APNS_ENVIRONMENTS=sandbox,production` allows both device environments; topic and APNs hosts are fixed by the Worker. There is no database, challenge/grant lifecycle, version/distribution policy or Bun/OpenSSL runtime. See [Cloudflare setup](/en/docs/push-relay#cloudflare-quick-setup). The public endpoint accepts abuse risk: known valid tokens can receive junk/generic notifications, while arbitrary traffic or an attacker's own token can consume quota. Strict payload limits, bounded streaming reads, deadlines, concurrency and bounded isolate-local source/target rate limits are best-effort safeguards, not global admission or cost guarantees. Content keys and business plaintext never cross Relay. The Server retains authenticated subscriptions, recipient checks, durable encrypted outbox/retry, and a 30-minute deadline from original event creation. Alert triggers/recoveries, administrator security-rule matches and owner-only final scheduled-task outcomes remain supported; task success is opt-in. Role/session/subscription/revision changes stop later eligible sends. Terminal ciphertext is erased, and accepted or in-flight notifications cannot be recalled. APNs acceptance is a provider receipt, not proof of device presentation. Shared app/NSE Keychain signing and live device acceptance still require separate verification. See [Mobile](/en/docs/mobile) and [Push Relay Operations](/en/docs/push-relay). ### `[firewall]` -- Firewall Blocklist Guardrail [#firewall----firewall-blocklist-guardrail] | Key | Type | Default | Description | | ------------ | --------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `allow_list` | string\[] | `[]` | CIDRs / IPs the server refuses to insert into `block_list`. Tier-2 guardrail on top of the hard-coded Tier-1 protected ranges (loopback + RFC 1918 + link-local + multicast + unspecified). See [Firewall Blocklist](/en/docs/firewall) | ### `[ip_quality]` -- IP Quality Risk Scoring [#ip_quality----ip-quality-risk-scoring] By default, the Server calls [ipapi.is](https://ipapi.is/developers.html) anonymously (100 requests per client IP per UTC day). ServerBee uses the minimal response's proxy, VPN, datacenter, Tor, and abuse flags and derives an IP type, but no numeric risk score. A free account/API key provides 1,000 requests per day and the full response required for the 0–100 score. The Server falls back to [ip-api.com](https://ip-api.com) only when the primary request fails, including HTTP 429, not when a successful response lacks a score. Local baseline metadata is available only when a usable GeoIP MMDB is loaded. See [IP Quality](/en/docs/ip-quality) for details and the fallback's non-commercial/HTTP warning. | Key | Type | Default | Description | | ------------------------ | ------ | ------------ | ------------------------------------------------------------------------- | | `risk_provider` | string | `"ipapi_is"` | Primary risk provider. One of: `none`, `ipapi_is`, `ip-api`. | | `risk_provider_fallback` | string | `"ip-api"` | Fallback provider triggered on primary failure. Set to `none` to disable. | ### `[ip_quality.ipapi_is]` -- ipapi.is [#ip_qualityipapi_is----ipapiis] | Key | Type | Default | Description | | ---------- | ------ | ------- | ---------------------------------------------------------------------------------------------------------------- | | `api_key` | string | -- | Optional. A free account key provides 1,000 requests per day and the full response required for the 0–100 score. | | `endpoint` | string | `""` | Override for self-hosted mirrors or testing. Empty falls back to the built-in default `https://api.ipapi.is`. | ### `[network_probe]` -- Anomaly Thresholds [#network_probe----anomaly-thresholds] | Key | Type | Default | Description | | ---------------------- | ---- | ------- | ------------------------------------------------------------------------ | | `high_latency_ms` | f64 | `500` | Records above this average latency are classified as `high_latency` | | `very_high_latency_ms` | f64 | `800` | Records above this average latency are classified as `very_high_latency` | *** ## Agent Configuration (agent.toml) [#agent-configuration-agenttoml] ### Top-Level Options [#top-level-options] | Key | Type | Default | Description | | ----------------- | ------ | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `server_url` | string | **required** | URL of the ServerBee server (e.g., `http://10.0.0.1:9527`) | | `token` | string | `""` | Agent authentication token. When enrollment is needed, the Agent generates and persists this token before claiming the offer; the Server stores only its hash | | `enrollment_code` | string | `""` | One-time enrollment offer from Add Server or the Server's Agent Authority actions (used only when `token` is empty; consumed on first successful claim) | ### `[collector]` -- Metric Collection [#collector----metric-collection] | Key | Type | Default | Description | | -------------------- | ---- | ------- | ---------------------------------------------------------------------------------------------------------------------------- | | `interval` | u32 | `3` | Compatibility-only field. The Agent currently ignores it and uses the Server's `Welcome.report_interval`, fixed at 3 seconds | | `enable_gpu` | bool | `false` | Enable NVIDIA GPU monitoring (requires a build with the `gpu` feature and an NVIDIA driver that provides NVML) | | `enable_temperature` | bool | `true` | Enable CPU temperature sensor monitoring | ### `[file]` -- File Management [#file----file-management] | Key | Type | Default | Description | | --------------- | --------- | ------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------- | | `enabled` | bool | `false` | Enable the file-management subsystem (path sandbox). The agent must also report the `file` capability — add `file` to the `[capabilities]` allow list | | `root_paths` | string\[] | `[]` | Restrict browsing to these directories. Empty array rejects all file operations | | `max_file_size` | u64 | `1073741824` | Maximum file size (bytes) for read and download operations (default 1 GB) | | `deny_patterns` | string\[] | `["*.key", "*.pem", "id_rsa*", ".env*", "shadow", "passwd"]` | Glob patterns for files the agent will refuse to access | ### `[capabilities]` -- Agent-Owned Capability Policy [#capabilities----agent-owned-capability-policy] Capabilities are owned by the agent host. The agent starts from the default set (`CAP_DEFAULT = 1852`), applies `allow`/`deny` from this block, then applies any `--allow-cap`/`--deny-cap` CLI flags, and reports the result to the server. The server **cannot** override this. See [Capabilities](/en/docs/capabilities) for the full key list and enforcement model. | Key | Type | Default | Description | | ------------------------ | --------- | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `allow` | string\[] | `[]` | Capability keys to enable on top of the default set, e.g. `["terminal", "file"]`. Valid keys: `terminal`, `exec`, `upgrade`, `ping_icmp`, `ping_tcp`, `ping_http`, `file`, `docker`, `security_events`, `firewall_block`, `ip_quality` | | `deny` | string\[] | `[]` | Capability keys to strip from the default set. `deny` wins over `allow`. Unknown keys are rejected at startup | | `temporary_max_duration` | string | `"24h"` | Maximum duration a single temporary capability grant can last (e.g. `30m`, `2h`, `1d`). Used by the host-local `serverbee-agent grant` CLI. See [Capabilities → Temporary grants](/en/docs/capabilities#temporary-grants) | | `state_dir` | string | `"/var/lib/serverbee"` | Directory where the agent persists `capability_grants.json` (the temporary-grants store). Must be writable by the agent | ```toml [capabilities] allow = ["terminal", "file"] deny = ["ip_quality"] # Footgun guard for `serverbee-agent grant --for ...`; defaults to 24h. temporary_max_duration = "24h" state_dir = "/var/lib/serverbee" ``` ### `[ip_change]` -- IP Change Detection [#ip_change----ip-change-detection] | Key | Type | Default | Description | | ------------------ | --------- | --------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `enabled` | bool | `true` | Enable periodic IP change detection. Agent enumerates NIC addresses and reports changes | | `external_ip_urls` | string\[] | `["https://api.ipify.org", "https://ifconfig.me/ip", "https://icanhazip.com", "https://checkip.amazonaws.com"]` | Ordered list of services that return the agent's public IP. Tried in sequence at startup and on every check; first success wins. Required for agents behind NAT or in containers. Set to `[]` to disable external lookups (air-gapped deployments) | | `interval_secs` | u64 | `300` | IP check interval in seconds (default 5 minutes) | ### `[security]` -- Security Event Detection [#security----security-event-detection] | Key | Type | Default | Description | | ----------------------------------- | ------ | ------------------------------- | ------------------------------------------------------------------ | | `enabled` | bool | `true` | Enable local security-event detection | | `data_dir` | string | `"/var/lib/serverbee/security"` | Writable directory for detector state | | `ssh.window_seconds` | u32 | `60` | Sliding window for failed SSH-login detection | | `ssh.failed_threshold` | u32 | `10` | Failed SSH attempts in the window before reporting an event | | `port_scan.enabled` | bool | `false` | Enable local port-scan detection | | `port_scan.window_seconds` | u32 | `30` | Sliding port-scan detection window | | `port_scan.distinct_port_threshold` | u32 | `20` | Distinct destination ports in the window before reporting an event | ### `[log]` -- Logging [#log----logging-1] | Key | Type | Default | Description | | ------- | ------ | -------- | -------------------------------------------------------- | | `level` | string | `"info"` | Log verbosity: `trace`, `debug`, `info`, `warn`, `error` | | `file` | string | `""` | Path to log file. If empty, logs go to stdout only | ### `[upgrade]` -- Upgrade Source (Agent) [#upgrade----upgrade-source-agent] The agent downloads upgrades from a locally-pinned release source rather than trusting a URL from the server. | Key | Type | Default | Description | | -------------------------- | ------ | --------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `release_repo_url` | string | `"https://github.com/ZingerLittleBee/ServerBee/releases"` | Base URL for agent upgrade release assets. Must replicate the GitHub releases directory layout: `{base}/download/v{version}/{asset}` for binaries and `{base}/download/v{version}/sha256sums.txt` for checksum verification. The compile-time default is overridable via the `SERVERBEE_RELEASE_REPO` build-time env var | | `release_cert_spki_sha256` | string | `""` | Optional TLS SPKI pin for the release host. Set to 64 lowercase hex characters (SHA-256 of the leaf certificate's SubjectPublicKeyInfo DER encoding). Empty disables pinning. If set, the agent additionally validates the leaf cert SPKI after standard chain validation. Invalid format (non-64-char or non-hex) is rejected at startup | **Configuration precedence** (highest wins): 1. `--release-repo` CLI flag 2. `SERVERBEE_UPGRADE__RELEASE_REPO_URL` environment variable 3. `/etc/serverbee/agent.toml` or `agent.toml` `[upgrade]` section 4. Compile-time default (official GitHub releases URL) The dashboard "latest version" check uses the Server-configured release source (`upgrade.release_base_url` / `upgrade.latest_version_url`). Those are separate Server-side settings used only for the dashboard's "latest version" lookup, and are distinct from the Agent's `[upgrade] release_repo_url`, which governs what the Agent actually downloads. For consistent upgrade behavior, point both the Server and the Agent at the same release repository unless you intentionally want them to track different sources. **How to obtain the SPKI pin** for a release host certificate: ```bash openssl x509 -in cert.pem -pubkey -noout \ | openssl pkey -pubin -outform der \ | openssl dgst -sha256 -r | awk '{print $1}' ``` *** ## Example: Minimal Server Configuration [#example-minimal-server-configuration] ```toml [server] data_dir = "./data" ``` Everything else uses sensible defaults. On first startup, ServerBee creates the `admin` user with a random password and prints it once to the server logs. ## Example: Production Server Configuration [#example-production-server-configuration] ```toml [server] listen = "127.0.0.1:9527" data_dir = "/var/lib/serverbee" [auth] secure_cookie = true [retention] records_days = 14 records_hourly_days = 180 [geoip] mmdb_path = "/var/lib/serverbee/GeoLite2-City.mmdb" [asn] mmdb_path = "/var/lib/serverbee/GeoLite2-ASN.mmdb" [log] level = "info" file = "/var/log/serverbee/server.log" [oauth] base_url = "https://monitor.example.com" allow_registration = false [oauth.github] client_id = "Iv1.abc123" client_secret = "secret123" ``` ## Example: Minimal Agent Configuration [#example-minimal-agent-configuration] ```toml server_url = "http://your-server-ip:9527" enrollment_code = "" ``` ## Example: Production Agent Configuration [#example-production-agent-configuration] ```toml server_url = "https://monitor.example.com" token = "" [collector] enable_gpu = true # Requires a GPU-enabled build and an NVIDIA driver that provides NVML enable_temperature = true [file] enabled = true root_paths = ["/home", "/var/log", "/etc"] max_file_size = 1073741824 deny_patterns = ["*.key", "*.pem", "id_rsa*", ".env*", "shadow", "passwd"] [ip_change] enabled = true # external_ip_urls defaults to a curated list of public IP services. # Override only to point at internal mirrors or to disable (set to []). [log] level = "info" file = "/var/log/serverbee/agent.log" ``` # Monitoring > Real-time system monitoring, dashboards, historical data, and data retention. URL: https://docs.serverbee.app/en/docs/monitoring ServerBee monitors all your connected servers in real time through a unified web dashboard. Metrics are streamed over WebSocket for instant updates without polling, with historical data available for querying and trend analysis. ## Dashboard Overview [#dashboard-overview] The main dashboard shows all registered servers with their current status at a glance: * **Top summary cards** -- Online / Offline / Total counts, average CPU usage, average memory usage, and total bandwidth * **Online/Offline status** with color indicators * **Ring grid** of four donut charts per card: CPU, Memory, Disk, and monthly **Traffic quota** utilization. When no quota is configured the traffic ring falls back to cumulative bytes transferred, and when a billing cycle is active a "days remaining" hint is shown under the footer * **Disk I/O throughput** (read / write bytes per second, aggregated across devices) streamed live alongside network speed * **Network throughput** (upload / download speed) * **Load trend** (`load5 · load15` next to `load1`) and **uptime / swap / process / TCP / UDP** summary row * **Region and country** flags (when GeoIP is enabled) All data is driven over WebSocket, so the view refreshes in real time without a manual reload. Servers are organized by groups and sorted by weight. You can filter, search, and batch-operate on servers from this view. For custom operations views, use [Dashboards & Widgets](/en/docs/dashboards) to create additional dashboard layouts with charts, maps, service status widgets, Markdown notes, and uptime timelines. ### GeoIP Display [#geoip-display] Region/country labels and the Server Map widget require GeoIP data. You can configure a custom MaxMind-compatible MMDB path with `geoip.mmdb_path`, or download the DB-IP Lite database from **Settings → GeoIP Database**. The status endpoint is `GET /api/geoip/status`, and administrators can trigger a download with `POST /api/geoip/download`. ## Real-Time Updates [#real-time-updates] The browser connects to the server via WebSocket at `/api/ws/servers`, then receives: | Message | When | Description | | --------------- | ------------------------- | ---------------------------------- | | `FullSync` | On connection | Current state of all servers | | `Update` | When an agent reports | Broadcast of changed server status | | `ServerOnline` | When an agent connects | A server has come online | | `ServerOffline` | When an agent disconnects | A server has gone offline | The dashboard therefore updates in real time -- no page refresh or polling interval required. ## Metric Types [#metric-types] The agent collects and reports the following metrics every 3 seconds. The server sends this fixed `report_interval` in the `Welcome` message: ### System Resources [#system-resources] | Metric | Unit | Description | | ----------- | ----- | ------------------------------- | | CPU Usage | % | Overall CPU utilization (0-100) | | Memory Used | bytes | Current RAM usage | | Swap Used | bytes | Current swap space usage | | Disk Used | bytes | Total disk space consumption | ### Network [#network] | Metric | Unit | Description | | -------------------- | --------- | ------------------------------------------- | | Network In Speed | bytes/sec | Current download speed | | Network Out Speed | bytes/sec | Current upload speed | | Network In Transfer | bytes | Cumulative total download since agent start | | Network Out Transfer | bytes | Cumulative total upload since agent start | ### System Load [#system-load] | Metric | Description | | ---------------- | ------------------------------------ | | Load Average 1m | System load over the last 1 minute | | Load Average 5m | System load over the last 5 minutes | | Load Average 15m | System load over the last 15 minutes | ### Connections and Processes [#connections-and-processes] | Metric | Description | | --------------- | --------------------------------- | | TCP Connections | Number of active TCP connections | | UDP Connections | Number of active UDP connections | | Process Count | Total number of running processes | ### Environment [#environment] | Metric | Description | | ----------- | --------------------------------------------- | | Temperature | CPU temperature in degrees Celsius (optional) | | Uptime | System uptime in seconds | ### Disk I/O [#disk-io] The agent collects per-disk read/write throughput on all major platforms: | Metric | Unit | Description | | ----------- | --------- | ------------------------- | | Read Speed | bytes/sec | Read throughput per disk | | Write Speed | bytes/sec | Write throughput per disk | **Linux**: Reads `/proc/diskstats` directly. Only physical block devices are tracked (e.g., `sda`, `nvme0n1`). Virtual devices (`loop*`, `dm-*`, `ram*`, `sr*`) and partitions are excluded. DiskIo.name is the block device name. **macOS / Windows**: Uses sysinfo `Disk::usage()` API with `mount_point()` as the key. This provides per-mount-path semantics (e.g., `/`, `/home`, `C:\`) rather than per-physical-disk. Known limitation: on macOS with APFS, multiple volumes sharing one physical disk may report overlapping I/O counters. On the first sample after agent startup, a baseline is established and an empty list is reported. Subsequent samples compute delta-based rates. Disk I/O data is stored as a JSON column (`disk_io_json`) in the `records` and `records_hourly` tables. The hourly aggregator computes per-device average read/write rates. ### GPU (Optional) [#gpu-optional] When GPU monitoring is enabled (`enable_gpu = true`), per-device metrics are collected: | Metric | Description | | ---------------- | ------------------------------------- | | GPU Name | Device model name | | GPU Utilization | GPU core utilization percentage | | GPU Memory Used | VRAM usage in bytes | | GPU Memory Total | Total VRAM in bytes | | GPU Temperature | Device temperature in degrees Celsius | ## Server Information [#server-information] In addition to periodic metrics, each agent reports static system information when it first connects: * CPU name, core count, and architecture * Operating system and kernel version * Total memory, swap, and disk capacity * IPv4 and IPv6 addresses * Virtualization type (KVM, Xen, Docker, etc.) * Agent version This information is displayed on the server detail page and stored in the database. ## Historical Data and Charts [#historical-data-and-charts] ServerBee stores metric records at two levels of granularity: ### Raw Records [#raw-records] * Written every 60 seconds by the RecordWriter background task * Retained for **7 days** by default (configurable via `retention.records_days`) * Each record captures all metric values at a single point in time ### Hourly Aggregated Records [#hourly-aggregated-records] * Computed by the Aggregator background task * Averages of all raw records within each hour * Retained for **90 days** by default (configurable via `retention.records_hourly_days`) * Used for long-term trend visualization The dashboard charts automatically switch between raw and hourly data depending on the selected time range. ### GPU Records [#gpu-records] GPU metrics are stored separately in a dedicated table with per-device granularity. Each record includes the device index, name, memory, utilization, and temperature. These are retained for **7 days** by default (configurable via `retention.gpu_records_days`). ### Query Strategy [#query-strategy] Historical metrics are queried via the REST API: ``` GET /api/servers/:id/records?from=2026-03-13T00:00:00Z&to=2026-03-14T00:00:00Z&interval=auto ``` The `interval` parameter controls data granularity: | Value | Description | | -------- | --------------------------------------------------------------------------------- | | `raw` | Returns raw minute-level records | | `hourly` | Returns hourly aggregated records | | `auto` | Chooses automatically: `raw` for ranges within 24 hours, `hourly` beyond 24 hours | ### Storage Tiers [#storage-tiers] ``` Agent report (every 3s) --> In-memory cache: keeps only the latest sample per server (for real-time push) --> Every 1 minute: written to the records table (minute-level records) --> Every 1 hour: aggregated into the records_hourly table (averages) ``` ## Server Details [#server-details] Each server has a detail page showing: * **Basic info** -- OS, CPU model, total memory, IP addresses, region, agent version * **Real-time streaming charts** (default mode) -- live data streamed from WebSocket updates * **Historical trend charts** -- switch to 1h / 6h / 24h / 7d / 30d to query records from the database * **Disk I/O charts** -- merged and per-disk views (all platforms, historical mode) * **90-day uptime timeline** -- daily availability breakdown as a colored bar chart * **GPU panel** -- per-GPU utilization and VRAM charts, if the server reports GPU data (historical mode only) * **Traffic statistics** -- cumulative network traffic summary * **Server metadata** -- group, tags, remarks, pricing * **Actions** -- terminal access, edit, delete ### Real-Time Charts [#real-time-charts] The server detail page defaults to **Real-time mode**. In this mode, charts display live data streamed from WebSocket updates: * **Data source**: Accumulated from `BrowserMessage::Update` events through the live server catalog * **Update interval**: \~3 seconds (matches the agent report interval) * **Buffer size**: Grows to 250 data points, then trims back to the newest 200 points * **Deduplication**: Uses the server-side `last_active` timestamp to filter duplicate events * **Available charts**: CPU, Memory, Disk, Network In/Out, Load Average (1m) * **Time axis**: First tick shows `HH:mm:ss`, subsequent ticks show `mm:ss` Temperature, GPU, and Disk I/O charts are not currently available in real-time mode. WebSocket `BrowserMessage::Update` messages include aggregate disk read/write rates in `LiveMetrics`, but the detail-page real-time chart model does not yet map those fields. Switch to a historical view to see temperature, GPU, and per-disk I/O data. ### Disk I/O Charts [#disk-io-charts] When historical disk I/O data is available, the server detail page displays a Disk I/O chart with two views: * **Merged** -- Combined read/write throughput across all physical disks * **Per Disk** -- Individual charts for each physical disk (e.g., `sda`, `nvme0n1`) Both views show read speed (blue) and write speed (green) as area charts. Missing data points are filled with zero values to maintain a continuous timeline. ### Uptime Timeline [#uptime-timeline] The server detail page includes an uptime card with a 90-day timeline. Each day is shown as a colored bar: * **Green** -- 100% uptime * **Yellow** -- Below the yellow threshold (degraded) * **Red** -- Below the red threshold (major outage) * **Gray** -- No data Uptime data is queried via `GET /api/servers/{server_id}/uptime-daily?days=90`. The endpoint returns a `UptimeDailyEntry` per day with `date`, `online_minutes`, `total_minutes`, and `uptime_percent` fields. Missing dates are gap-filled with zero values. ## Data Retention [#data-retention] ServerBee automatically cleans up expired data via a background task that runs once per hour: | Data Type | Default Retention | Config Key | | -------------------------------- | ----------------- | ------------------------------- | | Minute-level metrics (records) | 7 days | `retention.records_days` | | Hourly metrics (records\_hourly) | 90 days | `retention.records_hourly_days` | | GPU metrics (gpu\_records) | 7 days | `retention.gpu_records_days` | | Ping probe records | 7 days | `retention.ping_records_days` | | Traffic hourly (traffic\_hourly) | 7 days | `retention.traffic_hourly_days` | | Traffic daily (traffic\_daily) | 400 days | `retention.traffic_daily_days` | | Task results (task\_results) | 7 days | `retention.task_results_days` | | Audit logs | 180 days | `retention.audit_logs_days` | Aggregation and cleanup run as separate hourly background tasks with no fixed ordering. Cleanup starts with a 60-second offset, which only staggers their work. Each aggregation run rolls up only the previous completed hour, so a missed hour is not backfilled; monitor task errors if long-term hourly history matters. Example of overriding the retention policy: ```toml title="server.toml" [retention] records_days = 14 # keep minute-level data for 14 days records_hourly_days = 365 # keep hourly data for 1 year ``` ### Disk Space Estimate [#disk-space-estimate] Database growth depends on server count, enabled writers, sampling intervals, and retention settings. Use [Storage & Sizing](/en/docs/storage-sizing) as the sizing reference; it documents measured April 2026 results, a single 30-day baseline, feature multipliers, and WAL headroom. ## Server Groups and Management [#server-groups-and-management] ### Server Groups [#server-groups] Organize your servers into logical groups by purpose, region, or any other dimension: * Create groups with custom names and sort weights * Assign servers to different groups * The dashboard lists servers grouped by section * Sort weight controls the display order (lower weight = higher position) * Ungrouped servers appear in the default section Groups can represent environments (production, staging), regions (US-East, EU-West), providers (AWS, Hetzner), or any other organizational structure that makes sense for your setup. ### Server Tags [#server-tags] In addition to groups, you can attach multiple tags to a server for more flexible classification and filtering. Tags are a many-to-many relationship -- a single server can carry multiple tags. ### Server Management Actions [#server-management-actions] | Action | Description | | --------------------- | ------------------------------------------------ | | Edit name and remarks | Change the server's display name and remarks | | Set group | Assign the server to a specific group | | Manage tags | Add or remove server tags | | Adjust sorting | Control display order via the weight value | | Hide server | Hide a specific server on the dashboard | | Delete server | Remove the server and all of its historical data | | Batch operations | Delete multiple servers at once | ## Data Flow [#data-flow] ``` Agent Server Browser | | | |-- Report (3s) ------>| | | |-- cache in AgentManager | | | | | |-- RecordWriter (60s) -->| | | writes to SQLite | | | | | |-- Update (broadcast) -->| | | real-time UI | | | | |-- Aggregator (hourly) ->| | | hourly averages | | | | | |-- Cleanup (hourly) ---->| | | delete old records | ``` The agent reports every 3 seconds. The server caches the latest report in memory and immediately broadcasts it to connected browsers. Every 60 seconds, all cached reports are batch-written to SQLite. Separate hourly tasks aggregate raw records into hourly summaries and clean up expired data based on retention settings. ## Network Quality Views [#network-quality-views] The `/network` overview and `/network/{server_id}` detail pages summarize configured probe targets for each server. * Newly assigned targets appear immediately, even before the first probe result is written * Targets without probe data render a no-data state instead of disappearing from the summary * The overview search box follows the active UI language ### Time Range Selector [#time-range-selector] The time range bar offers these options: | Mode | Data Source | Description | | ------------- | -------------------------- | ----------------------------- | | **Real-time** | WebSocket in-memory buffer | Live streaming data (default) | | **1h** | REST API (raw records) | Last 1 hour from database | | **6h** | REST API (raw records) | Last 6 hours | | **24h** | REST API (raw records) | Last 24 hours | | **7d** | REST API (hourly records) | Last 7 days (aggregated) | | **30d** | REST API (hourly records) | Last 30 days (aggregated) | When switching from Real-time to a historical view, the REST API queries are enabled and chart data loads from the database. When switching back to Real-time, the accumulated buffer data is displayed immediately (data continues to accumulate in the background even while viewing historical data). ## Traffic Statistics [#traffic-statistics] ServerBee tracks network traffic at hourly and daily granularity, with billing cycle-aware usage and end-of-cycle prediction. ### How It Works [#how-it-works] Traffic tracking is integrated into the existing metric recording pipeline: 1. Each agent reports cumulative network byte counters (`net_in_transfer`, `net_out_transfer`) every 3 seconds 2. The RecordWriter calculates the delta between consecutive reports and accumulates hourly traffic 3. The Aggregator rolls up hourly data into daily totals (timezone-aware via `scheduler.timezone`) 4. The Cleanup task removes expired traffic records based on retention settings ### Traffic API [#traffic-api] Query traffic statistics for any server via `GET /api/servers/{id}/traffic`. The response includes: * **Cycle totals** -- Total bytes in/out for the current billing cycle * **Usage percentage** -- Traffic used vs. the server's traffic limit (if configured) * **Prediction** -- Estimated end-of-cycle usage based on current consumption rate * **Daily breakdown** -- Per-day traffic totals within the billing cycle * **Hourly breakdown** -- Per-hour traffic totals for today ### Billing Cycles [#billing-cycles] Traffic is queried within billing cycles determined by: * **`billing_cycle`** -- The cycle type: `monthly` (default), `quarterly`, or `yearly` * **`billing_start_day`** -- The day of month when the billing cycle starts (1-28, default 1) For example, if `billing_start_day = 15` and `billing_cycle = monthly`, the cycle runs from the 15th of each month to the 14th of the next month. ### Frontend Display [#frontend-display] The server detail page shows a collapsible traffic card with: * **Progress bar** -- Visual usage against the traffic limit * **Daily chart** -- Bar chart showing daily in/out traffic within the cycle * **Hourly chart** -- Bar chart showing today's hourly traffic breakdown * **Prediction** -- Estimated total usage by end of cycle ### Data Retention [#data-retention-1] | Data Type | Default Retention | Config Key | | -------------- | ----------------- | ------------------------------- | | Traffic hourly | 7 days | `retention.traffic_hourly_days` | | Traffic daily | 400 days | `retention.traffic_daily_days` | ### Scheduler Timezone [#scheduler-timezone] Daily traffic aggregation respects the `scheduler.timezone` setting. Set this to your billing timezone (e.g., `Asia/Shanghai`) to ensure daily totals align with your billing provider's day boundaries. ## Docker Container Monitoring [#docker-container-monitoring] ServerBee supports real-time Docker container monitoring when the agent has access to the Docker daemon. This feature requires the `docker` capability (`CAP_DOCKER`), which is off by default. Capabilities are agent-owned — enable it on the agent host by adding `docker` to its `[capabilities]` allow list (or `--allow-cap docker`) and restarting the agent. See [Capabilities](/en/docs/capabilities). ### Overview [#overview] The Docker monitoring page shows: * **Overview cards** -- Running/Stopped container counts, total CPU usage, total memory usage, Docker version * **Container list** -- Searchable, filterable table with Name, Image, Status, CPU%, Memory, Network I/O * **Events timeline** -- Container lifecycle events (start, stop, die, create, destroy) in reverse chronological order * **Networks dialog** -- List of Docker networks with driver, scope, and container count * **Volumes dialog** -- List of Docker volumes with driver, mountpoint, and creation time ### Container Details [#container-details] Click any container row to view: * **Container info** -- Image, status, ports, creation time, container ID * **Stats cards** -- CPU usage, memory (with progress bar), network I/O, block I/O * **Log stream** -- Real-time log output with: * stdout in white text, stderr in red text * Follow toggle for auto-scrolling * Clear button to reset the log view * Connection status indicator (Connected/Disconnected) ### How It Works [#how-it-works-1] 1. When browsers navigate to the Docker page, a `DockerSubscribe` message is sent via WebSocket 2. The server instructs the connected agent to start Docker monitoring 3. The agent uses the bollard Docker API client to poll containers and stats 4. Updates are broadcast to subscribing browsers in real time 5. When all browsers leave the Docker page, monitoring stops automatically (viewer ref-counting) Container logs use a dedicated WebSocket endpoint (`/api/ws/docker/logs/{server_id}`) with subscribe/unsubscribe protocol for per-container log streaming. ### Data Retention [#data-retention-2] | Data Type | Default Retention | Config Key | | ------------- | ----------------- | ------------------------------ | | Docker events | 7 days | `retention.docker_events_days` | Container stats and logs are not persisted — they are streamed in real time only. ## Network Quality Monitoring [#network-quality-monitoring] ServerBee includes a built-in network quality monitoring system that probes network targets from each agent and visualizes latency, packet loss, and anomalies. ### Preset Targets [#preset-targets] 96 preset probe targets are embedded in the server binary (not stored in the database): * **China Telecom** -- 31 provincial nodes (TCP probe via Zstatic CDN) * **China Unicom** -- 31 provincial nodes (TCP probe via Zstatic CDN) * **China Mobile** -- 31 provincial nodes (TCP probe via Zstatic CDN) * **International** -- Cloudflare (1.1.1.1), Google DNS (8.8.8.8), AWS Tokyo (ICMP probe) China targets use domain names (`{province}-{isp}-v4.ip.zstaticcdn.com:80`) that auto-resolve to the latest CDN node IPs. No IP maintenance is needed. Preset targets are read-only and cannot be edited or deleted. You can also create custom targets via the settings page. ### Configuration [#configuration] Navigate to **Settings > Network Probes** to configure: * **Target Management** -- View all 96 preset targets (with lock icon and ISP label) and manage custom targets (create/edit/delete) * **Global Settings** -- Probe interval (30-600 seconds, default 60), packets per round (5-20, default 10), and default targets auto-assigned to new servers * **Per-Server Targets** -- Assign up to 20 probe targets per server via the network detail page's "Manage Targets" dialog ### Network Overview Page [#network-overview-page] The `/network` page shows a card for each server with: * Target count and average latency * Availability percentage * Anomaly count with severity indicators ### Network Detail Page [#network-detail-page] Click a server card to see `/network/:serverId` with: * **Time range selector** -- Real-time, 1h, 6h, 24h, 7d, 30d * **Target cards** -- Per-target latency and packet loss, with toggle visibility * **Multi-line latency chart** -- One colored line per target, tooltips with timestamps * **Anomaly table** -- High latency, high packet loss, and unreachable events * **Statistics bar** -- Average latency, availability percentage, target count * **CSV export** -- Download probe data for the selected time range ### Traceroute [#traceroute] The network detail page can run traceroute from the selected agent to a target host or IP to diagnose routing changes and packet loss. * Requires the agent's effective `CAP_PING_ICMP` capability. * The target may contain only letters, numbers, dots, hyphens, and colons. * The dialog exposes an ICMP / UDP / TCP protocol dropdown. * 5 rounds by default; each round is streamed over WebSocket and the table fills in real time with: Hop / IP / Hostname / ASN / Loss% / Best / Avg / Worst / Jitter / StdDev. * For ECMP multi-path routes, a single TTL may have multiple IPs — the first row shows the primary IP, hover the `+N` chip to see the rest. * Completed traceroutes are persisted to local SQLite. Click any history row to load that snapshot; admins can delete one row or clear the whole history. * Triggering a trace requires admin. Read-only members can browse history but cannot start a new trace or delete records. #### Privileges [#privileges] The ServerBee agent now embeds [trippy-core](https://crates.io/crates/trippy-core) to send raw ICMP/UDP/TCP probes directly, **no longer depending on the system `traceroute` / `mtr` binaries**. Raw sockets still require OS-level privileges: | Platform | Requirement | | -------- | --------------------------------------------------------------------------------------------------- | | Linux | `CAP_NET_RAW` or run as root. One-shot setup: `sudo setcap cap_net_raw+ep $(which serverbee-agent)` | | macOS | Run as root (sudo). In some scenarios, unprivileged ICMP datagram sockets work as a fallback. | | Windows | Start the agent as Administrator. | If privileges are missing, the traceroute call returns an error immediately with the matching platform hint — no agent restart needed. #### API flow [#api-flow] ```http POST /api/servers/{id}/traceroute { target, protocol? } → { request_id } GET /api/servers/{id}/traceroute/{rid} → latest snapshot (protocol/started_at/round/total_rounds + full fields) GET /api/servers/{id}/traceroute → history summaries for that server, newest first DELETE /api/servers/{id}/traceroute/{rid} → delete one (admin) DELETE /api/servers/{id}/traceroute → clear all (admin) ``` The server broadcasts `BrowserMessage::TracerouteUpdate` carrying the incremental result of each round. After a WebSocket reconnect, clients can re-fetch the latest cumulative snapshot via the per-record GET endpoint. ### Data Retention [#data-retention-3] Network probe records follow the same two-tier storage as system metrics: * **Raw records** -- Retained for 7 days (configurable via `retention.network_probe_days`) * **Hourly aggregates** -- Retained for 90 days (configurable via `retention.network_probe_hourly_days`) ### Alert Integration [#alert-integration] Two alert rule types are available for network quality: * `network_latency` -- Triggers when average latency exceeds a threshold * `network_packet_loss` -- Triggers when packet loss exceeds a threshold # Dashboards & Widgets > Build custom monitoring dashboards with draggable widgets and reusable layouts. URL: https://docs.serverbee.app/en/docs/dashboards ServerBee dashboards are configurable pages made of widgets. You can keep the default overview dashboard, create separate dashboards for regions or teams, and choose one dashboard as the default for all users. ## Managing Dashboards [#managing-dashboards] Use the dashboard switcher in the top bar of the home page to: * Switch between dashboards * Create a new dashboard * Rename a dashboard * Delete dashboards you no longer need * Set a dashboard as the default The first dashboard is created automatically if none exists. A dashboard marked as default cannot be unset directly; set another dashboard as default instead. ## Editing Layouts [#editing-layouts] 1. Open a dashboard. 2. Click **Edit**. 3. Add widgets from the widget picker. 4. Drag widgets to rearrange them. 5. Resize widgets within their min/max size constraints. 6. Configure each widget's data source and title. 7. Click **Save**. The layout is stored as grid coordinates (`grid_x`, `grid_y`, `grid_w`, `grid_h`) and widget configuration JSON. Saving a dashboard performs a diff: existing widgets are updated, new widgets are inserted, and widgets removed from the layout are deleted. ## Widget Types [#widget-types] | Widget | Category | Typical use | | ------------------ | --------- | ------------------------------------------------------------------------------------------------- | | `stat-number` | Real-time | Show one fleet-wide summary metric (server count, average CPU/memory, total bandwidth, or health) | | `metric-card` | Real-time | Show several key metrics for one server as a colored card | | `server-cards` | Real-time | Show compact server cards for selected servers | | `gauge` | Real-time | Gauge visualization for one metric and server | | `line-chart` | Charts | Historical chart for one metric on one server | | `multi-line` | Charts | Compare one metric across multiple servers | | `top-n` | Real-time | Rank servers by a selected metric | | `alert-list` | Status | Show active or recent alert state | | `service-status` | Status | Show Service Monitor status | | `traffic-bar` | Charts | Show traffic usage for one server | | `disk-io` | Charts | Show disk read/write throughput history | | `server-map` | Status | Show server locations on a map when GeoIP is installed | | `markdown` | Status | Add notes, runbooks, or links using Markdown | | `uptime-timeline` | Status | Show uptime bars for selected servers | | `network-latency` | Charts | Show network latency probe history for a server | | `network-quality` | Real-time | Show network quality score and live metrics | | `network-overview` | Status | Show aggregate network quality across servers | ## Common Widget Configuration [#common-widget-configuration] Most widgets store their settings in `config_json`. Common fields include: | Field | Used by | Meaning | | ------------- | --------------------- | ------------------------------------------------------ | | `server_id` | Single-server widgets | Server ID to query | | `server_ids` | Multi-server widgets | List of server IDs to include | | `metric` | Metric widgets | Metric key such as CPU, memory, disk, traffic, or load | | `hours` | Historical widgets | Lookback window for chart data | | `interval` | Historical widgets | Data granularity (`raw`, `hourly`, or `auto`) | | `monitor_ids` | Service Status | Service Monitor IDs to display | | `content` | Markdown | Markdown content | ## GeoIP and Server Map [#geoip-and-server-map] The Server Map widget requires GeoIP data. You can either: * Configure a custom MaxMind-compatible MMDB file with `geoip.mmdb_path`, or * Download the DB-IP Lite database from **Settings → GeoIP Database**. The widget shows an installation prompt when GeoIP data is missing. ## Performance & Widget Capacity [#performance--widget-capacity] The dashboard uses viewport-gated lazy mounting (IntersectionObserver): off-screen widgets do not mount their charts at first paint, so cold-load cost barely grows with widget count. **While scrolling**, however, charts measure their container and SVG text synchronously. When many widgets enter the viewport at once, those forced reflows accumulate and the scroll noticeably stutters. Measured (desktop Chrome, no CPU/network throttling, **local backend**, widgets mixed across line-chart / gauge / multi-line / disk-io): | Widget count | Cold-load LCP | Scrolling | | ------------ | ------------- | ---------------------------------------------------------------------------------- | | ≤ 30 | \~360 ms | Smooth | | 30 – 60 | \~360 ms | Usable, mild jank during fast scroll | | > 60 | \~360 ms | Visible stutter while scrolling, main thread saturated by chart layout measurement | Against a remote server over the public internet, cold-load LCP grows with the number of **in-viewport** charts, because each one waits on its own history query before painting — a 40-widget dashboard measured over WAN loads in seconds, not \~360 ms. The scrolling characteristics above still apply. **Recommended sizing**: * **Comfortable**: ≤ **30 widgets** per dashboard. * **Acceptable**: 30 – 60 widgets. If you must exceed 30, favour static/non-chart widgets (`markdown`, `alert-list`, `service-status`, `stat-number`) and keep the chart-heavy ones (`line-chart`, `multi-line`, `disk-io`, `traffic-bar`, `gauge`) under 30. * **Not recommended**: > 60 widgets per dashboard — split by team, region, or use case across multiple dashboards instead. If you genuinely need dozens of charts in one dashboard, **group them in the same vertical region** rather than spreading them across a long page. Fewer charts enter the viewport in any given scroll, lowering peak main-thread pressure. ## API [#api] | Method | Path | Description | | ------ | ------------------------- | ------------------------------------------------- | | GET | `/api/dashboards` | List dashboards | | GET | `/api/dashboards/default` | Get the default dashboard, creating one if needed | | GET | `/api/dashboards/{id}` | Get a dashboard with widgets | | POST | `/api/dashboards` | Create a dashboard | | PUT | `/api/dashboards/{id}` | Update metadata and/or widgets | | DELETE | `/api/dashboards/{id}` | Delete a dashboard | Update request example: ```json { "name": "Production", "is_default": true, "widgets": [ { "widget_type": "stat-number", "title": "Web CPU", "config_json": { "server_id": "server-id", "metric": "cpu", "unit": "%" }, "grid_x": 0, "grid_y": 0, "grid_w": 2, "grid_h": 1, "sort_order": 0 } ] } ``` # Alerts & Notifications > Configure alert rules with threshold monitoring and multi-channel notifications. URL: https://docs.serverbee.app/en/docs/alerts ServerBee includes a flexible alerting system that evaluates metric thresholds and security events, then sends notifications through multiple channels when conditions are met. ## How Alerts Work [#how-alerts-work] A background task evaluates all enabled rules every 60 seconds: 1. Resolve which servers each rule covers. 2. Check whether the rule's conditions are met for each covered server. 3. Send a notification when a rule triggers (subject to debounce). 4. Clear the alert state when a previously triggered rule recovers. Alert state is persisted in the database and survives server restarts. Event-driven rules (IP change, SSH login, brute-force, port scan, temporary capability grant) are evaluated when an agent reports the event, not on the 60-second cycle. ## Creating Alert Rules [#creating-alert-rules] An alert rule consists of: * **Name** -- A descriptive label. * **Rules** -- One or more metric conditions. All conditions must be true simultaneously (AND logic). * **Trigger mode** -- `always` (repeat with debounce) or `once` (notify only on the first trigger). * **Cover type** -- Which servers the rule applies to. * **Notification group** -- Where notifications are sent. * **Trigger/recover tasks** -- Optional. Remote commands run automatically when the rule triggers or recovers. * **Block source IP** -- Optional. For security-event rules, automatically instructs the agent's firewall to block the offending source IP when the rule triggers. ## Supported Metric Types [#supported-metric-types] ### Resource Thresholds [#resource-thresholds] | Rule Type | Description | `min` Threshold Meaning | | --------------- | ------------------------ | ---------------------------- | | `cpu` | CPU usage percentage | Triggers when usage >= value | | `memory` | Memory used (bytes) | Triggers when usage >= value | | `swap` | Swap used (bytes) | Triggers when usage >= value | | `disk` | Disk used (bytes) | Triggers when usage >= value | | `load1` | 1-minute load average | Triggers when load >= value | | `load5` | 5-minute load average | Triggers when load >= value | | `load15` | 15-minute load average | Triggers when load >= value | | `temperature` | CPU temperature (C) | Triggers when temp >= value | | `gpu` | GPU utilization (%) | Triggers when usage >= value | | `tcp_conn` | TCP connections | Triggers when count >= value | | `udp_conn` | UDP connections | Triggers when count >= value | | `process` | Process count | Triggers when count >= value | | `net_in_speed` | Download speed (bytes/s) | Triggers when speed >= value | | `net_out_speed` | Upload speed (bytes/s) | Triggers when speed >= value | ### Traffic Cycle [#traffic-cycle] Used to monitor cumulative traffic over a time window: | Rule Type | Description | | -------------------- | --------------------------------------------- | | `transfer_in_cycle` | Inbound traffic accumulated per cycle | | `transfer_out_cycle` | Outbound traffic accumulated per cycle | | `transfer_all_cycle` | Combined inbound + outbound traffic per cycle | Cycle options: `hour` / `day` / `week` / `month` / `year` ### Network Quality [#network-quality] | Rule Type | Description | | --------------------- | -------------------------------------------------------------- | | `network_latency` | Triggers when average probe latency (ms) exceeds the threshold | | `network_packet_loss` | Triggers when the packet loss percentage exceeds the threshold | ### Offline, Expiration & Events [#offline-expiration--events] | Rule Type | Description | | --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `offline` | Triggers when the server has been offline for `duration` seconds | | `expiration` | Triggers when `expired_at` is within `duration` days | | `ip_changed` | Event-driven; triggers when the agent reports an IP change event (not part of the 60-second cycle) | | `ssh_login_detected` | Event-driven; successful SSH login. Supports `first_seen_only` filter. See [Security Events](/en/docs/security-events) | | `ssh_brute_force_detected` | Event-driven; SSH brute-force burst. Supports `severity_min` (medium/high/critical) and `exclude_cidrs` | | `port_scan_detected` | Event-driven; port scan from a single source IP. Supports `severity_min` and `exclude_cidrs` | | `capability_grant_detected` | Event-driven; fires when a high-risk capability (`terminal` / `exec` / `file` / `docker`) is **temporarily granted** on a server via the host-local `serverbee-agent grant` CLI. Expiry, revoke, and low-risk grants are audited but do not alert. See [Capabilities → Temporary grants](/en/docs/capabilities#temporary-grants) | The Alerts page offers a one-click **preset card** for `capability_grant_detected` ("Capability Temporarily Granted") with sensible defaults; the rule type is also selectable directly in the rule editor. It carries no `severity_min` / `exclude_cidrs` filters and no auto-block action — it fires on any high-risk temporary grant — but it follows the same cover-type and maintenance-suppression behavior as the other event-driven rules. ## Threshold Configuration [#threshold-configuration] Each rule item supports these fields: ```json { "rule_type": "cpu", "min": 90.0, "max": null, "duration": null, "cycle_interval": null, "cycle_limit": null } ``` * **`min`** -- Lower bound. For most metrics, the alert triggers when the value is at or above this threshold. * **`max`** -- Upper bound. When both `min` and `max` are set, the alert triggers when the value falls within the range. * **`duration`** -- Used by `offline` (seconds before triggering) and `expiration` (days threshold). * **`cycle_interval`** -- Time window for transfer cycle rules: `hour`, `day`, `week`, `month`, `year`. * **`cycle_limit`** -- Traffic limit in bytes for transfer cycle rules. ### Examples [#examples] **CPU over 90%:** ```json { "rule_type": "cpu", "min": 90.0 } ``` **Memory over 8 GB:** ```json { "rule_type": "memory", "min": 8589934592 } ``` **Server offline for more than 2 minutes:** ```json { "rule_type": "offline", "duration": 120 } ``` **Monthly outbound traffic over 1 TB:** ```json { "rule_type": "transfer_out_cycle", "cycle_interval": "month", "cycle_limit": 1099511627776 } ``` **Server expiring within 7 days:** ```json { "rule_type": "expiration", "duration": 7 } ``` **Multiple conditions (AND logic):** a rule with several conditions triggers only when all of them hold. The following fires when CPU usage is at or above 90% *and* memory usage is at or above 8 GB: ```json [ { "rule_type": "cpu", "min": 90.0 }, { "rule_type": "memory", "min": 8589934592 } ] ``` ## Sampling and Trigger Logic [#sampling-and-trigger-logic] For resource threshold alerts (CPU, memory, disk, load, etc.), ServerBee does not trigger on a single spike: 1. The evaluator reads all raw metric records from the **last 10 minutes** (one sample per minute, so up to 10 samples). 2. It counts how many samples exceed the threshold. 3. The alert triggers only if **70% or more** of the samples exceed the threshold (at least 7 of 10). This prevents false positives from brief, transient spikes. ## Cover Types [#cover-types] Each alert rule specifies which servers it applies to: | Cover Type | Behavior | | ---------- | ----------------------------------------------- | | `all` | Applies to every server in the system | | `include` | Applies only to the specified server IDs | | `exclude` | Applies to all servers except the specified IDs | ## Trigger Modes and Debounce [#trigger-modes-and-debounce] ### `always` Mode (Default) [#always-mode-default] * Sends a notification every time the condition is evaluated as true. * **5-minute debounce** prevents notification spam: for the same (rule + server) combination, after a notification is sent, the next one is suppressed for 5 minutes even if the condition persists. ### `once` Mode [#once-mode] * Sends a notification only on the **first trigger**. * No further notifications are sent until the condition recovers and triggers again. ### Renewal Expiration Reminders [#renewal-expiration-reminders] For a server with a stored renewal occurrence, an expiration rule in `once` mode sends at most one reminder for each rule, server and occurrence. A new monthly deadline can therefore send its own reminder even when a 60-day window stays continuously true. Retries, Server restart and disabling automatic tracking do not resend the same occurrence. The threshold uses the stored billing timezone's local expiry date. A zero-day reminder remains eligible throughout that date. Automatic tracking catches up to the current deadline after downtime without sending a backlog for missed occurrences. Previous automatic targets appear as **Superseded** in alert history; advancement sends no recovery notification and does not confirm provider renewal, payment or service availability. An explicitly edited date or a condition that stops matching can still produce the normal recovery transition. Authenticated Web and native iOS views refetch the Server-owned deadline, alert history and cost advisories after catalog changes and reconnects. Clients do not calculate recurring deadlines. ### State Persistence [#state-persistence] Trigger state is persisted to the `alert_states` table in SQLite and loaded into a hot cache on startup. As a result: * A `once` rule that is already triggered will not re-fire after a server restart. * Triggered-but-not-yet-recovered alerts are restored automatically after a restart. ### Recovery [#recovery] When a previously triggered alert recovers (the condition is no longer met): 1. The alert is marked as recovered, and its state is cleared in both the in-memory cache and the database. 2. If recover tasks (`recover_trigger_tasks`) are configured, the corresponding remote commands run. 3. If the condition triggers again later, notifications fire according to the trigger mode. ## Maintenance Suppression [#maintenance-suppression] Alert notifications are suppressed while the affected server is in an active maintenance window. Rule evaluation still runs, but ServerBee skips notification delivery for that server until the window ends. Event-driven rules follow the same cover-type and maintenance suppression rules as polling-based rules. ## Blocking Source IPs [#blocking-source-ips] Security-event rules (`ssh_brute_force_detected`, `port_scan_detected`) can enable **Block source IP**. When the rule triggers, ServerBee tells the affected agent's firewall to block the offending IP, turning detection into automatic mitigation. This requires the agent to have the `firewall_block` capability (`CAP_FIREWALL_BLOCK`), which is on by default but can be disabled on the agent host via `[capabilities]`. See [Security Events](/en/docs/security-events) and [Firewall](/en/docs/firewall). ## Notification Channels [#notification-channels] ServerBee supports five notification channel types. Each channel is a separate entity that can be reused across multiple notification groups. ### Webhook [#webhook] Send HTTP requests to any URL. Supports custom methods, headers, and body templates. ```json { "url": "https://hooks.slack.com/services/xxx", "method": "POST", "headers": { "Content-Type": "application/json" }, "body_template": "{\"text\": \"{{server_name}} {{event}}: {{message}}\"}" } ``` If no `body_template` is provided, the default template is used. If no `Content-Type` header is set, it defaults to `application/json`. ### Telegram [#telegram] Send messages to a Telegram chat via the Bot API. Messages are sent with HTML parse mode enabled. ```json { "bot_token": "123456:ABC-DEF", "chat_id": "-1001234567890" } ``` ### Bark [#bark] Send push notifications to iOS devices via [Bark](https://github.com/Finb/Bark). ```json { "server_url": "https://api.day.app", "device_key": "your-device-key" } ``` ### Email (via Resend) [#email-via-resend] Email notifications are delivered through [Resend](https://resend.com/). Two steps before use: 1. Set `SERVERBEE_RESEND__API_KEY` on the server (see the [Configuration](/en/docs/configuration) page). 2. Add and verify your sender domain at [resend.com/domains](https://resend.com/domains). The `from` address on each channel must belong to a verified domain. Channel config: ```json { "from": "alerts@yourdomain.com", "to": ["ops@example.com", "oncall@example.com"] } ``` `to` is an array — a single channel can deliver to multiple recipients in one API call. The subject follows the format `[ServerBee] {server_name} {event}`; the body is HTML with a plain-text fallback. ### APNs [#apns] Send native Apple Push Notification service pushes to registered mobile devices. ```json { "key_id": "ABC123DEFG", "team_id": "TEAM999888", "private_key": "-----BEGIN PRIVATE KEY-----...", "bundle_id": "com.example.serverbee", "sandbox": false } ``` APNs requires an Apple developer key, team ID, bundle ID, and private key. Set `sandbox: true` only for development builds. ### Template Variables [#template-variables] Notification messages support the following template variables: | Variable | Description | | ----------------- | -------------------------------- | | `{{server_name}}` | Name of the affected server | | `{{server_id}}` | Unique ID of the affected server | | `{{rule_name}}` | Name of the triggered alert rule | | `{{event}}` | Event type (e.g., "triggered") | | `{{message}}` | Human-readable alert message | | `{{time}}` | Timestamp of the event (UTC) | | `{{cpu}}` | Current CPU usage string | | `{{memory}}` | Current memory usage string | The default notification template is: ``` [ServerBee] {{server_name}} {{event}} {{message}} Time: {{time}} ``` ### Testing a Channel [#testing-a-channel] After creating a channel, verify its configuration with the test endpoint: ``` POST /api/notifications/:id/test ``` ## Notification Groups [#notification-groups] Notification channels are organized into **groups**. An alert rule is linked to a notification group, and when the rule triggers, all enabled channels in the group are dispatched. This allows you to: * Send the same alert to multiple channels (e.g., Telegram + Email). * Reuse channel configurations across different alert rules. * Enable or disable individual channels without modifying alert rules. ## Offline Detection [#offline-detection] Offline status is determined by a dedicated background task and works together with `offline` rules: * The task scans agent connection state every 10 seconds. * A server is marked offline when its last report is more than 30 seconds old. * The `offline` rule's `duration` sets how many seconds a server must stay offline before the alert fires. For example, `{ "rule_type": "offline", "duration": 60 }` triggers after 60 seconds offline. ## Example: Complete Alert Setup [#example-complete-alert-setup] Here is a typical setup for monitoring CPU usage across all servers with Telegram notifications: 1. **Create a Telegram notification channel** with your bot token and chat ID. 2. **Create a notification group** that includes the Telegram channel. 3. **Create an alert rule:** * Name: "High CPU Usage" * Rules: `[{"rule_type": "cpu", "min": 90.0}]` * Trigger mode: `always` * Cover type: `all` * Notification group: (the group you created) Now, when any server's CPU stays above 90% for at least 70% of a 10-minute sampling window, you will receive a Telegram message. Subsequent notifications are debounced to once every 5 minutes while the condition persists. # Service Monitors > Monitor SSL certificates, DNS records, HTTP keywords, TCP ports, and WHOIS expiry from the ServerBee server. URL: https://docs.serverbee.app/en/docs/service-monitors Service Monitors are synthetic checks that run from the central ServerBee server. Use them to monitor public-facing services even when those services do not run on a ServerBee agent host. Unlike Ping Monitoring, which asks agents to probe network targets, Service Monitors are evaluated by the server process. Results are stored in SQLite, shown in the dashboard, and can trigger notifications through notification groups. ## Supported Monitor Types [#supported-monitor-types] | Type | Target format | What it checks | | -------------- | ---------------------------------- | ------------------------------------------------------------------------------------------------- | | `ssl` | `example.com` or `example.com:443` | TLS handshake, certificate validity period, issuer/subject, SHA-256 fingerprint | | `dns` | `example.com` | DNS records for `A`, `AAAA`, `CNAME`, `MX`, or `TXT`, optionally compared against expected values | | `http_keyword` | `https://example.com/health` | HTTP status code and optional keyword presence/absence in the response body | | `tcp` | `host:port` | TCP connection success and connection latency | | `whois` | `example.com` or URL | Domain expiration date and registrar, using WHOIS lookup with a system-command fallback | ## Creating a Monitor [#creating-a-monitor] 1. Open **Settings → Service Monitors**. 2. Click **New Monitor**. 3. Choose a monitor type and enter the target. 4. Set the check interval in seconds. 5. Configure type-specific options. 6. Optionally select a notification group. 7. Save the monitor. The background checker wakes every 10 seconds, schedules due monitors based on their `interval`, and runs up to 20 checks concurrently. As an SSRF safeguard, targets that resolve to loopback (`127.0.0.0/8`, `::1`, `localhost`), link-local, the cloud metadata endpoint (`169.254.169.254`), or other non-routable addresses are blocked. SSL, TCP, and HTTP Keyword monitors reject such targets when you save the monitor, and the guard is re-applied on every HTTP redirect hop. Private LAN ranges (`10/8`, `172.16/12`, `192.168/16`, and IPv6 ULA) remain allowed so you can monitor internal hosts. ## Type-Specific Configuration [#type-specific-configuration] ### SSL Certificate [#ssl-certificate] ```json { "port": 443, "warning_days": 14, "critical_days": 7, "timeout": 10 } ``` * `port` defaults to `443` unless the target already includes a port. * The check fails when remaining certificate lifetime is less than or equal to `critical_days`. * A warning is included in the detail payload when remaining lifetime is less than or equal to `warning_days`. ### DNS Record [#dns-record] ```json { "record_type": "A", "expected_values": ["203.0.113.10"], "nameserver": "8.8.8.8" } ``` * `record_type` defaults to `A` and supports `A`, `AAAA`, `CNAME`, `MX`, and `TXT`. * If `expected_values` is omitted, the check passes when resolution returns at least one value. * If `expected_values` is present, the sorted returned values must exactly match the sorted expected values. * `nameserver` is optional. When omitted, the system resolver is used. ### HTTP Keyword [#http-keyword] ```json { "method": "GET", "expected_status": [200], "keyword": "ok", "keyword_exists": true, "headers": { "User-Agent": "ServerBee" }, "body": null, "timeout": 10 } ``` * `method` supports `GET` and `POST`. * `expected_status` defaults to `[200]`. * If `keyword` is set, `keyword_exists: true` requires it to appear; `false` requires it to be absent. * `headers` values must be strings. * `body` is used for `POST` requests. ### TCP Port [#tcp-port] ```json { "timeout": 10 } ``` The target must be `host:port`. The check succeeds if a TCP connection can be established before timeout. ### WHOIS Expiry [#whois-expiry] ```json { "warning_days": 30, "critical_days": 7 } ``` * The target is normalized to a domain name, so `https://example.com/path` becomes `example.com`. * The check fails when the domain expires within `critical_days`. * Some TLDs such as `.app`, `.dev`, and `.page` do not expose a standard WHOIS service for this monitor. Use an SSL monitor for those domains instead. ## Notifications and Retries [#notifications-and-retries] Each monitor can link to a notification group. On every check: 1. A record is written with `success`, `latency`, `detail_json`, `error`, and `time`. 2. The monitor updates `last_status`, `last_checked_at`, and `consecutive_failures`. 3. Failure notifications are sent only after `consecutive_failures > retry_count`. 4. Recovery notifications are sent when a monitor was failing and the next check succeeds. If a monitor is associated with servers and any of those servers is currently in an active maintenance window, notifications are skipped for that check. The check record is still stored. ## History and Retention [#history-and-retention] Open a monitor detail page to view: * Latest status and latency * Recent success/failure history * Latency chart over time * Raw detail payload and error message per check Service monitor records are retained for 30 days by default (`retention.service_monitor_days`). ## API [#api] | Method | Path | Description | | ------ | ------------------------------------ | ----------------------------------------------------- | | GET | `/api/service-monitors` | List monitors; optional `?type=ssl` filter | | GET | `/api/service-monitors/{id}` | Get one monitor with its latest record | | POST | `/api/service-monitors` | Create a monitor | | PUT | `/api/service-monitors/{id}` | Update a monitor | | DELETE | `/api/service-monitors/{id}` | Delete a monitor and its records | | GET | `/api/service-monitors/{id}/records` | Query records with optional `from`, `to`, and `limit` | | POST | `/api/service-monitors/{id}/check` | Trigger an immediate check | Create request example: ```json { "name": "Website SSL", "monitor_type": "ssl", "target": "example.com", "interval": 300, "config_json": { "warning_days": 14, "critical_days": 7 }, "notification_group_id": "notification-group-id", "retry_count": 1, "server_ids_json": ["server-id"], "enabled": true } ``` `server_ids_json` is used to associate a service monitor with servers, mainly for maintenance-window suppression and dashboard context. The monitor itself still runs from the central ServerBee server. # Web Terminal > Access a full shell on your servers directly from the browser. URL: https://docs.serverbee.app/en/docs/terminal ServerBee includes a built-in web terminal that gives administrators direct shell access to any connected server through the browser. No SSH client needed -- just click and type. ## How It Works [#how-it-works] The web terminal uses a three-hop WebSocket relay, with the server acting as a proxy between the browser and the agent: ``` Browser (xterm.js) <--WebSocket--> Server (proxy) <--WebSocket--> Agent (PTY) ``` 1. The browser opens a WebSocket connection to the server at `/api/ws/terminal/{server_id}` 2. The server authenticates the user (session cookie or API key) and verifies the admin role 3. The server sends a `TerminalOpen` command to the agent over its existing WebSocket connection 4. The agent spawns a PTY (pseudo-terminal) process via the `portable-pty` library, running the system's default shell 5. Input from the browser is forwarded through the server to the agent's PTY, and output flows back along the same path The shell process runs entirely on the agent machine. Terminal payloads are relayed over WebSocket. ## Usage [#usage] 1. Sign in to the ServerBee dashboard 2. Open the server list and select the target server 3. On the server detail page, click the **Terminal** button (available only for online servers) 4. Once the connection is established, you have a full interactive shell in the browser The web terminal is restricted to the **admin** role. Non-admin (member) users cannot access the terminal and receive a 403 Forbidden response when attempting to open a session. ## Features [#features] ### Full Shell Experience [#full-shell-experience] * Supports standard shells (bash, zsh, etc., depending on the agent host's default shell) * Tab completion, command history, and keyboard shortcuts * Interactive programs such as `vim`, `top`, and `htop` * Color output and ANSI escape sequences ### Automatic Resizing [#automatic-resizing] The terminal window adapts to the browser window automatically. When you resize the browser, the terminal's rows and columns are synced to the remote PTY so the display always renders correctly. ### Tokyo Night Theme [#tokyo-night-theme] The web terminal uses the Tokyo Night color scheme to match ServerBee's dark theme. ### Multiple Sessions [#multiple-sessions] You can open multiple terminal sessions to the same server simultaneously, making it easy to run different tasks in different tabs. ## Authentication and Access Control [#authentication-and-access-control] The web terminal also requires the **terminal** capability (`CAP_TERMINAL`), which is off by default. It must be enabled on the agent host — add `terminal` to the agent's `[capabilities]` allow list (or pass `--allow-cap terminal`) and restart the agent. Capabilities are agent-owned; the server cannot enable them. If the capability is off, the WebSocket upgrade is rejected with 403 Forbidden. See [Capabilities](/en/docs/capabilities). Authentication uses the same mechanisms as the REST API: * **Session cookie** -- sent automatically by the browser after login * **API key** -- sent via the `X-API-Key` header (useful for programmatic access) The server validates the user's identity and role before upgrading the connection to a WebSocket. ## Session Limits [#session-limits] To prevent resource exhaustion, the following limits are enforced: | Limit | Value | | ----------------------------------------------- | ---------- | | Maximum concurrent terminal sessions per server | 3 | | Idle timeout | 10 minutes | | Maximum message size | 1 MB | | Access | Admin only | ### Idle Timeout [#idle-timeout] If no input is received from the browser for 10 minutes, the session is closed automatically: the browser receives a timeout error and the agent-side PTY process is terminated. Any input, including resize events, resets the idle timer. ## Terminal Protocol [#terminal-protocol] The browser and server exchange JSON control messages. ### Browser to Server [#browser-to-server] **Input** -- Send keystrokes to the shell: ```json { "type": "input", "data": "bHM=" } ``` The `data` field contains base64-encoded input. **Resize** -- Notify the terminal of a window size change: ```json { "type": "resize", "rows": 40, "cols": 120 } ``` ### Server to Browser [#server-to-browser] **Session established:** ```json { "type": "session", "session_id": "uuid-here" } ``` **Terminal started:** ```json { "type": "started" } ``` **Terminal output:** ```json { "type": "output", "data": "base64-encoded-output" } ``` **Error:** ```json { "type": "error", "error": "Session timed out due to inactivity" } ``` ## Session Lifecycle [#session-lifecycle] 1. **Open** -- Browser connects, server assigns a `session_id` and tells the agent to create a PTY 2. **Active** -- Input flows browser-to-agent, output flows agent-to-browser 3. **Close** -- Triggered by any of: * User closes the terminal panel * Browser WebSocket disconnects * Idle timeout (10 minutes) * Agent disconnects * Server sends `TerminalClose` to the agent On close, the server sends a `TerminalClose` message to the agent, which terminates the PTY process, and the session is unregistered from the agent manager. ## Security [#security] ### PTY Run-As User [#pty-run-as-user] The PTY process runs as the system user the agent runs as. The install script configures the agent as a systemd service running as `root` by default, so terminal sessions also run with root privileges. If this is a concern, run the agent under a dedicated, less-privileged user via the systemd `User=` directive: ```ini title="serverbee-agent.service" [Service] User=serverbee Group=serverbee ExecStart=/usr/local/bin/serverbee-agent ``` Running the agent as a non-root user limits the user's permissions in the terminal and may restrict some system-metric collection (such as TCP/UDP connection counts). ICMP ping probes additionally require the `CAP_NET_RAW` capability. ### Audit Logging [#audit-logging] Terminal connect and disconnect events are written to the audit log. Administrators can review all session access under Settings → Audit Logs, including the operator's username, target server, connect/disconnect times, and source IP address. ### Transport Encryption [#transport-encryption] In production, always serve the terminal behind an HTTPS reverse proxy. Unencrypted WebSocket connections can expose terminal input and output to eavesdropping. ## Troubleshooting [#troubleshooting] ### "Agent is offline" [#agent-is-offline] The terminal requires the target server's agent to be connected. If the agent is offline, the WebSocket upgrade is rejected and an error is shown. ### Connection drops frequently [#connection-drops-frequently] Behind a reverse proxy, ensure WebSocket connections are forwarded correctly and that read/write timeouts are set to at least 86400 seconds (24 hours). See the [Server Setup](/en/docs/server) guide for an nginx example. ### Terminal is slow or laggy [#terminal-is-slow-or-laggy] Terminal data is relayed through the central server. If the server is geographically distant from the browser or the agent, you may notice latency -- this is inherent to the relay architecture. For latency-sensitive work, use direct SSH access instead. # File Manager > Browse, read, edit, upload, download, and manage remote files through ServerBee. URL: https://docs.serverbee.app/en/docs/file-manager File Manager provides controlled remote filesystem access through the ServerBee agent. It is meant for operational tasks such as checking logs, editing small configuration files, and transferring files without opening a full terminal. File Manager is available only to Admin users. This includes browsing, stat, reading, downloading, and transfer management as well as operations that modify files. File Manager is a high-risk feature. Enable it only on trusted servers, and restrict `root_paths` to the minimum directories needed. ## Requirements [#requirements] File Manager must be enabled at two layers, **both on the agent host** — capabilities are agent-owned, so the server cannot turn them on: 1. **`file` capability** -- the `file` capability is off by default. Add it to the agent's `[capabilities]` allow list (or pass `--allow-cap file`). See [Capabilities](/en/docs/capabilities). 2. **File subsystem policy** -- set `[file].enabled = true` and configure at least one allowed root path. Both layers must be satisfied before the agent advertises the capability: if `file` is allowed but the subsystem is disabled (or `root_paths` is empty), the agent drops `file` from its reported capabilities at startup, so the server and UI never show a File Manager that cannot serve requests. Example `agent.toml`: ```toml [capabilities] # Turn on the high-risk file capability for this host. allow = ["file"] [file] enabled = true root_paths = ["/home", "/var/log", "/etc/serverbee"] max_file_size = 1073741824 # Defaults shown here for clarity deny_patterns = ["*.key", "*.pem", "id_rsa*", ".env*", "shadow", "passwd"] ``` Equivalent environment variables: ```bash SERVERBEE_CAPABILITIES__ALLOW=["file"] SERVERBEE_FILE__ENABLED=true SERVERBEE_FILE__ROOT_PATHS=/home,/var/log,/etc/serverbee SERVERBEE_FILE__MAX_FILE_SIZE=1073741824 ``` Restart the agent after editing the config for the capability change to take effect. ## Accessing the File Manager [#accessing-the-file-manager] Open a server's action menu and click **Files**, or navigate directly to: ``` /files/{serverId} ``` The button is hidden when `CAP_FILE` is not in the server's effective capabilities. ## Permissions [#permissions] Only Admin users can use File Manager. Every file API endpoint is behind the Admin authorization check because even read operations can expose sensitive files from a managed host. All high-risk file operations are recorded in the audit log, including attempts denied because the capability is disabled. ## Supported Operations [#supported-operations] | Operation | Description | | -------------- | ----------------------------------------------------------------------- | | List directory | Browse files and directories below allowed roots | | Stat | Load metadata for one path | | Read | Read UTF-8 text content for preview/editor use | | Write | Replace file content with provided text | | Upload | Upload a local file to a remote path | | Download | Start a server-mediated download transfer, then fetch it from ServerBee | | Delete | Delete a file or recursively delete a directory | | Mkdir | Create a directory | | Move | Rename or move a file/directory | | Transfers | View and cancel active file transfers | ## Security Model [#security-model] The agent enforces path safety before touching the filesystem: * `root_paths` is an allow-list; an empty list rejects all file operations. * Every path must resolve inside one of the configured roots. * `deny_patterns` blocks sensitive names such as private keys, `.env*`, `shadow`, and `passwd`. * The agent also checks its local capabilities, so a server-side change cannot override an agent-local deny. * The server checks `CAP_FILE` before dispatching any file message to the agent. ## Limits [#limits] | Limit | Default | Where configured | | --------------------------------- | ------- | --------------------------------------------------------------------- | | Upload size | 100 MB | Server `file.max_upload_size` / `SERVERBEE_FILE__MAX_UPLOAD_SIZE` | | Agent read/download max file size | 1 GB | Agent `[file].max_file_size` / `SERVERBEE_FILE__MAX_FILE_SIZE` | | Inline read chunk | 384 KB | Protocol limit to keep WebSocket frames below the configured max size | Uploads and downloads are chunked. A download creates a temporary transfer on the server that can be cancelled while pending or in progress. ## API [#api] All File Manager endpoints require Admin, including list, stat, read, download, and transfer endpoints. | Method | Path | Description | | ------ | ------------------------------------ | --------------------------------------------------- | | POST | `/api/files/{server_id}/list` | List a directory; body `{ "path": "/var/log" }` | | POST | `/api/files/{server_id}/stat` | Stat a path | | POST | `/api/files/{server_id}/read` | Read UTF-8 text content | | GET | `/api/files/download/{transfer_id}` | Download a ready transfer owned by the current user | | GET | `/api/files/transfers` | List transfers owned by the current user | | POST | `/api/files/{server_id}/write` | Replace file content | | POST | `/api/files/{server_id}/delete` | Delete file/directory; supports `recursive` | | POST | `/api/files/{server_id}/mkdir` | Create a directory | | POST | `/api/files/{server_id}/move` | Move or rename a path | | POST | `/api/files/{server_id}/download` | Start a download transfer | | POST | `/api/files/{server_id}/upload` | Upload multipart form with `path` and `file` fields | | DELETE | `/api/files/transfers/{transfer_id}` | Cancel a transfer | Examples: ```bash curl -X POST https://your-server/api/files/server-id/list \ -H "X-API-Key: serverbee_..." \ -H "Content-Type: application/json" \ -d '{"path":"/var/log"}' ``` ```bash curl -X POST https://your-server/api/files/server-id/upload \ -H "X-API-Key: serverbee_..." \ -F 'path=/tmp/example.txt' \ -F 'file=@example.txt' ``` # Ping Monitoring > Monitor endpoint availability and latency with ICMP, TCP, and HTTP probes. URL: https://docs.serverbee.app/en/docs/ping ServerBee includes built-in ping monitoring that tracks the availability and latency of external endpoints from the perspective of your monitored servers. Use it to verify network connectivity, measure latency between regions, and detect outages. ## Probe Types [#probe-types] Three probe types are available. Each measures round-trip latency and reports a success/failure status. | Type | What it does | Target format | Timeout | Privileges | | -------- | ------------------------------------- | ---------------------------------------------- | ------- | ---------------------- | | **ICMP** | Standard ICMP echo request/reply | IP or hostname (e.g., `8.8.8.8`, `google.com`) | 10 s | `CAP_NET_RAW` on Linux | | **TCP** | TCP connection test (handshake) | `host:port` (e.g., `google.com:443`) | 10 s | None | | **HTTP** | HTTP(S) GET request and response time | Full URL (e.g., `https://example.com/health`) | 10 s | None | ## Creating Ping Tasks [#creating-ping-tasks] Create probe tasks from the **Ping** page in the dashboard, or via the API. Each task requires: | Field | Description | | ---------- | ---------------------------------------------------- | | Name | A descriptive label, e.g., "Cloudflare DNS" | | Probe type | `icmp`, `tcp`, or `http` | | Target | The endpoint to probe (format depends on probe type) | | Interval | How often to run the probe, in seconds | | Server IDs | Which agents should run this probe | | Enabled | Whether the task is active | ### Example: ICMP Ping [#example-icmp-ping] Monitor basic connectivity to Cloudflare DNS: | Field | Value | | ---------- | -------------- | | Name | Cloudflare DNS | | Probe Type | icmp | | Target | 1.1.1.1 | | Interval | 60 | ### Example: TCP Port Check [#example-tcp-port-check] Verify that a database port is reachable: | Field | Value | | ---------- | ------------------ | | Name | PostgreSQL Primary | | Probe Type | tcp | | Target | db.internal:5432 | | Interval | 30 | ### Example: HTTP Health Check [#example-http-health-check] Monitor a web service endpoint: | Field | Value | | ---------- | ---------------------------------------------------------------- | | Name | API Health | | Probe Type | http | | Target | [https://api.example.com/health](https://api.example.com/health) | | Interval | 60 | ## Target Format [#target-format] The target format depends on the probe type: | Probe type | Target format | Example | | ---------- | ---------------------- | ---------------------------------------------------- | | ICMP | IP address or hostname | `1.1.1.1`, `google.com` | | TCP | host:port | `example.com:443`, `10.0.0.1:3306` | | HTTP | Full URL | `https://example.com`, `http://10.0.0.1:8080/health` | ## Choosing an Interval [#choosing-an-interval] The interval is in seconds. Tune it to the target's importance and probe type: | Scenario | Suggested interval | | -------------------- | ------------------ | | Critical service | 15--30 s | | Regular website | 60 s | | Infrastructure check | 120--300 s | Very short intervals increase agent load and network overhead, and may trip the target's firewall or rate limits. Set an interval that matches your actual needs. ## How Tasks Are Distributed [#how-tasks-are-distributed] When you create or update a ping task, the server syncs it to the appropriate agents: 1. The server stores the task configuration in the database 2. A `PingTasksSync` message is sent to each assigned agent containing all their active tasks 3. Each agent starts (or updates) its local probe scheduler 4. Probe results are sent back to the server as `PingResult` messages The server syncs probe tasks to agents at these moments: * When an agent first connects or reconnects * When an admin creates, updates, or deletes a probe task If an agent disconnects and reconnects, the server automatically re-syncs all assigned ping tasks. ## UI Feedback [#ui-feedback] In the web dashboard, ping task create/delete/enable/disable actions show localized success and error toasts in the current language. While an enable/disable request is in flight, the toggle button is temporarily disabled to reduce accidental double submissions. ## Assigning Tasks to Servers [#assigning-tasks-to-servers] The `server_ids_json` field controls which agents execute the probe: * **Specific servers** -- Provide an array of server IDs: `["srv-1", "srv-2"]` * **All servers** -- Use an empty array `[]` `["*"]` is not a wildcard. It is treated as a literal server ID and normally assigns the task to no agents. Use `[]` when every server should run the probe, or provide IDs copied from the server list. Running the same probe from multiple agents lets you: * Probe one target from different regions simultaneously and compare network quality * Rule out single-point network issues for a more accurate availability verdict * Localize a fault to the probing node when one agent fails while others succeed ## Viewing Results [#viewing-results] ### Probe Records [#probe-records] Ping results are stored in the database with the following data: | Field | Description | | ----------- | ---------------------------------------- | | `task_id` | The ping task that generated this result | | `server_id` | The agent that ran the probe | | `latency` | Round-trip time in milliseconds | | `success` | Whether the probe succeeded | | `error` | Error message if the probe failed | | `time` | Timestamp of the measurement | ### Latency Charts [#latency-charts] The ping task detail page shows latency trend charts per agent. Charts support filtering by time range to help you analyze how network quality changes over time. When multiple servers probe the same target, results are broken down per agent, and a success-rate percentage is shown. ### API Query [#api-query] Query probe records through the REST API: ``` GET /api/ping-tasks/:id/records?from=2026-03-13T00:00:00Z&to=2026-03-14T00:00:00Z&server_id=xxx ``` Filtering by time range and agent is supported. ## Data Retention [#data-retention] Ping records are retained for **7 days** by default. This is configurable via the `retention.ping_records_days` setting in `server.toml`: ```toml [retention] ping_records_days = 14 ``` The cleanup task runs hourly and removes records older than the configured retention period. ## Integration with Alerts [#integration-with-alerts] Ping monitoring works with alert rules. The `network_latency` and `network_packet_loss` rule types let you alert on probe latency or packet loss, so you're notified when a critical endpoint degrades or becomes unreachable. Ping data is stored alongside regular monitoring data and reuses the same alerting infrastructure. See [Alerts & Notifications](/en/docs/alerts). # Security Events > Detect SSH logins, SSH brute-force attempts, and port scans on each agent, then turn them into alerts. URL: https://docs.serverbee.app/en/docs/security-events Agents detect host-level intrusion signals and stream structured events to the server. Raw logs never leave the host — only parsed event metadata is reported. Events are browsable in the UI and can drive notifications through the standard alert pipeline. ## Event Types [#event-types] | Type | Trigger | Severity | | ----------------- | --------------------------------------------------------------------------------------- | ------------------------------ | | `ssh_login` | Successful SSH login. Marked `first_seen=true` when the `(user, source IP)` pair is new | `info` | | `ssh_brute_force` | Repeated SSH failures from one source IP within a sliding window | `medium` / `high` / `critical` | | `port_scan` | One source IP hitting many distinct destination ports within a sliding window | `medium` | Each event carries structured evidence (failed count, distinct users, scanned ports, …) and a detector source — one of `journal`, `auth_log`, `conntrack`, or `nflog`. ## Requirements [#requirements] * **Linux only.** SSH detection reads systemd journal or `/var/log/auth.log`; port-scan detection uses `conntrack`. * **`CAP_SECURITY_EVENTS`** must be reported by the agent (bit `256`, on by default). Capabilities are agent-owned — to turn it off, add `security_events` to the agent's `[capabilities]` deny list. See [Capabilities](/en/docs/capabilities). * For port-scan detection, install the `conntrack` CLI and set `security.port_scan.enabled = true`: ```bash # Debian / Ubuntu apt install conntrack # RHEL / Fedora dnf install conntrack-tools ``` ## Viewing Events [#viewing-events] | Where | Path | What it shows | | ------------ | -------------------------------- | ---------------------------------------------------------------------- | | Overview | `/security` | KPI cards (24h counts by type), 7-day timeline, filterable table | | Per-server | Server detail → **Security** tab | Same view scoped to one server | | Event detail | Click any row | Full evidence payload, detector source, GeoIP lookup (when configured) | New events appear in real time over WebSocket — no refresh required. ## Detector Tuning [#detector-tuning] Thresholds live on the agent and apply per host. Defaults are conservative to avoid noise on busy bastions. | TOML Key | Env Var | Default | Notes | | -------------------------------------------- | -------------------------------------------------------- | ----------------------------- | --------------------------------------------------------------------------------------- | | `security.enabled` | `SERVERBEE_SECURITY__ENABLED` | `true` | Master switch for all detectors | | `security.ssh.window_seconds` | `SERVERBEE_SECURITY__SSH__WINDOW_SECONDS` | `60` | Sliding window for SSH failure counting | | `security.ssh.failed_threshold` | `SERVERBEE_SECURITY__SSH__FAILED_THRESHOLD` | `10` | Failures in the window that fire one `ssh_brute_force` event. Queue clears after firing | | `security.port_scan.enabled` | `SERVERBEE_SECURITY__PORT_SCAN__ENABLED` | `false` | Off by default. Requires `conntrack` CLI | | `security.port_scan.window_seconds` | `SERVERBEE_SECURITY__PORT_SCAN__WINDOW_SECONDS` | `30` | Sliding window for distinct-port counting | | `security.port_scan.distinct_port_threshold` | `SERVERBEE_SECURITY__PORT_SCAN__DISTINCT_PORT_THRESHOLD` | `20` | Distinct ports from one source IP that fire one `port_scan` event | | `security.data_dir` | `SERVERBEE_SECURITY__DATA_DIR` | `/var/lib/serverbee/security` | Persistent `first_seen` store for `(user, IP)` pairs | Full env var reference: [Configuration → Security (Agent)](/en/docs/configuration). sshd typically writes two failure lines per attempt (`Invalid user …` followed by `Failed password …`). With the default `failed_threshold=10`, expect roughly one `ssh_brute_force` event per **5** real failed attempts from the same IP. ## Brute-Force Severity [#brute-force-severity] Severity escalates with the number of distinct usernames an attacker tries inside the window — a strong signal of credential stuffing: | Distinct usernames | Severity | | ------------------ | ---------- | | 1 | `medium` | | 2 – 4 | `high` | | ≥ 5 | `critical` | Evidence also reports `invalid_user_count` and a `sample_users` array (first 5 distinct names) for quick pattern spotting. ## Alerting [#alerting] Three event-driven rule types live under **Settings → Alerts**: | Rule Type | Fires on | | -------------------------- | --------------------------- | | `ssh_login_detected` | Any `ssh_login` event | | `ssh_brute_force_detected` | Any `ssh_brute_force` event | | `port_scan_detected` | Any `port_scan` event | ### Quick setup [#quick-setup] The Alerts page surfaces three **preset cards** for one-click rule creation. Each preset ships with sensible defaults — pick a notification group and, optionally, the servers it applies to. A fourth event-driven preset, **Capability Temporarily Granted** (`capability_grant_detected`), lives next to these and fires when a high-risk capability is temporarily granted on a host. It is not an intrusion signal, so it is documented separately under [Alerts](/en/docs/alerts) and [Capabilities → Temporary grants](/en/docs/capabilities#temporary-grants). ### Filters [#filters] Every security rule supports: * **`severity_min`** — minimum severity required to fire (e.g. `high` for brute force). * **`exclude_cidrs`** — suppress events from trusted networks, e.g. `["10.0.0.0/8", "192.168.0.0/16"]`. * **`first_seen_only`** *(SSH login only)* — fire only on never-before-seen `(user, IP)` pairs. ### Deduplication [#deduplication] Notifications are deduplicated per `(rule_id, server_id, event_key)`. The `event_key` includes the source IP, so: * Two different attackers on the same server → **two** notifications. * The same attacker re-triggering inside the dedupe window → **one** notification. Security rule types cannot be combined with metric rules in the same alert rule, and each rule allows **one** security item. The validator rejects mixed or duplicated configurations — create one rule per event type. ### Auto-block source IP [#auto-block-source-ip] Brute-force and port-scan alert rules can optionally carry a `block_source_ip` action that auto-creates a `block_list` row from the triggering event's source IP. Only `ssh_brute_force_detected` and `port_scan_detected` rules accept this action — `ssh_login_detected` is intentionally forbidden because a legitimate first-time login would lock the user out. The auto-block row uses `origin = "auto"` and stores the triggering `origin_event_id`. It is deduplicated by canonical target: if a manual or earlier auto-block already covers the triggering server, the action is silently skipped; if a row exists but does **not** cover the triggering server, the conflict is recorded in the audit log as `firewall_auto_block_skipped_conflict` and no new row is created. See [Firewall Blocklist](/en/docs/firewall) for the full feature, guardrails, and audit-log reference. ## Retention [#retention] Security events are kept for **30 days** by default. Tune with `retention.security_event_days` (env: `SERVERBEE_RETENTION__SECURITY_EVENT_DAYS`). The cleanup task checks for expired rows hourly. ## Data Flow [#data-flow] ```text sshd / kernel │ (journal · auth.log · conntrack) ▼ agent detector │ AgentMessage::SecurityEvent (WebSocket) ▼ server ─► security_event table ─► alert evaluator ─► notification group ─► browser broadcast (WebSocket) │ ▼ /security + Security tab ``` # Firewall Blocklist > Block inbound traffic from IPs and CIDRs across one or more agents via nftables. URL: https://docs.serverbee.app/en/docs/firewall ServerBee can centrally manage an inbound-traffic blocklist. The server holds the canonical list, and each opted-in agent applies it via `nftables`. ## Requirements [#requirements] * **Linux only** — agent uses the `nft` CLI. * **`CAP_FIREWALL_BLOCK`** (bit `512`) must be enabled on the agent. It is **on by default**; capabilities are agent-owned, so to turn it off add `firewall_block` to the agent's `[capabilities]` deny list. See [Capabilities](/en/docs/capabilities). * Agent process needs root or `CAP_NET_ADMIN`. ## Manual blocking [#manual-blocking] Settings → Firewall → **Add block**. Enter an IP (`1.2.3.4`) or a CIDR (`10.0.0.0/8`). Choose the scope (`All servers`, `Selected`, `All except`). Optionally add a comment. The server rejects targets that overlap a protected range: * Loopback (`127.0.0.0/8`, `::1`) * RFC 1918 (`10/8`, `172.16/12`, `192.168/16`, `fc00::/7`, `fe80::/10`) * Multicast / unspecified * Any CIDR in `firewall.allow_list` (`server.toml`) * `server.trusted_proxies` * Any agent's reported external IP A `409` response with the matched reason is returned in those cases. **Protect your own public IP.** The "agent external IP" guardrail relies on the IPv4 / IPv6 address each agent reports about itself. On hosts where the agent's primary interface is a private bridge (Docker, NAT VPS, multi-homed), the reported address may **not** be the real public IP — and the guardrail will not catch an attempt to block it. To be safe, **add your VPS's public IP (or CIDR) to `firewall.allow_list` in `server.toml`**. Anything that overlaps this list is refused at create time, no matter who requests it. Example: ```toml [firewall] allow_list = ["198.51.100.42", "203.0.113.0/29"] ``` This is also the way to protect your bastion / admin IPs from accidental auto-block actions. ## Auto-block from alerts [#auto-block-from-alerts] Brute-force and port-scan alert rules can append a `block_source_ip` action that auto-inserts the event's source IP. See [Security Events → Auto-block source IP](/en/docs/security-events). Auto-block is deduplicated by canonical target. If a row already exists and covers the triggering server, the auto-block is silently skipped. If it exists but does not cover the triggering server, the conflict is audited (`firewall_auto_block_skipped_conflict`) and no row is created — the operator can broaden the existing row manually. ## Agent execution [#agent-execution] Each opted-in agent maintains an `inet serverbee` nftables table: ```text table inet serverbee { set block_v4 { type ipv4_addr; flags interval; } set block_v6 { type ipv6_addr; flags interval; } chain input { type filter hook input priority -10; ip saddr @block_v4 drop ip6 saddr @block_v6 drop } } ``` The server pushes incremental adds/removes over WebSocket. On agent reconnect or capability transition, it sends a `Reset` followed by a full `Sync`. The agent acks the result of each entry; failed entries stay eligible for retry on the next sync. ## Removing the cleanup [#removing-the-cleanup] Capabilities are Agent-owned, so there is no Server-side switch. To stop using the feature: 1. On the Agent host, add `firewall_block` to `[capabilities].deny` in `/opt/serverbee/etc/agent.toml` (preserve any existing deny entries). 2. Run `sudo serverbee restart agent`. The capability transition resets the synchronized blocklist and removes ServerBee's table. 3. Verify with `sudo nft list table inet serverbee`. If the Agent could not complete the reset (for example it was offline or lacked nftables privileges), remove only ServerBee's table manually: ```bash sudo nft delete table inet serverbee ``` ## Audit log [#audit-log] Every action is recorded in the audit log and shown on the Firewall page's Activity tab: `firewall_block_created`, `firewall_block_deleted`, `firewall_block_applied_agent`, `firewall_block_removed_agent`, `firewall_block_rejected_server`, `firewall_block_rejected_agent`, `firewall_auto_block_skipped_conflict`, `firewall_reset_acked`. ## Limitations [#limitations] * nftables only — no iptables fallback * IPv4 and IPv6, no domain names * Permanent blocks — no scheduled expiry * `input` chain only — does not filter `forward` / `output` # IP Quality > Check each agent's egress IP against streaming and AI services, and score it for proxy, VPN, and fraud risk. URL: https://docs.serverbee.app/en/docs/ip-quality IP Quality lets each agent assess its VPS egress IP and report the results back to the server. It does two things: 1. **Service unlock detection** — the agent issues HTTP requests from its egress IP to determine the unlock status of popular streaming, AI, and social services. 2. **IP metadata and risk signals** — the server derives country and region from its local GeoIP database, then uses the configured third-party provider for supported network-type flags and, when available, a fraud risk score. Results appear on a dedicated **IP Quality** sidebar route (global overview), a per-server detail tab, and — when enabled per status page — on the public status page. ## Requirements [#requirements] * **`CAP_IP_QUALITY`** (bit `1024`) must be enabled on the agent. It is **on by default** (part of `CAP_DEFAULT`). * A usable GeoIP MMDB is required for local baseline metadata. If no database is loaded, those fields remain unavailable; third-party enrichment can still provide supported risk and network-type fields. See [Configuration](/en/docs/configuration) for `geoip.mmdb_path`. ## Capability (agent-owned) [#capability-agent-owned] `CAP_IP_QUALITY` is owned by the agent host, like every capability — the server cannot turn it on or off. It is enabled by default, so no action is needed to use IP Quality. To **disable** it on a specific host, add `ip_quality` to the agent's `[capabilities]` deny list (or pass `--deny-cap ip_quality`) and restart the agent: ```toml [capabilities] deny = ["ip_quality"] ``` ```bash # Or via the command line ./serverbee-agent --deny-cap ip_quality ``` If the agent does not report `ip_quality`, the agent runs no checks and the UI shows the standard **disabled** placeholder. See [Capabilities](/en/docs/capabilities). ## Built-In Services [#built-in-services] Nine services are seeded at startup. Each ships with a hardcoded detector that issues an HTTP request from the agent's egress IP and interprets the response to determine unlock status. | Service | Category | Notes | | ------------------ | --------- | -------------------------------------------------- | | Netflix | Streaming | Detects full unlock vs. originals-only vs. blocked | | Disney+ | Streaming | | | YouTube Premium | Streaming | | | Amazon Prime Video | Streaming | | | HBO Max | Streaming | | | Spotify | Social | | | ChatGPT | AI | | | Gemini | AI | | | TikTok | Social | | Each service can be individually enabled or disabled in **Settings → IP Quality → Service Catalog**. Built-in services cannot be deleted; their detection logic is immutable. ## Custom Services [#custom-services] In addition to the built-in set, administrators can define custom services in **Settings → IP Quality → Service Catalog → Add service**. Each custom service consists of: * **URL** — the endpoint to probe. Must use `https://` or `http://` on port 80 or 443. Private / loopback addresses are rejected at creation time and re-validated by the agent on each request and redirect hop (SSRF guard). * **Method** — HTTP method (`GET`, `HEAD`, etc.) and optional request headers. * **Match rules** — an ordered list of rules evaluated against the response. The first matching rule wins. Each rule matches on one of: * HTTP status code (exact or range) * Response body regex * Redirect target URL pattern The possible results are `unlocked`, `restricted`, `blocked`, `failed`, or `unsupported`. ## Check Cadence [#check-cadence] The agent runs checks: * **On schedule** — every 12 hours by default, configurable in **Settings → IP Quality → Settings → Check interval**. * **When the egress IP changes** — the agent automatically reruns on detected IP changes. * **On demand** — click **Check now** on any server's IP Quality tab. Check schedules are agent-side. The server only pushes the service catalog and forwards manual triggers. ## IP Metadata and Risk Scoring [#ip-metadata-and-risk-scoring] After receiving unlock results from an agent, the server: 1. **Derives available baseline metadata** from a loaded local GeoIP MMDB, without an external request. The downloadable DB-IP Lite Country database provides country and region. If the MMDB is missing, unreadable, or does not cover the address, local baseline fields remain empty. 2. **Runs the configured third-party provider unless `risk_provider` is `none`.** Anonymous ipapi.is supplies proxy, VPN, datacenter, Tor, and abuse flags, which ServerBee also uses to derive IP type, but its minimal response does not contain the nested score fields. A numeric 0–100 score and risk level (`low` / `medium` / `high`) require an ipapi.is API key and its full response. Results are cached by IP for 24 hours, so repeated checks of the same IP do not generate repeated API calls. If no provider returns a numeric score, `risk_score` and `risk_level` are `null` / `unknown`. Local metadata appears only when a usable MMDB is loaded. ### Configuring a Risk Provider [#configuring-a-risk-provider] Set `ip_quality.risk_provider` in `server.toml` (or `SERVERBEE_IP_QUALITY__RISK_PROVIDER`) to one of: | Provider | Value | Notes | | --------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | ipapi.is (default) | `ipapi_is` | Anonymous: 100 requests per client IP per UTC day with flags but no numeric score. Free account/API key: 1,000 requests per day with the full response required for the 0–100 score. | | ip-api.com (fallback) | `ip-api` | **Free tier, non-commercial use only. HTTP-only endpoint.** No API key. Proxy/hosting flags and a derived IP type, but no numeric risk score. | | Disabled | `none` | Local GeoIP metadata only when a usable MMDB is loaded; no risk score. | **Default behavior** (no configuration needed): the Server calls ipapi.is anonymously. Anonymous access is limited to 100 requests per client IP per UTC day and returns flags that ServerBee uses for risk signals and a derived IP type, but no numeric risk score. Only a failed primary request, including an HTTP 429 after quota exhaustion, triggers the `ip-api` fallback; a successful response without a score does not. The fallback can supply proxy/hosting flags and a derived IP type, but not a numeric risk score. See the official [ipapi.is developer limits](https://ipapi.is/developers.html) and [pricing](https://ipapi.is/pricing.html). Create a free ipapi.is account and configure its API key for 1,000 requests per day and the full response required for the 0–100 score: ```toml [ip_quality] risk_provider = "ipapi_is" risk_provider_fallback = "ip-api" # default; set to "none" to disable [ip_quality.ipapi_is] api_key = "your_api_key" # endpoint = "https://api.ipapi.is" # optional; override for self-hosted instances ``` To disable risk scoring entirely: ```toml [ip_quality] risk_provider = "none" ``` `ip-api` (ip-api.com) free tier allows non-commercial use only. Its API endpoint is HTTP (not HTTPS). Review its [terms of service](https://ip-api.com/docs/legal) before using it in production. **Migrating from an older config?** Legacy provider names (`scamalytics`, `ipqs`, `proxycheck`, `abuseipdb`) are no longer supported. If your `server.toml` or environment variables reference them, the server will log a startup warning and fall back to no-op scoring. Update `risk_provider` to `ipapi_is` (or `none` to opt out). Full configuration reference: [Configuration → IP Quality](/en/docs/configuration). ## Viewing Results [#viewing-results] | Where | What it shows | | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | | **IP Quality** sidebar route (`/ip-quality`) | All-servers × services unlock matrix; IP quality card per server (ASN, IP type, risk score and level badge, proxy/VPN markers) | | Server detail → **IP Quality** tab | Single server's IP quality card, its unlock matrix, status-change history, and a **Check now** button | | Public status page | Unlock matrix section, shown only when `show_ip_quality` is enabled for that page | Unlock status updates appear in real time over WebSocket — no page refresh required. ## Unlock Status Values [#unlock-status-values] | Status | Meaning | | ------------- | ----------------------------------------------- | | `unlocked` | Service is fully accessible from this egress IP | | `restricted` | Partial access (e.g. Netflix originals-only) | | `blocked` | Service is not accessible from this egress IP | | `failed` | Network error or timeout during the check | | `unsupported` | Service check not applicable for this IP type | ## Public Status Page Exposure [#public-status-page-exposure] IP Quality is **off by default** on all public status pages. To enable it for a specific page: 1. Go to **Settings → Status Pages**, edit the page. 2. Toggle on **Show IP Quality**. For unauthenticated visitors, the egress IP is masked as `*.*.*.*`. Authenticated viewers see the full IP. ## Retention [#retention] Status-change history events (the `unlock_event` log) are retained for **90 days** by default. Tune with `retention.ip_quality_event_days` (env: `SERVERBEE_RETENTION__IP_QUALITY_EVENT_DAYS`). The hourly cleanup task purges expired rows automatically. The latest unlock result and IP quality snapshot per server are kept indefinitely (they are replaced on each run, not accumulated). ## Data Flow [#data-flow] ```text Agent │ (CAP_IP_QUALITY effective) ├─ runs unlock checks every interval_hours / on IP change / on RunNow │ AgentMessage::UnlockResults (WebSocket) ▼ Server ├─ upserts unlock_result + appends unlock_event on status change ├─ broadcasts BrowserMessage::IpQualityUpdate (unlock results, ip_quality: null) ├─ background task: ip_risk scoring (GeoIP + optional third-party provider, cache-first) ├─ upserts ip_quality_snapshot └─ broadcasts BrowserMessage::IpQualityUpdate (ip_quality: full data) │ ▼ /ip-quality + IP Quality tab + public status page ``` # Capabilities > How each agent decides which features it exposes, configured entirely on the agent host. URL: https://docs.serverbee.app/en/docs/capabilities ServerBee enforces the principle of least privilege through per-agent capabilities. **Capabilities are owned by the agent host**: each agent computes its own capability set from its config file (and optional CLI flags) and reports it to the server. The server can read and display these capabilities, but it **cannot** change them — there is no server-side toggle. This is a deliberate trust-boundary decision. The machine that runs the agent is the only place that decides what that agent will do. A compromised or misconfigured server cannot silently turn on a remote shell, file access, or command execution on your hosts. ## Capability List [#capability-list] ServerBee defines 11 capability bits across two risk levels. The valid mask is `2047` (bits 0..=10). ### High Risk (Disabled by Default) [#high-risk-disabled-by-default] | Capability | Key | Bit Value | Description | | --------------------- | ---------- | ------------------ | ----------------------------------------------------------------------- | | **Web Terminal** | `terminal` | `CAP_TERMINAL` (1) | Allow opening a remote terminal via browser | | **Remote Exec** | `exec` | `CAP_EXEC` (2) | Allow remote command execution | | **File Manager** | `file` | `CAP_FILE` (64) | Allow remote file browsing, editing, upload/download | | **Docker Management** | `docker` | `CAP_DOCKER` (128) | Allow Docker container monitoring, log streaming, and container actions | These capabilities allow executing arbitrary code or accessing the filesystem on the target server, so they are disabled by default. Enable them only on trusted hosts by editing that host's agent config. File Manager requires additional agent-side configuration (`root_paths`, `deny_patterns`) for path sandbox security. See the [Agent Setup](/en/docs/agent) and [Configuration](/en/docs/configuration) pages for details. ### Low Risk (Enabled by Default) [#low-risk-enabled-by-default] | Capability | Key | Bit Value | Description | | ---------------------- | ----------------- | --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Auto Upgrade** | `upgrade` | `CAP_UPGRADE` (4) | Allow remote binary upgrades | | **ICMP Ping** | `ping_icmp` | `CAP_PING_ICMP` (8) | Allow ICMP probe tasks | | **TCP Probe** | `ping_tcp` | `CAP_PING_TCP` (16) | Allow TCP port probe tasks | | **HTTP Probe** | `ping_http` | `CAP_PING_HTTP` (32) | Allow HTTP probe tasks | | **Security Events** | `security_events` | `CAP_SECURITY_EVENTS` (256) | Allow agent to report SSH login / brute-force / port-scan events (see [Security Events](/en/docs/security-events)) | | **Firewall Blocklist** | `firewall_block` | `CAP_FIREWALL_BLOCK` (512) | Allow agent to apply the server-pushed nftables blocklist. Requires root or `CAP_NET_ADMIN` plus the `nft` CLI on the host. See [Firewall Blocklist](/en/docs/firewall) | | **IP Quality** | `ip_quality` | `CAP_IP_QUALITY` (1024) | Allow the Agent to run service-unlock probes and report results; optional metadata and risk enrichment runs on the Server | An agent with no `[capabilities]` overrides defaults to `1852` (auto upgrade + the three ping probes + security events + firewall blocklist + IP quality), which keeps the high-risk terminal, exec, file, and Docker capabilities off. ## Configuring Capabilities (on the agent host) [#configuring-capabilities-on-the-agent-host] An agent computes its capability bitmask as: 1. Start from the built-in default set (`CAP_DEFAULT = 1852`). 2. Apply the config file `[capabilities]` block — `allow` adds bits, `deny` removes bits. Within this layer, `deny` wins over `allow`. 3. Apply CLI `--allow-cap` / `--deny-cap` flags on top, for ad-hoc overrides. The CLI layer wins over the config layer. 4. Reconcile against subsystem availability: `file` is dropped unless `[file].enabled = true` with at least one `root_path`, and `firewall_block` is dropped when the startup `nft` probe fails (binary, kernel, or privileges missing). A reported capability always means the agent can actually serve it. Unknown capability keys are a hard error at startup, so a typo fails fast rather than silently dropping a capability. ### Config file [#config-file] For installer-managed deployments, edit `/opt/serverbee/etc/agent.toml` on the agent host. Manual deployments may use `/etc/serverbee/agent.toml` or `agent.toml` in the working directory: ```toml [capabilities] # Turn on high-risk capabilities this host should expose. allow = ["terminal", "file"] # Strip default capabilities this host should not expose. deny = ["ip_quality"] ``` Restart the agent for changes to take effect: ```bash sudo serverbee restart agent ``` ### Environment variables [#environment-variables] The same block can be supplied via environment variables (Figment, `SERVERBEE_` prefix, `__` nesting): ```bash SERVERBEE_CAPABILITIES__ALLOW=["terminal","file"] SERVERBEE_CAPABILITIES__DENY=["ip_quality"] ``` ### CLI flags [#cli-flags] For one-off overrides, pass repeated flags when launching the agent: ```bash serverbee-agent --allow-cap terminal --allow-cap file --deny-cap ip_quality ``` ### At install time [#at-install-time] The install script accepts `--caps` to seed the agent's capability config during enrollment: ```bash curl -fsSL https://raw.githubusercontent.com/ZingerLittleBee/ServerBee/main/deploy/install.sh | sudo sh -s -- agent \ --server-url https://monitor.example.com \ --enrollment-code YOUR_ONE_TIME_CODE \ --caps terminal,file ``` Running the installer interactively also prompts for capabilities, with the defaults pre-checked. ## Temporary grants [#temporary-grants] Editing the config and restarting the agent is the right way to enable a capability permanently. For short-lived, ad-hoc needs — "give me a terminal on this box for the next 30 minutes" — the agent ships a host-local CLI that temporarily turns on an off-by-default capability, then automatically turns it back off when the window ends. Temporary grants are a **host-local** mechanism, fully consistent with the agent-owned trust model: only someone with a shell on the agent host can issue one. The server and the web/iOS UI remain read-only and **cannot** grant a capability. The server merely mirrors what the agent reports, gates control-plane requests on it, audits the change, and can fire an alert. ### CLI [#cli] Run these subcommands on the **agent host** (typically with `sudo`, since they write under `state_dir` and the daemon usually runs as the agent user/root). They share the daemon's config (`/opt/serverbee/etc/agent.toml` for installer-managed deployments), so the grants file location and the maximum duration come from the same `[capabilities]` block. ```bash # Temporarily enable an off-by-default capability for a bounded duration. serverbee-agent grant terminal --for 30m --reason "debugging a stuck deploy" serverbee-agent grant file --for 2h serverbee-agent grant docker --for 1d # Revoke an active grant early. serverbee-agent revoke terminal # List the currently active grants (cap, remaining seconds, who granted it, reason). serverbee-agent grants ``` * The duration passed to `--for` is `` where the unit is one of `s` (seconds), `m` (minutes), `h` (hours), or `d` (days) — e.g. `90s`, `30m`, `2h`, `1d`. `--reason` is optional free-form text recorded with the grant. * `grant` only **turns on** a capability that is currently **off**. Granting a capability that is already enabled in `agent.toml` is rejected (`'terminal' is already enabled in agent.toml; nothing to grant`). * A duration longer than `temporary_max_duration` (default `24h`, see [Configuration](/en/docs/configuration)) is refused. * The CLI is one-shot: it writes the grants file and exits. The running daemon picks the change up within a few seconds — there is no need to restart the agent. ### Restart and expiry semantics [#restart-and-expiry-semantics] Grants are persisted to `/capability_grants.json` (default `/var/lib/serverbee/capability_grants.json`, written `0600`) with an **absolute** `expires_at` (a Unix epoch timestamp), not a relative countdown. This makes the behavior robust: * **Survives restarts** — if the agent restarts mid-window, the grant is reloaded and remains active for the rest of its original window. * **Expires at the original deadline** — restarting does not extend a grant. A grant for `30m` expires 30 minutes after it was issued regardless of how many times the agent restarts in between. * **Fails safe to OFF** — if the grants file is missing, empty, corrupt, or written by an unknown schema version, the agent treats it as "no grants" and the capability stays off. A temporary grant can never *strengthen* the permanent set; it can only flip an otherwise-off bit on for its window. * **Expiry bounds running work, not just new requests** — when a `terminal` grant expires or is revoked, live terminal sessions opened under it are closed immediately (the browser sees the session end), and a `security_events` grant starts the agent's security pipeline the moment it becomes effective and stops it when the window closes — including grants issued while the agent was already running. ### What the server sees [#what-the-server-sees] When a grant becomes active (or expires/is revoked), the agent re-reports its effective capability set, so the server opens or closes the gate **live**: * The change is mirrored into `servers.capabilities` and broadcast to browsers, so the UI updates in real time. * The transition is written to the [audit log](/en/docs/admin) as `capability_temporarily_granted`, `capability_grant_expired`, or `capability_grant_revoked`. * A temporary grant of a **high-risk** capability (`terminal`, `exec`, `file`, or `docker`) also evaluates the event-driven `capability_grant_detected` [alert rule](/en/docs/alerts). Expiry, revoke, and low-risk grants are audited but do not fire an alert. * Everything the agent executes for the server is re-synced against the new capability set: ping tasks are re-filtered, network probe targets and IP-quality services are re-pushed, and the firewall blocklist is reset (and re-sent only while `firewall_block` is still effective). Revoking a ping capability stops those probes immediately, and revoking `firewall_block` removes ServerBee's nftables rules from the host without waiting for a reconnect. A grant issued while the agent is **disconnected** from the server is applied locally and shows up on reconnect, but it does **not** produce a `granted` audit entry or alert. The server only sees the post-reconnect capability state, not the moment of transition, so it cannot distinguish a freshly-granted capability from one that was already active. Plan around this if you rely on the alert as a tripwire. ### In the web UI [#in-the-web-ui] While a temporary grant is active, the affected capability is shown with an amber **Temporary** badge and a live countdown to expiry, in both the **Server Detail → Capabilities** dialog and the **Settings → Capabilities** fleet matrix. The badge disappears automatically when the grant expires or is revoked. ## Enforcement model [#enforcement-model] The agent host is the single source of truth, but enforcement happens at two boundaries for defense in depth. ### Server-side gating (based on reported capabilities) [#server-side-gating-based-on-reported-capabilities] The server records what each agent reports and rejects control-plane requests for capabilities the agent has not enabled: * **Terminal**: the WebSocket upgrade is rejected with 403 * **Exec**: `POST /api/tasks` and scheduled task runs filter out servers whose agent lacks `exec` and write synthetic results (`exit_code = -2`) * **Auto Upgrade**: `POST /api/servers/{id}/upgrade` returns 403 when `upgrade` is not reported * **Ping and Traceroute**: probe tasks are filtered by capability; traceroute requires `ping_icmp` * **File Manager**: file endpoints reject requests before dispatch when `file` is not reported * **Docker**: Docker read/action endpoints and the Docker log WebSocket routes require `docker` and agent runtime Docker support Because capabilities are agent-owned, the denial reason is always `agent_capability_disabled`. ### Agent-side enforcement [#agent-side-enforcement] Even if a server-side request slips through, the agent re-checks capabilities locally: * It returns a `CapabilityDenied` message for unauthorized commands * On receiving `CapabilityDenied`, the server writes a synthetic result (`exit_code = -1`) * Denial events are recorded in the audit log ### Reporting and display [#reporting-and-display] When an agent connects (or reconnects), it sends `SystemInfo` carrying `agent_local_capabilities`. The server: 1. Persists that value into the `servers.capabilities` column as a display mirror (so the dashboard can show capabilities even while the agent is offline). 2. Broadcasts `CapabilitiesChanged` to all connected browsers so the UI reflects the current set in real time. If ping-related bits change between reconnects, the server automatically re-syncs ping tasks for the agent. ## Frontend behavior [#frontend-behavior] The web and iOS clients display capabilities **read-only**: * **Server Detail → Capabilities**: a read-only summary of which capabilities the agent has enabled, with a note that they are configured in the agent config file * **Settings → Capabilities**: a read-only fleet matrix showing the enabled/disabled state per server * **Tasks page**: servers whose agent lacks `exec` are greyed out * **Terminal button**: hidden for servers whose agent lacks `terminal` * **Files button**: hidden for servers whose agent lacks `file` * **Docker link**: hidden for servers whose agent lacks `docker` ## Runtime capability fields [#runtime-capability-fields] The server response exposes three capability fields, which are all equal under the agent-owned model: * `capabilities`: the persisted mirror of the last value the agent reported * `agent_local_capabilities`: the live value reported by the connected agent * `effective_capabilities`: the value actually enforced (identical to the agent-local value) The fields are kept distinct in the API for forward compatibility and so an offline server can still display its last-known capabilities from the mirror. # Status Page > Publish a public server health page with live metrics, incidents, maintenance windows, and uptime history. URL: https://docs.serverbee.app/en/docs/status-page ServerBee serves a single public status page at `https://your-server/status`. It requires no authentication, so you can share it with users or stakeholders to communicate service health. The page shows the servers and sections an administrator selects, backed by the public `GET /api/status/config` and `GET /api/status` endpoints. ## What the Page Shows [#what-the-page-shows] * Online/total server count. * Each selected server's online/offline status and group label. * Live metrics for online servers: CPU, memory, swap, disk, disk I/O, network speed/transfer, load, connections, and uptime. * A 90-day uptime timeline per server. * Public server remarks where configured. * Optional sections: server detail, network quality, IP quality, incidents, and maintenance windows. Sensitive identifiers such as IP addresses, hostnames, and interface details are stripped at the API boundary and never appear on the public page. ## Configuring the Page [#configuring-the-page] Configure the page in **Settings → Status Page**. The status page is a singleton: there is one page, edited in place rather than created per slug. | Setting | API field | Description | | ----------------------- | ------------------------- | ------------------------------------------------- | | Enabled | `enabled` | When disabled, the public page returns no data | | Title | `title` | Public page title | | Description | `description` | Optional introductory text | | Servers | `server_ids` | Servers shown on the page | | Default layout | `default_layout` | `list` or `grid` | | Show server detail | `show_server_detail` | Allow drilling into a server's detail view | | Show network | `show_network` | Show the network quality section | | Show IP quality | `show_ip_quality` | Show the IP quality section | | Show incidents | `show_incidents` | Show the incidents section | | Show maintenance | `show_maintenance` | Show the maintenance section | | Yellow uptime threshold | `uptime_yellow_threshold` | Days below this percentage show as degraded | | Red uptime threshold | `uptime_red_threshold` | Days below this percentage show as a major outage | ### Admin API [#admin-api] | Method | Path | Description | | ------ | ------------------ | ------------------------------------ | | GET | `/api/status-page` | Read the status page configuration | | PUT | `/api/status-page` | Update the status page configuration | Update example: ```json { "title": "Production Status", "description": "Public health for production services", "server_ids": ["server-id-1", "server-id-2"], "show_ip_quality": false, "default_layout": "grid", "show_server_detail": true, "show_network": true, "show_incidents": true, "show_maintenance": true, "enabled": true, "uptime_yellow_threshold": 99.9, "uptime_red_threshold": 95 } ``` ## Public API [#public-api] These endpoints are unauthenticated and power the public page: | Method | Path | Description | | ------ | --------------------------------------- | ----------------------------------------------------- | | GET | `/api/status/config` | Page metadata and display options | | GET | `/api/status` | Selected server statuses with live metrics and uptime | | GET | `/api/status/servers/{id}` | Detail view for one server | | GET | `/api/status/servers/{id}/metrics` | Time-series metrics for one server | | GET | `/api/status/servers/{id}/uptime-daily` | 90-day daily uptime for one server | | GET | `/api/status/network` | Network quality overview | | GET | `/api/status/network/{id}` | Network quality detail for one server | | GET | `/api/status/ip-quality` | IP quality overview | | GET | `/api/status/incidents` | Active and recent incidents | | GET | `/api/status/maintenances` | Active and upcoming maintenance windows | Each server entry includes `id`, `name`, `group_name`, region/country, `os`, `online`, `in_maintenance`, `uptime_percent`, and `uptime_daily`. ## Uptime Timeline [#uptime-timeline] Each server shows a 90-day uptime timeline where every bar represents one day: * **Green** -- healthy uptime. * **Yellow** -- uptime below the yellow threshold. * **Red** -- uptime below the red threshold. * **Gray** -- no data. Uptime data comes from the `uptime_daily` table, populated by the server's background aggregation tasks. Missing dates are gap-filled so the timeline stays continuous. ## Incidents [#incidents] Incidents are public announcements for outages or degraded service. They can optionally be linked to specific servers. ### Fields [#fields] | Field | Description | | ----------------- | ---------------------------------------------------------- | | `title` | Incident title | | `status` | `investigating`, `identified`, `monitoring`, or `resolved` | | `severity` | `minor`, `major`, or `critical` | | `server_ids_json` | Optional affected servers | | `is_public` | Whether the incident is shown on the public status page | An incident can carry multiple updates, each with its own `status` and `message`. Adding an update records the message and moves the incident to that update's status. Setting the status to `resolved` also sets `resolved_at`. ### API [#api] | Method | Path | Description | | ------ | ----------------------------- | ----------------------------------------- | | GET | `/api/incidents` | List incidents; supports status filtering | | POST | `/api/incidents` | Create an incident | | PUT | `/api/incidents/{id}` | Update an incident | | DELETE | `/api/incidents/{id}` | Delete an incident | | POST | `/api/incidents/{id}/updates` | Add an incident update | ## Maintenance Windows [#maintenance-windows] Maintenance windows announce planned work and also suppress alert notifications for the associated servers while active. ### Fields [#fields-1] | Field | Description | | ----------------- | ----------------------------------------------------- | | `title` | Maintenance title | | `description` | Optional details | | `start_at` | UTC start time | | `end_at` | UTC end time; must be after `start_at` | | `server_ids_json` | Optional affected servers | | `is_public` | Whether the window is shown on the public status page | | `active` | Whether the window is enabled | ### API [#api-1] | Method | Path | Description | | ------ | ------------------------ | --------------------------- | | GET | `/api/maintenances` | List maintenance windows | | POST | `/api/maintenances` | Create a maintenance window | | PUT | `/api/maintenances/{id}` | Update a maintenance window | | DELETE | `/api/maintenances/{id}` | Delete a maintenance window | # Custom Widgets > Author custom dashboard widgets with React + Zod, and install them into ServerBee as a single file or a zip collection. URL: https://docs.serverbee.app/en/docs/custom-widgets ServerBee dashboards natively support custom widget modules. Each widget is a standalone ES module that an admin installs once and can then drop onto any dashboard, exactly like a built-in widget. This guide covers the two supported install methods: * **Method B** — a single `.js` file (one widget per file) * **Method C** — a `.zip` collection bundle (multiple widgets in one upload, sharing the same build output) ## Concepts [#concepts] A widget module has three parts: 1. **Static JSDoc manifest** — a `@serverbee-widget {...}` JSON block at the top of the file declaring the widget's `id`, `version`, `name`, `category`, default sizing, required SDK version, and so on. The manifest is **statically parseable**: the server can index it without ever executing the module, which means offline scanning and permission checks work the same way as runtime loading. 2. **Default export** — the object returned by `defineWidget({ configSchema, component, actions? })`. `configSchema` is a Zod schema describing the configurable fields; `component` is a React component that receives `{ config, size, isEditing, actions }` props at runtime. 3. **Runtime dependencies** — imported as `react`, `react/jsx-runtime`, and `@serverbee/widget-sdk`. These are **provided by the host** and must be marked `external` at build time; the browser reuses the main app's instances. ### Trust model [#trust-model] Custom widgets are **admin-only to install** and run **same-origin** in the browser, sharing the session of the logged-in user. That means: * An approved widget can issue any API request that the current user is authorized for. * All widgets share the same React context, so do not introduce global side effects that could break sibling widgets. * The platform does not sandbox widget code — **only install code you trust**. ## Method B — single `.widget.js` file [#method-b--single-widgetjs-file] ### Anatomy [#anatomy] ```js /** * @serverbee-widget { * "id": "com.example.hello", * "version": "1.0.0", * "name": "Hello", * "description": "A minimal example widget.", * "author": "Your Name", * "category": "Real-time", * "sizing": { "defaultW": 3, "defaultH": 2, "minW": 2, "minH": 2, "strategy": "free" }, * "sdkVersion": "^0.1.0" * } */ import { defineWidget, useServers, useTheme, z } from '@serverbee/widget-sdk' const ConfigSchema = z.object({ greeting: z.string().describe('Greeting text').default('Hello, ServerBee') }) export default defineWidget({ configSchema: ConfigSchema, component: ({ config }) => { const servers = useServers() const theme = useTheme() const online = servers.filter((s) => s.online).length const { greeting } = config return (
{greeting}
{online} / {servers.length} online · {theme.mode} mode
) } }) ``` ### Manifest fields [#manifest-fields] | Field | Required | Notes | | ------------------- | -------- | ----------------------------------------------------------- | | `id` | yes | Reverse-DNS-style unique identifier, e.g. `com.example.cpu` | | `version` | yes | Semantic version `MAJOR.MINOR.PATCH` | | `name` | yes | Display name in the widget picker | | `description` | – | Free-form description | | `author` | – | Author name or organization | | `category` | yes | One of `Real-time`, `Charts`, `Status` | | `sizing.defaultW/H` | yes | Default width / height in grid cells | | `sizing.minW/H` | yes | Minimum allowed size | | `sizing.maxW/H` | – | Optional maximum size | | `sizing.strategy` | yes | `free` / `fixed` / `aspect-square` / `content-height` | | `sdkVersion` | yes | SDK version range, e.g. `^0.1.0` | ### Build requirements [#build-requirements] A custom widget must be built as an **ES module** with the following dependencies marked `external`: * `react` * `react/jsx-runtime` * `@serverbee/widget-sdk` Just as important: the **JSDoc manifest comment must be preserved verbatim at the top of the output**. Most minifiers strip comments by default, so explicitly opt into preserving the manifest comment. A typical Vite + Terser config: ```ts // vite.config.ts import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' export default defineConfig({ plugins: [react()], build: { lib: { entry: 'src/index.tsx', formats: ['es'], fileName: () => 'index.js' }, rollupOptions: { external: ['react', 'react/jsx-runtime', '@serverbee/widget-sdk'] }, minify: 'terser', terserOptions: { format: { // Keep the @serverbee-widget manifest comment. comments: /@serverbee-widget/ } } } }) ``` ### Install via UI [#install-via-ui] 1. Sign in as an admin. 2. Open **Settings → Widget Modules**. 3. Choose **Upload `.js`** to upload a local file, or **Import URL** to fetch over HTTPS. 4. Once installed, open any dashboard, click **Edit → Add Widget**, and the new widget appears in the picker. ### Install via API [#install-via-api] ```bash # Upload a local file curl -X POST "https://your-host/api/widget-modules" \ -H "X-API-Key: $SERVERBEE_API_KEY" \ -F file=@my.widget.js # Fetch from URL curl -X POST "https://your-host/api/widget-modules?url=https://cdn.example.com/my.widget.js" \ -H "X-API-Key: $SERVERBEE_API_KEY" ``` On success: ```json { "data": { "id": "com.example.hello", "version": "1.0.0" } } ``` If a widget with the same `id` already exists, it is upgraded in place — the version, manifest, and code are all replaced. ## Method C — `.zip` collection bundle [#method-c--zip-collection-bundle] A collection lets you install **multiple widgets in a single upload**. Each widget still follows the Method B rules (its own JSDoc manifest, its own entry file). They are simply packaged together in one `.zip`, and a root-level `collection.json` enumerates them. ### Package layout [#package-layout] ``` my-pack.zip ├── collection.json ├── weather/ │ ├── index.js ← contains the @serverbee-widget manifest │ └── icon.svg ← optional asset ├── clock/ │ └── index.js └── shared/ └── helpers.js ← optional; not auto-injected — entry files must import as needed ``` ### `collection.json` schema [#collectionjson-schema] ```json { "widgets": [ { "entry": "weather/index.js" }, { "entry": "clock/index.js" } ] } ``` Constraints: * `entry` must be a relative path (no leading `/`, no `..`). * Must point to a `.js` or `.mjs` file. * Every entry file must contain a valid `@serverbee-widget` JSDoc block. * Widget `id`s must be **unique within the bundle** — duplicates are rejected. ### Asset resolution [#asset-resolution] Assets inside the bundle (images, JSON, helper scripts, etc.) are served via `GET /api/widget-modules/{id}/{relative-path}`. The relative path is resolved against the **folder of the entry file**: | Request URL | Resolved zip path | | -------------------------------------------------- | ------------------ | | `/api/widget-modules/com.example.weather/index.js` | `weather/index.js` | | `/api/widget-modules/com.example.weather/icon.svg` | `weather/icon.svg` | | `/api/widget-modules/com.example.clock/index.js` | `clock/index.js` | Reference assets from widget code like this: ```ts const iconUrl = `/api/widget-modules/com.example.weather/icon.svg` ``` Note: widgets cannot reach into each other's folders — asset resolution is scoped per `id`. ### Build and pack [#build-and-pack] 1. Use your bundler of choice (Vite, tsup, esbuild, ...) to build each widget's `index.js`, externalizing React and the SDK and preserving the manifest comment. 2. Lay the build outputs out in the directory structure above. 3. Write `collection.json` at the package root. 4. Zip it: ```bash zip -r my-pack.zip collection.json weather/ clock/ ``` ### Install [#install] The UI flow is identical to Method B — just upload the `.zip`. The server sniffs the `PK\x03\x04` magic bytes and dispatches automatically. ```bash curl -X POST "https://your-host/api/widget-modules" \ -H "X-API-Key: $SERVERBEE_API_KEY" \ -F file=@my-pack.zip ``` A collection install returns an **array** — one entry per widget in the bundle: ```json { "data": [ { "id": "com.example.weather", "version": "1.0.0" }, { "id": "com.example.clock", "version": "1.0.0" } ] } ``` Each widget gets its own row in the database and can be enabled, disabled, or uninstalled individually. They share a single underlying zip blob for storage. ## Sizing strategies [#sizing-strategies] `sizing.strategy` controls how a widget can be resized on the dashboard: * `free` — user can resize freely between `minW/H` and `maxW/H`. * `fixed` — locked to `defaultW/H`; resizing is disabled. * `aspect-square` — always 1:1; snaps to the nearest tier. * `content-height` — width is resizable; height is driven by content. See [Dashboards & Widgets](/en/docs/dashboards) for more on layout and editing. ## SDK surface at a glance [#sdk-surface-at-a-glance] `@serverbee/widget-sdk` is the stable API exposed to custom widgets. It includes: * `defineWidget` — declares a widget's entry point. * `z` / `ZodSchema` — bundled Zod for describing config schemas. * Live hooks: `useServers`, `useServer`, `useMetric`, `useCapability` (subscribe to the live WebSocket store via `useSyncExternalStore`). * Domain hooks: `useHistory`, `useTraffic`, `useAlerts`, `useServiceMonitors`, `useUptime`, `useGeoIp`. * Host hooks: `useTheme`, `useConfigUpdate`. * Generic escape hatches: `useApiQuery` / `useApiMutation` for direct REST calls. * `createActionsHelper` and `ActionDefinition` for registering action buttons on a widget. See `packages/widget-sdk/README.md` in the repo for the complete API reference. ## Uninstall [#uninstall] Click the delete button next to a widget in **Settings → Widget Modules**, or: ```bash curl -X DELETE "https://your-host/api/widget-modules/com.example.hello" \ -H "X-API-Key: $SERVERBEE_API_KEY" ``` Built-in widgets (for example `com.serverbee.hello-world`) cannot be uninstalled — `DELETE` against them returns `400`. ## Limits and safety [#limits-and-safety] * **Size caps**: a single `.js` file or `.zip` may not exceed **1 MiB** on upload; each entry inside a zip is capped at **5 MiB** uncompressed; the whole zip is capped at **32 MiB** total uncompressed across at most **64 entries**. * **SSRF protection**: URL installs resolve DNS and reject any host whose IP falls in a reserved/private range — loopback (`127.0.0.0/8`, `::1`), private networks (`10/8`, `172.16/12`, `192.168/16`), CGNAT (`100.64/10`), link-local (`169.254/16` — includes cloud metadata endpoints), IPv6 ULA (`fc00::/7`) and link-local (`fe80::/10`), benchmarking, documentation, multicast, and reserved ranges. HTTP redirects are disabled — 3xx responses are rejected outright so a public URL cannot pivot into the private network. * **Zip-slip protection**: extraction rejects entries with `..` or absolute paths. * **Static parsing only**: the manifest must be extractable with a plain regex — there is no `eval` / `Function()` involved in installation. The extractor itself rejects sources > 1 MiB up front. * **ID conflict rejection**: an upload whose `id` already belongs to a module from a different source (e.g. trying to overwrite a built-in with an upload) is refused with `409 Conflict`. * **SDK version gating**: each manifest declares `sdkVersion` as a semver range; the loader refuses to register a module whose required range does not match the host SDK. * **Admin-gated**: install and uninstall endpoints require admin privileges; members can only list and fetch served assets. * **Audit logged**: every install and uninstall is recorded in the audit log with the actor, source, id, version, and code SHA-256. * **Same-origin execution**: widgets run in the same browsing context as the main SPA — only install code from sources you trust. * **Action buttons** declared via `defineWidget({ actions })` get a built-in confirm dialog (when `confirm` is set), pending state, and toast notifications on success/failure. # iOS Mobile Companion > Native iOS app for ServerBee with QR pairing, push notifications, and real-time monitoring. URL: https://docs.serverbee.app/en/docs/mobile ServerBee provides a native iOS companion app that brings server monitoring to your phone: real-time metrics, alert history, and server management on the go. Verified notification setup and final scheduled-task outcome notifications are available, with successes requiring opt-in. Encrypted alert triggers and recoveries are available through confirmed mobile subscriptions. Administrator security-rule notifications use the same verified encrypted delivery path. ## Availability [#availability] This repository does not currently publish an installable iOS artifact or an App Store/TestFlight acquisition link. The guide below applies to a build you receive from your ServerBee operator or build from source. To build it yourself, install Xcode and XcodeGen, then run: ```bash cd apps/ios xcodegen generate open ServerBee.xcodeproj ``` Source builds require your Apple development team. The official-app design keeps APNs credentials at a dedicated publisher-operated Relay; official-app users and self-hosted Server operators do not need the publisher's keys. This repository does not yet distribute that signed app. A local build does not prove live APNs delivery. ## Features [#features] * **QR Code Pairing** -- Scan a QR code from the web app to authenticate instantly, no manual token entry required * **Verified Notification Setup** -- Explicit permission and subscription setup with authenticated registration; final task outcomes for administrator owners, with success opt-in; encrypted alert triggers and recoveries without a notification group; administrator security-rule matches * **Real-time Metrics** -- Live server status, CPU, memory, disk, and network metrics streamed via WebSocket * **Alert History** -- View recent alerts and their status (triggered/resolved) with detailed information * **Secure Authentication** -- Bearer token-based auth with automatic refresh (15-min access token, 30-day refresh token) ## Requirements [#requirements] * iOS 17.0 or later * A ServerBee server instance (v0.8.0+) with mobile auth enabled * For verified notification setup: a configured HTTPS Push Relay and a correctly signed, correctly signed physical device ## Pairing Your Device [#pairing-your-device] ### 1. Enable Mobile Auth (Server-side) [#1-enable-mobile-auth-server-side] Mobile authentication is enabled by default in v0.8.0+. No additional server configuration is required unless you want to adjust token TTLs. Optional configuration in `server.toml`: ```toml [mobile] access_ttl = 900 # Access token lifetime in seconds (default: 15 minutes) refresh_ttl = 2592000 # Refresh token lifetime in seconds (default: 30 days) ``` ### 2. Start Pairing from Web App [#2-start-pairing-from-web-app] 1. Log in to your ServerBee web app 2. Go to **Settings** → **Mobile Devices** 3. Click **Pair New Device** 4. A QR code will be displayed with a 5-minute expiry timer ### 3. Scan with iOS App [#3-scan-with-ios-app] 1. Open the ServerBee iOS app 2. Tap **Scan QR Code** on the welcome screen 3. Point your camera at the QR code displayed in the web app 4. The app will automatically authenticate and show your server list ## Recovering an Expired Login [#recovering-an-expired-login] If the app cannot refresh or safely sign out an old login, it preserves the original Server and account and opens session recovery. On the same Server's web app, log in as that original account and generate a fresh pairing QR code from **Settings** → **Mobile Devices**. In the iOS recovery page, use **Scan QR Code to recover session**. This scan verifies the account for cleanup; it does not sign in a replacement login. If the page lists sessions, select the original session by its device name and timestamps, then confirm **Sign out selected session**. The app retains that exact selection for retries and leaves other devices and newer logins intact. Once cleanup is confirmed, use the normal pairing flow to log in again. After a restart or an expired recovery QR authorization, scan a fresh code. Password and TOTP recovery is also available when you use those credentials. ## Managing Paired Devices [#managing-paired-devices] ### View Paired Devices [#view-paired-devices] In the web app, go to **Settings** → **Mobile Devices** to see all paired iOS devices: * Device name (e.g., "iPhone 15 Pro") * Paired date and time * Last active timestamp ### Revoke Access [#revoke-access] To remove a device's access: 1. In the **Mobile Devices** list, find the device you want to remove 2. Click the **Revoke** button 3. Confirm the action The device will be immediately logged out and all its tokens invalidated. ## Verified Notification Setup [#verified-notification-setup] Signing in does not request notification permission. Open **Settings → Notifications**, choose subscriptions, then tap **Enable notifications**. The Server confirms the saved preferences before iOS asks for permission. Security and task subscriptions require an administrator. Successful-task delivery is disabled by default; eligible task recipients can opt in. The notification page uses permissions confirmed by the Server, even if the cached login role is outdated. After an administrator is demoted, saving subscriptions or disabling notifications clears unavailable security and task categories. Failed saves do not replace the confirmed preferences. A failed subscription save remains visible after returning to the app, reconnecting or recovering registration. The page retains the unconfirmed choices until a successful save or a newer settings revision from the Server confirms the submitted permitted preferences. A successful registration alone does not confirm those choices. The screen reports system permission and Server registration independently. Failed saves retain the previous confirmed preferences and show an error. **Retry notification setup** retries verification, and **Open notification settings** opens iOS permission settings. Setup is reconciled on launch, foreground entry and restored connectivity without prompting during sign-in. **Enable Alerts and recoveries to receive alert transitions.** Use **Send test notification** to test only this authenticated installation. The Server encrypts the message, the public Relay forwards its ciphertext to APNs, and the Notification Service Extension decrypts and localizes it. The screen reports provider acceptance separately from device presentation, which must be observed on a real device. Foreground APNs delivery uses one system banner while WebSocket updates only update app state. Tapping a valid test opens the current account; old account/deployment or invalid payloads cannot navigate. Final task failures and opted-in successes use the same encrypted pipeline. Administrators can also enable security-rule notifications. Tests first enter a durable encrypted Server outbox. **Pending** means queued locally, **Retryable** means provider acceptance is unconfirmed and another attempt is scheduled, **Accepted** is an APNs provider receipt, **Permanent** stops retries, and **Expired** ends the test 30 minutes after its original creation. Retries and Server restarts never extend that deadline. The screen refreshes the current test status; retrying a lost response retains the same logical test identity. Delivery can be duplicated after an ambiguous network failure, so neither acceptance nor retry promises exactly-once display. Before every attempt the Server checks the account, current role, mobile session, subscription and registration revision. Re-registering the identical token, environment, content key and deployment preserves the revision, ciphertext and original retry deadline; a real identity change still fences old work. Disabling delivery, logout, device/account revocation, session expiry or a registration replacement stops later eligible attempts. Already in-flight or accepted APNs notifications cannot be retracted. Queue records contain ciphertext, not another plaintext notification history. Terminal ciphertext is erased; secret-free delivery receipts remain to prevent duplicate enqueueing from restarting the expiry window. ### Targeted Test API [#targeted-test-api] `POST /api/mobile/push/test` requires `expected_revision` and a client-retained UUID `event_id`. It queues encrypted work and returns its pending status with `presentation=unobserved`. Retry an ambiguous response with the same UUID and revision; `GET /api/mobile/push/test/{event_id}` reads only the current authenticated installation/login receipt. A deliberate new test uses a new UUID. Status recovery never restarts the original 30-minute deadline. ### Alert Triggers and Recoveries [#alert-triggers-and-recoveries] The Server queues encrypted notifications for each currently eligible subscribed installation, independently of external notification groups. Enabled rules retain their existing coverage, maintenance, trigger-mode and suppression behavior. Recovery follows the existing evaluator's transition behavior, including during maintenance. Unsubscribing before dispatch stops later eligible sends for that installation. External channels retain their own groups; migrated installations stay excluded from legacy APNs delivery. Taps wait for authentication restoration and validate the current deployment, account and installation. The alert target includes the rule, Server, event dimension and trigger cycle. A deleted alert or overwritten cycle shows **Alert not found** with **Back to alerts**, rather than opening a newer cycle. The extension localizes trigger/recovery text in English or Simplified Chinese; decryption failure shows generic text without a navigation target. Queued delivery and extension presentation expire 30 minutes after creation. Tapping a notification already shown in Notification Center after that deadline still authenticates its exact target and requests current Server detail. Expiry does not authorize access: a changed login is rejected, and a deleted or unavailable alert uses the same safe fallback. ### Final Task Failures [#final-task-failures] Enable **Final task failures** as an administrator. A manually started scheduled task notifies its initiator; an automatic run notifies its creator, independently of other subscribed administrators. Each eligible installation receives one logical summary after all targets finish their final attempts. Intermediate failures stay silent; a successful final retry follows the success opt-in policy. Final failed exits, timeouts, offline targets and execution denials are counted separately, including runs with no executable targets. Interrupted or incomplete runs are never treated as successful. After all final attempts drain, the Server durably records their summary before queue admission. A failed admission or Server restart automatically retries that recorded summary with the original run identity and 30-minute delivery window, checking current access and subscriptions again. Intermediate retry rows are not a completion signal. Summaries contain counts and task/run identifiers, never task names, commands or command output. Tapping opens authenticated results filtered to that exact run, including when an already displayed notification is tapped after its delivery window. Expired new deliveries still render a generic fallback; a tap retains the authenticated target and checks current Server access. Deleted tasks, unavailable runs or lost access show a dismissible fallback; old account/deployment notifications cannot navigate. Current administrator access, ownership and subscription are checked again before queued delivery. No notification group is required. Successful-run notifications require a separate opt-in. ### Successful Task Runs [#successful-task-runs] **Successful task runs** is disabled by default and appears only when the Server confirms current task access. Enable it and save subscriptions to receive one encrypted summary for a complete run in which every target ultimately succeeded, including success after a retry. This choice is independent of **Final task failures**; a mixed or failed run follows only the failure subscription and never emits a duplicate success summary. Interrupted, cancelled or incomplete runs cannot produce a success summary. Successful summaries use the same initiator/creator ownership, per-installation eligibility, run identity, authenticated navigation and 30-minute window as failures. They contain only counts and identifiers, never task names, commands or output. Failed saves retain the previous confirmed preference and keep the unconfirmed choice and error visible. Opting out or losing administrator access cancels pending eligible success delivery when the queue rechecks access before sending; in-flight or accepted APNs requests cannot be retracted. ### Security Rule Notifications [#security-rule-notifications] Administrators can enable **Security rule matches** and save the Server-confirmed subscription. Members cannot subscribe. A raw detection alone never sends a mobile notification: an enabled SSH new-IP login, brute-force or port-scan rule must admit it. Thresholds, username/CIDR exclusions, Server coverage, maintenance and the existing sliding deduplication window apply. A matching event produces one logical security notification per eligible installation, even if multiple rules match, without requiring a notification group. External channels keep their configured groups. An authorized Agent WebSocket transfers each original detection to a Server-owned recovery queue without waiting for storage retries inside its message or lifecycle lock. Before the first successful raw write, this retention is in memory and cannot survive process death. Connection replacement or closure does not discard accepted work, but authority transfer/revocation or capability removal cancels raw writes and unresolved rule admissions that are no longer authorized. The Server persists the original security event, authority binding, maintenance decision at the original event time, and recovery marker, then commits the rule cooldown together with a durable recipient intent. Maintenance lookup failures preserve raw history and browser updates while leaving rule admission unresolved; recovery never treats a failed lookup as permission. Once stored, the maintenance decision survives delayed recovery and restart across a window beginning or ending. Originals sharing a rule deduplication key are admitted in original time order across memory retention, missing maintenance snapshots and database reopen; an unresolved older event holds only that key, while other ready keys and installations continue. Rule cooldown remains monotonic in persistent storage. Only then does it encrypt and queue all eligible installations in one transaction. A queue write failure preserves raw history, browser updates, firewall actions and external notifications, leaves no partial fan-out, and retries the original intent automatically after recovery or a Server restart. Recipient ownership, session and registration revision are fixed by the intent and rechecked before queueing; replacement registrations cannot inherit an older intent. Security notifications expire 30 minutes after the event is recorded. Before each attempt, the Server rechecks the current administrator role, security subscription and mobile session. A queued notification stops after a role downgrade or subscription change. Multiple devices are evaluated independently. Retries after an ambiguous network failure can still lead to duplicate device presentation. The Notification Service Extension localizes the admitted event type. Tapping opens the corresponding Server security feed and event detail after verifying the deployment/account, current administrator role and resource identities. A deleted, unavailable or unauthorized target shows a dismissible unavailable screen; an old account or deployment payload cannot navigate. Local tests do not establish foreground/background/terminated APNs delivery. ### Privacy Boundary [#privacy-boundary] The Server owns account/session authorization and recipient selection. The Relay sees APNs tokens, source IPs, timing, request sizes, environment, event/delivery metadata and encrypted content. It must never receive content keys or plaintext notification content. Users see this explanation before requesting permission. Registration is confirmed by the authenticated Server with account, installation, session and revision checks. The app never contacts Relay. Each paired login has an independently scoped shared content key; account replacement and logout remove the old key. Pending registration retries cannot overwrite a replacement account. The Worker is public and stateless. A known valid device token can be abused for junk or replayed notifications; generic fallback text may appear when decryption fails. Requests or an attacker's own token can consume quota without another person's token. Encryption protects business content, not admission. Hard request/resource bounds and best-effort isolate-local rate limits reduce exposure but cannot guarantee a global budget or prevent all abuse. ### Server and Relay Configuration [#server-and-relay-configuration] Set `[push_relay].url` or `SERVERBEE_PUSH_RELAY__URL` to the dedicated HTTPS Relay. No notification group or legacy APNs channel is required. Verified migration removes this account's old token and permanently excludes that installation/account from legacy direct-APNs fan-out, even after disabling or unregistering. Other installations retain their legacy channels. Existing external channels continue to use their own configuration. The Cloudflare Worker uses WebCrypto JWT signing and platform fetch with a fixed topic and APNs host/environment allowlist. Worker bindings are `APNS_TEAM_ID`, `APNS_KEY_ID`, `APNS_TOPIC`, `APNS_ENVIRONMENTS` and the `APNS_PRIVATE_KEY` secret containing PKCS#8 PEM text. They belong exclusively to Relay. No App Attest, challenge/grant service, Relay database, Bun/OpenSSL production runtime or self-managed HTTP/2 pool is required. Do not point tests at Heeler's Relay. For the isolated signing/deployment runbook, troubleshooting by layer, and the genuine-device acceptance record, see [Push Relay Operations](/en/docs/push-relay). No live device acceptance is established by a successful local check. ## Security Considerations [#security-considerations] * **Token Rotation**: Refresh tokens are rotated on every use (single-use). Consumed refresh tokens cannot authenticate APIs or refresh again. Their retained hashes can only revoke the original session, supporting cleanup after a staged app upgrade or a lost rotation response. * **Fixed Expiry**: Access tokens have fixed 15-minute expiry (no sliding renewal) to limit exposure if stolen * **Secure Storage**: Tokens are stored in iOS Keychain with appropriate accessibility levels * **Session Expiry**: Expired access-token sessions are removed hourly. Paired-device refresh sessions are not deleted by that task: the device list hides them after expiry, refresh rotation preserves their identity and installation registration, and logout or revoke deletes them together with their retained revocation-proof hashes. ## Troubleshooting [#troubleshooting] ### QR Code Won't Scan [#qr-code-wont-scan] * Ensure the QR code is displayed clearly on your screen * Check that the QR code hasn't expired (5-minute timeout) * Try generating a new QR code from the web app ### Notification Setup Fails [#notification-setup-fails] * Check the configured Relay URL and its HTTPS trust chain. * Check Settings → Notifications for permission, verification and save errors. * Confirm the paired device has not been revoked and the session has not expired. * Match sandbox/production APNs environment to the signed app, and verify the embedded Notification Service Extension. Simulator success is not live APNs evidence. * Use the final HTTPS Server URL before registering a content key. Server requests reject redirects; failed setup can be retried at the configured endpoint. * Check signing for the app-private credential group and the app/extension shared content-key group. Decryption failures show generic text. * Enable **Alerts and recoveries**, or **Final task failures** and optional **Successful task runs** as an administrator. Administrator security-rule notifications use the same verified encrypted delivery path. ### Authentication Failures [#authentication-failures] * If the app shows "Session Expired", it will automatically refresh the token * If refresh fails, you'll need to re-pair using a new QR code * Check that your server time is correctly synced (NTP) ## Technical Details [#technical-details] ### Authentication Flow [#authentication-flow] 1. **Login/QR Pair**: POST `/api/mobile/auth/login` or QR pair endpoint → returns access\_token + refresh\_token 2. **API Requests**: Include `Authorization: Bearer {access_token}` header 3. **Token Refresh**: On 401, POST `/api/mobile/auth/refresh` with refresh\_token → new token pair 4. **Logout**: POST `/api/mobile/auth/revoke` with a deletion-only proof and the exact original `expected_session_id`; the older authenticated `/logout` route remains available for compatibility ### Offline Sign-out Recovery [#offline-sign-out-recovery] Before changing verified notification settings, the app confirms a deletion-only capability for that exact mobile session. When an established session signs out offline, its normal credentials are cleared only after its deletion capability is saved in the app-private Keychain. Pending cleanup retries on launch, foreground/network recovery, and before login or pairing; it never uses a replacement account's credentials. An acknowledgement for an already-absent original session does not delete another login using the same installation. An upgraded installation without a confirmed capability must connect once before secure sign-out can complete. Storage errors or a full pending-cleanup list also block destructive credential clearing or replacement login. The app reports these cases rather than losing the original session's cleanup proof. Existing push content keys and test identities keep their original scope across migration and proof bootstrap. Server-side sign-out cannot retract notifications already in flight or accepted by APNs. If an upgraded session has no confirmed deletion capability and its credentials are permanently rejected, the app shows **Recover session** and stops ordinary authenticated requests, including after a restart or reinstall that retains Keychain data. Verify the original account with its password and, when required, a verification code. The Server removes only the captured original session or confirms that it is already absent; only then does the app release its saved identity and return to login. **Retry secure sign-out** retries the original cleanup credentials without creating a new login. A lost response, unavailable Server, unsupported Server version, or ambiguous identity keeps the recovery record. If the original credentials no longer identify a session, reauthentication lists only the original account and installation's sessions, including expired ones. Choose the original session by its device name and dates, then confirm sign-out. The selected ID is saved before cleanup so a lost response can retry that exact target after a restart. Other sessions are never selected automatically. Unattributed state remains blocked with an explicit error. ### WebSocket Connection [#websocket-connection] The iOS app maintains a WebSocket connection for real-time updates: * Connection URL: `wss://your-server/api/ws/servers` * Authentication via `Authorization: Bearer` header during handshake * Automatic reconnection with exponential backoff * Connection auto-closes when access token expires (reconnect triggers refresh) # Push Relay Operations > Stateless Cloudflare Worker setup, accepted abuse risks, signing and genuine-device acceptance. URL: https://docs.serverbee.app/en/docs/push-relay The path is **Server → dedicated ServerBee Cloudflare Worker → APNs → iOS Notification Service Extension**. The Server owns subscriptions, user/session authorization, encrypted pending work and final task aggregation. Relay holds the publisher's APNs credentials and forwards ciphertext. [Mobile](/en/docs/mobile) covers user setup; [Configuration](/en/docs/configuration#push_relay----verified-mobile-setup) covers the Server URL. The revised architecture uses one public `POST /v1/send`. It has no App Attest, build/distribution policy, challenges, delivery grants, durable Relay database or replacement authorization service. The Server's durable business outbox remains in its own database. Local tests and Simulator results do not establish live APNs delivery or signed-device presentation. This runbook describes separately authorized operator actions; it does not deploy infrastructure, provision secrets or change Apple accounts. ## Accepted Public-Relay Risks [#accepted-public-relay-risks] Anyone can submit requests. Knowing a valid device token permits junk ciphertext or replay attempts, which can cause generic notifications. Consuming Relay resources does not require someone else's token: invalid requests or an attacker's own token can consume request quota. Token confidentiality is helpful but is not authorization. AES-256-GCM protects business content and its authenticated identity when the per-installation key remains secret. It does not authenticate public Relay callers. The original APNs alert is fixed generic text because a Notification Service Extension can fail or time out; invalid ciphertext does not guarantee silent dismissal. The app refuses invalid or old-account navigation. Safeguards are strict schema and size limits, a hard streaming-read limit before parsing, read/send deadlines, fixed APNs hosts/topic/environment allowlist, a bounded concurrent-request cap, and hard-capacity source/target limiter maps with an isolate-wide request cap. Rate state is best effort and local to each Worker isolate: isolates restart and scale independently. It is not a global budget, exact global rate limit, replay database or denial-of-service guarantee. Confirm available platform limits and account billing controls independently before deployment; no paid plan or WAF feature is assumed. ## Cloudflare Quick Setup [#cloudflare-quick-setup] Use a dedicated ServerBee Worker and synthetic test data, separate from Heeler and production services. Record exact revisions, signing build, APNs environment and UTC timestamps. 1. Install locked development dependencies with the repository's Bun version and Node 24. Worker production code uses Web APIs, WebCrypto and fetch; Bun is tooling, not the deployed runtime. 2. Review `apps/push-relay/wrangler.jsonc`. Choose the dedicated Worker name and fixed `APNS_TOPIC`, `APNS_TEAM_ID`, `APNS_KEY_ID`, and `APNS_ENVIRONMENTS`. The official topic is `app.serverbee`, matching the main app bundle identifier; never use the extension identifier `app.serverbee.notifications`. The optional environment allowlist accepts `sandbox`, `production`, or both as a comma-separated list; omission allows both. 3. Provision `APNS_PRIVATE_KEY` as a Worker secret containing the publisher's PKCS#8 P-256 PEM contents. Use the authorized secret-entry flow; never put it in Git, public variables, the app or the Server. No PEM file path is used by the deployed Worker. 4. Run the Relay type check, Workers-runtime tests and deployment dry run described in `apps/push-relay/README.md`. A dry run bundles code locally; it does not validate Apple credentials or make a deployment. 5. Deploy only with separate authorization, then set the Server's `SERVERBEE_PUSH_RELAY__URL` to the final HTTPS Worker URL. The app must also use the final HTTPS Server URL; secret-bearing Server requests reject redirects. 6. Use a signed app and the live acceptance matrix below. Never report registration or an APNs receipt as proof that a phone displayed a notification. ### Configuration Ownership [#configuration-ownership] | Owner | Configuration | | ---------------------- | --------------------------------------------------------------------------------------------------------------------------- | | Self-hosted Server | `SERVERBEE_PUSH_RELAY__URL` or `[push_relay].url`; encrypted durable outbox and current recipient/session/revision checks | | Worker variables | `APNS_TEAM_ID`, `APNS_KEY_ID`, `APNS_TOPIC`, `APNS_ENVIRONMENTS` | | Worker secret | `APNS_PRIVATE_KEY`, PKCS#8 PEM contents; publisher-controlled only | | Signed iOS app and NSE | Matching APNs environment, embedded extension and app/extension shared Keychain group; login credentials remain app-private | Cloudflare manages inbound HTTPS and outbound connections. There is no Bun listener, trusted reverse-proxy header configuration, OpenSSL subprocess, self-managed HTTP/2 pool, container or Relay SQLite volume. Source rate limiting uses the platform-provided client IP, not arbitrary forwarded headers. Keep request-body/token/content-key logging disabled. ## Registration, Delivery and Privacy [#registration-delivery-and-privacy] The app calls authenticated `POST /api/mobile/push/encrypted-register` with its token, environment, revision, deployment binding and per-installation content key. It never calls Relay. Account/installation/mobile-session ownership and revision checks remain on the Server. The legacy direct-APNs registration route remains separate for existing clients. The Server submits `device_token`, `environment`, `event_id`, `expires_at` and the versioned encrypted `envelope` to `POST /v1/send`. Relay sees token, source IP, timing, size, environment, event/delivery metadata and ciphertext. It never receives content keys or business plaintext. Do not log request bodies, device tokens, keys or provider authorization headers. The original 30-minute delivery deadline survives retries and Server restart. Before dispatch the Server rechecks ownership, session, role, subscription and registration revision. Retryable responses retain ciphertext; terminal responses erase it while retaining secret-free receipts. Logout/revocation cannot retract already in-flight or accepted requests. Exact-target, deletion-only offline mobile-session cleanup remains independent of Relay. APNs status/reason classification distinguishes acceptance, retryable service/rate/network errors, permanent payload/configuration errors and expiry. Only a confirmed APNs `410 Unregistered` invalidates the exact still-current registration. `BadDeviceToken` or other HTTP 400 responses do not indiscriminately erase tokens. The app/NSE checks authenticated content identity and rejects invalid navigation. ## Signing and Troubleshooting [#signing-and-troubleshooting] The source identities are main app `app.serverbee`, Notification Service Extension `app.serverbee.notifications`, and test bundle `app.serverbee.tests`. The main app lists `$(AppIdentifierPrefix)app.serverbee` first as its private Keychain group, followed by `$(AppIdentifierPrefix)app.serverbee.push`. The extension has only the shared `.push` group. Preserve the actual provisioning App ID prefix; do not substitute a guessed Team ID. The Keychain **service labels** `com.serverbee.mobile` and `com.serverbee.mobile.push` intentionally remain stable. They are item lookup namespaces, not Bundle IDs or APNs topics. Keeping the private service label and original default access group allows existing `app.serverbee` credentials, installation identity and pending logout recovery to remain readable. It does not establish migration from a separately installed `com.serverbee.mobile` app or from a build with different effective groups/prefix. Before an upgrade, inspect a prior signed build and test session continuity; if a push-enabled test build used the old shared group, repeat notification setup to create/register a key in the corrected group. Never widen the extension's access to private credentials as a migration shortcut. Inspect effective signed entitlements on both app and extension. Keep APNs and shared Keychain access; the extension must not have the app-private credential group. Debug uses sandbox APNs and distribution uses production. Existing Apple account App Attest capability or extension registration is not changed by this source refactor and is not required by the protocol. | Symptom | Check | | -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | | Registration unavailable | Final HTTPS Server URL, configured Relay URL, mobile session ownership, revision conflict and permitted subscription categories | | Generic notification | Shared Keychain entitlement/access, content-key and account binding, valid envelope, original expiry, NSE packaging and execution | | Retryable delivery | Worker timeout/rate/concurrency limits and APNs service/network status; retries stop at original expiry | | Permanent provider failure | Fixed topic/environment and signing configuration, payload bounds, exact APNs reason; do not assume every 400 means revoked device | | No banner after acceptance | Device notification permission, Focus/network/app state and NSE behavior; acceptance alone proves no presentation | | Old notification cannot navigate | Account/deployment/installation mismatch or missing current Server authorization; rejection is expected | ## Genuine-Device Acceptance Record [#genuine-device-acceptance-record] Keep each row **NOT RUN** until separately observed with the candidate SHA, app/extension build and effective entitlements, physical device/iOS version, APNs environment, UTC time and language. Synthetic fixtures, Simulator success and dry-run bundling are separate evidence. | Scenario | Provider receipt | Native presentation | Authenticated navigation | | ------------------------------------------------------------- | ---------------- | ------------------- | ------------------------ | | Current-installation test, foreground/background/terminated | NOT RUN | NOT RUN | NOT RUN | | Alert trigger/recovery and old-cycle fallback | NOT RUN | NOT RUN | NOT RUN | | Administrator security rule match and unavailable target | NOT RUN | NOT RUN | NOT RUN | | Final task failure and opted-in success, owner isolation | NOT RUN | NOT RUN | NOT RUN | | English and Simplified Chinese rendering | NOT RUN | NOT RUN | NOT RUN | | Sandbox and production signing/environment | NOT RUN | NOT RUN | NOT RUN | | Refresh/token rotation, logout/account replacement and revoke | NOT RUN | NOT RUN | NOT RUN | | Retry after outage, expiry and already-presented late tap | NOT RUN | NOT RUN | NOT RUN | Use [combined verification](https://github.com/ZingerLittleBee/ServerBee/blob/main/tests/manual/mobile-push-integration.md) for retained HTTP, storage, shared Rust/Swift crypto and native lifecycle checks. No deployment, credential provisioning or live acceptance is implied by green CI. # Security > Configure two-factor authentication, OAuth login, password management, and login security policies. URL: https://docs.serverbee.app/en/docs/security ServerBee layers several security controls: two-factor authentication (2FA), OAuth social login, password policies, and login rate limiting. ## Two-Factor Authentication (2FA) [#two-factor-authentication-2fa] ServerBee supports TOTP (Time-based One-Time Password) based two-factor authentication, compatible with all standard authenticator apps (Google Authenticator, Authy, 1Password, etc.). ### Enabling 2FA [#enabling-2fa] 1. Log in and go to Settings → Security 2. In the **Two-Factor Authentication** section, click **Setup** 3. Scan the QR code (or enter the Base32 secret manually) 4. Enter the 6-digit code from your authenticator app 5. Click **Enable** to finish setup Once enabled, a 6-digit TOTP code is required for every login. Codes refresh every 30 seconds. ### Disabling 2FA [#disabling-2fa] 1. Go to Settings → Security 2. Click **Disable 2FA** 3. Enter your current password to confirm 4. 2FA is disabled ### API Endpoints [#api-endpoints] | Endpoint | Method | Description | | ----------------------- | ------ | -------------------------------- | | `/api/auth/2fa/setup` | POST | Generate TOTP secret and QR code | | `/api/auth/2fa/enable` | POST | Verify code and enable 2FA | | `/api/auth/2fa/disable` | POST | Verify password and disable 2FA | | `/api/auth/2fa/status` | GET | Check current 2FA status | ## OAuth Social Login [#oauth-social-login] ServerBee supports three OAuth providers: | Provider | Config Section | Callback URL | | -------- | ---------------- | ------------------------------------------- | | GitHub | `[oauth.github]` | `{base_url}/api/auth/oauth/github/callback` | | Google | `[oauth.google]` | `{base_url}/api/auth/oauth/google/callback` | | OIDC | `[oauth.oidc]` | `{base_url}/api/auth/oauth/oidc/callback` | ### Configuring OAuth [#configuring-oauth] Add OAuth configuration to `server.toml` (see [Server Setup](/en/docs/server)): ```toml [oauth] base_url = "https://monitor.example.com" allow_registration = false [oauth.github] client_id = "your-github-client-id" client_secret = "your-github-client-secret" ``` ### OAuth Account Management [#oauth-account-management] * View linked OAuth accounts in Settings → Security * Click **Unlink** to disconnect an OAuth account * If `allow_registration = false` (default), first-time OAuth logins do not create new users — an admin must create the user first Enable `allow_registration = true` only when the OAuth provider itself is access-controlled (self-hosted OIDC or an org-internal IdP). Auto-created accounts get the Member role, which can read **all** monitoring data — including security events and public IPs. With a public provider such as GitHub, enabling it grants that access to every user of the provider. ### Login Flow [#login-flow] 1. Click an OAuth provider button on the login page (e.g., "Login with GitHub") 2. Redirect to the provider's authorization page 3. After authorization, redirect back to ServerBee 4. If the OAuth account is linked to an existing user, log in directly 5. If not linked and `allow_registration = true`, create a new Member user and log in 6. If not linked and `allow_registration = false`, return an error ## Password Management [#password-management] ### Changing Password [#changing-password] 1. Go to Settings → Security 2. Enter your current password and new password in "Change Password" 3. Click **Change Password** Passwords are hashed with argon2, following OWASP recommendations. ### First-Run Admin Credentials [#first-run-admin-credentials] On first start (when no users exist), ServerBee auto-creates an admin account with a randomly generated password and prints it once to the server/container logs as a highlighted credentials banner. The username and password cannot be preset via environment variables. On first login you must change this password, and may optionally choose a different username. ## Login Security [#login-security] ### Login Rate Limiting [#login-rate-limiting] ServerBee enforces IP-level rate limiting on the login endpoint: * Maximum **5 failed attempts** per 15-minute window by default (configurable via `rate_limit.login_max`) * Returns 429 Too Many Requests when exceeded * Expired rate limit records are cleaned up by the background session\_cleaner task ### Agent Registration Rate Limiting [#agent-registration-rate-limiting] Agent registration endpoints are also rate limited: * Maximum **10 registration attempts** per 15-minute window by default (configurable via `rate_limit.register_max`). The Railway template deliberately overrides this to **3** as a stricter hosted default ### Session Security [#session-security] * Session cookies are set with `HttpOnly` + `Secure` flags by default * Session lifetime is 24 hours (configurable via `auth.session_ttl`) * For HTTP-only development, set `auth.secure_cookie = false` # Admin Guide > User management, audit logs, remote commands, and billing management. URL: https://docs.serverbee.app/en/docs/admin This page covers features available only to administrators (Admin role). ## User Management [#user-management] ServerBee supports multiple users across two roles: | Role | Permissions | | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Admin** | Full management: users, server config, alert rules, notifications, audit logs, etc. | | **Member** | Read-only access to **all** servers' monitoring data — dashboards, server details, Ping results, security events, public IPs — plus self-service settings (own password, 2FA, API key list, mobile devices) | Member visibility is fleet-wide; there is no per-server scoping. A member account is for trusted collaborators only. To show selected servers to outsiders (e.g., customers), use a [status page](/en/docs/status-page) instead. ### Managing Users [#managing-users] Go to Settings → Users: * **Create user**: Enter username, password, and role * **Edit role**: Change a user's role (Admin/Member) * **Delete user**: Remove a user account (cannot delete the last Admin) ### Recovering a Lost Admin Account [#recovering-a-lost-admin-account] There is no password-recovery flow (self-hosted deployments have no mail channel), and passwords are argon2 hashes that cannot be hand-edited into the database. If the only admin credential (password or 2FA device) is lost: 1. Stop the server. 2. Run `sqlite3 /path/to/serverbee.db "DELETE FROM users;"`. 3. Start the server — with an empty users table it re-creates the `admin` user and prints a fresh one-time password banner in the startup log. This removes **all** user accounts, and their API keys stop working. Monitoring data, servers, and configuration are untouched. ### API Endpoints [#api-endpoints] | Endpoint | Method | Description | | ----------------- | ------ | ---------------- | | `/api/users` | GET | List all users | | `/api/users` | POST | Create a user | | `/api/users/{id}` | GET | Get user details | | `/api/users/{id}` | PUT | Update user role | | `/api/users/{id}` | DELETE | Delete a user | ## Audit Logs [#audit-logs] ServerBee automatically records audit logs for critical operations so administrators can track security events. ### Recorded Events [#recorded-events] * User login (success/failure) * Password changes * 2FA enable/disable * Terminal connect/disconnect * Capability denial events * `capability_temporarily_granted` — a host-local temporary grant turned an off-by-default capability on * `capability_grant_expired` — a temporary grant reached its deadline and the capability turned back off * `capability_grant_revoked` — a host operator revoked a temporary grant early ### Viewing Audit Logs [#viewing-audit-logs] Go to Settings → Audit Logs: * Browse all audit records (paginated) * View user, action type, details, IP address, and timestamp ### API Endpoint [#api-endpoint] ``` GET /api/audit-logs?limit=50&offset=0 ``` Returns a paginated list of audit records. Each entry contains: ```json { "id": 1, "user_id": "user-uuid", "action": "login", "detail": "Login successful", "ip": "192.168.1.100", "created_at": "2026-03-14T10:30:00Z" } ``` Audit logs are retained for 180 days by default (configurable via `retention.audit_logs_days`). ## Remote Commands [#remote-commands] Administrators can dispatch commands to online servers and retrieve execution results. ### Usage [#usage] 1. Go to Settings → Tasks 2. Enter the command to execute 3. Select target servers (multi-select supported) 4. Click **Execute** ### Execution Flow [#execution-flow] 1. Server creates a Task record 2. Sends the command to each target agent via WebSocket 3. Agent executes the command, reports stdout/stderr and exit\_code 4. Results are stored in the `task_results` table ### Limitations [#limitations] * Command execution timeout: 300 seconds * Requires the target agent to have the `exec` capability (CAP\_EXEC), which is off by default. Capabilities are agent-owned — enable it on the agent host by adding `exec` to its `[capabilities]` allow list (or `--allow-cap exec`). See [Capabilities](/en/docs/capabilities) * Servers whose agent does not report CAP\_EXEC receive a synthetic result: `exit_code = -2`, message: "Capability disabled" * Only Admin role can create tasks ### API Endpoints [#api-endpoints-1] | Endpoint | Method | Description | | ------------------------- | ------ | ----------------------------- | | `/api/tasks` | POST | Create and dispatch a command | | `/api/tasks/{id}` | GET | Get task details | | `/api/tasks/{id}/results` | GET | Get execution results | Request body for creating a one-shot task: ```json { "command": "uptime", "server_ids": ["server-id-1", "server-id-2"], "timeout": 60 } ``` ## Scheduled Tasks [#scheduled-tasks] Scheduled tasks extend remote commands with cron-based execution, retries, and run history. ### Creating a Scheduled Task [#creating-a-scheduled-task] 1. Go to **Settings → Tasks**. 2. Switch to the scheduled task section. 3. Enter a name, command, cron expression, target servers, timeout, retry count, and retry interval. 4. Save the task. Enabled tasks are registered with the server-side scheduler immediately. Scheduled tasks use the server's `scheduler.timezone` setting when calculating `next_run_at`. ### Fields [#fields] | Field | Description | | ----------------- | -------------------------------------------------------------------------------- | | `name` | Display name for the scheduled task | | `task_type` | Use `scheduled` for cron tasks; omitted/default is `oneshot` | | `cron_expression` | Cron expression parsed by the server scheduler | | `command` | Shell command sent to each target agent | | `server_ids` | Target servers | | `timeout` | Per-attempt command timeout in seconds; default execution timeout is 300 seconds | | `retry_count` | Number of retry attempts after the first attempt; must be 0-10 | | `retry_interval` | Seconds to wait between retries; must be at least 1 | | `enabled` | Disabled tasks are removed from the scheduler and do not run automatically | ### Execution Behavior [#execution-behavior] * Each scheduled trigger creates a `run_id` used to group results from all target servers and retry attempts. * ServerBee prevents overlapping runs of the same scheduled task. If the previous run is still active, the new trigger is skipped. * `POST /api/tasks/{id}/run` starts a manual run and skips retry logic. * Disabling or deleting a task cancels its active run. * Results include an `attempt` number so you can distinguish retries. Synthetic exit codes: | Exit code | Meaning | | --------- | --------------------------------------------------------------- | | `-2` | `CAP_EXEC` disabled or blocked by agent-local capability policy | | `-3` | Server offline or dispatch failed | | `-4` | No agent response before timeout | ### API Example [#api-example] ```json { "task_type": "scheduled", "name": "Daily disk check", "command": "df -h", "server_ids": ["server-id-1"], "cron_expression": "0 0 8 * * *", "timeout": 120, "retry_count": 2, "retry_interval": 60 } ``` Relevant endpoints: | Endpoint | Method | Description | | --------------------------- | ------ | -------------------------------------------------------------- | | `/api/tasks?type=scheduled` | GET | List scheduled tasks | | `/api/tasks` | POST | Create one-shot or scheduled task | | `/api/tasks/{id}` | PUT | Update task fields, enable/disable, and scheduler registration | | `/api/tasks/{id}` | DELETE | Delete a task and cancel active execution | | `/api/tasks/{id}/run` | POST | Manually run a task | | `/api/tasks/{id}/results` | GET | Fetch task results grouped by task/run/attempt | ## Billing Information [#billing-information] Administrators can record billing details for each server to track VPS costs and expiration dates. ### Managing Billing Info [#managing-billing-info] 1. Go to server detail page → click Edit 2. Fill in the "Billing" section: | Field | Description | | --------------- | ---------------------------------------------- | | `price` | Price amount | | `billing_cycle` | Billing cycle (monthly/quarterly/yearly, etc.) | | `currency` | Currency unit (CNY/USD, etc.) | | `expired_at` | Expiration date | | `traffic_limit` | Traffic limit in bytes | ### Expiration Alerts [#expiration-alerts] Create an `expiration` alert rule to receive notifications before a server expires: 1. Go to Settings → Alerts 2. Create a new rule with metric type **expiration** 3. Set the threshold to the number of days before expiration (e.g., 7 for 7-day advance notice) 4. Link a notification group ### Traffic Alerts [#traffic-alerts] Use `transfer_in_cycle` / `transfer_out_cycle` / `transfer_all_cycle` alert types to monitor cumulative traffic within the current billing cycle. See [Alerts](/en/docs/alerts) for details. ### Cost Insights [#cost-insights] Once `price` and `billing_cycle` are set, ServerBee derives cost-aware signals from the recorded billing inputs and the agent's reported resources, utilization, and uptime. Insights are surfaced in three places: * **Servers list cost cell** -- The `/servers` table shows each server's monthly-equivalent cost plus any advisory warnings, so cost sits next to CPU/memory/disk/traffic instead of being hidden in the edit dialog * **Dashboard server card** -- Each card shows a compact cost signal with a footnote summarizing burn rate. The signal is hidden when cost config is missing or invalid * **Per-server cost insights panel** -- The server detail page renders the full breakdown: monthly-equivalent cost, billing-cycle elapsed/remaining cost and burn percent, days remaining, normalized resource unit costs (per CPU core, per GB memory, per GB disk, per TB traffic limit), and any cost advisories #### Cost advisories [#cost-advisories] Instead of a composite value score, each configured server surfaces a list of objective, per-server `advisories` -- `expired_billing`, `sleeping_money`, `idle_burn`, and `low_uptime` -- each computed without any fleet comparison. The exact triggers, input validation, and data sources live in [Cost Insights](/en/docs/cost-insights). #### API [#api] For automation, the same data is exposed read-only at `GET /api/cost/overview` (fleet-wide currency totals + per-server summaries) and `GET /api/servers/{id}/cost-insights` (full per-server breakdown). See the [API Reference](/en/docs/api-reference#authenticated-read-endpoints) for authentication details. ## Agent Authority Management [#agent-authority-management] Admins manage onboarding and re-enrollment from **Add Server** and each Server's detail actions. Adding a Server creates its profile and one bound enrollment offer atomically. The plaintext code is displayed once; later views expose only the offer ID, prefix, expiry, and Agent Authority state. For an unclaimed Server, an admin may issue an offer. For a claimed Server, start either graceful re-enrollment (keep current authority until the replacement claim) or emergency re-enrollment (revoke and fence it immediately). Only one offer may be outstanding, and replacement or revocation targets that exact offer ID. Revoking Agent Authority is a separate action and never creates an offer implicitly. The Agent Authority history records actor, source, transition, mode, offer outcome, and before/after status without storing plaintext secrets. These events survive Server deletion for operational investigation. ### Clean Up Unconnected Placeholders [#clean-up-unconnected-placeholders] Abandoned onboarding can leave behind offline Server placeholders. The Servers page then shows a **Clean up unconnected** action that removes only never-initialized offline placeholders and deliberately keeps online-but-uninitialized agents. # Cost Insights > How ServerBee turns billing fields plus agent metrics into burn rate, per-resource unit cost, and objective cost advisories. URL: https://docs.serverbee.app/en/docs/cost-insights ServerBee combines admin-entered billing fields (`price`, `billing_cycle`, `currency`, `expired_at`, ...) with the resource capacity, utilization, and uptime each agent reports, then derives a set of objective cost signals: burn rate, per-resource unit cost, and per-server **advisories**. These appear in the server list, the dashboard server card, and the cost panel on the server detail page. To learn how to enter billing data in the UI, see the [Billing Information section in the admin guide](/en/docs/admin#billing-information). ## Derived outputs [#derived-outputs] Every server with a valid billing configuration produces the fields below (exposed via both `GET /api/cost/overview` and `GET /api/servers/{id}/cost-insights`): | Field | Meaning | | ------------------------------------------- | ------------------------------------------------------------------------------- | | `cost_per_second / per_hour / per_day` | Cost amortized to a single unit of time within the current billing cycle | | `cost_per_month_equivalent` | Cost normalized to "per 30 days" so cycles of different lengths can be compared | | `cycle_cost_elapsed / cycle_cost_remaining` | Money already burned vs. left in the current cycle | | `cycle_burn_percent` | Elapsed portion of the cycle expressed as a percentage | | `days_remaining` | Days left in the current billing cycle | | `resource_value.cost_per_cpu_core` | Monthly-equivalent unit cost per CPU core | | `resource_value.cost_per_gb_memory` | Monthly-equivalent unit cost per GB of memory | | `resource_value.cost_per_gb_disk` | Monthly-equivalent unit cost per GB of disk | | `resource_value.cost_per_tb_traffic_limit` | Monthly-equivalent cost per TB of `traffic_limit`, when configured | | `advisories` | Objective per-server warnings (see below); an empty array when nothing applies | | `invalid_reason` | Populated instead of the cost fields when billing fields fail validation | ServerBee deliberately does **not** compute a single composite "value score". A server's cost-effectiveness is best judged from the objective unit costs above (which need no fleet comparison) plus the actionable advisories below — not from an opaque 0–100 number that conflates unrelated dimensions. ## Input validation [#input-validation] The cost breakdown is skipped when any of the following are true. The reason is surfaced via `invalid_reason` so the UI can flag the entry rather than silently treating it as configured: | `invalid_reason` | Trigger | | ----------------------- | ------------------------------------------------------------------------------- | | `missing_price` | `price` is NULL | | `missing_billing_cycle` | `billing_cycle` is NULL or empty | | `invalid_billing_cycle` | `billing_cycle` is not in the recognized set (`monthly`, `quarterly`, `yearly`) | | `invalid_price` | `price < 0` or NaN | `price == 0` is treated as a valid "free server" entry: it gets the full cost breakdown (all zeros) but never raises the `idle_burn` / `sleeping_money` advisories, which only make sense when money is actually being spent. ## Cost advisories [#cost-advisories] Each advisory is an objective fact about a single server, computed without any fleet comparison, so it is meaningful even for a one-server deployment. They are emitted in the priority order below and rendered as warning chips next to the cost figures. | Advisory | Trigger | What it suggests | | ----------------- | -------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ | | `expired_billing` | `expired_at` is in the past | Renew before you lose the host | | `sleeping_money` | Offline, with no recent samples, while paying a non-zero bill | Investigate why a paid host is down | | `idle_burn` | Resources idle (avg CPU \< 5%, avg memory \< 20%, no real network/disk I/O over the last 24h) while paying a non-zero bill | Consider downsizing or consolidating | | `low_uptime` | 30-day uptime ratio below 90% | Investigate reliability | `sleeping_money` and `idle_burn` are mutually exclusive: "sleeping" requires the absence of recent samples, while "idle" requires the low-usage samples that prove the host was recently alive. ## Data sources and refresh cadence [#data-sources-and-refresh-cadence] * Advisories and cost figures are computed on demand by the server; there is no persisted cache * Utilization averages come from the `record` table (the same table the monitoring charts use), 24h lookback (`RECORD_LOOKBACK_HOURS`). Network activity is judged from per-window throughput rates, not the cumulative byte counters * Uptime ratio is aggregated from `uptime_daily` over a 30-day lookback (`UPTIME_RECENT_DAYS`) * Resource capacity (CPU cores / total memory / total disk) comes from each agent's last `SystemInfo`; the last known value is reused while the agent is disconnected ## Known limitations [#known-limitations] * Per-resource unit costs are absolute (cost ÷ capacity); they are not ranked against other servers, so judging "cheap vs. expensive" is left to you * `idle_burn` inspects CPU + memory + I/O activity, not application-level usefulness. A host that is up but doing nothing useful while its CPU/RAM are idle reads as idle * When `traffic_limit` is missing, `cost_per_tb_traffic_limit` is omitted * Monthly-equivalent conversion uses a fixed 30-day basis. Quarterly / yearly cycles are converted into this single basis first # API Reference > ServerBee REST API overview, authentication, WebSocket endpoints, and Swagger UI interactive docs. URL: https://docs.serverbee.app/en/docs/api-reference ServerBee exposes the same capabilities the web dashboard uses through REST and WebSocket APIs. The authoritative, schema-level reference is generated from OpenAPI annotations in the server binary. ## Swagger UI [#swagger-ui] Open the built-in interactive documentation at: ```text https://your-server/swagger-ui/ ``` Swagger UI lets you inspect request/response schemas and authentication requirements, and send test requests against your own deployment. The raw OpenAPI document is available at: ```text https://your-server/api-docs/openapi.json ``` ## Response Format [#response-format] Successful REST responses are wrapped as: ```json { "data": {} } ``` Application errors use an error code and message, with optional `details`: ```json { "error": { "code": "BAD_REQUEST", "message": "Bad request: description of what went wrong" } } ``` ## Authentication [#authentication] ### Session Cookie [#session-cookie] Used by the web dashboard after login: ```bash curl -X POST https://your-server/api/auth/login \ -H "Content-Type: application/json" \ -d '{"username":"admin","password":"your-password"}' \ -c cookies.txt curl https://your-server/api/servers -b cookies.txt ``` ### API Key [#api-key] Use API keys for automation. Admin users can create them in Settings → API Keys. ```bash curl https://your-server/api/servers \ -H "X-API-Key: serverbee_your-api-key-here" ``` API keys use the `serverbee_` prefix and are shown only once when created. An API key authenticates as its owning user; it is a credential, not a separate role. Requests inherit the owner's current Admin or Member permissions. Only Admin users can create new API keys. ### Bearer Session Token [#bearer-session-token] Mobile flows use Bearer tokens for selected REST and WebSocket endpoints: ```bash curl https://your-server/api/auth/me \ -H "Authorization: Bearer " ``` ## Public Endpoints [#public-endpoints] | Method | Path | Description | | ------ | --------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | | POST | `/api/auth/login` | Web login | | GET | `/api/auth/oauth/providers` | List enabled OAuth providers | | GET | `/api/auth/oauth/{provider}` | OAuth authorization redirect | | GET | `/api/auth/oauth/{provider}/callback` | OAuth callback | | POST | `/api/mobile/auth/login` | Mobile login | | POST | `/api/mobile/auth/refresh` | Refresh a mobile session | | POST | `/api/mobile/auth/revoke` | Revoke one original mobile session using an installation-scoped deletion-only proof | | POST | `/api/mobile/auth/recover` | Reauthenticate the original account to delete one captured session or confirm its absence, without issuing tokens | | POST | `/api/mobile/auth/pair` | Redeem a mobile pairing code | | POST | `/api/agent/register` | Claim a bound enrollment offer with an Agent-proposed run token | | GET | `/api/status/config` | Public status page configuration | | GET | `/api/status` | Servers included on the public status page | | GET | `/api/status/servers/{id}` | Public detail for an included server | | GET | `/api/status/servers/{id}/metrics` | Public metrics for an included server | | GET | `/api/status/servers/{id}/uptime-daily` | Public daily uptime for an included server | | GET | `/api/status/network`, `/api/status/network/{id}` | Public network overview and per-server detail | | GET | `/api/status/ip-quality` | Public IP quality overview | | GET | `/api/status/incidents`, `/api/status/maintenances` | Public incidents and maintenance windows | | GET | `/api/settings/brand` | Public brand settings | | GET | `/api/brand/logo` | Serve uploaded logo | | GET | `/api/brand/favicon` | Serve uploaded favicon | Ordinary mobile refresh preserves the installation registration and invalidates the previous access and refresh credentials. Login returns a stable `revocation_token`; the client retains it across refreshes and sends it with `installation_id` to `/api/mobile/auth/revoke` when signing out or discarding permanently rejected credentials. Cleanup uses the original login context even if a replacement login starts while the request is pending. This proof can only delete its original session, credentials, and registration; it cannot authenticate other APIs or refresh a session. Cleanup captures both the saved proof and the original login's current refresh secret before rotation. The saved proof can be absent or stale when an older Server replaced sessions during an iOS-first upgrade. If the saved proof is rejected, the client tries only the captured refresh secret at the deletion endpoint, without adopting a replacement login's credentials. An absent proof is saved before networking. Revocation can verify that secret before consumption; every successful rotation also retains the consumed secret's hash in the same transaction. Verification and deletion are serialized with rotation, so cleanup remains possible whether it reaches the Server before consumption or after a committed response is lost. These retained hashes grant deletion of only the original session and are removed with that session. They never grant ordinary API access or allow reuse of a consumed refresh token. Other installations, users, and replacement logins remain independent. Recovery through `/api/mobile/auth/recover` uses the same login rate limit and accepts exactly one authentication mode: `username` plus `password` (with `totp_code` when enabled), `pairing_code` from the authenticated web QR pairing flow, or a previously returned `recovery_token`. Password fields are optional for QR/grant mode; mixing modes or supplying TOTP with QR/grant is rejected. The QR code is consumed once by recovery and cannot also be redeemed for a normal login. Account security changes after code generation or grant creation invalidate that authority; password onboarding remains enforced. Every request includes `expected_user_id`, `installation_id`, optional `expected_session_id`, and captured original access/refresh/revocation material. Recovery issues no login tokens. Expired material may identify an original session, but cannot authenticate ordinary requests. A response contains `outcome`, `user_id`, `installation_id`, nullable `mobile_session_id`, and `candidates`. Outcomes `ok` and `already_absent` confirm cleanup and return no candidates. Without a known ID or matching material, remaining scoped sessions produce `selection_required` with device names, session IDs, creation dates and last-use dates, including expired sessions. No session is selected or removed automatically. The user chooses the original session, and another reauthenticated request uses that exact `expected_session_id`. Absence without an ID is confirmed only when the original account and installation have no remaining operational state. Unattributed state or more than 64 candidates returns 409 without discarding records. Recovery never chooses the newest session or deletes all sessions for an installation. QR/grant responses also contain a cleanup-only `recovery_token`; password responses omit it. This random grant expires five minutes after creation, is kept only in client memory, and is lost on Server restart. The Server retains at most 128 grants, cleans expired entries, and returns 429 at capacity. It is bound to the original account, installation, and exact captured access/refresh/revocation material, and cannot authorize ordinary login, pairing, refresh or API calls. Reuse it with unchanged captured material for session selection and retries. Selection is restricted to the initially offered candidates and pinned before deletion; retries cannot change the target or broaden the list. A terminal grant retains the exact resolved target until expiry, so lost-response retries confirm only that target’s absence. An ID-less absence grant cannot acknowledge or clean up later replacement state. After restart, expiry, or new state following an ID-less absence, scan a fresh web QR code. ## Authenticated Read Endpoints [#authenticated-read-endpoints] These endpoints require a user credential. Admin and Member users can use the listed read operations, plus operations that apply only to the signed-in account. API keys receive the same access as their owning users. Admin-only management endpoints are listed separately below. | Family | Representative endpoints | | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Current user and credentials | `GET /api/auth/me`, `PUT /api/auth/password`, `GET /api/auth/api-keys`, `DELETE /api/auth/api-keys/{id}` | | 2FA and OAuth accounts | `/api/auth/2fa/*`, `GET/DELETE /api/auth/oauth/accounts/*` | | Mobile sessions and devices | `POST /api/mobile/auth/logout`, `GET /api/mobile/auth/devices`, `DELETE /api/mobile/auth/devices/{id}`, `POST /api/mobile/pair`, `POST /api/mobile/push/register`, `POST /api/mobile/push/unregister` | | Servers | `GET /api/servers`, `GET /api/servers/{id}`, `GET /api/servers/{id}/records`, `GET /api/servers/{id}/gpu-records` | | Agent Authority | `GET /api/servers/{id}/agent-authority`, `GET /api/agent-authority/events?server_id={id}` | | Groups and tags | `GET /api/server-groups`, `GET /api/servers/{id}/tags` | | Uptime and traffic | `GET /api/servers/{id}/uptime-daily`, `GET /api/servers/{id}/traffic` | | GeoIP | `GET /api/geoip/status` | | Ping tasks | `GET /api/ping-tasks`, `GET /api/ping-tasks/{id}/records` | | Network probes | `GET /api/network-probes/targets`, `GET /api/network-probes/setting`, `GET /api/network-probes/overview`, `GET /api/servers/{id}/network-probes/targets`, `GET /api/servers/{id}/network-probes/records`, `GET /api/servers/{id}/network-probes/summary`, `GET /api/servers/{id}/network-probes/anomalies` | | Traceroute results | `GET /api/servers/{id}/traceroute/{request_id}` | | Docker, read-only | `GET /api/servers/{id}/docker/containers`, `stats`, `info`, `events`, `networks`, `volumes` | | Service monitors | `GET /api/service-monitors`, `GET /api/service-monitors/{id}`, `GET /api/service-monitors/{id}/records` | | Status page config | `GET /api/status-page` | | Dashboards | `GET /api/dashboards`, `GET /api/dashboards/default`, `GET /api/dashboards/{id}` | | Cost insights | `GET /api/cost/overview`, `GET /api/servers/{id}/cost-insights` | | Alert events | `GET /api/alert-events`, `GET /api/alert-events/{alert_key}` | ## Admin Write and Management Endpoints [#admin-write-and-management-endpoints] The following management and host-control operations require Admin. Operations listed above that apply only to the signed-in account remain available to authenticated Member users. | Family | Representative endpoints | | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | API key creation | `POST /api/auth/api-keys` | | Server management | `POST /api/servers` (idempotent onboarding), `PUT/DELETE /api/servers/{id}`, `POST /api/servers/{id}/upgrade` | | Agent Authority | re-enrollment, offer issue/replace/revoke, and authority revocation under `/api/servers/{id}/agent-authority/*` | | Groups and tags | `POST /api/server-groups`, `PUT/DELETE /api/server-groups/{id}`, `PUT /api/servers/{id}/tags` | | Ping and network probes | `POST /api/ping-tasks`, `PUT/DELETE /api/ping-tasks/{id}`, `POST /api/network-probes/targets`, `PUT/DELETE /api/network-probes/targets/{id}`, `PUT /api/network-probes/setting`, `PUT /api/servers/{id}/network-probes/targets` | | Traceroute | `POST /api/servers/{id}/traceroute` | | Files | All `/api/files/*` endpoints, including list, stat, read, download, transfer management, and file changes | | Docker actions | `POST /api/servers/{id}/docker/containers/{cid}/action` | | Service monitors | `POST /api/service-monitors`, `PUT/DELETE /api/service-monitors/{id}`, `POST /api/service-monitors/{id}/check` | | Dashboards | `POST /api/dashboards`, `PUT/DELETE /api/dashboards/{id}` | | Brand appearance | `PUT /api/settings/brand`, `POST /api/settings/brand/logo`, `POST /api/settings/brand/favicon` | | Status page | `PUT /api/status-page` | | Incidents | `GET/POST /api/incidents`, `PUT/DELETE /api/incidents/{id}`, `POST /api/incidents/{id}/updates` | | Maintenance windows | `GET/POST /api/maintenances`, `PUT/DELETE /api/maintenances/{id}` | | Alerts and notifications | Manage `/api/alert-rules`, `/api/notifications`, `/api/notification-groups`, and their `/{id}` routes | | Tasks | `GET/POST /api/tasks`, `GET/PUT/DELETE /api/tasks/{id}`, `GET /api/tasks/{id}/results`, `POST /api/tasks/{id}/run` | | Users | `GET/POST /api/users`, `GET/PUT/DELETE /api/users/{id}` | | Audit and settings | `GET/DELETE /api/audit-logs`, `GET/PUT /api/settings`, `POST /api/settings/backup`, `POST /api/settings/restore` | | GeoIP | `POST /api/geoip/download` | The backup endpoint returns a raw SQLite download. Restore accepts a raw SQLite body with `Content-Type: application/octet-stream` and requires a Server restart. Both are Admin-only, high-risk operations; see [Backup and Restore](/en/docs/deployment#backup-and-restore) for verified commands and safeguards. ## Agent Authority and Enrollment [#agent-authority-and-enrollment] Responses follow the standard `{ "data": ... }` envelope. Agent Authority management requires Admin; state and event history are authenticated read endpoints. ### `POST /api/servers` -- Onboard a Server [#post-apiservers----onboard-a-server] Creates the Server profile and its initial bound offer atomically. `onboarding_request_id` is required and is scoped to the authenticated actor. Retrying the same ID with identical normalized input returns the same `server_id`; reusing it with different input returns `409 ONBOARDING_IDEMPOTENCY_CONFLICT`. ```json { "onboarding_request_id": "019f...", "name": "edge-tpe-1", "tags": ["production"], "ttl_secs": 600 } ``` A new operation returns `replayed: false` and `enrollment: { id, code, code_prefix, expires_at }`. A replay returns `replayed: true`, never returns plaintext `code`, and may return `outstanding_offer` metadata. ### `POST /api/agent/register` -- Claim an offer [#post-apiagentregister----claim-an-offer] This public Agent endpoint authenticates with `Authorization: Bearer ` and requires an Agent-generated token: ```json { "proposed_run_token": "<32-or-more non-whitespace characters>" } ``` On success it consumes the offer, stores only the run-token hash, and returns `{ "data": { "server_id": "..." } }`. The Server never returns the plaintext run token. ### Agent Authority state and history [#agent-authority-state-and-history] * `GET /api/servers/{id}/agent-authority` returns `status` (`claimed` or `unclaimed`) and the current `outstanding_offer`, if any. * `GET /api/agent-authority/events?server_id={id}&limit=100` returns secret-free transition history. Events survive Server deletion. ### Offer and authority actions [#offer-and-authority-actions] * `POST /api/servers/{id}/agent-authority/re-enrollment` with `{ "mode": "graceful" | "emergency", "ttl_secs": 600 }` starts re-enrollment for a claimed authority. Graceful keeps the old authority valid; emergency revokes and fences it immediately. * `POST /api/servers/{id}/agent-authority/offers` with optional `{ "ttl_secs": 600 }` issues an offer only when authority is unclaimed. * `POST /api/servers/{id}/agent-authority/offers/{offer_id}/replace` replaces exactly the current outstanding offer and returns new plaintext once. * `DELETE /api/servers/{id}/agent-authority/offers/{offer_id}` revokes exactly that offer. * `DELETE /api/servers/{id}/agent-authority` revokes the current authority and disconnects its Agent without issuing an offer. Only one offer may be outstanding per Server. Its terminal outcome is exactly one of `consumed`, `revoked`, `replaced`, or `expired`. Exact-ID replacement is compare-and-swap: stale or terminal offers return `409` and cannot overwrite newer state. ## WebSocket Endpoints [#websocket-endpoints] | Path | Auth | Description | | --------------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------ | | `/api/agent/ws` | Agent Bearer token (`?token=` accepted for legacy Agents) | Agent metrics, commands, pings, files, Docker, traceroute | | `/api/ws/servers` | Session cookie, API key, or Bearer token | Browser/mobile real-time server updates | | `/api/ws/terminal/{server_id}` | Authenticated Admin + `CAP_TERMINAL` | Web terminal proxy; JSON text messages with terminal data base64-encoded | | `/api/ws/docker/logs/{server_id}` | Authenticated Admin + `CAP_DOCKER` | Per-container Docker log streaming | ## Common Status Codes [#common-status-codes] | Code | Meaning | | ---- | ----------------------------------------------------------------------- | | 400 | Bad request or invalid operation | | 401 | Not authenticated | | 403 | Forbidden: role, capability, or local agent policy blocks the operation | | 404 | Resource not found, server offline, or disabled public page | | 409 | Conflict, such as duplicate slug/name | | 422 | Validation error | | 429 | Rate limited | | 500 | Internal server error | # Resource Usage > Historical CPU, memory, disk, and network observations for the ServerBee Agent and Server. URL: https://docs.serverbee.app/en/docs/resource-usage This page preserves historical resource observations for the ServerBee **Agent** and **Server**. They are not benchmarks of the current release. For database growth estimates, see [Storage & Capacity Planning](/en/docs/storage-sizing). Source: the measurement notes originally recorded on this page for v0.9.3, dated 2026-05-19 unless noted otherwise. Raw benchmark logs and exact binary linkage were not archived with those notes. Memory/CPU readings are historical observations; the Agent network figure is a protocol-based estimate. Measure your deployed version, allocator, Agent count, enabled collectors, and query load before setting resource limits. ## Agent [#agent] The original notes describe a 4-core AMD EPYC 7B13 / 8 GB / Ubuntu 24.04 (KVM) host, Agent v0.9.3, a 3-second collection interval, temperature collection enabled, and reporting to a remote Server over an HTTPS WebSocket. | Resource | Cold start (\~90s) | After 8h | Scope | | ----------------------- | ----------------------- | --------------------- | --------------------------------------------------------------------------------- | | Memory (systemd cgroup) | \~4.3 MB, peak \~5.1 MB | \~27 MB, peak \~28 MB | Cgroup accounting; a different scope from process RSS | | Memory (`ps` RSS) | \~10 MB | \~34 MB | Process resident memory, including shared mappings | | CPU | \~0.7% (single core) | \~0.5% (single core) | Observed collection workload | | Threads | 10 | 9–10 | Observed tokio runtime | | Binary size | \~11 MB | — | Historical linux-amd64 binary | | Network | Estimated ≤ 1–2 KB/s | — | One `SystemReport` JSON per 3-second interval plus ping/pong; varies with metrics | Memory increased during the eight-hour observation. Those readings alone do not establish a long-term plateau or distinguish allocator retention from a leak. The recorded host OS does not establish which libc the binary used. ## Server [#server] ### Historical Railway observation [#historical-railway-observation] The original page recorded roughly 140–170 MB with a small number of Agents and `MALLOC_ARENA_MAX=2` present in the environment. It also reported 800 MB–1 GB after 2–3 days in another run. The notes do not capture exact build linkage, Agent count, query load, or a controlled comparison. These are historical readings, not evidence that the environment variable caused the difference or a memory target for the current release. ### Historical self-hosted idle observation [#historical-self-hosted-idle-observation] The v0.9.3 notes describe a near-idle Server on the same VPS with no active Agent connections: | Resource | Observed | Scope | | ----------------------- | ----------------------- | ------------------------------------------------------------ | | Memory (systemd cgroup) | \~15 MB, peak \~23.8 MB | Historical idle observation, not a loaded-server requirement | | Threads | 7 | Observed tokio runtime | | CPU | Near zero | Idle workload | | Binary size | \~47 MB | Historical linux-amd64 binary with the frontend SPA embedded | ### Allocator and build scope [#allocator-and-build-scope] `MALLOC_ARENA_MAX` applies to glibc's allocator. Official Linux release binaries and Docker images use musl, where this variable has no effect. The standard deployment examples therefore do not set it. For a custom glibc build, assess allocator behavior against that build's measurements rather than applying the old Railway numbers. A separate release-toolchain regression affected `v1.0.0-beta.1`: the recorded idle Server grew about 20 MB per day because of cross-thread free behavior in Zig 0.16's bundled allocator. The release workflow now pins Zig 0.15.2; the correction shipped in `v1.0.0-beta.2`. Its cause, observation scope, and reproduction procedure are recorded in the [release memory soak runbook](https://github.com/ZingerLittleBee/ServerBee/blob/main/tests/manual/server-memory-soak.md). This is a distinct observation from the v0.9.3 figures above. Use a current release and investigate sustained growth on the actual deployed build. ### Disk [#disk] Server disk use includes SQLite, uploaded brand images, downloaded GeoIP/ASN MMDB files, and configuration. Database growth depends on Agent count, enabled features, and retention. See [Storage & Capacity Planning](/en/docs/storage-sizing) for database estimates and [Backup and Restore](/en/docs/deployment#persistent-data) for files outside SQLite. Reserve space for WAL, bursts, and complete backup sets in addition to the base database. ## Caveats [#caveats] * The historical idle Server readings do not describe many Agents or heavy query load. * Cgroup memory and process RSS measure different scopes; compare like with like. * Agent network usage is an estimate, not a captured traffic benchmark. * A steady eight-hour reading does not prove long-term stability. Investigate unexplained growth with workload and allocator context. ## Related Docs [#related-docs] # Storage & Sizing > Database sizing formulas and 30-day storage estimates for common ServerBee deployments. URL: https://docs.serverbee.app/en/docs/storage-sizing This page explains how ServerBee grows its SQLite database over time and gives a 30-day estimate for common 20-server deployments. These numbers were measured in April 2026 from temporary SQLite databases generated with the repository schema and retention defaults at that time. Use this page as the dated sizing reference, not as a guarantee for later schemas or workloads. The results describe the main database file after 30 days from a fresh deployment. In production, reserve another 10% to 20% for SQLite WAL headroom and transient growth. ## Assumptions [#assumptions] All estimates on this page use the same baseline: * **20 servers** * **30 days** from a fresh deployment * Agents connected continuously * Agent report interval of **3 seconds** * Server raw metric writer persisting one record per server every **60 seconds** * Default retention: * Raw metrics: **7 days** * Hourly aggregates: **90 days** * GPU records: **7 days** * Ping records: **7 days** * Network probe raw records: **7 days** * Network probe hourly records: **90 days** * Service monitor records: **30 days** * Disk I/O enabled and temperature enabled * No extra growth from audit log bursts, large task output, or unusual Docker event churn unless called out explicitly ## What Actually Changes Database Size [#what-actually-changes-database-size] Not every capability creates ongoing database growth. | Feature | Default cadence | Retention | Main table(s) | Notes | | ------------------------------ | ------------------------------------------- | ------------------ | -------------------------------------------------------- | ------------------------------------------------- | | Base monitoring | Agent reports every 3s, persisted every 60s | 7d raw, 90d hourly | `records`, `records_hourly`, `traffic_*`, `uptime_daily` | Always present for connected servers | | Ping tasks | 60s per task | 7d | `ping_records` | Scales with task count x server count | | Network probes | 60s per target | 7d raw, 90d hourly | `network_probe_record`, `network_probe_record_hourly` | Scales with server-target assignments | | Service monitors | 300s per monitor | 30d | `service_monitor_record` | Scales with monitor count | | GPU monitoring | 3s per GPU device | 7d | `gpu_records` | This is one of the largest multipliers | | Docker events | Event-driven | 7d | `docker_event` | Small unless containers churn frequently | | Terminal / Exec / File Manager | User-driven | mixed | `audit_logs`, `task_results` | Usually negligible compared with time-series data | Turning on a capability such as Terminal, Exec, File Manager, or Docker Management does not automatically create large time-series tables. Continuous growth comes from features that are actively writing sampled results. ## 30-Day Sizing Formula [#30-day-sizing-formula] For the assumptions above, the 30-day database size can be estimated with: ```text S_30d = 70,541,312 + 48,974,507 × P + 2,373,018 × T + 1,593,958 × M + 41,525,248 × G + 285 × E ``` Where: * `P` = number of **60-second ping tasks** applied to all 20 servers * `T` = total **server-target network probe assignments** * If every server has the same number of targets, then `T = 20 × targets_per_server` * `M` = total **300-second service monitors** * `G` = total **GPU devices** * `E` = total **Docker events** written during the 30-day window ## Feature Multipliers [#feature-multipliers] These coefficients come from measured SQLite files, not rough guesswork: | Component | Exact size contribution | Approx size | | --------------------------------- | ----------------------- | ----------- | | Base monitoring for 20 servers | `70,541,312 B` | `67.27 MiB` | | 1 ping task across all 20 servers | `48,974,507 B` | `46.71 MiB` | | 1 network probe assignment | `2,373,018 B` | `2.26 MiB` | | 1 service monitor | `1,593,958 B` | `1.52 MiB` | | 1 GPU device | `41,525,248 B` | `39.60 MiB` | | 1 Docker event | `285 B` | `0.28 KiB` | ## Base Monitoring Breakdown [#base-monitoring-breakdown] With no extra ping tasks, network probes, service monitors, or GPUs, most of the 30-day footprint comes from the raw metrics table and its index: | Object | Exact size | Approx size | | -------------------------------- | -------------- | ----------- | | `records` | `48,701,440 B` | `46.45 MiB` | | `idx_records_server_id_time` | `16,482,304 B` | `15.72 MiB` | | `records_hourly` | `3,485,696 B` | `3.32 MiB` | | `idx_records_hourly_server_time` | `1,163,264 B` | `1.11 MiB` | | `traffic_hourly` + index | `512,000 B` | `0.49 MiB` | | `traffic_daily` + index | `90,112 B` | `0.09 MiB` | | `uptime_daily` + index | `86,016 B` | `0.08 MiB` | ## Scenario Catalog [#scenario-catalog] The table below lists common 30-day scenarios for a 20-server deployment. | Scenario | Assumptions | Exact size | Approx size | | ---------------------------- | ------------------------------------------------------------------------------- | ----------------- | ------------ | | Base monitoring only | No ping tasks, no network probes, no service monitors, no GPU | `70,541,312 B` | `67.27 MiB` | | Base + 1 ping task | 1 x 60s ping task across all 20 servers | `119,515,819 B` | `113.98 MiB` | | Base + 3 ping tasks | ICMP + TCP + HTTP across all 20 servers | `217,464,833 B` | `207.39 MiB` | | Base + 1 target per server | 20 total network probe assignments | `118,001,672 B` | `112.54 MiB` | | Base + 5 targets per server | 100 total network probe assignments | `307,843,112 B` | `293.58 MiB` | | Base + 20 targets per server | 400 total network probe assignments, which is the current max | `1,019,748,512 B` | `972.51 MiB` | | Base + 1 monitor per server | 20 total service monitors at 300s | `102,420,472 B` | `97.68 MiB` | | Base + 5 monitors per server | 100 total service monitors at 300s | `229,937,112 B` | `219.29 MiB` | | Base + 1 GPU per server | 20 total GPU devices | `901,046,272 B` | `859.30 MiB` | | Small production stack | 3 ping tasks + 1 target per server + 1 monitor per server | `296,804,353 B` | `283.05 MiB` | | Medium production stack | 3 ping tasks + 5 targets per server + 2 monitors per server | `518,524,953 B` | `494.50 MiB` | | Heavy stack without GPU | 3 ping tasks + 20 targets per server + 1 monitor per server | `1,198,551,193 B` | `1.12 GiB` | | Heavy stack with GPU | Heavy stack without GPU + 1 GPU per server | `2,029,056,153 B` | `1.89 GiB` | | Maxed practical stack | 3 ping tasks + 20 targets per server + 5 monitors per server + 1 GPU per server | `2,156,572,793 B` | `2.01 GiB` | ## Disk Budget Recommendation [#disk-budget-recommendation] If you are capacity-planning a real deployment, reserve more than the database file itself: * Use the table above for the **base database size** * Add **10% to 20%** for WAL growth and transient write bursts * Add extra space if you expect: * large Docker event volume * large task output retained in `task_results` * unusually chatty service monitors with large `detail_json` payloads Examples: * A `494.50 MiB` medium stack should reserve about **550 MiB to 600 MiB** * A `1.89 GiB` heavy stack with GPU should reserve about **2.1 GiB to 2.3 GiB** * A `2.01 GiB` maxed practical stack should reserve about **2.2 GiB to 2.5 GiB** ## Notes and Caveats [#notes-and-caveats] * These estimates are for the **first 30 days** after a fresh deployment. Some hourly tables retain up to 90 days, so a long-running deployment will continue to grow beyond the 30-day numbers shown here. * The base monitoring estimate already includes `traffic_hourly`, `traffic_daily`, and `uptime_daily`. * GPU growth is per **device**, not per server. A server with 4 GPUs multiplies the GPU portion by 4. * Network probe growth is per **assignment**, not per target definition. A target shared by 20 servers counts as 20 assignments. * Docker growth is event-driven. Quiet fleets add almost nothing; container-heavy hosts can add a measurable amount. ## Related Docs [#related-docs] # Architecture > Technical overview of ServerBee's system design, components, and protocols. URL: https://docs.serverbee.app/en/docs/architecture This page describes ServerBee's internal architecture for developers and operators who want to understand how the system works under the hood. ServerBee follows a hub-and-spoke design: a central **Server** receives metrics from distributed **Agents** over WebSocket, stores them in SQLite, and serves a React SPA dashboard. ## System Overview [#system-overview] ``` +--------------------------------------------------+ | ServerBee Dashboard | | | | +---------------------------------------------+ | | | Frontend (React SPA, embedded via rust-embed)| | | | React 19, TanStack Router/Query | | | | shadcn/ui, Tailwind CSS v4, Bklit charts | | | +---------------------------------------------+ | | +---------------------------------------------+ | | | Server (Rust) | | | | Axum Router | | | | +-- REST API handlers | | | | +-- WebSocket: Agent + Browser + Terminal | | | | +-- Static files (rust-embed) | | | | Service Layer | | | | +-- AgentManager (connections/state) | | | | +-- AlertService (rule evaluation) | | | | +-- RecordService (metrics/aggregation) | | | | +-- NotificationService (dispatch) | | | | Entity Layer (sea-orm) | | | | +-- SQLite | | | +---------------------------------------------+ | +--------------------------+-----------------------+ | WebSocket (JSON) +--------------------------v-----------------------+ | ServerBee Agent (Rust) | | common crate (shared types/protocol) | | +-- Collector (system metrics) | | +-- Reporter (WebSocket + reconnect) | | +-- Pinger (ICMP/TCP/HTTP probes) | | +-- TerminalManager (PTY sessions) | +---------------------------------------------------+ ``` ## Component Roles [#component-roles] ### Server (`crates/server`) [#server-cratesserver] The central hub that: * Serves the web dashboard (React SPA embedded via `rust-embed`) * Exposes REST API endpoints for CRUD operations * Manages WebSocket connections from agents and browsers * Stores all data in SQLite * Runs background tasks (record writing, aggregation, cleanup, alerting, offline detection) * Dispatches notifications Its main service modules are: | Module | Responsibility | | ------------------- | ----------------------------------------------------------------------------------- | | Axum Router | Routes and handles HTTP/WebSocket requests | | AgentManager | Manages agent WebSocket connections, the live metric cache, and update broadcasting | | AuthService | User login (argon2), session management, API key verification | | ServerService | Server CRUD, grouping, tagging, ordering | | RecordService | Metric writes, history queries, hourly aggregation, retention cleanup | | AlertService | Alert rule CRUD, periodic evaluation, notification dispatch | | NotificationService | Notification channel management and message dispatch (Webhook/Telegram/Email/Bark) | | PingService | Ping task CRUD, task assignment, result storage | | TaskService | Remote command dispatch, result queries | | ConfigService | System configuration read/write (key-value store) | | GeoIpService | IP geolocation lookup (MaxMind MMDB) | | UserService | User CRUD and role management | ### Agent (`crates/agent`) [#agent-cratesagent] A lightweight daemon deployed on each monitored server that: * Collects system metrics using the `sysinfo` crate * Reports metrics to the server over WebSocket every N seconds * Runs ping probes as instructed by the server * Provides PTY terminal sessions for remote shell access * Executes remote commands dispatched by the server * Supports self-upgrade when instructed Its main modules are: | Module | Responsibility | | ------------- | ---------------------------------------------------------------------------------------------------- | | Collector | Periodically collects CPU, memory, disk, network, and other metrics via `sysinfo` | | Reporter | Manages the WebSocket connection, reports metrics, handles server commands, reconnects on disconnect | | PingManager | Runs scheduled ICMP/TCP/HTTP ping tasks dispatched by the server | | NetworkProber | Runs scheduled network quality probes and reports aggregate results | | Terminal | Manages PTY terminal sessions and forwards terminal I/O | ### Common (`crates/common`) [#common-cratescommon] A shared library crate containing the types and protocol shared between the agent and server: * **Protocol definitions** -- `AgentMessage`, `ServerMessage`, `BrowserMessage` enums * **Data types** -- `SystemInfo`, `SystemReport`, `GpuReport`, `PingResult`, `ServerStatus`, etc. * **Constants** -- Protocol version, default ports, timeouts, retention periods, alert parameters ### Frontend (`apps/web`) [#frontend-appsweb] A React 19 single-page application, embedded into the server binary via `rust-embed` after build: * **Routing** -- TanStack Router (file-based) * **Data fetching** -- TanStack Query for REST, native WebSocket for real-time updates (sharing the same cache) * **UI components** -- shadcn/ui with the base-nova theme * **Styling** -- Tailwind CSS v4 * **Charts** -- Bklit chart primitives (visx) behind shared wrappers in `components/charts/` * **Terminal** -- xterm.js web terminal emulator * **Build** -- Vite 7 The built frontend is embedded into the server binary at compile time, so there is nothing to deploy separately. ## Communication Protocol [#communication-protocol] Agent reporting and control traffic uses WebSocket. Browsers use REST for initial data, history, and management operations, plus WebSocket for real-time updates. WebSocket application messages are JSON text frames; terminal I/O data is carried inside the message's `data` field, base64-encoded. The current Agent protocol version is `6`, sent in the `Welcome` message on connect. The agent connects to `ws:///api/agent/ws` and authenticates with `Authorization: Bearer `. The Server temporarily accepts the legacy `?token=` form so older Agents can reconnect and upgrade. ### Agent to Server (`AgentMessage`) [#agent-to-server-agentmessage] | Message Type | Purpose | Needs ACK | | ------------------ | --------------------------------------------------------- | --------- | | `SystemInfo` | Static system information, sent once on connect/reconnect | Yes | | `Report` | Periodic metric report (every N seconds) | No | | `PingResult` | Result of a ping probe | No | | `TaskResult` | Output from a remote command execution | Yes | | `TerminalOutput` | PTY output data (base64-encoded) | No | | `TerminalStarted` | Confirmation that a PTY session was created | No | | `TerminalError` | Error from a terminal session | No | | `Pong` | Protocol-level heartbeat response | No | | `DockerInfo` | Docker system info and feature report | No | | `DockerContainers` | Current container list with status | No | | `DockerStats` | Container resource usage statistics | No | | `DockerLog` | Container log entries (batched) | No | | `DockerEvent` | Container lifecycle event | No | ### Server to Agent (`ServerMessage`) [#server-to-agent-servermessage] | Message Type | Purpose | | ----------------- | -------------------------------------------------------------------- | | `Welcome` | Sent on connect with server\_id, protocol\_version, report\_interval | | `Ack` | Acknowledges a received message (by msg\_id) | | `PingTasksSync` | Sync all assigned ping tasks to the agent | | `Exec` | Execute a shell command on the agent | | `TerminalOpen` | Request to open a new PTY session | | `TerminalInput` | Forward keyboard input to a PTY session | | `TerminalResize` | Resize a PTY session | | `TerminalClose` | Close a PTY session | | `Ping` | Protocol-level heartbeat (every 30s) | | `Upgrade` | Instruct the agent to self-upgrade | | `DockerLogsStart` | Start streaming logs for a container | | `DockerLogsStop` | Stop streaming logs | | `DockerAction` | Execute container action (start/stop/restart/remove) | ### Server to Browser (`BrowserMessage`) [#server-to-browser-browsermessage] Browsers obtain data two ways: the REST API for initial load and historical queries, and a WebSocket at `ws:///api/ws/servers` for real-time pushes. The WebSocket is one-way (Server -> Browser). REST results and WebSocket messages are projected into the same live server catalog so views stay current. | Message Type | Purpose | | --------------------------- | ---------------------------------------------------------- | | `FullSync` | Complete state of all servers (sent on browser connect) | | `Update` | Incremental update with latest metrics for changed servers | | `ServerOnline` | Notification that a server came online | | `ServerOffline` | Notification that a server went offline | | `DockerUpdate` | Docker container and stats update | | `DockerEvent` | Docker container lifecycle event | | `DockerAvailabilityChanged` | Docker daemon availability changed | ### Handshake [#handshake] ``` Agent Server | | |--- WebSocket connect + token --->| | | verify token, look up server |<-- Welcome { server_id, | | protocol_version: 6, | | report_interval: 3 } -------| | | |--- SystemInfo { cpu_name, | static info on connect/reconnect | os, mem_total, ... } ------>| |<-- Ack { msg_id } -------------| | | |--- Report (every 3s) ---------->| periodic metric report ``` ### Message Size Limits [#message-size-limits] | Limit | Maximum Size | | ---------------------- | ------------ | | WebSocket message size | 1 MB | | Command output | 512 KB | | Command length | 8 KB | ### Terminal Data Transport [#terminal-data-transport] Terminal traffic uses the same JSON text protocol as the rest of the system, relayed through the server: ``` Browser <--JSON WebSocket--> Server <--JSON WebSocket--> Agent (PTY) ``` The raw PTY byte stream is base64-encoded into the `data` field of a JSON message, because terminal output is not UTF-8 safe. `Browser <-> Server` messages (all JSON text): * Browser -> Server: `{ "type": "input", "data": }`, `{ "type": "resize", "rows": , "cols": }` * Server -> Browser: `{ "type": "session", "session_id": }`, `{ "type": "started" }`, `{ "type": "output", "data": }`, `{ "type": "error", "error": }` `Server <-> Agent` messages reuse the agent protocol: * Server -> Agent: `ServerMessage::TerminalOpen`, `TerminalInput` (`data` base64), `TerminalResize`, `TerminalClose` * Agent -> Server: `AgentMessage::TerminalStarted`, `TerminalOutput` (`data` base64), `TerminalError` ## Database Design [#database-design] ServerBee uses SQLite (WAL mode), managed through sea-orm. The representative tables below cover the core schema, grouped by category. ### Authentication [#authentication] | Table | Purpose | | ---------------- | --------------------------------------------------------------------- | | `users` | User accounts (id, username, argon2 password hash, role, TOTP secret) | | `sessions` | Active login sessions (token, IP, user agent, expiry) | | `api_keys` | API key credentials (argon2-hashed key, prefix, last used) | | `oauth_accounts` | Linked OAuth provider accounts | ### Server Management [#server-management] | Table | Purpose | | --------------- | --------------------------------------------------------------------------------- | | `servers` | Registered servers (token hash, system info, metadata, pricing, group assignment) | | `server_groups` | Logical groups for organizing servers | | `server_tags` | Tag labels attached to servers (many-to-many) | | `configs` | Key-value configuration store (runtime settings, etc.) | ### Monitoring Data [#monitoring-data] | Table | Purpose | | ---------------- | ------------------------------------------------------------------------------------------ | | `records` | Raw metric records, one row per server per minute (composite index server\_id + time) | | `records_hourly` | Hourly aggregated metric averages (same shape as `records`) | | `gpu_records` | Per-GPU device metrics (device index, name, memory, utilization, temp) | | `ping_records` | Ping probe results (latency, success, error; composite index task\_id + server\_id + time) | | `ping_tasks` | Ping task definitions (probe type, target, interval, assigned servers) | | `traffic_hourly` | Hourly traffic byte counters (in/out) per server | | `traffic_daily` | Daily traffic byte counters (in/out) per server | | `traffic_state` | Last-known cumulative traffic counters for delta calculation | | `docker_events` | Docker container lifecycle events (start/stop/die/create) | ### Alerting & Operations [#alerting--operations] | Table | Purpose | | --------------------- | --------------------------------------------------------------------------------------- | | `alert_rules` | Alert rule definitions (conditions, cover type, trigger mode) | | `alert_states` | Current alert state per rule/server pair (composite unique index rule\_id + server\_id) | | `notifications` | Notification channel configs (webhook, telegram, bark, email) | | `notification_groups` | Groups of notification channels | | `tasks` | Remote command tasks (command, target servers, created by) | | `task_results` | Command execution results (output, exit code) | | `audit_logs` | User action audit trail (action, detail, IP) | All tables use UTC timestamps. IDs are UUIDs (string type) for most entities, and auto-incrementing integers for high-volume records. ### SQLite Configuration [#sqlite-configuration] * **WAL mode** -- `PRAGMA journal_mode=WAL` runs on startup to improve concurrent read performance. * **Busy timeout** -- 5000ms; writers wait automatically on write contention. * **Synchronous mode** -- `PRAGMA synchronous=NORMAL`. * **Connection pool** -- Up to 10 connections, primarily for concurrent reads. ## Background Tasks [#background-tasks] The server spawns several long-running background tasks via `tokio::spawn`: | Task | Interval | Description | | ------------------ | -------------------------- | ---------------------------------------------------------------------------------------------------------- | | **RecordWriter** | 60s | Flushes the latest cached agent reports from memory to the `records` table | | **AlertEvaluator** | 60s (after record write) | Evaluates all enabled alert rules against current data and dispatches notifications | | **OfflineChecker** | 10s | Scans agent connection state and triggers offline events/alerts for agents with no heartbeat in 30 seconds | | **Aggregator** | 1 hour | Computes hourly averages from raw `records` into `records_hourly` for long-term storage | | **Cleanup** | 1 hour (after aggregation) | Deletes records, GPU data, ping records, and audit logs older than their retention periods | | **PingTasksSync** | On config change | Pushes `PingTasksSync` to the relevant agents | | **SessionCleaner** | 1 hour | Removes expired user login sessions | **Ordering guarantees:** * Hourly tasks: aggregation -> cleanup (aggregate before cleaning so no long-term data is lost). * Minute tasks: record write -> alert evaluation (so evaluation reads the freshly written data). ## Security Model [#security-model] ### Authentication Paths [#authentication-paths] User-facing REST and WebSocket requests resolve a user from a session cookie, API key, or Bearer session token. The authorization check then uses that user's current role. Agent WebSocket connections use a separate per-Agent run token. ``` Request arrives +-- Cookie: session_token=xxx --> Session auth (browser) +-- Header: X-API-Key: serverbee_xxx --> API key auth (automation) +-- Header: Authorization: Bearer xxx --> Session auth (mobile) Agent WebSocket connects +-- Header: Authorization: Bearer xxx --> Agent run-token auth +-- Query: ?token=xxx --> Legacy Agent compatibility only ``` #### Session-based (browser) [#session-based-browser] * User logs in with username/password, verified with argon2. * A random 32-byte token (base64url-encoded) is generated. * The token is stored in the `sessions` table with an HttpOnly + SameSite=Strict cookie. * Sliding expiry: each valid request extends `expires_at` (default TTL 24 hours). #### API Key (automation) [#api-key-automation] * Admin users can create named API keys for programmatic access. * Key format: `serverbee_` + 32 random bytes (base64url). * Keys are hashed with argon2; an 8-character plaintext prefix is stored for identification. * On verification, the prefix narrows the lookup, then argon2 verifies the key. * A key authenticates as its owning user and inherits that user's current `admin` or `member` role. An API key is a credential type, not a third role. #### Agent Token (agents) [#agent-token-agents] * An enrollment offer is bound to one Server, single-use, and short-lived. Its lifecycle is owned by the Agent Authority module. * Before claiming an offer, the Agent generates and persists its run token, then submits it as `proposed_run_token`. The Server never generates or returns that token. * The run token is hashed with argon2 and stored in the `servers` table; only the Agent retains the plaintext. * Agents authenticate with the `Authorization: Bearer ` header. Current Agents never place the raw token in the URI; query-token authentication remains a deprecated compatibility path for older releases. * WebSocket admission has two stages: preflight validation before the HTTP upgrade, then final validation while holding the Server lifecycle lock. Authority transitions and frame dispatch share that lock, so a revoked or replaced connection cannot race through after fencing. * Every authority/offer transition appends a secret-free event. Events retain a snapshot of the Server identity and survive Server deletion. ### Role-Based Access [#role-based-access] Two user roles exist: `admin` (full access) and `member` (read-only dashboard access). Session cookies, Bearer session tokens, and API keys are credentials for one of these users; they do not define additional roles. Browser-facing WebSockets re-check the persisted session or API key and the user's role while connected. Logout, API-key deletion, password-reset session revocation, account deletion, or a role change closes the existing socket within two seconds instead of preserving handshake-time privileges. | Resource | Admin user | Member user | Agent | | --------------------------- | ---------- | ------------- | ----- | | Server list/detail | Yes | Yes (limited) | - | | Server create/update/delete | Yes | No | - | | Alert rule management | Yes | No | - | | Notification config | Yes | No | - | | User management | Yes | No | - | | System settings | Yes | No | - | | Real-time server updates | Yes | Yes | - | | File Manager | Yes | No | - | | Web terminal | Yes | No | - | | Remote commands | Yes | No | - | | Metric reporting | - | - | Yes | ### OAuth [#oauth] OAuth login is supported for GitHub, Google, and generic OIDC providers. The `allow_registration` flag controls whether new users are automatically created on first OAuth login. ### Rate Limiting [#rate-limiting] Implemented as `tower` middleware with an in-memory map of IP to counter. Login and agent registration endpoints are rate-limited per IP to prevent brute-force attacks. | Endpoint | Limit | | -------------------------- | -------------------------------------------- | | `POST /api/auth/login` | 5 attempts per IP within a 15-minute window | | `POST /api/agent/register` | 10 attempts per IP within a 15-minute window | Exceeding a limit returns `429 Too Many Requests`. Admins can clear an active window from Settings → Rate limits. ### TOTP [#totp] Users can enable TOTP-based two-factor authentication for additional security. ## Workspace Structure [#workspace-structure] The project uses a Cargo workspace for the Rust crates. The frontend is a separate Bun/Node project under `apps/web/`. ``` ServerBee/ Cargo.toml # Workspace root crates/ common/ # Shared: protocol, data types, constants src/ lib.rs protocol.rs # Agent <-> Server message types types.rs # SystemReport, GpuInfo, etc. constants.rs # Protocol version, default values server/ # Server binary (Axum, sea-orm, background tasks) src/ main.rs config.rs state.rs # AppState router/ # REST API + WebSocket handlers service/ # Business logic entity/ # sea-orm entities (one module per table) migration/ # Database migrations middleware/ # Auth, logging middleware agent/ # Agent binary (collection, reporting, control) src/ main.rs config.rs collector/ # Per-metric collectors reporter/ # WebSocket reporting, reconnect, commands, and file operations pinger.rs # ICMP/TCP/HTTP ping tasks probe_utils.rs # Shared probe validation and networking helpers network_prober.rs # Scheduled network probes traceroute.rs # Traceroute execution terminal.rs # PTY terminal file_manager.rs # Restricted remote file operations docker/ # Docker inspection, actions, and log streaming security/ # Host security event detection firewall/ # Firewall block management ip_quality/ # IP quality checks upgrade.rs # Self-upgrade coordinator upgrade/ # Transactional self-upgrade support apps/ web/ # React frontend (Vite, TanStack, shadcn/ui) docs/ # Documentation site (TanStack Start + Fumadocs) docs/ # Design documents and plans ``` # Testing & Quality > How ServerBee verifies Rust, WebSocket, and frontend behavior with layered automated tests and path-aware CI gates. URL: https://docs.serverbee.app/en/docs/testing ServerBee treats reliability as a feature. The codebase ships with a large, fast automated test suite that exercises the full stack — from pure Rust logic to the live WebSocket control plane and the React UI. CI applies the relevant quality gates based on the event and changed paths. ## Test layers at a glance [#test-layers-at-a-glance] | Area | What it covers | | --------------------- | --------------------------------------------------------------------------------------------------- | | `common` crate | Protocol messages, capability bitmask, SSRF guard, shared types | | `agent` crate | Collectors, reporter WebSocket loop, pinger, file manager, security detectors, firewall, IP quality | | `server` crate | REST handlers, WebSocket handlers, services, background tasks, migrations | | Frontend (`apps/web`) | Hooks, components, stores, and utilities (Vitest) | Together, these layers contain **more than 3,800 automated tests**. The exact total changes as coverage grows, so this page documents the testing layers and enforced commands instead of a point-in-time count for each area. ## How we test [#how-we-test] ServerBee follows a layered strategy that matches each layer to the right kind of test. ### Unit tests [#unit-tests] Pure logic is tested next to the code in inline `#[cfg(test)]` modules: protocol (de)serialization, the capability bitmask, the SSRF guard, alert evaluation, cost math, parsers, and the collectors. These run in milliseconds and make up the bulk of the agent and `common` suites. ### Integration tests [#integration-tests] The server integration suites drive the **real Axum router over HTTP and WebSocket** against a freshly migrated, isolated SQLite database. A purpose-built **mock-agent harness** (`crates/server/tests/common/mod.rs`) stands up a simulated agent on a real WebSocket connection, so the control plane is exercised end-to-end without a live host: * terminal relay, Docker log streaming, file operations, and scheduled-task dispatch * real-time browser fan-out (initial full sync plus incremental updates) * security events, alerts, capability gating, and Admin/Member RBAC On the agent side, the reporter's connect → handshake → dispatch → reconnect loop is driven against an **in-process fake WebSocket server**, so the networking core is tested without a real backend. ### Frontend tests [#frontend-tests] The React app is covered by [Vitest](https://vitest.dev) across hooks, components, state stores, and utilities. Run the frontend suite with `make web-test`. The integration harness lets contributors verify real request/response and WebSocket flows on a laptop, with no agent, Docker daemon, or external service required. ## Coverage [#coverage] Coverage is measured with [`cargo-llvm-cov`](https://github.com/taiki-e/cargo-llvm-cov): | Crate | Region | Line | | -------- | ------ | ---- | | `common` | 98% | 97% | | `agent` | 90% | 90% | | `server` | 92% | 93% | Overall Rust region coverage is above 91%. The largest remaining gaps are concentrated in process startup and I/O paths that need a live Docker daemon, Linux kernel facilities (nftables / conntrack / journald), raw ICMP sockets, a PTY, or external services (OAuth, SMTP, APNs). Those environment-dependent paths are exercised through the integration harness and the manual end-to-end checklists in `tests/` where practical. ## Running tests locally [#running-tests-locally] ```bash make cargo-test # Rust: unit + integration across the workspace make web-test # Frontend: Vitest make cargo-clippy # Rust lint (0 warnings enforced) ``` Measure coverage for a single crate: ```bash cargo llvm-cov -p serverbee-server --summary-only ``` ## CI gates [#ci-gates] The main CI workflow runs for pushes and pull requests to `main`. Markdown, MDX, `docs/`, and iOS-only changes do not start it. For everything else, a first job compares the changed files with the base commit and runs only the gates for the areas they touch: | Area | Changed paths | Gates | | --------- | ----------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | | Rust | `crates/`, Cargo manifests, `.cargo/`, the toolchain file, built-in widget sources and the web build config | `cargo check`, zero-warning Clippy, the Rust test jobs | | Web | `apps/web/`, `packages/`, root `package.json`, `bun.lock`, `tsconfig`, `turbo.json` | frontend build, type checking, Vitest | | Lint | any file Biome handles (JS/TS, JSON, CSS) or `biome.json` | Ultracite lint | | Installer | `deploy/install.sh`, `scripts/extract-changelog.sh`, and their tests | installer transaction tests plus POSIX, BusyBox, and ShellCheck validation | The server integration tests exercise the embedded SPA and built-in widgets, so widget and web build config changes also run the Rust gates. A change to any workflow file, a new branch, or a push without a usable base commit runs every gate. The iOS workflow runs for `apps/ios/` changes other than Markdown, and the documentation workflow for `apps/docs/` and the files its contracts check. Every pull request also runs **Check commit attribution**, which rejects `Co-authored-by` trailers, "Generated by/with" agent text, and email addresses in its commits, title, and description. The same rules run locally as a Lefthook `commit-msg` hook. Release builds cross-compile the binary for five targets (Linux x86\_64/aarch64 musl, macOS x86\_64/aarch64, Windows x86\_64) and publish multi-arch Docker images. # Deployment Guide > Production deployment strategies for the ServerBee server and agents. URL: https://docs.serverbee.app/en/docs/deployment This guide covers production deployment best practices for ServerBee: Railway, Docker, systemd, reverse proxy configuration, TLS, and backup strategies. ## Mobile Push Relay [#mobile-push-relay] The Server uses a dedicated HTTPS Relay; Apple signing credentials stay at the publisher, outside self-hosted Servers. See [Mobile](/en/docs/mobile) for explicit subscriptions and [Push Relay Operations](/en/docs/push-relay) for isolated configuration, signing, privacy, troubleshooting and genuine-device acceptance. This deployment guide does not provision Apple accounts, install secrets or establish live APNs delivery. ## Railway (One-Click Deploy) [#railway-one-click-deploy] [Deploy on Railway](https://railway.com/deploy/serverbee-server) The fastest way to get ServerBee running. Click the button above, then configure the environment variables: ```bash SERVERBEE_LOG__LEVEL="info" # Log level (trace/debug/info/warn/error) SERVERBEE_RETENTION__RECORDS_DAYS="7" # Raw metrics retention in days SERVERBEE_RETENTION__RECORDS_HOURLY_DAYS="90" # Hourly aggregates retention in days SERVERBEE_RETENTION__AUDIT_LOGS_DAYS="180" # Audit log retention in days SERVERBEE_SCHEDULER__TIMEZONE="UTC" # Timezone for daily traffic aggregation (e.g. Asia/Shanghai) SERVERBEE_OAUTH__BASE_URL="" # OAuth callback URL (e.g. https://xxx.up.railway.app) SERVERBEE_OAUTH__GITHUB__CLIENT_ID="" # GitHub OAuth Client ID SERVERBEE_OAUTH__GITHUB__CLIENT_SECRET="" # GitHub OAuth Client Secret SERVERBEE_OAUTH__ALLOW_REGISTRATION="false" # Auto-create accounts on first OAuth login (true=open, false=linked only) ``` After deploying: 1. Add a **Volume** mounted at `/data` to persist data across deploys 2. Configure your agents to connect using the Railway-provided URL 3. On first start, the server auto-creates an admin account with a randomly generated password. Open the Railway deploy logs and look for the highlighted credentials banner to retrieve it. On first login you must change this password and may optionally choose a different username. Railway automatically assigns a port and provides HTTPS. No need to configure `SERVERBEE_SERVER__LISTEN` or TLS certificates. ### Selecting a Railway Version [#selecting-a-railway-version] The source template pins the repository's current package version, currently `1.0.0-beta.6`. This avoids silently deploying the older image behind `:latest` while ServerBee has no stable `1.x` release. To pin a specific version (stable or pre-release), add the following variable under **Variables** on the Railway service: ```bash SERVERBEE_IMAGE_TAG=1.0.0-beta.6 ``` Save and trigger a redeploy. Check the [release page](https://github.com/ZingerLittleBee/ServerBee/releases) before changing this pin. Future prereleases also publish a moving `beta` tag, while `latest` moves only for suffix-free stable releases; an exact version remains the safest repeatable choice. The Dockerfile exposes this switch via `ARG SERVERBEE_IMAGE_TAG=1.0.0-beta.6`. Railway injects service variables as both build args and runtime environment variables; since this variable is not part of the `SERVERBEE_*` configuration schema, it has no runtime side effects. ## Bootstrap Installer [#bootstrap-installer] On Linux hosts, the bootstrap installer is the quickest binary/systemd path: ```bash # Install the server curl -fsSL https://raw.githubusercontent.com/ZingerLittleBee/ServerBee/main/deploy/install.sh | sudo sh -s -- server # Install an agent curl -fsSL https://raw.githubusercontent.com/ZingerLittleBee/ServerBee/main/deploy/install.sh | sudo sh -s -- agent \ --server-url http://your-server-ip:9527 \ --enrollment-code YOUR_ONE_TIME_CODE ``` After installation, manage your deployment with the `serverbee` CLI (automatically installed to `/usr/local/bin/serverbee`): ```bash sudo serverbee status sudo serverbee upgrade -y sudo serverbee upgrade --channel beta -y # Explicitly stay on prereleases sudo serverbee restart sudo serverbee config sudo serverbee env sudo serverbee uninstall agent -y ``` `install server` / `install agent` are bootstrap commands. If the binary already exists in `/usr/local/bin`, the installer adopts it instead of overwriting it. Use `upgrade` (or replace the binary manually) when you need to roll out a refreshed release binary to an existing host. The default `auto` channel selects the newest suffix-free stable release when one exists, otherwise it falls back to the newest prerelease. The installer saves that policy per component and reuses it for upgrades. Pass `--channel stable` or `--channel beta` to opt into one track explicitly, or `--version vX.Y.Z-beta.N` to pin an exact release. The CLI refuses to replace an installed version with an older SemVer. ## Docker Compose (Recommended) [#docker-compose-recommended] Docker Compose is the simplest way to deploy ServerBee in production. First create the configuration file (a missing bind source must not become a directory): ```bash mkdir -p config cat > config/server.toml <<'EOF' [server] data_dir = "/data" EOF ``` Then create `docker-compose.yml` beside the `config` directory: ```yaml services: serverbee-server: image: ghcr.io/zingerlittlebee/serverbee-server:1.0.0-beta.6 container_name: serverbee-server restart: unless-stopped ports: - "127.0.0.1:9527:9527" volumes: - serverbee-data:/data - ./config/server.toml:/etc/serverbee/server.toml:ro environment: - SERVERBEE_AUTH__SECURE_COOKIE=true healthcheck: test: ["CMD", "wget", "--spider", "-q", "http://127.0.0.1:9527/healthz"] interval: 30s timeout: 5s retries: 3 start_period: 10s volumes: serverbee-data: ``` Binding to `127.0.0.1:9527` instead of `0.0.0.0:9527` ensures the server is only accessible through the reverse proxy, not directly from the internet. Start the service: ```bash docker compose up -d ``` View logs: ```bash docker compose logs -f serverbee-server ``` ## Build from Source [#build-from-source] Developers and users with customization needs can build ServerBee from source. This requires Rust 1.89+ and Bun 1.x (or Node.js 22+). ```bash # Clone the repo git clone https://github.com/ZingerLittleBee/ServerBee.git cd ServerBee # Build the frontend cd apps/web bun install bun run build cd ../.. # Build the server (embeds the frontend via rust-embed) cargo build --release -p serverbee-server # Build the agent cargo build --release -p serverbee-agent ``` The binaries will be located in `target/release/`: * `serverbee-server` — the server, with the frontend assets embedded * `serverbee-agent` — the metrics agent Start the server: ```bash ./target/release/serverbee-server ``` After deploying the compiled binaries to the target host, use the systemd service configuration below to manage the processes. ## Systemd Services [#systemd-services] For deployments without Docker, use systemd to manage the server and agent processes. If you do not need hand-written unit files, prefer the bootstrap installer above. It creates the config files and systemd units for you, then the `serverbee` CLI handles upgrades, restarts, and config edits. Note: the unit examples below use custom paths (`/usr/local/bin`, `/var/lib/serverbee`, `/etc/serverbee`) to illustrate a manual deployment. The bootstrap installer uses a different layout — binaries in `/opt/serverbee/bin/`, config in `/opt/serverbee/etc/`, data in `/opt/serverbee/data/` — managed by the `serverbee` CLI, with no hand-written units. ### Server Service [#server-service] Create `/etc/systemd/system/serverbee-server.service`: ```ini [Unit] Description=ServerBee Server After=network-online.target Wants=network-online.target [Service] Type=simple ExecStart=/usr/local/bin/serverbee-server Restart=always RestartSec=5 User=serverbee Group=serverbee WorkingDirectory=/var/lib/serverbee # Security hardening NoNewPrivileges=true ProtectSystem=strict ProtectHome=true ReadWritePaths=/var/lib/serverbee /var/log/serverbee PrivateTmp=true # Resource limits MemoryMax=512M [Install] WantedBy=multi-user.target ``` Create the service user and directories: ```bash # Create system user sudo useradd -r -s /sbin/nologin -d /var/lib/serverbee serverbee # Create directories sudo mkdir -p /var/lib/serverbee /var/log/serverbee /etc/serverbee sudo chown serverbee:serverbee /var/lib/serverbee /var/log/serverbee # Place configuration sudo cp server.toml /etc/serverbee/server.toml # Place binary sudo cp serverbee-server /usr/local/bin/ sudo chmod +x /usr/local/bin/serverbee-server ``` A production `server.toml` for systemd: ```toml [server] listen = "127.0.0.1:9527" data_dir = "/var/lib/serverbee" [log] level = "info" file = "/var/log/serverbee/server.log" ``` Enable and start: ```bash sudo systemctl daemon-reload sudo systemctl enable serverbee-server sudo systemctl start serverbee-server sudo systemctl status serverbee-server ``` ### Agent Service [#agent-service] See the [Agent Setup](/en/docs/agent) guide for a complete systemd service unit file. ## Reverse Proxy [#reverse-proxy] Running ServerBee behind a reverse proxy is strongly recommended in production. The proxy provides TLS termination, HTTP/2, and additional security headers. ### Access URL and Cookie Settings [#access-url-and-cookie-settings] Pick one public access URL and keep the browser URL, the agent's `server_url`, and the cookie setting aligned: | Scenario | Public URL | `auth.secure_cookie` | Docker Environment Variable | Agent URL | | ---------------------------------- | ----------------------------- | -------------------- | --------------------------------------------------------- | ----------------------------- | | Direct IP over HTTP | `http://203.0.113.10:9527` | `false` | `SERVERBEE_AUTH__SECURE_COOKIE=false` | `http://203.0.113.10:9527` | | Domain through HTTPS reverse proxy | `https://monitor.example.com` | `true` | `SERVERBEE_AUTH__SECURE_COOKIE=true` or omit the variable | `https://monitor.example.com` | For domain access, create a DNS `A` or `AAAA` record pointing to the server, terminate HTTPS in Nginx, Caddy, or Traefik, and proxy traffic to ServerBee on `127.0.0.1:9527`. If you installed with the quick-start script, it writes `auth.secure_cookie = false` for direct HTTP access. Before switching that same installation to HTTPS, update `/opt/serverbee/etc/server.toml`: ```toml [auth] secure_cookie = true ``` Then restart the server: ```bash sudo systemctl restart serverbee-server ``` If ServerBee was installed with the install script, you can also let the CLI configure Caddy HTTPS automatically: ```bash sudo serverbee domain setup --domain monitor.example.com --email admin@example.com -y ``` This command verifies that the domain resolves to the current server, installs and configures Caddy, changes ServerBee to listen only on `127.0.0.1:9527`, and sets `auth.secure_cookie = true`. If DNS is not ready yet, it stops and prints the `A`/`AAAA` record you need to add. ### Nginx [#nginx] ```nginx upstream serverbee { server 127.0.0.1:9527; } server { listen 80; server_name monitor.example.com; return 301 https://$host$request_uri; } server { listen 443 ssl http2; server_name monitor.example.com; ssl_certificate /etc/letsencrypt/live/monitor.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/monitor.example.com/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; # Security headers add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; location / { proxy_pass http://serverbee; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # WebSocket support (required for agents, browser updates, and terminal) proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; # Long timeouts for persistent WebSocket connections proxy_read_timeout 86400s; proxy_send_timeout 86400s; # Disable buffering for real-time data proxy_buffering off; } } ``` ### Caddy [#caddy] Caddy handles TLS automatically with Let's Encrypt: ``` monitor.example.com { reverse_proxy 127.0.0.1:9527 { # WebSocket support is automatic in Caddy } } ``` That is the entire Caddy configuration needed. Caddy handles HTTPS certificates, HTTP/2, WebSocket upgrades, and security headers by default. ### Traefik [#traefik] Traefik integrates with Docker via labels — no separate config file needed. Traefik automatically detects WebSocket connections, so no extra configuration is required. ```yaml services: serverbee-server: image: ghcr.io/zingerlittlebee/serverbee-server:1.0.0-beta.6 volumes: - serverbee-data:/data labels: - "traefik.enable=true" - "traefik.http.routers.serverbee.rule=Host(`monitor.example.com`)" - "traefik.http.routers.serverbee.entrypoints=websecure" - "traefik.http.routers.serverbee.tls.certresolver=letsencrypt" - "traefik.http.services.serverbee.loadbalancer.server.port=9527" restart: unless-stopped networks: - traefik networks: traefik: external: true volumes: serverbee-data: ``` ## TLS / HTTPS [#tls--https] For production deployments, always use HTTPS. The recommended approaches: ### Let's Encrypt with Certbot (Nginx) [#lets-encrypt-with-certbot-nginx] ```bash sudo apt install certbot python3-certbot-nginx sudo certbot --nginx -d monitor.example.com ``` Certbot will automatically configure nginx and set up certificate renewal. ### Let's Encrypt with Caddy [#lets-encrypt-with-caddy] Caddy obtains and renews certificates automatically. No additional setup is needed. ### Manual Certificates [#manual-certificates] If you have your own certificates, place them in a secure directory and reference them in your reverse proxy configuration. Ensure the certificate files are readable by the proxy process and that you have a renewal process in place. ## Agent Configuration for HTTPS [#agent-configuration-for-https] When your server is behind HTTPS, update the agent's `server_url` accordingly: ```toml server_url = "https://monitor.example.com" ``` The agent automatically handles WebSocket connections using the provided URL. ServerBee does not terminate TLS itself. All HTTPS/WSS encryption is handled by the reverse proxy in front of it (Nginx/Caddy/Traefik). This keeps the server implementation simple and lets you manage certificates in one place. When using HTTPS, keep `auth.secure_cookie = true` in your server configuration. Leaving it `false` may still allow login, but it removes the browser's Secure cookie protection and is not appropriate for production. ## Backup and Restore [#backup-and-restore] ### What to Back Up [#persistent-data] SQLite stores monitoring records, users, settings, and uploaded widget packages. Several assets are separate files. A database download alone does not restore the complete installation: | Item | Location | Included in database backup | Description | | --------------------------------- | ------------------------------------------------------------------------------- | --------------------------- | ------------------------------------------------------------------------------------------------- | | Database | `{data_dir}/serverbee.db` | Yes | Default `database.path`; includes database-backed settings and widget packages | | Brand assets | `{data_dir}/brand/` | No | Uploaded logo and favicon; the database stores their URLs, not the image bytes | | Downloaded GeoIP Country database | `{data_dir}/dbip-country-lite.mmdb` | No | DB-IP Lite Country database, if downloaded | | Downloaded ASN database | `{data_dir}/dbip-asn-lite.mmdb` | No | DB-IP Lite ASN database, if downloaded | | Custom MMDB files | `geoip.mmdb_path`, `asn.mmdb_path` | No | Preserve these configured files separately if they are outside `data_dir` | | Server configuration | `/opt/serverbee/etc/` | No | Installer configuration and release policy; also preserve external environment files and secrets | | Agent configuration and state | `/opt/serverbee/etc/agent.toml`, configured `state_dir` and `security.data_dir` | No | Back up on each Agent host, including its run token; the Server backup does not copy remote files | `serverbee.db-wal` may contain changes not yet in the main database. Use SQLite `.backup` or the backup API for a running server, rather than copying its database and sidecars independently. A stopped full-directory snapshot preserves any remaining `serverbee.db-wal` and `serverbee.db-shm` files together. ### Backup Strategy [#backup-strategy] The following examples use the installer defaults: `/opt/serverbee/data/serverbee.db` and `/opt/serverbee/etc/`. Substitute your actual data directory, database filename, configuration directory, and any external MMDB paths. For Docker, the example assumes the Compose layout above (`/data`, `config/server.toml`, and `docker-compose.yml`). Run host filesystem commands as root, install the `sqlite3` CLI, and keep backup sets private. Create one timestamped directory for the database and its matching assets: ```bash set -e umask 077 SERVERBEE_BACKUP_DIR="/backups/serverbee-$(date -u +%Y%m%dT%H%M%SZ)" mkdir -p "$SERVERBEE_BACKUP_DIR" ``` **Option 0: Built-in admin backup API, database only** The admin-only `/api/settings/backup` endpoint uses SQLite `VACUUM INTO` and exports only the database. It does not include brand images, MMDB files, configuration, or Agent state. Store an admin-scoped API key in the environment: ```bash curl --fail --request POST \ --header "X-API-Key: ${SERVERBEE_ADMIN_API_KEY}" \ --output "$SERVERBEE_BACKUP_DIR/serverbee.db" \ https://monitor.example.com/api/settings/backup test "$(sqlite3 "$SERVERBEE_BACKUP_DIR/serverbee.db" 'PRAGMA integrity_check;')" = ok ``` If downloading from another host, collect the asset and configuration archives on the Server host and copy them into the same backup set. **Option 1: SQLite backup command, database only** ```bash sqlite3 /opt/serverbee/data/serverbee.db ".backup '$SERVERBEE_BACKUP_DIR/serverbee.db'" test "$(sqlite3 "$SERVERBEE_BACKUP_DIR/serverbee.db" 'PRAGMA integrity_check;')" = ok ``` For either database-only option, complete the backup set with the following archives. `assets.tar.gz` saves the data directory except the live database and its sidecars, so it includes `brand/` and both downloaded MMDB files when present. `config.tar.gz` saves the installer configuration directory: ```bash tar czf "$SERVERBEE_BACKUP_DIR/assets.tar.gz" \ --exclude='./serverbee.db*' -C /opt/serverbee/data . tar czf "$SERVERBEE_BACKUP_DIR/config.tar.gz" -C /opt/serverbee etc ``` Keep branding, MMDB downloads, and configuration unchanged while collecting this set. Separate online copies are not an atomic snapshot; use a stopped snapshot below when those files are changing. Add any external `geoip.mmdb_path`, `asn.mmdb_path`, environment files, and secrets to the backup set with their original paths recorded. Copy verified backup sets off the ServerBee host. **Option 2: Complete host snapshot, requires stopping the server** ```bash set -e sudo systemctl stop serverbee-server tar czf "$SERVERBEE_BACKUP_DIR/data.tar.gz" -C /opt/serverbee/data . tar czf "$SERVERBEE_BACKUP_DIR/config.tar.gz" -C /opt/serverbee etc sudo systemctl start serverbee-server ``` The data archive includes the database, any remaining WAL/SHM files, brand assets, and downloaded MMDB files. Save external files separately as described above. If an archive command fails, resolve it before restarting and treating the set as complete. **Option 3: Complete Docker volume and configuration snapshot** ```bash set -e umask 077 SERVERBEE_BACKUP_DIR="$PWD/backups/serverbee-$(date -u +%Y%m%dT%H%M%SZ)" mkdir -p "$SERVERBEE_BACKUP_DIR" docker compose stop serverbee-server SERVERBEE_DATA_VOLUME="$( docker inspect serverbee-server \ --format '{{range .Mounts}}{{if eq .Destination "/data"}}{{println .Name}}{{end}}{{end}}' )" test -n "$SERVERBEE_DATA_VOLUME" docker run --rm \ --mount "type=volume,src=${SERVERBEE_DATA_VOLUME},dst=/data,readonly" \ --mount "type=bind,src=${SERVERBEE_BACKUP_DIR},dst=/backups" \ alpine tar czf /backups/data.tar.gz -C /data . tar czf "$SERVERBEE_BACKUP_DIR/config.tar.gz" config docker-compose.yml docker compose start serverbee-server ``` This snapshots everything in the actual `/data` volume, including brand and downloaded MMDB assets, and archives the separately mounted configuration. Also preserve any Compose `.env` file, external secrets, and MMDB bind mounts. Do not assume the volume is literally named `serverbee-data`: Compose usually adds a project prefix, and a wrong name can silently create an empty volume. ### Restore [#restore] Restore the database and the matching assets/configuration before starting the Server. The example below uses a database-only backup completed with `assets.tar.gz` and `config.tar.gz`; it assumes the installer layout and a backup already checked with `PRAGMA integrity_check`: ```bash set -e umask 077 SERVERBEE_RESTORE_DIR="/backups/serverbee-20260314T020000Z" test "$(sqlite3 "$SERVERBEE_RESTORE_DIR/serverbee.db" 'PRAGMA integrity_check;')" = ok test -f "$SERVERBEE_RESTORE_DIR/assets.tar.gz" test -f "$SERVERBEE_RESTORE_DIR/config.tar.gz" sudo systemctl stop serverbee-server # Preserve the current state before replacing it. SERVERBEE_BEFORE_RESTORE="/backups/before-restore-$(date -u +%Y%m%dT%H%M%SZ)" mkdir -p "$SERVERBEE_BEFORE_RESTORE" mv /opt/serverbee/data /opt/serverbee/etc "$SERVERBEE_BEFORE_RESTORE/" # Restore into an empty directory, without the previous database WAL/SHM. mkdir -p /opt/serverbee/data cp "$SERVERBEE_RESTORE_DIR/serverbee.db" /opt/serverbee/data/serverbee.db tar xzf "$SERVERBEE_RESTORE_DIR/assets.tar.gz" -C /opt/serverbee/data tar xzf "$SERVERBEE_RESTORE_DIR/config.tar.gz" -C /opt/serverbee # Restore external MMDB/environment files and service ownership before starting. # For a dedicated service user: chown -R serverbee:serverbee /opt/serverbee/data sudo systemctl start serverbee-server ``` For a stopped host snapshot, extract `data.tar.gz` into the empty data directory instead of copying `serverbee.db` and extracting `assets.tar.gz`. Restore the matching `config.tar.gz` and external files in either case. Review restored configuration before startup; old environment variables or secret-store values can override the restored TOML. The admin-only `/api/settings/restore` endpoint accepts a raw SQLite file, keeps the previous database as `.pre-restore`, and requires a restart. It restores only the database, leaving file assets and configuration untouched. It is not a complete installation restore; use the stopped procedure above or the [Docker recovery runbook](#restore-a-docker-backup-into-a-new-volume) when those files must also be restored. ### Automated Backups [#automated-backups] This cron entry schedules **database-only** backups. Install `sqlite3` and create the destination with `sudo install -d -m 0700 /backups` first: ```bash # /etc/cron.d/serverbee-backup 0 2 * * * root umask 077; sqlite3 /opt/serverbee/data/serverbee.db ".backup '/backups/serverbee-$(date +\%Y\%m\%d).db'" && find /backups -name 'serverbee-*.db' -mtime +30 -delete ``` It runs at 2:00 AM and deletes database backups older than 30 days. Schedule the matching asset/configuration archives too, especially after branding, MMDB, or configuration changes; verify and copy complete sets off-host. ## Health Checks [#health-checks] ServerBee exposes a health check endpoint: ``` GET /healthz ``` Use this to verify the server is running and responsive. Configure it in your monitoring system, Docker health check, or load balancer. ### Docker Health Check [#docker-health-check] The Docker Compose example above includes a health check. Three consecutive failures mark the container `unhealthy`; Docker does **not** restart a merely unhealthy container. The restart policy only reacts when the process exits, so monitor the health state externally and investigate or restart it deliberately. ### External Monitoring [#external-monitoring] If you use an external uptime monitoring service, point it at: ``` https://monitor.example.com/healthz ``` This creates a "monitor the monitor" setup, so you are alerted if ServerBee itself goes down. ## Resource Requirements [#resource-requirements] ServerBee is designed for lightweight VPS instances: | Component | Minimum | Recommended | | ----------- | ------------------------------------ | ---------------------------- | | Server CPU | 1 vCPU | 2 vCPU | | Server RAM | 128 MB | 256 MB | | Server Disk | 100 MB + data | 1 GB+ (depends on retention) | | Agent CPU | Negligible | \< 1% of 1 core | | Agent RAM | \~27 MB measured cgroup steady state | 30–40 MB headroom | Database size depends on server count, enabled writers, sampling intervals, and retention settings. Use [Storage & Sizing](/en/docs/storage-sizing) for the measured 30-day baseline, feature multipliers, and WAL headroom instead of combining rough estimates from different retention windows. Official Linux release binaries and Docker images use musl; `MALLOC_ARENA_MAX` does not affect those builds. Measure the version and workload you deploy rather than sizing from the historical glibc notes. See [Resource Usage](/en/docs/resource-usage). ## Security Checklist [#security-checklist] Before exposing ServerBee to the internet: * [ ] Change the default admin password * [ ] Use HTTPS with a valid TLS certificate * [ ] Set `auth.secure_cookie = true` (default) * [ ] Bind the server to localhost and use a reverse proxy * [ ] Set strong rate limits for login attempts * [ ] Enable TOTP two-factor authentication for admin accounts * [ ] Use Add Server or explicit re-enrollment to issue a bound, short-lived offer only when needed * [ ] Keep the GeoIP database updated (if used) * [ ] Set up automated backups * [ ] Monitor the server itself with an external health check ## Upgrading [#upgrading] ServerBee runs database migrations automatically on startup, so no manual migration step is needed after an upgrade. Back up the complete data directory, configuration, and external assets before upgrading. **Docker:** ```bash docker compose pull docker compose up -d # restarts and runs migrations automatically docker compose ps ``` **Binary:** ```bash wget https://github.com/ZingerLittleBee/ServerBee/releases/download/v1.0.0-beta.6/serverbee-server-linux-amd64 sudo systemctl stop serverbee-server sudo mv serverbee-server-linux-amd64 /usr/local/bin/serverbee-server sudo chmod +x /usr/local/bin/serverbee-server sudo systemctl start serverbee-server ``` If you deployed with the install script, `sudo serverbee upgrade -y` handles the binary download, replacement, and restart for you. ## Uninstalling and Purging [#uninstalling-and-purging] `sudo serverbee uninstall ` stops and removes the managed service/container and executable, but intentionally preserves configuration and data for a reinstall. Add `--purge -y` only after taking a verified backup; it also removes installer-managed config, Server data or Docker volumes/images, and removes the management CLI when no components remain. Review these leftovers separately: * Agent runtime state defaults to `/var/lib/serverbee/capability_grants.json` and `/var/lib/serverbee/security`. A custom `state_dir` or `security.data_dir` may point elsewhere; remove it only after confirming no remaining Agent uses it. * Domain setup may have installed Caddy and added a domain block to `/etc/caddy/Caddyfile`. The uninstaller does not delete packages or shared proxy configuration. Back up the file, remove only the ServerBee domain block, run `sudo caddy validate --config /etc/caddy/Caddyfile`, then `sudo systemctl reload caddy`. Remove Caddy itself only if no other site uses it. * A non-purge Docker uninstall preserves its Compose file, config, and named volume. Inspect the exact volume with `docker inspect serverbee-server` before deleting anything. After cleanup, verify that no `serverbee-*` service/container is running and that ports `9527` (and, if removing the proxy, `80`/`443`) have the expected owners. ## Database Migration Failures and Recovery [#database-migration-failures-and-recovery] ServerBee runs database migrations during Rust server startup. Migrations finish before the HTTP listener starts; when a migration fails, the process exits, and Docker's `restart: unless-stopped` or a systemd restart policy may put the service into a restart loop. The current release does not create an automatic pre-migration snapshot and cannot automatically roll migrations back. A Docker volume persists files, but it is not a versioned restore point. All migrations move forward, and migration `down()` implementations do not restore the old schema. ### Failure Scenarios [#failure-scenarios] | Category | Typical causes | Recommended response | | --------------------------------------- | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------- | | Storage is not writable | Incorrect `/data` permissions, read-only mount, exhausted disk space or inodes | Stop the restart loop, repair storage, then retry the migration | | Database lock | Another ServerBee instance, backup process, or SQLite client holds a long-lived lock | Ensure there is one database writer, stop the conflicting process, then retry | | Database corruption | Damaged SQLite database, WAL, or SHM file; abrupt host power loss | Preserve the failed state, run integrity checks, and prefer a verified backup | | Schema drift | Tables, indexes, or migration history were edited manually | Do not fabricate migration records; restore a compatible backup or ship a forward repair migration | | Existing data violates a new constraint | Duplicate values, null values, orphaned foreign keys, or unexpected legacy formats | Locate and repair the data in a database copy, then retry the migration | | Migration implementation defect | Incorrect SQL, backfill logic, or target schema | Preserve the database and deploy a new image containing a corrected migration | | Interrupted execution | The container was killed or the host restarted during a migration | Snapshot the failed state first; verify schema and migration history before retrying | | Multiple instances compete | Multiple containers migrate the same SQLite file concurrently | Scale down to one instance; multiple Server instances must not share writes to one SQLite database | | Incompatible application rollback | A new migration succeeded, then only the application image was downgraded | Restore the matching pre-upgrade database backup or run an application version compatible with the new schema | ### Step 1: Stop the Restart Loop [#step-1-stop-the-restart-loop] Docker Compose: ```bash docker compose stop serverbee-server ``` If an external tool still keeps starting the container, temporarily disable its container-level restart policy: ```bash docker update --restart=no serverbee-server docker stop serverbee-server ``` systemd: ```bash sudo systemctl stop serverbee-server sudo systemctl reset-failed serverbee-server ``` Stopping the service does not delete its Docker volume or database. Do not run `docker compose down -v`; the `-v` flag deletes Compose-managed volumes. ### Step 2: Read the Migration Error [#step-2-read-the-migration-error] Docker: ```bash docker logs --tail 200 serverbee-server ``` systemd: ```bash sudo journalctl -u serverbee-server -b -n 200 --no-pager ``` A successful migration writes `Database migrations complete` to the log. If that message is absent, investigate the first database or migration error rather than the later restart messages. ### Step 3: Preserve the Failed State [#step-3-preserve-the-failed-state] Create a complete copy even when the database may be partially migrated or damaged. It is both the input for repair and protection against later attempts overwriting evidence. Docker: ```bash SERVERBEE_DATA_VOLUME="$( docker inspect serverbee-server \ --format '{{range .Mounts}}{{if eq .Destination "/data"}}{{println .Name}}{{end}}{{end}}' )" test -n "$SERVERBEE_DATA_VOLUME" mkdir -p backups docker run --rm \ --mount "type=volume,src=${SERVERBEE_DATA_VOLUME},dst=/data,readonly" \ --mount "type=bind,src=$(pwd)/backups,dst=/backups" \ alpine \ tar czf /backups/serverbee-failed-migration-$(date -u +%Y%m%dT%H%M%SZ).tar.gz -C /data . ``` For systemd or binary deployments, copy the entire data directory after stopping the service, not just the main database file: ```bash sudo mkdir -p /backups sudo cp -a /opt/serverbee/data \ "/backups/serverbee-failed-migration-$(date -u +%Y%m%dT%H%M%SZ)" ``` Preserve the matching configuration, external MMDB files, environment files, and secrets alongside the failed data snapshot. They may explain the failure and are required for a complete restore. ### Step 4: Inspect the Database State [#step-4-inspect-the-database-state] Run checks against an extracted copy of the failed snapshot, not the only production volume: ```bash mkdir -p recovery-data SERVERBEE_FAILED_BACKUP="$(ls -1t backups/serverbee-failed-migration-*.tar.gz | head -n 1)" test -f "$SERVERBEE_FAILED_BACKUP" tar xzf "$SERVERBEE_FAILED_BACKUP" -C recovery-data sqlite3 recovery-data/serverbee.db "PRAGMA integrity_check;" sqlite3 recovery-data/serverbee.db "PRAGMA foreign_key_check;" sqlite3 recovery-data/serverbee.db \ "SELECT * FROM seaql_migrations ORDER BY applied_at;" ``` * `PRAGMA integrity_check` should return `ok`. * `PRAGMA foreign_key_check` should normally return no rows. * `seaql_migrations` shows migrations recorded as successful; the table may not exist if a brand-new database failed before migration bookkeeping was created. Do not delete rows from `seaql_migrations` or manually mark a failed migration as successful. When migration history and the actual schema disagree, a later release can fail in a more difficult-to-recover state. ### Step 5: Choose a Recovery Path [#step-5-choose-a-recovery-path] **Environmental failure** If the cause was disk space, permissions, a read-only mount, or a temporary database lock, repair the root cause and restart. SeaORM will retry migrations not recorded as successful: ```bash docker update --restart=unless-stopped serverbee-server docker compose up -d serverbee-server docker logs -f serverbee-server ``` **Incompatible existing data or schema** Identify conflicting data in a database copy. Prefer a forward repair migration that handles the legacy state. If manual data repair is unavoidable, validate the complete migration and application startup against the copy before applying the same reviewed operations to production. **Migration code defect** Do not repeatedly restart the same failing image. Preserve the failed state and deploy a new image containing a corrected migration. Switching only to the old image is not a database rollback; it is safe only when you have established that the failed migration made no schema changes. Otherwise restore the pre-upgrade backup. **Database corruption** Prefer the latest backup that passes integrity checks. SQLite `.recover` is a last-resort data salvage tool when no usable backup exists; its output may be incomplete and must be validated in isolation. ### Restore a Docker Backup into a New Volume [#restore-a-docker-backup-into-a-new-volume] Restore into a new volume instead of overwriting the failed volume. This preserves the failed state for further investigation: ```bash SERVERBEE_RECOVERY_VOLUME="serverbee-data-recovery-$(date -u +%Y%m%dT%H%M%SZ)" docker volume create "$SERVERBEE_RECOVERY_VOLUME" docker run --rm \ --mount "type=volume,src=${SERVERBEE_RECOVERY_VOLUME},dst=/data" \ --mount "type=bind,src=$(pwd)/backups/serverbee-20260314T020000Z,dst=/backups,readonly" \ alpine \ tar xzf /backups/data.tar.gz -C /data echo "$SERVERBEE_RECOVERY_VOLUME" # Inspect the matching Compose/configuration archive before restoring it. mkdir -p recovered-config tar xzf backups/serverbee-20260314T020000Z/config.tar.gz -C recovered-config ``` Review `recovered-config/config/server.toml` and `recovered-config/docker-compose.yml`, restore them to the deployment project, and reapply the matching external MMDB mounts, environment files, and secrets. The data archive restores `brand/` and downloaded MMDB files; it cannot restore files outside `/data`. Then point the existing `serverbee-data` logical Compose volume at the newly created volume: ```yaml volumes: serverbee-data: external: true name: serverbee-data-recovery-20260722T103000Z ``` Confirm that `name` exactly matches the preceding command's output before starting the service. Restoring an older database may run migrations again, so first decide whether to run the original application version or a newer version containing the migration fix. ### Verify Recovery [#verify-recovery] ```bash docker compose up -d serverbee-server docker compose ps docker logs --tail 200 serverbee-server curl --fail http://127.0.0.1:9527/healthz ``` Consider recovery complete only when all of the following are true: * The logs contain `Database migrations complete`. * The container remains running and its health check passes. * `PRAGMA integrity_check` returns `ok`. * You can sign in and read existing servers, alerts, and historical records. * Previously configured logos, favicons, and GeoIP/ASN databases are available. * Database lock, foreign key, and missing table or column errors do not continue. ### Prevention [#prevention] * Create and verify a complete data/configuration backup before every upgrade; the current upgrade command does not create one automatically. * Copy backups off the Docker host so one disk failure cannot damage both the production volume and its local backups. * Keep SQLite deployments at one Server instance; do not share one writable database file among multiple containers. * Check available disk space before upgrading, and use an explicit image version tag so the migration source is identifiable. * Keep the pre-upgrade image and its matching database, asset, and configuration backups. Database rollback depends on the backup, not just the old image. * Wait for the migration success log and health check after an upgrade; a successful `docker compose up -d` return is not sufficient evidence.