From 3429321eaea66bbf93bc5f131d688323d3196508 Mon Sep 17 00:00:00 2001 From: Linus Fischer Date: Wed, 21 Oct 2026 13:11:09 +0200 Subject: [PATCH 1/4] docs: rewrite the documentation for NetIgnite v2 Rewrite every docs page against the v2 code of the server, the ESP32 firmware and the new Linux agent: - Introduction: overview, how it works (enrollment, status checks, shutdown), getting started with the ESP32 web installer. - Self-hosting: Docker install, every NETIGNITE_* variable with its default, HTTPS and the /ws/agent and /ws/console WebSockets, users, sign-up, logins and API keys, and a new "Upgrading from v1" page. - Using NetIgnite: dashboard, agents (setup, console, OTA updates), devices (status methods, power actions, favorites), and new Sharing and SSH pages. - Agents: ESP32 agent, a new Linux agent page, and agent protocol v2. - Reference: REST API v1, troubleshooting and development for v2. Add the new pages to the sidebar data and the sitemap, highlight js and ini code blocks, and mention Agent-Linux in AGENTS.md and the README. --- AGENTS.md | 5 +- README.md | 5 +- app/data/docs.ts | 48 ++- content/docs/agents/esp32.md | 329 ++++++++------- content/docs/agents/linux.md | 340 ++++++++++++++++ content/docs/agents/protocol.md | 466 +++++++++++++++++----- content/docs/getting-started.md | 82 ++-- content/docs/guides/agents.md | 151 +++++-- content/docs/guides/dashboard.md | 82 ++-- content/docs/guides/devices.md | 97 +++-- content/docs/guides/sharing.md | 63 +++ content/docs/guides/ssh.md | 126 ++++++ content/docs/how-it-works.md | 127 +++--- content/docs/index.md | 45 ++- content/docs/reference/api.md | 451 ++++++++++----------- content/docs/reference/development.md | 99 ++++- content/docs/reference/troubleshooting.md | 244 ++++++++--- content/docs/server/configuration.md | 179 +++++++-- content/docs/server/installation.md | 96 +++-- content/docs/server/reverse-proxy.md | 80 ++-- content/docs/server/upgrading.md | 156 ++++++++ content/docs/server/users.md | 119 ++++-- nuxt.config.ts | 2 +- public/static/utils/sitemap.xml | 50 ++- 24 files changed, 2522 insertions(+), 920 deletions(-) create mode 100644 content/docs/agents/linux.md create mode 100644 content/docs/guides/sharing.md create mode 100644 content/docs/guides/ssh.md create mode 100644 content/docs/server/upgrading.md diff --git a/AGENTS.md b/AGENTS.md index e0b917a..b03fcd6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -39,6 +39,7 @@ NetIgnite keeps its own look on top of the template: - Label code blocks with a file name or extension from `app/data/code-icons.ts`, or a plain label without a dot (`[Terminal]`); `tests/code-icons.test.ts` fails otherwise. Add new highlight languages to `content.build.markdown.highlight.langs` in `nuxt.config.ts`. -- The docs describe https://github.com/NetIgnite-project/Server and - https://github.com/NetIgnite-project/Agent-ESP32. Verify behavior against their code, not +- The docs describe https://github.com/NetIgnite-project/Server, + https://github.com/NetIgnite-project/Agent-ESP32 and + https://github.com/NetIgnite-project/Agent-Linux. Verify behavior against their code, not their READMEs. diff --git a/README.md b/README.md index abe13c1..663f929 100644 --- a/README.md +++ b/README.md @@ -48,8 +48,9 @@ Docs live in `content/docs/` as markdown files. Navigation is declared in `app/d new page by creating a `.md` file, adding it to the sidebar data and to `public/static/utils/sitemap.xml`. -The docs describe the [Server](https://github.com/NetIgnite-project/Server) and the -[ESP32 agent](https://github.com/NetIgnite-project/Agent-ESP32). Keep them in sync with those +The docs describe the [Server](https://github.com/NetIgnite-project/Server), the +[ESP32 agent](https://github.com/NetIgnite-project/Agent-ESP32) and the +[Linux agent](https://github.com/NetIgnite-project/Agent-Linux). Keep them in sync with those repositories when behavior changes. ## Structure diff --git a/app/data/docs.ts b/app/data/docs.ts index 12262c4..5a9109f 100644 --- a/app/data/docs.ts +++ b/app/data/docs.ts @@ -29,7 +29,7 @@ export const docsSections: DocsSection[] = [ { to: "/docs/how-it-works", title: "How It Works", - description: "Server, agents and devices, and what happens when you press Power On.", + description: "Server, agents and devices, and what happens when you power on a device.", icon: "i-lucide-workflow", }, { @@ -52,21 +52,27 @@ export const docsSections: DocsSection[] = [ { to: "/docs/server/configuration", title: "Configuration", - description: "config.json, environment variables, data paths and logging.", + description: "NETIGNITE_* environment variables, server settings, data paths and logging.", icon: "i-lucide-settings", }, { to: "/docs/server/reverse-proxy", title: "HTTPS & Reverse Proxy", - description: "Serve the dashboard and the agent WebSocket over TLS.", + description: "Serve the dashboard, the API and the WebSockets over TLS.", icon: "i-lucide-lock", }, { to: "/docs/server/users", title: "Users & Accounts", - description: "The initial admin account, sessions, roles and passwords.", + description: "The first admin, user management, sign-up, logins and API keys.", icon: "i-lucide-users", }, + { + to: "/docs/server/upgrading", + title: "Upgrading from v1", + description: "Import your v1 data into v2 and set your agents up again.", + icon: "i-lucide-history", + }, ], }, { @@ -81,15 +87,27 @@ export const docsSections: DocsSection[] = [ { to: "/docs/guides/agents", title: "Managing Agents", - description: "Create agents and get the credentials they connect with.", + description: "Set up agents, use the ESP32 console and update the firmware.", icon: "i-lucide-radio-tower", }, { to: "/docs/guides/devices", - title: "Devices & Wake-on-LAN", - description: "Add devices, power them on and check whether they are online.", + title: "Devices", + description: "Add devices, check their status, power them on and shut them down.", icon: "i-lucide-monitor", }, + { + to: "/docs/guides/sharing", + title: "Sharing", + description: "Share devices with other users as viewer, operator or manager.", + icon: "i-lucide-share-2", + }, + { + to: "/docs/guides/ssh", + title: "SSH", + description: "Shut down and reboot devices over SSH with a restricted key.", + icon: "i-lucide-key-square", + }, ], }, { @@ -98,13 +116,19 @@ export const docsSections: DocsSection[] = [ { to: "/docs/agents/esp32", title: "ESP32 Agent", - description: "Configure, build and flash the ESP32 agent firmware.", + description: "Web installer, serial console, updates and building the firmware.", icon: "i-lucide-cpu", }, + { + to: "/docs/agents/linux", + title: "Linux Agent", + description: "Install, pair, host mode and Wake-on-LAN mode of the Linux agent.", + icon: "i-lucide-terminal", + }, { to: "/docs/agents/protocol", title: "Agent Protocol", - description: "The WebSocket protocol between server and agents — build your own agent.", + description: "The WebSocket protocol v2 between server and agents — build your own agent.", icon: "i-lucide-cable", }, ], @@ -115,19 +139,19 @@ export const docsSections: DocsSection[] = [ { to: "/docs/reference/api", title: "REST API", - description: "Every HTTP endpoint of the NetIgnite server.", + description: "Authentication, API keys and the endpoints of the NetIgnite server.", icon: "i-lucide-braces", }, { to: "/docs/reference/troubleshooting", title: "Troubleshooting", - description: "Common problems with agents, wake-ups, status checks and logins.", + description: "Common problems with logins, agents, wake-ups, status checks and updates.", icon: "i-lucide-life-buoy", }, { to: "/docs/reference/development", title: "Development", - description: "Repositories, local setup and contributing.", + description: "Repositories, branches, local setup, tests and contributing.", icon: "i-lucide-code", }, ], diff --git a/content/docs/agents/esp32.md b/content/docs/agents/esp32.md index 0904548..25e52a3 100644 --- a/content/docs/agents/esp32.md +++ b/content/docs/agents/esp32.md @@ -1,190 +1,233 @@ --- title: "ESP32 Agent" -description: "Configure, build and flash the NetIgnite ESP32 agent firmware with PlatformIO." +description: "The NetIgnite ESP32 agent: hardware, the web installer, the serial console, over-the-air updates, release signing and building the firmware yourself." navigation: title: ESP32 Agent --- # ESP32 Agent -The [ESP32 agent](https://github.com/NetIgnite-project/Agent-ESP32) turns an ESP32 development -board into a NetIgnite agent. It joins your WiFi, connects to the NetIgnite server, and sends -Wake-on-LAN packets and status pings to the devices in its network. The board draws little power, -so it can stay on around the clock. +The [ESP32 agent](https://github.com/NetIgnite-project/Agent-ESP32) turns an ESP32 board into a +NetIgnite agent. It joins your Wi-Fi, connects to your NetIgnite server, wakes devices in its +network, checks whether they're online and can shut them down over [SSH](/docs/guides/ssh). The +board draws little power, so it can stay on around the clock. You install it from the browser, and +it updates itself over the network. ## Requirements -- An ESP32 development board supported by PlatformIO's `esp32dev` target (the original ESP32). -- A **2.4 GHz** WiFi network with WPA/WPA2-Personal. The ESP32 doesn't support 5 GHz or - WPA2-Enterprise. +- A **classic ESP32** board with 4 MB flash, for example an *ESP32 DevKit* with an ESP32-WROOM or + ESP32-WROVER module (PlatformIO's `esp32dev`). Other chips such as the ESP32-S3 or ESP32-C3 aren't + supported. +- A **2.4 GHz** Wi-Fi network with WPA, WPA2 or WPA3-Personal. 5 GHz and WPA2-Enterprise networks + don't work. - The ESP32 must be in the **same network and subnet** as the devices it wakes. Wake-on-LAN - broadcasts and IPv6 link-local pings don't cross routers. -- Outbound access from the ESP32 to the NetIgnite server, to DNS and to NTP (`pool.ntp.org`, - `time.nist.gov`). -- [PlatformIO](https://platformio.org/): the VS Code extension or PlatformIO Core (CLI). -- An agent created in the dashboard. You need its **Connection ID** and **Connection Secret** - (see [Managing Agents](/docs/guides/agents)). + broadcasts and IPv6 link-local addresses don't cross routers. +- Outbound access to your NetIgnite server and to DNS. For an `https://` server also to NTP + (`pool.ntp.org`, `time.cloudflare.com`, `time.google.com`): the agent needs the time to check the + server's certificate. +- For the first install: a USB data cable and a computer with Google Chrome, Microsoft Edge or + Firefox 151 or newer. + +## Install from the browser + +The dashboard flashes the firmware with [ESP Web Tools](https://esphome.github.io/esp-web-tools/). +Create an ESP32 agent first (**Agents → Add agent**, see +[Managing Agents](/docs/guides/agents#creating-an-agent)), then open its **Install** tab: + +1. **Choose the firmware.** The list contains the releases the server has synced from GitHub; the + recommended one is preselected. **Change server address** sets the address the agent connects + to, if it's not the dashboard's address, for example `http://192.168.1.10:18232` inside your + network. +2. **Prepare the installer.** The browser downloads the firmware files from your NetIgnite server + and checks their SHA-256. The server creates the agent's setup data: the server address, the + agent's name and a single-use token, valid for 15 minutes. +3. **Connect and install.** Plug the ESP32 into the computer, click **Connect and install** and pick + its serial port. ESP Web Tools flashes the firmware and the setup data. Then enter your Wi-Fi + name and password in the dialog: they're sent over USB to the ESP32 only, never to NetIgnite. +4. **Wait for the agent.** It joins the Wi-Fi, uses the token to register with the server, + generates its own secret and connects. The page shows *… is connected*. + +The browser must open the dashboard over **HTTPS** (or `localhost`): browsers only allow USB access +there. If the token expires before you flash, click **Prepare again**. -## Get the source - -```bash [Terminal] -git clone https://github.com/NetIgnite-project/Agent-ESP32.git -cd Agent-ESP32 -``` +::note +Installing replaces everything on the board, including a previous NetIgnite credential and Wi-Fi +settings. It's also how you move a board from NetIgnite v1 to v2: the v2 firmware uses a new +partition table, so it has to be installed over USB once. +:: -## Configure +If the board doesn't show up as a serial port, install the USB driver for its USB-to-serial chip +(often a CP210x or CH340), try another cable (some only carry power) and close other programs that +use the port. -Settings are compiled into the firmware. Copy the sample config and edit it: +### Change Wi-Fi -```bash [Terminal] -cp src/config/config.sample.h src/config/config.h -``` +To change the Wi-Fi without reinstalling, connect the board over USB, open the **Install** tab, +click **Prepare installer** and **Connect and install**. For a board that already runs NetIgnite, +the dialog offers **Change Wi-Fi** and **Logs & console** without touching the firmware or its +credential. A new network is only saved if the ESP32 can connect to it. -```cpp [config.h] -#define C_WIFI_SSID "WIFI_SSID" -#define C_WIFI_PASSWORD "WIFI_PASSWORD" +The `wifi` command in the [serial console](#serial-console) does the same. -#define C_WIFI_LOCAL_IP IPAddress(192, 168, 1, 32) -#define C_WIFI_GATEWAY_IP IPAddress(192, 168, 1, 1) -#define C_WIFI_SUBNET_MASK IPAddress(255, 255, 255, 0) +## What happens at startup -#define C_WIFI_PRIMARY_DNS IPAddress(1, 1, 1, 1) -#define C_WIFI_SECONDARY_DNS IPAddress(1, 0, 0, 1) +1. The agent connects to Wi-Fi (DHCP by default) and enables IPv6. +2. For an `https://` server, it waits until the time is synced over NTP. +3. If it has a setup token, it registers with the server (`POST /api/v1/agents/enroll`), stores + the credential and forgets the token. +4. It opens the WebSocket to `/ws/agent` and announces itself. -#define C_CONTROL_SERVER_HOST "example.com" -#define C_CONTROL_SERVER_PORT (uint16_t) 80 -#define C_CONTROL_SERVER_USE_SSL (bool) false +A start looks like this on the serial console (115200 baud): -#define C_CONTROL_SERVER_AUTH_ID "AUTH_ID" -#define C_CONTROL_SERVER_AUTH_SECRET "AUTH_SECRET" +```text [Serial console] +[ 0] I NetIgnite Agent 2.0.0 on ESP32-D0WD-V3 rev 3.1 +[ 0] I Device Office-ESP32 (24:6F:28:AA:BB:CC), partition app0, reset reason: power-on +[ 0] I Connecting to Wi-Fi "MyWiFi" +[ 3] I Wi-Fi connected: MyWiFi, IPv4 192.168.1.50, RSSI -58 dBm +[ 3] I Time sync started +[ 4] I Enrolling with https://netignite.example.com +[ 5] I Enrolled as agent 3 +[ 5] I Connecting to https://netignite.example.com/ws/agent +[ 6] I Connected to the server as agent 3 ``` -`src/config/config.h` is ignored by Git, so your credentials stay out of the repository. +The number is the time since start in seconds, the letter the level (`E`rror, `W`arning, `I`nfo, +`D`ebug). -### WiFi - -| Setting | Description | +| Event | Behavior | | --- | --- | -| `C_WIFI_SSID` | Name of the 2.4 GHz network. | -| `C_WIFI_PASSWORD` | WiFi password. | -| `C_WIFI_LOCAL_IP` | Static IPv4 address of the ESP32. Pick a free address outside your DHCP range. | -| `C_WIFI_GATEWAY_IP` | Your router's address. | -| `C_WIFI_SUBNET_MASK` | Subnet mask. The agent also uses it to compute the Wake-on-LAN broadcast address (for example `192.168.1.255`). | -| `C_WIFI_PRIMARY_DNS`, `C_WIFI_SECONDARY_DNS` | DNS servers, used to resolve the server host and the NTP servers. | +| Wi-Fi lost | The ESP32 reconnects on its own; after 30 seconds without a connection it starts over. | +| Server unreachable or the credential rejected | Retries after 5 seconds, then doubles the wait up to 60 seconds. | +| No message from the server for 100 seconds | Drops the connection and reconnects (the server pings every 30 seconds). | +| Token rejected (used, expired or for a Linux agent) | Logs `Enrollment rejected: …` and forgets the token. Create a new one. | +| Server not reachable while registering | Logs `Enrollment failed: …` and retries every 30 seconds. | -::tip -The agent always applies the static network settings. To use DHCP instead, set -`C_WIFI_LOCAL_IP` to `IPAddress(0, 0, 0, 0)`. The ESP32 Arduino core then requests an address -from your router; reserve one for the ESP32 there so it doesn't change. -:: +## Serial console -### Server +The firmware has a small command console on its USB serial port at 115200 baud. Open it with +**Logs & console** in the web installer dialog, PlatformIO's serial monitor (`pio device monitor`) +or any serial terminal. Type `help` for the list. -| Setting | Description | +| Command | What it does | | --- | --- | -| `C_CONTROL_SERVER_HOST` | Host name or IP of the NetIgnite server, without `https://` or a path. | -| `C_CONTROL_SERVER_PORT` | Server port: `443` behind an HTTPS proxy, `18232` for a direct connection to the Docker container. | -| `C_CONTROL_SERVER_USE_SSL` | `true` for an encrypted connection (`wss://`), `false` for plain `ws://`. | -| `C_CONTROL_SERVER_AUTH_ID` | The agent's **Connection ID**, as a string, for example `"3"`. | -| `C_CONTROL_SERVER_AUTH_SECRET` | The agent's **Connection Secret**. | - -The agent always connects to the path `/api/agent-control-service`. - -| Server | `HOST` | `PORT` | `USE_SSL` | -| --- | --- | --- | --- | -| Hosted app | `"app.netignite.dev"` | `443` | `true` | -| Self-hosted behind an [HTTPS proxy](/docs/server/reverse-proxy) | `"netignite.example.com"` | `443` | `true` | -| Self-hosted, direct to the container (LAN only) | `"192.168.1.10"` | `18232` | `false` | - -::warning -Without SSL, the agent's ID and secret are sent unencrypted. Only use plain connections inside a -network you trust. -:: +| `help` | Lists the commands. | +| `status` | Firmware, device, Wi-Fi, IP addresses, server, agent ID and connection state. Never shows the secret. | +| `setup ` | Connects the agent to a NetIgnite server with a setup token from the dashboard. | +| `wifi [password]` | Connects to a Wi-Fi network. It's saved only if the connection works. Quote names with spaces: `wifi "My Network" "my password"`. | +| `server ` | Changes the server address. | +| `enroll ` | Sets the agent up again with a new token. | +| `ip dhcp` | Gets the IP address from DHCP (the default). | +| `ip static [dns1] [dns2]` | A static IPv4 configuration. | +| `name ` | Host name: letters, digits and `-`, at most 63 characters. Used after a restart. | +| `log ` | Log level. | +| `reboot` | Restarts the agent. | +| `factory-reset confirm` | Erases Wi-Fi, server, credential and all other settings, then restarts. | + +To set up a board over the serial console instead of with the installer's setup data (for example +after flashing a firmware you built yourself): + +1. Connect it to Wi-Fi: `wifi "My Network" "my password"`. +2. On the agent's page in the dashboard, click **Create setup command** and paste the command: + `setup https://netignite.example.com netignite_enroll_…`. + +A server address without `http://` or `https://` means `https://`. The default name is +`NetIgnite-` plus the last six digits of the board's MAC address; the web installer sets the +agent's name from the dashboard instead. + +## What the agent does + +The agent implements these commands of the [agent protocol](/docs/agents/protocol): -## Certificate bundle +| Command | What the agent does | +| --- | --- | +| `WAKE` | Sends the magic packet three times, 100 ms apart, to its subnet's broadcast address and to `255.255.255.255`. | +| `STATUS` | Pings the device twice with a 1-second timeout (IPv4, IPv6, or the IPv6 link-local address derived from the MAC address) and tries the TCP port, if one is set. A refused TCP connection also counts as online. Host names resolve to IPv4 and IPv6 addresses. | +| `SSH_CHECK`, `SSH_EXEC` | Logs in to a device over SSH (one login at a time). See [SSH](/docs/guides/ssh). | +| `OTA_BEGIN` | Installs a firmware update; see [below](#updates-over-the-network). | +| `GET_LOGS`, `LOG_SUBSCRIBE` | Sends its stored log (the last 16 KB, including ESP-IDF's own messages) and new lines for the dashboard's console. | +| `GET_INFO`, `CONFIG_SET` | Reports diagnostics; changes the server address, host name, log level and IP settings. | +| `REBOOT`, `FACTORY_RESET` | Restarts; erases its settings and restarts. | -The firmware embeds a bundle of trusted root certificates (`data/cert/x509_crt_bundle.bin`) that -it uses to verify the server's TLS certificate. PlatformIO generates the bundle automatically -before each build from `data/cert/cacrt_all.pem`, Mozilla's list of public root CAs (including -Let's Encrypt). On the first build it installs the Python package `cryptography` into -PlatformIO's environment, which needs internet access. +The dashboard's [Console](/docs/guides/agents#console) tab uses the log and settings commands. -If your server uses a certificate from a private CA, put that CA's certificate (`.pem` or `.der`) -into `data/cert/custom/` and rebuild. It's added to the bundle; remove the file to take it out -again. +## Updates over the network -## Build and flash +The firmware has two app slots of 1.875 MB each. An update from the dashboard +([Firmware updates](/docs/guides/agents#firmware-updates)) works like this: -Connect the board over USB, then build, upload and open the serial monitor: +1. The server sends `OTA_BEGIN` with the version, size, SHA-256 and, for signed releases, the + signature of the new firmware. +2. The agent downloads it from its own server (`/api/v1/firmware//files/firmware.bin`) + with its own credential, writes it into the unused slot and checks size and SHA-256. A firmware + that requires signed updates also checks the signature and refuses unsigned ones. +3. It restarts into the new firmware, which is **on probation**: it marks itself as good once it + has reached the server. If it crashes or doesn't get there within **5 minutes**, the board goes + back to the previous firmware and reports the failed version to the server. -```bash [Terminal] -pio run -pio run -t upload -pio device monitor -``` +While the new firmware is on probation, the agent refuses another update. If memory is short, it +pauses the server connection during the download. -In VS Code, use the PlatformIO toolbar's **Build**, **Upload** and **Serial Monitor** buttons -instead. The monitor runs at 115200 baud. Add `--upload-port ` (for example `COM5` or -`/dev/ttyUSB0`) if PlatformIO doesn't find the board. +### Release signing -::note -The project targets the Arduino-ESP32 **2.x** core that PlatformIO's `espressif32` platform -provides. Arduino core 3.x (for example via pioarduino) isn't supported yet. -:: +Releases can be signed with an ECDSA P-256 key. Signing works at two points: -Every configuration change requires a rebuild and upload. The firmware has no setup portal and no -over-the-air updates. - -## What happens at boot - -A successful start looks like this on the serial monitor (shortened; the order of the lines can -vary): - -```text [Serial Monitor] -[INFO] Starting... -[INFO] Connecting to Wifi... -[INFO] STA Configured -[INFO] Connecting to MyWiFi... -[INFO] Wifi connected to MyWiFi. Waiting for IP... -[INFO] IPv6 enabled on WiFi -[INFO] Got IP address! -[INFO] IP address: 192.168.1.32 -[INFO] Waiting for NTP time sync: .... -[INFO] Current UTC time: Sun Oct 18 19:30:00 2026 -[INFO] Websocket Connected to netignite.example.com:443 SSL: true -``` +- **On the agent.** A firmware built with a public key (`keys/firmware-signing.pub.pem` in the + repository at build time) only installs updates with a valid signature. `status` on the serial + console shows `updates: signed only` then; the dashboard's firmware card says the agent checks + the *signature and SHA-256*. +- **On the server.** With + [`NETIGNITE_FIRMWARE_PUBLIC_KEY`](/docs/server/configuration#esp32-firmware) set to the same + public key, the server only syncs releases with a valid `firmware.sig`. The admin's firmware list + marks them as *signed*. -1. The agent connects to WiFi and enables IPv6. -2. It waits up to 30 seconds for the clock to sync via NTP, which is needed to check TLS - certificates. -3. It opens the WebSocket to the server and authenticates with the Connection ID and Secret. -4. It answers commands until the connection drops. +To sign releases of your own fork, create a key pair with `sh scripts/generate_signing_key.sh`, +store the private key as the repository secret `FIRMWARE_SIGNING_KEY` and commit only the public +key. The release workflow then attaches `firmware.sig` to every release. -| Event | Behavior | -| --- | --- | -| WiFi connection lost | Reconnects after 60 seconds. | -| Server connection lost or refused | Retries every 60 seconds. | -| No WiFi or NTP during boot | After 30 seconds the agent logs `NTP time sync timed out` and continues. It keeps retrying WiFi and the server every 60 seconds; TLS connections succeed once the time is synced. | -| Empty Connection ID or Secret | Logs `Missing credentials!` and never connects. | +## Certificates -## Commands +For `https://` servers, the agent checks the server's certificate against a bundle of trusted root +certificates compiled into the firmware: Mozilla's list of public root CAs, which includes Let's +Encrypt. A certificate from a private CA only works with a firmware you build yourself with that CA +added (see below). Don't update such an agent to an official release over the network: the official +firmware doesn't trust your CA, can't reach the server and goes back to your build after 5 minutes. -The agent implements the three commands of the [agent protocol](/docs/agents/protocol): +## Build from source -| Command | What the agent does | -| --- | --- | -| `WAKEUP` | Validates the MAC address and port, then sends the Wake-on-LAN magic packet three times, 100 ms apart, to the subnet's broadcast address. | -| `GET_STATUS` | Pings the device once with a 1-second timeout. A MAC address is converted to the device's IPv6 link-local (EUI-64) address; an IPv4 address is pinged directly. | -| `HEARTBEAT` | Nothing; the server uses it to keep the connection active. | +You need [PlatformIO](https://platformio.org/) Core **6.2 or newer** (`pio upgrade`). The build uses +[pioarduino](https://github.com/pioarduino/platform-espressif32) with Arduino-ESP32 3.3 on ESP-IDF +5.5, pinned in `platformio.ini`. -MAC addresses must have the form `AA:BB:CC:DD:EE:FF` or `AA-BB-CC-DD-EE-FF`. SecureOn (password -protected) magic packets aren't supported. +```bash [Terminal] +git clone https://github.com/NetIgnite-project/Agent-ESP32.git +cd Agent-ESP32 +pio run -e esp32dev # build +pio run -e esp32dev -t upload # flash over USB +pio device monitor # serial console, 115200 baud +``` + +Add `--upload-port ` (for example `COM5` or `/dev/ttyUSB0`) if PlatformIO doesn't find the +board. A board flashed this way has no setup data: set it up over the +[serial console](#serial-console). + +- **Private CA:** put its certificate (`.pem` or `.der`) into `data/cert/custom/` before building. + The build adds it to the certificate bundle. The bundle generator needs the Python package + `cryptography`, which the build installs on the first run. +- **Signed updates:** with `keys/firmware-signing.pub.pem` present, the firmware only accepts + signed updates (see [Release signing](#release-signing)). +- **Version:** the build takes it from `git describe`, or from the environment variable + `NETIGNITE_FW_VERSION`. + +::tip +On Windows, build from PowerShell or the Command Prompt rather than Git Bash: ESP-IDF's tools +refuse to run in an MSYS shell. +:: ## Limitations -- The agent uses IPv4 subnet broadcasts for Wake-on-LAN, so it only reaches devices in its own - subnet. Use one agent per network. -- The status check depends on the device's IPv6 link-local address being derived from its MAC - address. See [How It Works](/docs/how-it-works#checking-device-status). -- The `installer/` folder in the repository is an early placeholder for a desktop installer. It - can't flash boards yet; use PlatformIO. +- Wake-on-LAN uses IPv4 broadcasts, so the agent only reaches devices in its own subnet. Use one + agent per network. +- The *MAC (IPv6 link-local)* status check only works for devices that use their MAC-based IPv6 + address. See [Status checks](/docs/guides/devices#status-checks). +- Wi-Fi can only be changed over USB, never through the server. +- One SSH login runs at a time; a second one at the same moment is refused. diff --git a/content/docs/agents/linux.md b/content/docs/agents/linux.md new file mode 100644 index 0000000..d91f631 --- /dev/null +++ b/content/docs/agents/linux.md @@ -0,0 +1,340 @@ +--- +title: "Linux Agent" +description: "The NetIgnite agent for Linux: install it with one command, pair it, host mode and Wake-on-LAN mode, the systemd service, polkit, uninstalling and security." +navigation: + title: Linux Agent +--- + +# Linux Agent + +`netignite-agent` connects a Linux machine to your NetIgnite server. It has two modes: + +- **Host mode** (always on): the dashboard sees whether this machine is online and can **shut it + down or reboot it**. +- **Wake-on-LAN mode** (optional): the agent also **wakes other devices** in its network, + **checks whether they're online** and **logs in to them over [SSH](/docs/guides/ssh)** to shut + them down or reboot them. That's what the [ESP32 agent](/docs/agents/esp32) does, on a machine + you already run: a Raspberry Pi, a NAS or a home server. + +The agent is a single binary that runs as a systemd service under its own unprivileged user. It +talks to the server over one outgoing WebSocket; nothing listens on the machine. Source and +releases: [NetIgnite-project/Agent-Linux](https://github.com/NetIgnite-project/Agent-Linux). + +## Requirements + +- Linux with systemd, on a 64-bit x86 or ARM CPU. +- polkit, for shutdown and reboot (`apt install polkitd` on Debian and Ubuntu, `dnf install polkit` + on Fedora). Without it, the agent runs, but shutdown and reboot fail. +- Wake-on-LAN mode: `ping` (iputils or BusyBox) and `ip` (iproute2). +- `curl` and `sha256sum` for the install script. + +| Binary | For | +| --- | --- | +| `netignite-agent-linux-x64` | x86-64 CPUs with AVX2 (about 2013 and newer) | +| `netignite-agent-linux-x64-baseline` | Any x86-64 CPU | +| `netignite-agent-linux-arm64` | 64-bit ARM, for example a Raspberry Pi 3, 4 or 5 with a 64-bit OS | + +## Install + +1. In the dashboard, create an agent of the kind **Linux agent** (**Agents → Add agent**). +2. On its page, click **Create setup command**. +3. Run the command on the Linux machine within 15 minutes: + +```bash [Terminal] +curl -fsSL https://netignite.example.com/install/linux.sh | sudo sh -s -- netignite_enroll_… +``` + +The script, served by your NetIgnite server: + +1. checks that it runs on Linux as root with `curl`, `sha256sum` and systemd, +2. picks the binary for the CPU: `arm64`, `x64` if the CPU has AVX2, `x64-baseline` otherwise, +3. downloads it and `SHA256SUMS` from the + [GitHub releases](https://github.com/NetIgnite-project/Agent-Linux/releases) and checks the + checksum, +4. [pairs](#pair) the agent with the server using the token, +5. [installs](#the-systemd-service) the systemd service and starts it. + +The agent shows up as online on its page. Then: + +- To control **this machine**, open (or add) the device for it, choose the agent under + **Linux agent on the device**, and choose the status check **Linux agent on the device**. The + device gets **Shut down** and **Reboot**. +- To **wake other devices**, turn on [Wake-on-LAN mode](#wake-on-lan-mode) and choose the agent as + the **Agent** of those devices. + +By default, the script installs the newest release, pre-releases included. The server's admin can +pin a release with `NETIGNITE_LINUX_AGENT_VERSION` or use another repository with +`NETIGNITE_LINUX_AGENT_REPO` (see [Configuration](/docs/server/configuration#linux-agent-installer)). +On the machine, the variables `NETIGNITE_AGENT_VERSION` (a release tag) and +`NETIGNITE_AGENT_BASE_URL` (a mirror with the release files) override that; pass them through +`sudo env`: + +```bash [Terminal] +curl -fsSL https://netignite.example.com/install/linux.sh | sudo env NETIGNITE_AGENT_VERSION=v1.0.0 sh -s -- netignite_enroll_… +``` + +### Manual install + +Without the script, or if the machine reaches the server under another address than the +dashboard's: + +```bash [Terminal] +arch=x64 # or x64-baseline, arm64 +base=https://github.com/NetIgnite-project/Agent-Linux/releases/latest/download +curl -fsSLO "$base/netignite-agent-linux-$arch" +curl -fsSLO "$base/SHA256SUMS" +sha256sum --check --ignore-missing SHA256SUMS +chmod +x "netignite-agent-linux-$arch" + +sudo ./netignite-agent-linux-$arch pair https://netignite.example.com netignite_enroll_… +sudo ./netignite-agent-linux-$arch install +``` + +`releases/latest` skips pre-releases. `install` copies the binary to +`/usr/local/bin/netignite-agent`. + +### Updating + +Run the install script again without a token. It downloads the current release and reinstalls the +service with the existing pairing: + +```bash [Terminal] +curl -fsSL https://netignite.example.com/install/linux.sh | sudo sh +``` + +## Pair + +```bash [Terminal] +sudo netignite-agent pair [--wol] [--config ] +``` + +- The agent generates its own secret (32 random bytes), sends it with the token to + `POST /api/v1/agents/enroll` and writes the result to `/etc/netignite/agent.json` + (mode `0600`). Neither the secret nor the token is printed. +- `` is the server's address, for example `https://netignite.example.com` or + `http://192.168.1.10:18232`. A missing scheme means `https://`. +- Pairing again with a new token replaces the credential. The server disconnects the old one, and + an installed service is restarted with the new one. +- A token of an ESP32 agent is refused and stays valid for that ESP32. Create a Linux agent for this + machine instead. +- `--wol` turns on Wake-on-LAN mode. Without it, pairing again keeps the previous mode. + +The config file looks like this: + +```json [agent.json] +{ + "server": "https://netignite.example.com", + "agent_id": 3, + "token_id": "…", + "secret": "…", + "wol": { "enabled": false } +} +``` + +## Host mode + +In host mode, the agent announces the capabilities `host.status` and `host.control`. The server can +then: + +- show the machine as online while the agent is connected (status check + *Linux agent on the device*), +- send `HOST_CONTROL` with `shutdown` or `reboot`. The agent answers, waits about a second and runs + `systemctl --no-ask-password poweroff` or `systemctl --no-ask-password reboot`. + +Nothing else: the server can't run commands, read files or change the configuration. A failed +`systemctl` call (polkit missing, or a block inhibitor such as a running package upgrade) is logged +in the agent's journal. + +## Wake-on-LAN mode + +```bash [Terminal] +sudo netignite-agent wol-mode on # or: pair --wol +sudo netignite-agent wol-mode off +``` + +`wol-mode` updates the config and the installed service, and restarts it. In this mode, the agent +also announces `wake`, `status.link_local`, `status.ip`, `status.host`, `status.tcp` and `ssh`: + +- **Wake:** a magic packet over UDP to `255.255.255.255` and each network interface's broadcast + address, three times, 100 ms apart. +- **Status:** the device is online if it answers a ping (`ping -c 1 -W 1`) or the TCP port (a + refused connection counts too). For the MAC check, the agent first looks at the neighbor table + (`ip neigh`): a reachable entry for the MAC address counts as online. Otherwise it pings the + IPv6 link-local address derived from the MAC address and the addresses the neighbor table knows + for it. Every check answers within 4.5 seconds. +- **SSH:** logins to devices in the network for [shutdown and reboot over SSH](/docs/guides/ssh). + +To test Wake-on-LAN from the machine itself: + +```bash [Terminal] +netignite-agent wol AA:BB:CC:DD:EE:FF --port 9 +``` + +## The systemd service + +`install` needs root and a paired config. It: + +- creates the system user `netignite` (no home directory, no login shell), +- copies the binary to `/usr/local/bin/netignite-agent`, +- makes the config readable for the service: `/etc/netignite` owned by `root:netignite` with mode + `0750`, `agent.json` owned by `netignite` with mode `0600`, +- writes `/etc/systemd/system/netignite-agent.service`, +- writes the polkit rule `/etc/polkit-1/rules.d/50-netignite-agent.rules` (plus a `.pkla` file in + `/etc/polkit-1/localauthority/50-local.d/` on polkit older than 0.106, for example Debian 11), +- runs `systemctl daemon-reload`, `enable` and `restart`. + +`sudo netignite-agent install --dry-run` prints every step and the files' contents without changing +anything. The unit for host mode (comments left out): + +```ini [netignite-agent.service] +[Unit] +Description=NetIgnite Agent +Documentation=https://github.com/NetIgnite-project/Agent-Linux +Wants=network-online.target +After=network-online.target + +[Service] +Type=simple +User=netignite +Group=netignite +ExecStart=/usr/local/bin/netignite-agent run --config /etc/netignite/agent.json +Restart=always +RestartSec=5 +RestartPreventExitStatus=78 +TimeoutStopSec=10 + +NoNewPrivileges=yes +ProtectSystem=strict +ProtectHome=yes +PrivateTmp=yes +PrivateDevices=yes +ReadOnlyPaths=/etc/netignite +ProtectKernelTunables=yes +ProtectKernelModules=yes +ProtectKernelLogs=yes +ProtectControlGroups=yes +ProtectClock=yes +ProtectHostname=yes +RestrictNamespaces=yes +RestrictRealtime=yes +RestrictSUIDSGID=yes +LockPersonality=yes +SystemCallArchitectures=native +UMask=0077 +RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_NETLINK +CapabilityBoundingSet= + +[Install] +WantedBy=multi-user.target +``` + +In Wake-on-LAN mode, the last line is replaced by `CapabilityBoundingSet=CAP_NET_RAW` and +`AmbientCapabilities=CAP_NET_RAW`, which `ping` needs. Use `systemctl edit netignite-agent` for local +changes; `install` and `wol-mode` overwrite the unit file. + +### polkit + +The rule lets the user `netignite`, and nobody else, use exactly the logind actions behind +`systemctl poweroff` and `systemctl reboot`: + +```js [polkit rule] +polkit.addRule(function (action, subject) { + var allowed = [ + "org.freedesktop.login1.power-off", + "org.freedesktop.login1.power-off-multiple-sessions", + "org.freedesktop.login1.reboot", + "org.freedesktop.login1.reboot-multiple-sessions" + ]; + if (subject.user == "netignite" && allowed.indexOf(action.id) >= 0) { + return polkit.Result.YES; + } +}); +``` + +The `*-multiple-sessions` actions apply when other users are logged in, for example over SSH. The +`*-ignore-inhibit` actions aren't granted, so a block inhibitor (a running package upgrade, for +example) still stops a remote shutdown. `systemctl` asks logind over D-Bus, and logind asks polkit; +no setuid helper is involved, so `NoNewPrivileges` doesn't get in the way. + +## Commands + +| Command | What it does | +| --- | --- | +| `pair ` | Registers with the server and writes the config (`--wol`, `--config`). | +| `install` | Sets up and starts the systemd service (`--config`, `--dry-run`). | +| `uninstall` | Removes the service (`--purge`, `--config`, `--dry-run`). | +| `run` | Runs the agent in the foreground; this is what the service runs (`--wol`, `--config`). | +| `status` | Shows the config, the server, the agent ID, the service state and whether the server and the credential work. Never shows the secret. | +| `wol-mode on\|off` | Turns Wake-on-LAN mode on or off (`--config`). | +| `wol ` | Sends a magic packet from this machine (`--port`, default `9`). | +| `version` | Prints the version. | + +Global flags go before the command, for example `netignite-agent --log-level debug run`. The +default config path is `/etc/netignite/agent.json`. + +Exit codes: `0` success, `1` error, `78` the server rejected the credential. The service isn't +restarted after `78`; pair again. + +```bash [Terminal] +sudo netignite-agent status +journalctl -u netignite-agent -f +``` + +## Connection + +The agent keeps one WebSocket to `/ws/agent` (`wss://` for `https://` servers) and speaks +the [agent protocol](/docs/agents/protocol). Its `HELLO` reports the version, the capabilities and, +for the dashboard, the host name, OS, kernel, architecture, uptime, IP addresses and whether +Wake-on-LAN mode is on. + +- It reconnects after 5 seconds, doubling the wait up to 60 seconds. If the server sends nothing for + 100 seconds (it pings every 30 seconds), the agent drops the connection and reconnects. +- It stops for good and exits with `78` when the server rejects the credential (HTTP 401), revokes + it (close code `4003`, for example after pairing again or **Revoke credential**) or deletes the + agent (`4004`). +- If another connection with the same credential takes over (`4000`), it waits 60 seconds before + reconnecting, so two copies of the agent don't keep replacing each other. + +## Uninstall + +```bash [Terminal] +sudo netignite-agent uninstall # keeps /etc/netignite/agent.json (root-only again) +sudo netignite-agent uninstall --purge # also deletes the config with the credential +``` + +This stops and disables the service and removes the unit, the polkit rule, the user `netignite` +and `/usr/local/bin/netignite-agent`. `--dry-run` shows the steps. Delete the agent in the dashboard +too. + +## Security + +- **Fixed actions only.** The server can ask for `PING`, `HOST_CONTROL` (`shutdown` or `reboot`) + and, in Wake-on-LAN mode, `WAKE`, `STATUS`, `SSH_CHECK` and `SSH_EXEC` (one of four fixed commands + on another device). Every parameter is validated; commands run without a shell, and only IP + addresses reach `ping`. There's no remote shell, no file access and no configuration change from + the server. +- **The secret** is generated on the machine and only leaves it in the `Authorization` header. The + server stores only a hash. It's in `/etc/netignite/agent.json` with mode `0600` and never printed, + not even by `status`. Use an `https://` server address outside your LAN. +- **The service** runs as the unprivileged user `netignite` with the hardening options above, without + capabilities in host mode and with only `CAP_NET_RAW` in Wake-on-LAN mode. +- **SSH logins** for other devices arrive with each request and are only kept in memory for that + login; the agent never writes or logs them. See [SSH](/docs/guides/ssh#security). +- The binary doesn't load `.env` or `bunfig.toml` from the working directory, so a stray file there + can't change how `pair` or `install` behave when run as root. + +## Building from source + +The agent is written in TypeScript and compiled with [Bun](https://bun.sh) into single binaries. + +```bash [Terminal] +git clone https://github.com/NetIgnite-project/Agent-Linux.git +cd Agent-Linux +bun install +bun test +bun run compile all --no-version-tag # build/bin/netignite-agent-linux-{x64,x64-baseline,arm64} +``` + +Releases are built by the repository's release workflow when a tag `v` is pushed; it +attaches the three binaries and `SHA256SUMS`. Versions with a suffix, such as `1.0.0-beta.1`, are +published as pre-releases. diff --git a/content/docs/agents/protocol.md b/content/docs/agents/protocol.md index 38b8148..a7aa00c 100644 --- a/content/docs/agents/protocol.md +++ b/content/docs/agents/protocol.md @@ -1,197 +1,453 @@ --- -title: "Agent Protocol" -description: "The WebSocket protocol between the NetIgnite server and its agents: connection, authentication, message format and commands." +title: "Agent Protocol v2" +description: "The WebSocket protocol between the NetIgnite server and its agents: enrollment, frames, HELLO, capabilities, every command and event, close codes and an example agent." navigation: title: Agent Protocol --- -# Agent Protocol +# Agent Protocol v2 -Agents talk to the NetIgnite server over a single WebSocket connection that the agent opens. The -protocol is plain text and small enough to implement in a few dozen lines, so you can write an -agent for any platform: a Raspberry Pi, a NAS or a router. +Agents talk to the NetIgnite server over one WebSocket that the agent opens. Messages are JSON, and +the server only sends an agent the commands it said it supports, so you can write a small agent for +any platform. The server's source of truth is +[`server/lib/agents/protocol.ts`](https://github.com/NetIgnite-project/Server/blob/main/server/lib/agents/protocol.ts). + +::note +Protocol v2 replaces the v1 protocol (`/api/agent-control-service`, `COMMAND:ID:JSON` text frames, +HTTP Basic authentication). v1 agents can't connect to a v2 server. +:: + +## Overview + +1. The agent **enrolls** once with a single-use token and a secret it generated, and gets a token + ID. +2. It **connects** to `/ws/agent` with its credential and sends `HELLO` with its capabilities. +3. The server sends **requests**; the agent answers each one. The agent can send **events** at any + time after `HELLO`. + +## Enrollment + +The owner creates an enrollment token in the dashboard (**Create setup command** on the agent's +page, or `POST /api/v1/agents//enrollments`). It looks like `netignite_enroll_:`, +works once and expires after 15 minutes. + +The agent generates a secret of 32 to 128 characters from `A-Z a-z 0-9 _ -` (the official agents +use 32 random bytes as 64 hex characters) and sends it with the token: + +```http +POST /api/v1/agents/enroll HTTP/1.1 +Host: netignite.example.com +Content-Type: application/json + +{ "enrollment_token": "netignite_enroll_…", "secret": "", "kind": "linux" } +``` + +`kind` (`esp32` or `linux`) is optional. With it, the server refuses a token that belongs to an +agent of another kind before using it up. + +```json +{ + "success": true, + "code": 200, + "message": "Agent enrolled", + "data": { "agent_id": 3, "kind": "linux", "token_id": "9f2c…" } +} +``` + +| Status | Meaning | +| --- | --- | +| `200` | Enrolled. The agent's credential is `netignite_agent_:`. | +| `400` | Invalid body or secret. | +| `401` | Unknown, used or expired token. Get a new one. | +| `409` | The token is for another kind of agent. It stays valid. | +| `429` | More than 20 attempts from one address in 5 minutes. | + +The server stores only a hash of the secret. Enrolling again replaces the agent's credential and +disconnects a connection that uses the old one (close code `4003`). ## Connecting | Setting | Value | | --- | --- | -| **URL** | `wss:///api/agent-control-service` (or `ws://` without TLS) | -| **Port** | The server's HTTP port: `443` behind a proxy, `18232` for the Docker container | -| **Authentication** | HTTP Basic, sent with the WebSocket upgrade request | +| **URL** | `wss:///ws/agent` (`ws://` for an `http://` server) | +| **Authentication** | `Authorization: Bearer netignite_agent_:` on the upgrade request | | **Subprotocol** | None | +| **Frames** | UTF-8 text frames with one JSON object each, at most 64 KiB from the agent | ```http -GET /api/agent-control-service HTTP/1.1 +GET /ws/agent HTTP/1.1 Host: netignite.example.com Upgrade: websocket Connection: Upgrade -Authorization: Basic MzpzM2NyM3Q= +Authorization: Bearer netignite_agent_9f2c…:3a7d… ``` -The credentials are `base64(":")`; the example above encodes `3:s3cr3t`. The ID -is the agent's numeric **Connection ID** and the secret its **Connection Secret** (see -[Managing Agents](/docs/guides/agents)). - -- The server accepts the upgrade first and checks the credentials afterwards. With a missing or - wrong `Authorization` header, it closes the connection right away with code `1008` and reason - `Missing or invalid credentials`. -- Each agent can have one connection. When an agent connects again, the server closes the older - connection. -- If the agent's secret is regenerated or the agent is deleted, the server closes the connection. +- Without a valid credential, the server answers the upgrade with HTTP `401`. +- Each agent has one connection. When it connects again, the server closes the older connection + with `4000`. - Browsers can't set the `Authorization` header on WebSockets, so agents can't run in a browser. -## Message format +## Frames -Every message is a UTF-8 **text frame** with three parts separated by colons: +```json +{ "t": "req", "id": 7, "cmd": "WAKE", "p": { "mac": "AA:BB:CC:DD:EE:FF", "port": 9 } } +{ "t": "res", "id": 7, "ok": true, "p": {} } +{ "t": "res", "id": 7, "ok": false, "err": "The agent has no network connection" } +{ "t": "evt", "cmd": "HELLO", "p": { … } } +``` -```text -:: +| Field | Description | +| --- | --- | +| `t` | `req`: a request from the server. `res`: the agent's answer. `evt`: an event from the agent, without an answer. | +| `id` | `req`/`res`: a number from `0` to `4294967295`. The answer carries the request's `id`. | +| `cmd` | `req`/`evt`: the command or event name, for example `WAKE`. Case-sensitive. | +| `p` | The payload object. Optional in `res`; defaults to `{}`. | +| `ok`, `err` | `res`: whether the request succeeded, and an error message of up to 1024 characters. The dashboard shows `err` to the user. | + +Several requests can be in flight; answer them in any order. The server drops frames that aren't +valid JSON or don't match this shape, answers requests from the agent with +`{ "ok": false, "err": "Unknown command …" }`, and ignores answers to unknown IDs. + +## HELLO + +The first frame the agent sends. It must arrive within 10 seconds of connecting. + +```json +{ + "t": "evt", + "cmd": "HELLO", + "p": { + "protocol": 2, + "kind": "linux", + "version": "1.0.0", + "capabilities": ["host.status", "host.control"], + "info": { "hostname": "nas", "uptime_s": 86400 } + } +} ``` -| Part | Description | +| Field | Description | +| --- | --- | +| `protocol` | `2`. | +| `kind` | `esp32` or `linux`; must match the agent's kind in the dashboard. | +| `version` | The agent's version, 1–64 characters. Shown in the dashboard and compared for firmware updates. | +| `capabilities` | What the agent supports, up to 64 strings. Unknown ones are kept, so newer agents work with older servers. | +| `info` | Optional diagnostics, shown as key/value pairs on the agent's page. | + +An invalid `HELLO` closes the connection with `4001`. Events other than `HELLO` before it are +ignored. A later `HELLO` updates the stored capabilities, version and info. + +## Capabilities + +| Capability | Enables | ESP32 | Linux, host mode | Linux, Wake-on-LAN mode | +| --- | --- | --- | --- | --- | +| `wake` | `WAKE` | ✓ | | ✓ | +| `status.link_local` | `STATUS` with a `link_local` target | ✓ | | ✓ | +| `status.ip` | `STATUS` with an `ip` target | ✓ | | ✓ | +| `status.host` | `STATUS` with a `host` target | ✓ | | ✓ | +| `status.tcp` | `STATUS` with a `tcp_port` | ✓ | | ✓ | +| `host.status` | The agent runs on the device it reports (`HOST_STATUS` events) | | ✓ | ✓ | +| `host.control` | `HOST_CONTROL` | | ✓ | ✓ | +| `ota` | `OTA_BEGIN` and `OTA_PROGRESS` (ESP32 agents only) | ✓ | | | +| `logs` | `GET_LOGS`, `LOG_SUBSCRIBE` and `LOG` | ✓ | | | +| `config` | `GET_INFO`, `CONFIG_SET` | ✓ | | | +| `device.reboot` | `REBOOT` | ✓ | | | +| `device.reset` | `FACTORY_RESET` | ✓ | | | +| `ssh` | `SSH_CHECK`, `SSH_EXEC` | ✓ | | ✓ | + +`PING` needs no capability. A command without its capability fails on the server with +*… doesn't support …* and is never sent. + +## Requests + +The server waits for each answer; after the timeout the request fails (*The agent didn't answer in +time*). + +| Request | Timeout | | --- | --- | -| `COMMAND` | The command name, for example `WAKEUP`. Case-sensitive. | -| `ID` | A decimal number from `0` to `4294967295` that identifies the request. | -| `JSON` | A JSON object with the command's payload. May itself contain colons; split only at the first two. | +| `WAKE`, `GET_LOGS`, `GET_INFO`, `CONFIG_SET`, `REBOOT`, `FACTORY_RESET` | 5 s | +| `STATUS` | 6 s | +| `PING`, `HOST_CONTROL`, `OTA_BEGIN`, `LOG_SUBSCRIBE`, `SSH_CHECK` | 10 s | +| `SSH_EXEC` | 15 s | -The server sends commands; the agent answers each one with the **same command name and ID** and a -response object: +### PING -```text -→ WAKEUP:2876151413:{"macAddress":"AA:BB:CC:DD:EE:FF","port":9} -← WAKEUP:2876151413:{"status":"OK","message":"Wakeup successful"} -``` +Keep-alive. The server sends it every 30 seconds after `HELLO`. -The server waits **5 seconds** for each response; after that it treats the command as failed. -Several commands can be pending at once, and responses may arrive in any order. They're matched by -ID. +| Request | Answer | +| --- | --- | +| `{}` | `{}` | -## Commands +### WAKE -### WAKEUP +Send a Wake-on-LAN magic packet: 6 bytes `0xFF`, then the 6-byte MAC address 16 times, as a UDP +broadcast to the local network. -Send a Wake-on-LAN magic packet. +| Request field | Type | Description | +| --- | --- | --- | +| `mac` | string | Target MAC address, `AA:BB:CC:DD:EE:FF` (uppercase, colons). | +| `port` | number | UDP port, `1`–`65535`, usually `9`. | + +Answer `{}` once the packet was sent, or `ok: false` with the reason. + +### STATUS + +Check whether a device is online. Online means *any* requested probe answered. | Request field | Type | Description | | --- | --- | --- | -| `macAddress` | string | Target MAC address, as stored in the device record (for example `AA:BB:CC:DD:EE:FF`). | -| `port` | number | UDP port for the magic packet, usually `9`. | +| `target` | object | `{ "type": "link_local", "mac": "AA:BB:CC:DD:EE:FF" }`: the IPv6 link-local (EUI-64) address derived from the MAC. `{ "type": "ip", "address": "192.168.1.20" }`: an IPv4 or IPv6 address, possibly with a zone (`fe80::1%eth0`). `{ "type": "host", "host": "nas.local" }`: a host name. | +| `icmp` | boolean | Ping the target. `false` for the *SSH port* check. | +| `tcp_port` | number | Optional: also try a TCP connection to this port. | -| Response field | Type | Description | +| Answer field | Type | Description | | --- | --- | --- | -| `status` | `"OK"` \| `"ERROR"` | Whether the packet was sent. | -| `message` | string | Human-readable result. On `ERROR`, the dashboard shows it after *Wakeup command failed:*. | +| `online` | boolean | Whether the device answered. | -A magic packet is 6 bytes of `0xFF` followed by the 6-byte MAC address repeated 16 times, sent as -a UDP broadcast to the local subnet. +Answer within the 6-second timeout; the official agents give up after 4.5 seconds. `ok: false` +(for example an unresolvable host name) shows as *Unknown* with the error as the reason. -### GET_STATUS +### HOST_CONTROL -Report whether a device is reachable. +Shut down or reboot the machine the agent runs on. | Request field | Type | Description | | --- | --- | --- | -| `address` | string | The device's address. The current server always sends the device's **MAC address** here. | +| `action` | `"shutdown"` \| `"reboot"` | What to do. | + +Answer `{}` first, then act: once the machine goes down, the answer can't be sent anymore. + +### OTA_BEGIN -| Response field | Type | Description | +Install a firmware update (ESP32 agents). + +| Request field | Type | Description | | --- | --- | --- | -| `status` | `"OK"` \| `"ERROR"` | Whether the check could run. | -| `message` | string | Human-readable result. | -| `online` | `"online"` \| `"offline"` \| `"unknown"` | The device status shown in the dashboard. | +| `version` | string | The new version. | +| `path` | string | Where to download the image on the agent's server, for example `/api/v1/firmware/2.1.0/files/firmware.bin`. Download it with the agent's own `Authorization` header. | +| `size` | number | Image size in bytes. | +| `sha256` | string | SHA-256 of the image, 64 hex characters. | +| `signature` | string | Optional: the ECDSA P-256 signature (DER, hex) of the image. | -How you resolve a MAC address to something you can ping is up to the agent. The ESP32 agent pings -the device's EUI-64 IPv6 link-local address; an agent on a full computer could look the MAC up in -its ARP/neighbor table instead. +Answer `{}` when the download has started. Report progress with `OTA_PROGRESS` events, restart into +the new firmware and send a `HELLO` with the new version. If the new firmware fails and the agent +went back to the old one, its `HELLO` `info` contains `"ota_failed_version": ""`. The +server counts an update as failed after 15 minutes. -### HEARTBEAT +### GET_LOGS -Sent by the server to every connected agent once a minute, with an empty payload `{}`. The server -doesn't wait for the answer; replying with `{}` is fine. +| Request | Answer | +| --- | --- | +| `{}` | `{ "lines": ["…"], "level": "info" }`: the stored log lines, oldest first, and the log level (`error`, `warn`, `info` or `debug`). Both optional. | -```text -→ HEARTBEAT:1739004211:{} -← HEARTBEAT:1739004211:{} -``` +### LOG_SUBSCRIBE + +| Request field | Type | Description | +| --- | --- | --- | +| `enabled` | boolean | Start or stop sending `LOG` events. | + +With `enabled: true`, answer like `GET_LOGS` and then send new lines as `LOG` events until the +server sends `enabled: false`. The server subscribes while someone has the agent's console open. + +### GET_INFO + +| Request | Answer | +| --- | --- | +| `{}` | `{ "version": "…", "info": { … } }`: the same diagnostics as `HELLO`'s `info`. The server stores them. | + +### CONFIG_SET + +Change settings stored on the agent. Only the fields present change. + +| Request field | Type | Description | +| --- | --- | --- | +| `server_url` | string | New server URL (`http://` or `https://`). | +| `name` | string | Host name: letters, digits and `-`, at most 63 characters. | +| `log_level` | `"error"` \| `"warn"` \| `"info"` \| `"debug"` | Log level. | +| `ip` | object | `{ "mode": "dhcp" }`, or `{ "mode": "static", "address", "gateway", "netmask", "dns1"?, "dns2"? }` with IPv4 addresses. | + +Answer `{}` when the change was accepted. The ESP32 agent tries a new `server_url` or `ip` first +and keeps it only if it reaches the server within 2 minutes. + +### REBOOT and FACTORY_RESET + +| Request | Answer | +| --- | --- | +| `{}` | `{}`, then restart. `FACTORY_RESET` erases Wi-Fi, server and credential first. The server revokes the agent's credential after a successful `FACTORY_RESET`. | + +### SSH_CHECK + +Log in to a device over SSH without opening a session, so no command (not even a forced one) runs. + +| Request field | Type | Description | +| --- | --- | --- | +| `host` | string | Host name or IP address. | +| `port` | number | SSH port. | +| `username` | string | User name. | +| `auth` | object | `{ "type": "password", "password": "…" }` or `{ "type": "key", "private_key": "-----BEGIN OPENSSH PRIVATE KEY-----…" }`. | +| `host_key` | string \| null | The trusted host key fingerprint (`SHA256:…`), or `null` before it's trusted. If the device presents another key, stop before sending any credential. | + +| Answer field | Type | Description | +| --- | --- | --- | +| `online` | boolean | Whether the SSH port accepted the connection. | +| `host_key` | string | The device's host key fingerprint, `SHA256:` plus unpadded base64 (as `ssh-keygen -l` shows it). | +| `authenticated` | boolean | Whether the login worked. | +| `error` | string | `host_key_mismatch`, `auth_failed` or a short message. | + +### SSH_EXEC + +Log in and run a fixed shutdown or reboot command. The request contains the `SSH_CHECK` fields +(with a trusted `host_key`, never `null`) and: + +| Request field | Type | Description | +| --- | --- | --- | +| `action` | `"shutdown"` \| `"reboot"` | What to do. | +| `os` | `"posix"` \| `"windows"` | Which command: `shutdown -h now` / `shutdown -r now`, or `shutdown /s /t 0` / `shutdown /r /t 0`. | +| `sudo` | boolean | POSIX: prefix the command with `sudo -n `. | + +Answer `{}` when the command ran (or the connection dropped because the device went down), or +`ok: false` with the error, for example `sudo: a password is required`. Never run anything else, and +don't keep the credentials after the request. + +## Events + +### LOG + +| Field | Type | Description | +| --- | --- | --- | +| `lines` | string[] | New log lines, at most 500 per event and 1024 characters per line. | +| `dropped` | number | Optional: lines the agent dropped because its buffer was full. | + +### OTA_PROGRESS + +| Field | Type | Description | +| --- | --- | --- | +| `version` | string | The version being installed. | +| `state` | `"downloading"` \| `"installed"` \| `"failed"` | `installed`: the agent restarts next. | +| `percent` | number | `0`–`100`. | +| `error` | string | Optional: why it failed. | + +### HOST_STATUS + +Sent by the Linux agent after `HELLO` and every 5 minutes: `{ "hostname": "nas", "uptime_s": 86400 }`. +The current server accepts and ignores it; the device's status comes from the connection itself. + +## Keep-alive and close codes + +The server sends `PING` every 30 seconds and closes a connection that sent nothing for 75 seconds. +Agents should treat 100 seconds without any frame from the server as a dead connection and +reconnect, with a backoff (the official agents: 5 seconds, doubling up to 60 seconds). + +| Code | Meaning | What the agent should do | +| --- | --- | --- | +| `1001` | The server is shutting down. | Reconnect. | +| `4000` | Replaced: the same agent connected again. | Wait before reconnecting (the Linux agent waits 60 seconds); two copies may be running. | +| `4001` | No valid `HELLO` within 10 seconds, wrong protocol or kind. | Fix the `HELLO`. | +| `4002` | No message for 75 seconds. | Reconnect. | +| `4003` | Credential revoked: enrolled again, revoked or reset in the dashboard. | Stop; enroll again. | +| `4004` | The agent was deleted. | Stop. | ## Example agent -A minimal agent for [Bun](https://bun.sh) that handles all three commands. Create an agent of type -**Server** in the dashboard and fill in its credentials and your subnet's broadcast address: +A minimal agent for [Bun](https://bun.sh) that wakes devices. In the dashboard, create an agent of +the kind **Linux agent** (custom agents use that kind), click **Create setup command** and copy the +token (`netignite_enroll_…`) from the command. Set your server and your subnet's broadcast address: ```ts [agent.ts] +// bun run agent.ts (first run) +// bun run agent.ts (afterwards) import { createSocket } from "node:dgram"; +import { readFileSync, writeFileSync } from "node:fs"; -const SERVER_URL = "wss://netignite.example.com/api/agent-control-service"; -const AGENT_ID = "3"; -const AGENT_SECRET = "your-connection-secret"; -const BROADCAST_ADDRESS = "192.168.1.255"; +const SERVER = "https://netignite.example.com"; +const BROADCAST = "192.168.1.255"; +const CREDENTIAL_FILE = "./netignite-agent.json"; type Payload = Record; -function sendMagicPacket(macAddress: string, port: number) { - const mac = macAddress.split(/[:-]/).map((part) => Number.parseInt(part, 16)); - if (mac.length !== 6 || mac.some(Number.isNaN)) throw new Error("Invalid MAC address"); +async function enroll(token: string): Promise { + // The agent's own secret: 32 random bytes as 64 hex characters. + const secret = Buffer.from(crypto.getRandomValues(new Uint8Array(32))).toString("hex"); + const response = await fetch(`${SERVER}/api/v1/agents/enroll`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ enrollment_token: token, secret, kind: "linux" }), + }); + const body = await response.json(); + if (!body.success) throw new Error(`Enrollment failed: ${body.message}`); + + const credential = `netignite_agent_${body.data.token_id}:${secret}`; + writeFileSync(CREDENTIAL_FILE, JSON.stringify({ credential }), { mode: 0o600 }); + return credential; +} +function sendMagicPacket(mac: string, port: number) { + const bytes = mac.split(":").map((part) => Number.parseInt(part, 16)); // 6 × 0xFF, then the MAC address 16 times. const packet = Buffer.alloc(102, 0xff); - for (let i = 0; i < 16; i++) Buffer.from(mac).copy(packet, 6 + i * 6); + for (let i = 0; i < 16; i++) Buffer.from(bytes).copy(packet, 6 + i * 6); const socket = createSocket("udp4"); socket.bind(() => { socket.setBroadcast(true); - socket.send(packet, port, BROADCAST_ADDRESS, () => socket.close()); + socket.send(packet, port, BROADCAST, () => socket.close()); }); } -function handleCommand(command: string, payload: Payload): Payload { - switch (command) { - case "WAKEUP": - sendMagicPacket(String(payload.macAddress), Number(payload.port)); - return { status: "OK", message: "Wakeup successful" }; - case "GET_STATUS": - // Look up and ping the device here, then report "online" or "offline". - return { status: "OK", message: "Status check not implemented", online: "unknown" }; - case "HEARTBEAT": +function handle(cmd: string, p: Payload): Payload { + switch (cmd) { + case "PING": + return {}; + case "WAKE": + sendMagicPacket(String(p.mac), Number(p.port)); return {}; default: - return { status: "ERROR", message: "Unknown command" }; + throw new Error(`Unknown command ${cmd}`); } } -function connect() { - const ws = new WebSocket(SERVER_URL, { - headers: { Authorization: `Basic ${btoa(`${AGENT_ID}:${AGENT_SECRET}`)}` }, +function connect(credential: string) { + const ws = new WebSocket(`${SERVER.replace(/^http/, "ws")}/ws/agent`, { + headers: { Authorization: `Bearer ${credential}` }, }); - ws.onopen = () => console.log("Connected"); + ws.onopen = () => { + const hello = { protocol: 2, kind: "linux", version: "0.1.0", capabilities: ["wake"] }; + ws.send(JSON.stringify({ t: "evt", cmd: "HELLO", p: hello })); + }; ws.onmessage = (event) => { - const message = String(event.data); - const first = message.indexOf(":"); - const second = message.indexOf(":", first + 1); - const command = message.slice(0, first); - const id = message.slice(first + 1, second); - - let response: Payload; + const frame = JSON.parse(String(event.data)); + if (frame.t !== "req") return; try { - response = handleCommand(command, JSON.parse(message.slice(second + 1))); + const p = handle(frame.cmd, frame.p ?? {}); + ws.send(JSON.stringify({ t: "res", id: frame.id, ok: true, p })); } catch (error) { - response = { status: "ERROR", message: String(error) }; + ws.send(JSON.stringify({ t: "res", id: frame.id, ok: false, err: String(error) })); } - ws.send(`${command}:${id}:${JSON.stringify(response)}`); }; ws.onclose = (event) => { - console.log(`Disconnected (${event.code} ${event.reason}), reconnecting in 10 seconds`); - setTimeout(connect, 10_000); + if (event.code === 4003 || event.code === 4004) { + console.error(`Credential revoked or agent deleted (${event.code}); enroll again.`); + return; + } + console.log(`Disconnected (${event.code}), reconnecting in 10 seconds`); + setTimeout(() => connect(credential), 10_000); }; } -connect(); +const token = Bun.argv[2]; +const credential = token + ? await enroll(token) + : JSON.parse(readFileSync(CREDENTIAL_FILE, "utf8")).credential; +connect(credential); ``` ```bash [Terminal] -bun run agent.ts +bun run agent.ts netignite_enroll_… ``` -The agent's status in the dashboard turns **online** as soon as it connects. +The agent shows up as **Online** in the dashboard. Choose it as the **Agent** of a device and +**Power on** works. Status checks show *Unknown* until you add the `status.*` capabilities and +answer `STATUS`. diff --git a/content/docs/getting-started.md b/content/docs/getting-started.md index be83cd6..d6c5335 100644 --- a/content/docs/getting-started.md +++ b/content/docs/getting-started.md @@ -1,6 +1,6 @@ --- title: "Getting Started" -description: "Set up a self-hosted NetIgnite server, connect an ESP32 agent and wake your first device." +description: "Set up a self-hosted NetIgnite server, install an ESP32 agent from the browser and wake your first device." navigation: title: Getting Started --- @@ -10,21 +10,23 @@ navigation: This guide takes you from an empty machine to your first remote wake-up. You need: - a Linux host with [Docker](https://docs.docker.com/engine/install/) and Docker Compose for the - server, -- an ESP32 development board (`esp32dev`) and a USB cable, + server, and a host name with HTTPS for it (see [HTTPS & Reverse Proxy](/docs/server/reverse-proxy)), +- an ESP32 development board (a classic ESP32, for example an *ESP32 DevKit*) and a USB data cable, +- a computer with Google Chrome, Microsoft Edge or Firefox 151 or newer to install the agent, - a device that supports Wake-on-LAN, in the same LAN as the ESP32. ::tip Using the hosted app at [app.netignite.dev](https://app.netignite.dev/)? Skip steps 1 and 2, log -in, and continue with step 3. Use `app.netignite.dev`, port `443` and SSL as the agent's server -settings. +in, and continue with step 3. No ESP32? A [Linux agent](/docs/agents/linux) on an always-on Linux +machine in the same network works too. :: ::steps{level="3"} ### Start the server -Create a `docker-compose.yml` and start it: +Create a `docker-compose.yml`. Set `NETIGNITE_APP_URL` to the address you'll open the dashboard +at: ```yaml [docker-compose.yml] services: @@ -34,6 +36,8 @@ services: restart: unless-stopped ports: - "18232:18232" + environment: + NETIGNITE_APP_URL: https://netignite.example.com volumes: - /opt/netignite/data:/opt/netignite/data - /opt/netignite/config:/opt/netignite/config @@ -43,59 +47,59 @@ services: docker compose up -d ``` -The server listens on port `18232`. See [Installation](/docs/server/installation) for details and -other ways to run it. +The server listens on port `18232`. Put it behind a reverse proxy with HTTPS: logging in and the +ESP32 web installer only work over HTTPS (or on `localhost`). See +[Installation](/docs/server/installation) and [HTTPS & Reverse Proxy](/docs/server/reverse-proxy). -### Log in as admin +### Set the admin password -On first start, the server creates an `admin` account with a random password and writes it to -`data/initial_admin_credentials`: +On first start, the server creates the user `admin` and writes a link for setting its password to +`config/initial_admin_password_reset_token.txt`: ```bash [Terminal] -sudo cat /opt/netignite/data/initial_admin_credentials +sudo cat /opt/netignite/config/initial_admin_password_reset_token.txt ``` -Open the dashboard and log in. The session cookie is only accepted over HTTPS or on `localhost`, -so either open `http://localhost:18232` on the server itself or put the server behind -[HTTPS](/docs/server/reverse-proxy) first. Then change the password under **Settings**. +Open the link (it's valid for 7 days), choose a password and log in as `admin`. The welcome page +suggests a few first steps; start with **Complete your profile** and enter your real email +address. See [Users & Accounts](/docs/server/users). ### Create an agent -Go to **Agents → Add New Agent**, enter a name, choose the type **Microcontroller** and save. -The agent page now shows the **Connection ID** and the **Connection Secret**. You need both for -the next step. Copy the secret right away: it's shown only once. See +Go to **Agents → Add agent**, keep the kind **ESP32**, enter a name such as *Office ESP32* and click +**Create agent**. The agent's page opens; its status is **Not set up**. See [Managing Agents](/docs/guides/agents). -### Flash the ESP32 agent +### Install the ESP32 from the browser -Clone the [Agent-ESP32](https://github.com/NetIgnite-project/Agent-ESP32) repository, copy -`src/config/config.sample.h` to `src/config/config.h` and fill in your WiFi details, the server -address, and the Connection ID and Secret: +On the agent's page, click **Open the web installer** (or open the **Install** tab): -```cpp [config.h] -#define C_CONTROL_SERVER_HOST "netignite.example.com" -#define C_CONTROL_SERVER_PORT (uint16_t) 443 -#define C_CONTROL_SERVER_USE_SSL (bool) true +1. **Choose the firmware.** The recommended release is preselected. +2. Click **Prepare installer**. The browser downloads the firmware from your server, and the + server adds the agent's setup data: your server's address and a single-use token, valid for 15 + minutes. +3. Plug the ESP32 into your computer, click **Connect and install** and pick its serial port. + After flashing, enter your Wi-Fi name and password in the dialog. They go straight to the + ESP32, not to NetIgnite. +4. Wait a moment: the agent joins your Wi-Fi, connects to the server, and the page shows it as + connected. -#define C_CONTROL_SERVER_AUTH_ID "1" -#define C_CONTROL_SERVER_AUTH_SECRET "your-connection-secret" -``` - -Then build and flash the firmware with PlatformIO. The [ESP32 Agent](/docs/agents/esp32) page -walks through every setting and command. When the agent shows as **online** in the dashboard, it -is connected. +Details and alternatives are on the [ESP32 Agent](/docs/agents/esp32) page. ### Add a device -Go to **Devices → Add New Device** and enter a name, the device type, its **MAC address** (for -example `AA:BB:CC:DD:EE:FF`), the Wake-on-LAN **port** (`9` unless you know otherwise) and the -agent you just created. See [Devices & Wake-on-LAN](/docs/guides/devices). +Go to **Devices → Add device** and enter a name, the device type, its **MAC address** (for example +`AA:BB:CC:DD:EE:FF`), the **Wake-on-LAN port** (`9` unless you know otherwise) and the agent you +just set up. Under **Status check**, choose **IPv4 address** and enter the device's IP address to +see whether it's online. See [Devices](/docs/guides/devices). ### Power it on -Shut the device down, then press **Power On** on the dashboard. The agent sends the magic packet -and the device starts. +Shut the device down, then click the power button next to it on the **Overview** or **Devices** +page. The agent sends the magic packet and the device starts; its status turns **Online** a moment +later. :: -Nothing happened? Check [Troubleshooting](/docs/reference/troubleshooting). +Nothing happened? Check [Troubleshooting](/docs/reference/troubleshooting). Next, you could +[share the device](/docs/guides/sharing) or set it up for [shutdown over SSH](/docs/guides/ssh). diff --git a/content/docs/guides/agents.md b/content/docs/guides/agents.md index 59dc1f5..0bd1073 100644 --- a/content/docs/guides/agents.md +++ b/content/docs/guides/agents.md @@ -1,6 +1,6 @@ --- title: "Managing Agents" -description: "Create NetIgnite agents, find their Connection ID and Secret, and understand agent status." +description: "Create NetIgnite agents, set them up with the web installer or a setup command, use the ESP32 console and update the firmware over the network." navigation: title: Managing Agents --- @@ -8,64 +8,135 @@ navigation: # Managing Agents An agent is the part of NetIgnite that lives inside your network. It keeps a WebSocket connection -to the server and sends Wake-on-LAN packets for the devices assigned to it. You need one agent per -network (broadcast domain) that contains devices you want to wake. +to the server, wakes and checks devices, and shuts them down. You need one agent per network +(broadcast domain) that contains devices you want to wake. -## Agent types +## Agent kinds -| Type | Use it for | +| Kind | Use it for | | --- | --- | -| **Microcontroller** | The [ESP32 agent](/docs/agents/esp32) or other embedded agents. | -| **Server** | An agent running on an always-on computer, such as a [custom agent](/docs/agents/protocol). | +| **ESP32** | A small board in your network, installed from the browser. Wakes devices, checks their status and logs in to them over SSH. See [ESP32 Agent](/docs/agents/esp32). | +| **Linux agent** | A service for an always-on Linux machine, for example a Raspberry Pi or a server. Reports its own status and shuts the machine down or reboots it; in Wake-on-LAN mode it also wakes, checks and logs in to other devices. See [Linux Agent](/docs/agents/linux). | -The type is informational. It doesn't change how the server talks to the agent. +The kind can't be changed later. For an agent you [wrote yourself](/docs/agents/protocol), choose +**Linux agent**. ## Creating an agent -1. Open **Agents** and click **Add New Agent**. -2. Enter a **Name**, choose the **Agent Type** and optionally add a **Description**. -3. Click **Create Agent**. The server generates a random secret, and the agent's page opens. +1. Open **Agents** and click **Add agent**. +2. Choose the **Kind**, enter a **Name** and optionally a **Description**. +3. Click **Create agent**. The agent's page opens with the status **Not set up**. -The agent's page shows its **Connection Details**: +Then set it up. The **Set up** card on the agent's **General** tab shows how. -| Field | Agent setting | -| --- | --- | -| **Connection ID** | The agent's numeric ID, for example `3`. ESP32: `C_CONTROL_SERVER_AUTH_ID`. | -| **Connection Secret** | A random 32-character secret. ESP32: `C_CONTROL_SERVER_AUTH_SECRET`. | +## Setting up an agent + +Every setup uses a **single-use token** that is valid for **15 minutes**. The agent uses it once to +register and generates its own secret; NetIgnite never shows that secret. Creating a new token +invalidates older unused tokens of the agent. Setting an agent up again replaces its credential: a +device that still uses the old one is disconnected. + +### ESP32: web installer + +Click **Open the web installer** or open the **Install** tab. The installer flashes the firmware +over USB together with the agent's setup data (server address, name and token) and then asks for +your Wi-Fi. It needs Google Chrome, Microsoft Edge or Firefox 151 or newer on a computer, and the +dashboard must be opened over HTTPS. See +[Install from the browser](/docs/agents/esp32#install-from-the-browser). + +### ESP32: serial console + +For a board that already runs the v2 firmware, click **Create setup command** under +*Or over the serial console*. Connect to Wi-Fi in the board's serial console with +`wifi "" ""`, then paste the command: -::warning -The secret is shown **only once**, right after the agent is created. Copy it with **Copy Secret** -(or reveal it with the eye icon) before you leave the page. NetIgnite stores only a hash of it, so -it can't be displayed again; if you lose it, [generate a new one](#changing-the-secret). -:: +```text [Serial console] +setup https://netignite.example.com netignite_enroll_… +``` -Configure your agent with the two connection values and the server address. As soon as it -connects, its status turns **online**. To change the name, type or description later, edit the -fields and click **Update Agent**. +See [Serial console](/docs/agents/esp32#serial-console). + +### Linux agent: setup command + +Click **Create setup command** and run the command on the Linux machine: + +```bash [Terminal] +curl -fsSL https://netignite.example.com/install/linux.sh | sudo sh -s -- netignite_enroll_… +``` + +It downloads the agent, checks it, pairs it with the server and installs the systemd service. If +the agent is already installed, open *Agent already installed?* for a +`sudo netignite-agent pair …` command instead. See [Linux Agent](/docs/agents/linux). ## Agent status | Status | Meaning | | --- | --- | -| `online` | The agent is connected to the server. | -| `offline` | The agent is known but not connected. | -| `unknown` | The server has no state for this agent. | +| **Online** | The agent is connected. | +| **Offline** | The agent is set up but not connected. **Last seen** shows when it was. | +| **Not set up** | The agent has no credential yet, or it was revoked. | + +The **General** tab also shows the agent's version, its **capabilities** (what it can do, for +example `wake`, `status.ip`, `ssh` or `host.control`) and the diagnostics it reports: IP +addresses, Wi-Fi signal, uptime, memory and more. Agents that lose their connection reconnect on +their own, after 5 seconds at first and at most every 60 seconds. + +The **Devices** card lists the devices that use the agent, to wake them or as the Linux agent on +the device. + +## Console + +ESP32 agents with a v2 firmware that has the console get a **Console** tab: + +- **Log**: the agent's live log, starting with the lines it has stored (about the last 16 KB). + Choose the **log level** (*Errors*, *Warnings*, *Info*, *Debug*), turn **Follow** on or off, copy + or clear the view. The badge shows **Live**, **Agent offline** or **Disconnected**. +- **Settings on the agent**: + - **Server address**: where the agent connects. Leave it empty to keep it. + - **Host name**: letters, digits and `-`; used after the next restart. + - **IP address**: *Automatic (DHCP)* or *Static* with address, gateway, netmask and DNS server. + + Click **Send to agent**. A new server address or IP setting is only kept if the agent reaches the + server with it within 2 minutes; otherwise it restarts with the previous settings. +- **Restart**: the agent is back after a few seconds. +- **Factory reset**: erases Wi-Fi, server and credential on the agent and revokes the credential + in NetIgnite. Set it up again with the web installer. + +Wi-Fi can't be changed through the server. Use **Change Wi-Fi** in the web installer (see +[ESP32 Agent](/docs/agents/esp32#change-wi-fi)) or the `wifi` command in the serial console. Only +the agent's owner can open its console. + +## Firmware updates + +ESP32 agents whose firmware supports it (capability `ota`) are updated over the network. The +**Firmware** card on the agent's **General** tab shows the installed version and, when the admin's +[firmware channel](/docs/server/configuration#server-settings-in-the-dashboard) has a newer release, +**Update to …**. Use **Other version** to install any synced release, also an older one. The agent +must be online. + +An update goes through these steps, shown on the card: + +1. The agent downloads the firmware from your NetIgnite server with its own credential and checks + its size and SHA-256, and its signature if the agent's firmware requires signed updates. +2. It writes the firmware to its second firmware slot and restarts into it. +3. The new firmware is **on probation** until it has reached the server. If it crashes or doesn't + get there within 5 minutes, the board starts the previous firmware again, and the card reports + that the update was rolled back. -The **Agents** page refreshes the status every 20 seconds. The **Ping** button refreshes it right -away. An agent that loses its connection reconnects on its own (the ESP32 agent retries every 60 -seconds). +The card then shows *Updated from … to …* or *Update to … failed* with the reason. An update that +hasn't finished after 15 minutes counts as failed. -## Changing the secret +To update several agents, click **Update n agents** on the **Agents** page. It updates every online +ESP32 agent with an available update, one after another: each update has to finish before the next +one starts. -Click **Regenerate Secret** on the agent's page to replace the secret, for example if you lost it -or it may have leaked. The new secret is shown once. The old secret stops working right away and -the agent is disconnected; it can only reconnect once it's configured with the new secret. For the -ESP32 agent, that means rebuilding and reflashing the firmware. +ESP32 boards with a firmware that can't be updated over the network show *This firmware can't be +updated over the network*. Install a current firmware once with the web installer. -Secrets are always generated by the server; you can't choose your own. +## Revoking and deleting -## Deleting an agent +On the **General** tab, under **Danger Zone**: -Delete an agent with the trash icon on the **Agents** page. A connected agent is disconnected -immediately. Reassign or delete the devices that use it first: devices keep pointing at the -deleted agent and can no longer be woken. +- **Revoke credential** disconnects the agent. It can connect again only after it's set up again. +- **Delete agent** disconnects and deletes it. Devices that used it keep their settings but have no + agent, so they can't be woken until you choose another one. diff --git a/content/docs/guides/dashboard.md b/content/docs/guides/dashboard.md index 4a9e489..053d2fc 100644 --- a/content/docs/guides/dashboard.md +++ b/content/docs/guides/dashboard.md @@ -1,6 +1,6 @@ --- title: "Dashboard" -description: "A tour of the NetIgnite web app: the device overview, device and agent management, and account settings." +description: "A tour of the NetIgnite web app: overview, devices, agents, settings, API keys and the admin area." navigation: title: Dashboard --- @@ -8,43 +8,73 @@ navigation: # Dashboard The NetIgnite web app is the same on the [hosted app](https://app.netignite.dev/) and on a -self-hosted server. The sidebar has three entries: **Home**, **Devices** and **Agents**. Your -username at the bottom opens **Settings** and **Log out**. +self-hosted server. It lives under `/dashboard`; the server's root address redirects there. -## Home +The sidebar has: -The home page is an overview of all your devices: +- **Overview**, **Devices** and **Agents**, +- **Settings**: **General**, **Security** and **API Keys**, +- **Admin** (admins only): **Users** and **Server Settings**, +- **Documentation** (this site) and **API Reference** (the server's OpenAPI reference at + `/api/docs/v1`), +- your name at the bottom, with **Settings** and **Log out**. -- **Stat cards** with the number of devices that are online, offline, in an unknown state, and in - total. -- **Your Devices**: one card per device with its status and quick actions: +The first time you log in, a welcome page suggests three steps: complete your profile, add an agent +and add a device. Pick one or click **Skip for now**; it doesn't come back. -| Action | What it does | -| --- | --- | -| **Power On** | Sends a Wake-on-LAN packet through the device's agent. Shown while the device isn't online. | -| **Ping** | Refreshes the device's status right away. | -| **Edit** | Opens the device's settings. | +## Overview + +The overview shows your devices at a glance: + +- **Stat cards** with the number of devices that are online, offline and unknown, and how many of + your agents are online. +- A warning when you have agents but none of them is online. +- **Favorites**: your favorite devices with their status and power buttons. Without favorites, the + first eight devices are shown. Shared devices say who shared them. -Statuses refresh automatically every 20 seconds. +Everything refreshes every 20 seconds. ## Devices -**Devices** lists all your devices in a table. Search by name or description, filter by status or -type, and use the row actions to power on, ping, edit or delete a device. **Add New Device** opens -the device form. See [Devices & Wake-on-LAN](/docs/guides/devices). +**Devices** lists your devices and the devices shared with you. Search by name, filter by status or +type, and use the row's buttons: + +| Column | Shows | +| --- | --- | +| Star | Add the device to your favorites or remove it. | +| Name | Opens the device page. | +| Status | Online, offline or unknown. Hover the badge for the reason and when it was checked. | +| Access | Your role: owner, manager, operator or viewer, and who shared it. | +| Power buttons | **Power on**, and a menu with **Shut down** and **Reboot** when the device supports them. Viewers don't see them. | + +A device page has the tabs **General** (status, power buttons, **Check now**, settings) and, for the +owner, **Sharing** and **SSH**. See [Devices](/docs/guides/devices), [Sharing](/docs/guides/sharing) +and [SSH](/docs/guides/ssh). ## Agents -**Agents** lists your agents with their connection status. Search, filter by status or type, and -ping, edit or delete agents. **Add New Agent** creates a new agent. See -[Managing Agents](/docs/guides/agents). +**Agents** lists your agents with their kind, status (**Online**, **Offline** or **Not set up**), +firmware or agent version and when they were last seen. A badge shows when a firmware update is +available or running. **Update n agents** updates every online ESP32 agent with an available +update, one after another. + +An agent page has the tabs **General** (status, diagnostics, firmware, setup, settings) and, for +ESP32 agents, **Console** and **Install**. See [Managing Agents](/docs/guides/agents). ## Settings -**Settings** lets you change your username and password. See -[Users & Accounts](/docs/server/users). +- **General**: username, display name and email address. +- **Security**: change your password or delete your account. +- **API Keys**: keys for scripts and integrations. + +See [Users & Accounts](/docs/server/users). + +## Admin + +Admins see two more pages: + +- **Users**: create, edit and delete accounts and reset passwords. +- **Server Settings**: public sign-up, the default ESP32 firmware channel and the firmware sync. -::note -The **Shutdown** button appears for online devices, but remote shutdown isn't implemented yet. It -only shows a notice. Marking devices as favorites is not saved yet either. -:: +See [Users & Accounts](/docs/server/users#managing-users) and +[Configuration](/docs/server/configuration#server-settings-in-the-dashboard). diff --git a/content/docs/guides/devices.md b/content/docs/guides/devices.md index 8188db6..0ed5f7e 100644 --- a/content/docs/guides/devices.md +++ b/content/docs/guides/devices.md @@ -1,15 +1,15 @@ --- -title: "Devices & Wake-on-LAN" -description: "Add devices to NetIgnite, prepare them for Wake-on-LAN, power them on and check their status." +title: "Devices" +description: "Add devices to NetIgnite, prepare them for Wake-on-LAN, choose a status check, power them on, shut them down and mark favorites." navigation: - title: Devices & Wake-on-LAN + title: Devices --- -# Devices & Wake-on-LAN +# Devices -A device is a machine you want to power on remotely. NetIgnite wakes it with a +A device is a machine you want to control. NetIgnite wakes it with a [Wake-on-LAN](https://en.wikipedia.org/wiki/Wake-on-LAN) magic packet that an agent in the same -network broadcasts. +network sends, checks whether it's online, and can shut it down or reboot it. ## Preparing a device @@ -24,51 +24,90 @@ Wake-on-LAN is a hardware feature and usually disabled by default. Fast Startup helps on many systems. - Linux: check with `ethtool ` that `Wake-on` is `g`. Enable it with `ethtool -s wol g` and make it persistent through your network configuration. -3. **Wired connection.** Use Ethernet. Most computers can't be woken over WiFi. +3. **Wired connection.** Use Ethernet. Most computers can't be woken over Wi-Fi. 4. **Note the MAC address** of the wired network card, for example with `ip link` (Linux), `getmac /v` (Windows) or in your router's client list. ## Adding a device -Open **Devices → Add New Device** and fill in: +Open **Devices → Add device** and fill in: | Field | Description | | --- | --- | | **Name** | A name to recognize the device by. | -| **Device Type** | Server, Desktop PC, Laptop, Printer or NAS Storage. Only used for display. | +| **Type** | Server, Desktop PC, Laptop, Printer, NAS or Other. Only used for display. | | **Description** | Optional notes. | -| **MAC Address** | The MAC address of the device's network card, as `AA:BB:CC:DD:EE:FF` or `AA-BB-CC-DD-EE-FF`. | -| **Port** | The UDP port of the magic packet. Use `9` (the default) unless your device expects `7`. | -| **Agent** | One of your agents, in the same network as the device. | - -MAC addresses must be six pairs of hex digits separated by `:` or `-`. Other notations such as -`AABBCCDDEEFF` or `AABB.CCDD.EEFF` are rejected when you save the device. +| **MAC address** | The MAC address of the device's wired network card, as `AA:BB:CC:DD:EE:FF` or `AA-BB-CC-DD-EE-FF`. It's stored with colons and in uppercase. | +| **Wake-on-LAN port** | The UDP port of the magic packet. Use `9` (the default) unless the device expects `7`. | +| **Agent** | The agent in the same network that wakes the device and runs its status check and SSH login: an ESP32, or a Linux agent in [Wake-on-LAN mode](/docs/agents/linux#wake-on-lan-mode). | +| **Status check** | How NetIgnite finds out whether the device is online. See [below](#status-checks). | +| **Address** | For the IPv4, IPv6 and SSH checks: the address the agent checks. | +| **TCP port** | Optional for the MAC, IPv4 and IPv6 checks; the port for the SSH check (default `22`). | +| **Linux agent on the device** | The [Linux agent](/docs/agents/linux#host-mode) running on this device, if any. It enables **Shut down** and **Reboot** and the status check *Linux agent on the device*. | + +Only the owner chooses the agents; the lists show only your own agents. + +## Status checks + +| Status check | What the agent does | Use it for | +| --- | --- | --- | +| **Off** | Nothing; the status stays *Unknown*. | Devices you only wake. | +| **MAC (IPv6 link-local)** | Pings the IPv6 link-local address derived from the MAC address (EUI-64). A Linux agent also counts the device as online if its neighbor table (`ip neigh`) shows the MAC address as reachable. | Devices that use MAC-based IPv6 addresses. Doesn't work with Windows and many Linux defaults, which use random interface identifiers. | +| **IPv4 address** | Pings the address. | Most devices. Give them a fixed address (DHCP reservation). | +| **IPv6 address** | Pings the address. A zone such as `fe80::1%eth0` is allowed. | Devices with a stable IPv6 address. | +| **SSH port** | Tries a TCP connection to port 22, or the **TCP port** you enter. Works with an IP address or a host name. | Linux, macOS and NAS devices with an SSH server. It doesn't log in. | +| **Linux agent on the device** | Nothing: the device is online while its Linux agent is connected. | Machines that run the [Linux agent](/docs/agents/linux). | + +With **TCP port** set, the MAC, IPv4 and IPv6 checks also count the device as online when that port +answers. That helps with devices that don't answer pings, such as Windows PCs with the default +firewall: use `445` (file sharing) or `3389` (remote desktop). A refused connection also counts as +online: the device answered. + +The address must be reachable from the agent's network, because the agent checks it, not the +server. Checks run every 20 seconds; **Check now** on the device page checks right away. Hover the +status badge to see when the device was checked and, for *Unknown*, why: for example *Agent is +offline*, *No agent assigned* or *Status checks are turned off*. ## Powering on -Click **Power On** on the dashboard or in the device list. NetIgnite reports: +Click the power button on the **Overview**, in the **Devices** list or on the device page. NetIgnite +shows *Wake-on-LAN packet sent* or *Couldn't power on* with the reason: | Message | Meaning | | --- | --- | -| *Wakeup command sent* | The agent sent the magic packet. The device should start within a few seconds. | +| *Wake-on-LAN packet sent* | The agent sent the magic packet. The device should start within a few seconds. | +| *No agent is assigned to this device* | Choose an agent in the device's settings. | | *Agent is offline* | The device's agent isn't connected. Check the agent first. | -| *Failed to send wakeup command* | The agent didn't answer within 5 seconds. | -| *Wakeup command failed: …* | The agent rejected the request, for example because of an invalid MAC address or port. | +| *The agent didn't answer in time* | The agent didn't answer within 5 seconds. Try again. | +| *The agent doesn't support this action* | The agent can't send Wake-on-LAN packets, for example a Linux agent without Wake-on-LAN mode. | +| *The agent reported an error: …* | The agent couldn't send the packet, for example because it has no network. | -*Wakeup command sent* means the packet left the agent. It doesn't confirm that the device turned -on. Watch the device's status turn **online**, or check +The button is disabled (*No online agent with Wake-on-LAN*) while no suitable agent is online. +*Wake-on-LAN packet sent* means the packet left the agent. It doesn't confirm that the device +turned on; watch its status turn **Online**, or see [Troubleshooting](/docs/reference/troubleshooting#the-device-doesnt-wake-up). -## Device status +## Shutting down and rebooting + +When a device can be shut down, the power button gets a menu with **Shut down** and **Reboot**. +NetIgnite asks for confirmation, then shows *Shutdown requested* or *Reboot requested*. This needs: + +- the **Linux agent on the device**, online, or +- an **SSH login** with a trusted host key, and the device's agent online and able to log in over + SSH. See [SSH](/docs/guides/ssh). -NetIgnite asks the device's agent to ping the device. The ESP32 agent pings the device's IPv6 -link-local address, which it derives from the MAC address. See -[How It Works](/docs/how-it-works#checking-device-status) for what the statuses mean and when this -check reports a running device as `offline`. +If both are set up, NetIgnite uses the Linux agent while it's online, and SSH otherwise. -Statuses refresh every 20 seconds. The **Ping** button checks a device right away. +## Favorites + +Click the star in the **Devices** list, or **Add to favorites** on the device page. Favorites appear +on the **Overview**. They're personal: every user has their own. ## Editing and deleting -Click **Edit** to change any field, including the agent. Delete a device with the trash icon in -the device list; you'll be asked to confirm. +Change the settings on the device's **General** tab and click **Save**. The owner and managers can +edit a device; only the owner can change its agents and delete it. **Delete device** under +**Danger Zone** removes the device and all its shares. + +Someone shared a device with you? **Leave device** on its page removes it from your account. See +[Sharing](/docs/guides/sharing). diff --git a/content/docs/guides/sharing.md b/content/docs/guides/sharing.md new file mode 100644 index 0000000..72c06fd --- /dev/null +++ b/content/docs/guides/sharing.md @@ -0,0 +1,63 @@ +--- +title: "Sharing" +description: "Share NetIgnite devices with other users as viewer, operator or manager, and what each role can do." +navigation: + title: Sharing +--- + +# Sharing + +You can share a device with other users on the same NetIgnite server: let your family power on the +media PC, or let a colleague reboot a server. You stay the owner, and commands still go through +your agent. + +## Roles + +Each share has a role: + +| | Owner | Manager | Operator | Viewer | +| --- | --- | --- | --- | --- | +| See the device and its status, **Check now** | ✓ | ✓ | ✓ | ✓ | +| Add it to their favorites | ✓ | ✓ | ✓ | ✓ | +| **Power on**, **Shut down**, **Reboot** | ✓ | ✓ | ✓ | | +| Edit name, type, description, MAC address, port and status check | ✓ | ✓ | | | +| Choose the agent and the Linux agent on the device | ✓ | | | | +| Set up the SSH login | ✓ | | | | +| Share the device, change and remove shares | ✓ | | | | +| Delete the device | ✓ | | | | + +Agents are never shared. Users you share a device with see the name and status of its agents, but +can't open them, use them for their own devices or see their credentials. They never see the +device's SSH login either. + +## Sharing a device + +1. Open the device and its **Sharing** tab. Only the owner has it. +2. Enter the other user's **username or email address** and choose a role. +3. Click **Share**. + +The user must already have an account on the server; NetIgnite doesn't send invitations. The device +then appears in their **Devices** list with their role and *by* your name, and on their +**Overview** as *shared by* you. + +Under **People with access**, change someone's role with the role menu, or remove their access with +the **×** button. Removing a share also removes the device from their favorites. + +## Leaving a shared device + +On a device someone shared with you, the **General** tab shows your role and a **Leave device** +section. **Leave** removes the device from your account until the owner shares it again. + +## When the owner deletes things + +- Deleting the device removes all its shares. +- Deleting or changing the device's agent affects everyone: if the agent is gone, nobody can wake + the device until the owner chooses another one. +- Deleting the owner's account deletes their devices, and with them all shares. + +## With the API + +The owner manages shares with the [REST API](/docs/reference/api#sharing): +`GET /api/v1/devices//shares`, `POST` with `{ "user": "", "role": "operator" }`, +`PUT /api/v1/devices//shares/` with a new role, and `DELETE` to remove a share. A user +leaves a device with `DELETE /api/v1/devices//shares/`. diff --git a/content/docs/guides/ssh.md b/content/docs/guides/ssh.md new file mode 100644 index 0000000..9abdfaf --- /dev/null +++ b/content/docs/guides/ssh.md @@ -0,0 +1,126 @@ +--- +title: "SSH" +description: "Shut down and reboot devices over SSH: set up a login with a generated Ed25519 key or a password, restrict it to shutdown and reboot, and trust the host key." +navigation: + title: SSH +--- + +# SSH + +For devices that don't run the [Linux agent](/docs/agents/linux) (another Linux box, a NAS, a Mac, +a Windows PC with OpenSSH Server), NetIgnite can shut them down and reboot them over SSH. The +**device's agent** logs in, because it's the one inside the LAN; the server stores the login and +sends it to that agent for each command. + +What NetIgnite does over SSH is fixed: + +| | Linux, macOS, BSD | with *Use sudo* | Windows (OpenSSH) | +| --- | --- | --- | --- | +| Shut down | `shutdown -h now` | `sudo -n shutdown -h now` | `shutdown /s /t 0` | +| Reboot | `shutdown -r now` | `sudo -n shutdown -r now` | `shutdown /r /t 0` | + +There's no way to run anything else: no shell, no file access, no other commands. + +## Requirements + +- The device's **Agent** (on its **General** tab) must be able to log in over SSH and be online: an + ESP32 agent or a Linux agent in [Wake-on-LAN mode](/docs/agents/linux#wake-on-lan-mode), in the + device's network. The agent's capabilities on its page include `ssh` then. +- An SSH server on the device, reachable from the agent. +- You must be the device's owner. The **SSH** tab isn't available to users the device is shared + with. + +## Setting up the login + +Open the device and its **SSH** tab: + +| Field | Description | +| --- | --- | +| **Host** | Host name or IP address. Empty: the device's status check address. | +| **Port** | Usually `22`. | +| **Username** | The account the agent logs in with. Use a dedicated user, for example `netignite`. | +| **Operating system** | *Linux, macOS, BSD* or *Windows (OpenSSH)*. Decides the command (table above). | +| **Use sudo** | Linux, macOS, BSD: run `shutdown` with `sudo -n`. Not needed for `root`. | +| **Authentication** | **New key pair** (recommended): the server generates an Ed25519 key pair. **Password**: the account's password. **Keep the current**: change the other fields only. | + +Click **Save login**. With a generated key, the **Prepare the device** card shows what to add on +the device. + +### Linux, macOS, BSD + +1. Add the line from **Prepare the device** to `~/.ssh/authorized_keys` of the user. It looks + like this (with *Use sudo*): + + ```text [authorized_keys] + restrict,command="case \"$SSH_ORIGINAL_COMMAND\" in 'sudo -n shutdown -h now'|'sudo -n shutdown -r now') exec $SSH_ORIGINAL_COMMAND ;; *) echo 'Only shutdown and reboot are allowed' >&2; exit 1 ;; esac" ssh-ed25519 AAAA… netignite-device-7 + ``` + + `restrict` turns off port forwarding, agent forwarding and a terminal. The forced `command` lets + only the two commands through and refuses anything else, so the key is useless for anything but + shutdown and reboot. **Test login** doesn't open a session, so the forced command doesn't run + for it. You can add `from=""` after `restrict,` to accept the key only + from the agent. + +2. With *Use sudo*, allow the two commands without a password. Run + `sudo visudo -f /etc/sudoers.d/netignite` and add the line from **Prepare the device**: + + ```text [sudoers] + netignite ALL=(root) NOPASSWD: /usr/sbin/shutdown -h now, /usr/sbin/shutdown -r now, /sbin/shutdown -h now, /sbin/shutdown -r now + ``` + + `sudo -n` fails at once instead of asking for a password, so a missing line shows up in the + dashboard as `sudo: a password is required`. + +With a password instead of a key, there's no forced command: the account can do whatever its +password allows. Prefer a key. + +### Windows + +Install **OpenSSH Server** (Settings → System → Optional features), start the `sshd` service and +choose *Windows (OpenSSH)* as the operating system. Shutting down needs an administrator account: + +- **Key:** put the line from **Prepare the device** (`restrict ssh-ed25519 AAAA…`) into + `C:\ProgramData\ssh\administrators_authorized_keys`. Only Administrators and SYSTEM may have + access to that file. Windows' OpenSSH runs forced commands through `cmd.exe`, so this line has no + forced command. +- **Password:** use the account's password. + +## Trusting the host key + +NetIgnite trusts a device's SSH host key on first use, after you've checked it: + +1. In the **Host key** card, click **Test login**. The agent connects, checks the login without + running anything and reports the device's host key fingerprint (`SHA256:…`). +2. Compare it with the key on the device, for example + `ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub`. +3. Click **Trust this host key**. + +Shutdown and reboot over SSH work from then on: the power button of the device gets **Shut down** +and **Reboot**. Every later login checks the key first and stops before sending the password or +key if it differs. Changing the login's host or port removes the trusted key; test and trust again. + +| Test result | Meaning | +| --- | --- | +| *Login works* | The agent logged in. Trust the key if you haven't. | +| *The device didn't answer on its SSH port* | Wrong host or port, the device is off, or a firewall blocks it. | +| *Login failed*, `auth_failed` | Wrong username, password, or `authorized_keys` line. | +| *Login failed*, `host_key_mismatch` | The device presents another key than the trusted one. If you reinstalled the device, trust the new key; otherwise someone may be intercepting the connection. | + +## Security + +- **Write-only.** The API never returns a stored password or private key. The dashboard only shows + the public key. +- **Encrypted at rest.** Passwords and private keys are encrypted with the server's secrets key + (`NETIGNITE_SECRETS_KEY` or `config/secrets.key`). Back up the key with the database; without it, + you have to set the logins up again. +- **Only to the device's agent.** The login is sent over the agent's TLS WebSocket for each test or + command, kept in memory only for that login and wiped afterwards. The agent never stores or logs + it. +- **Fixed commands, trusted host key.** Commands only run on a device whose host key you trusted. +- **Owner only.** Users the device is shared with can use **Shut down** and **Reboot** (operators + and managers) but can't see or change the login. + +## Removing the login + +**Remove SSH login** deletes the stored password or key. Remove the `authorized_keys` line and the +sudoers file from the device too. diff --git a/content/docs/how-it-works.md b/content/docs/how-it-works.md index c605d59..9b6f629 100644 --- a/content/docs/how-it-works.md +++ b/content/docs/how-it-works.md @@ -1,6 +1,6 @@ --- title: "How It Works" -description: "The building blocks of NetIgnite (server, agents and devices) and what happens when you wake a device or check its status." +description: "The building blocks of NetIgnite (server, agents and devices) and what happens when you power on, check, shut down or share a device." navigation: title: How It Works --- @@ -8,79 +8,116 @@ navigation: # How It Works NetIgnite has three building blocks: the **server** you log in to, **agents** that live in your -networks, and the **devices** you want to power on. +networks, and the **devices** you want to control. ```text - Browser ──HTTPS──▶ NetIgnite Server ◀──WebSocket── Agent ──Wake-on-LAN / ping──▶ Devices - (dashboard + API) (outbound (ESP32 in (same LAN as the agent) - from the LAN) your LAN) + Browser ──HTTPS──▶ NetIgnite Server ◀──WebSocket── Agent ──Wake-on-LAN / ping / SSH──▶ Devices + (dashboard, API, (outbound (ESP32 or (same LAN as the agent) + agent gateway) from the LAN) Linux agent) ``` ## Concepts -**Server.** A [Nuxt](https://nuxt.com) application running on [Bun](https://bun.sh). It serves the -web dashboard and the [REST API](/docs/reference/api), and it accepts agent connections on -`/api/agent-control-service`. All of this uses one port (`18232` in the Docker image). Users, -agents and devices are stored in a SQLite database. +**Server.** A [Nuxt](https://nuxt.com) application with a [Hono](https://hono.dev) API, running on +[Bun](https://bun.sh) and storing its data in SQLite. It serves the web dashboard, the +[REST API](/docs/reference/api) under `/api/v1`, and the WebSocket endpoints `/ws/agent` (agents) +and `/ws/console` (the live log of an ESP32 agent). Everything uses one port: `18232`. **Agent.** A program inside your network that connects *out* to the server over a WebSocket and waits for commands. Because the agent dials out, you don't need port forwarding or a VPN into the -network you want to control. Every agent has a numeric **ID** and a **secret**; it authenticates -with both when it connects. The [ESP32 agent](/docs/agents/esp32) is the reference -implementation, and you can [write your own](/docs/agents/protocol). +network you want to control. There are two kinds: -**Device.** A machine you want to wake: a desktop PC, server, laptop, printer or NAS. A device -record stores the device's **MAC address**, the **Wake-on-LAN port** (usually `9`) and the -**agent** that is responsible for it. The agent must be in the same local network (broadcast -domain) as the device. +| Kind | Runs on | Does | +| --- | --- | --- | +| [ESP32](/docs/agents/esp32) | An ESP32 board in your LAN | Wakes and checks devices in its network, logs in to them over SSH, updates itself over the network. | +| [Linux agent](/docs/agents/linux) | A Linux machine with systemd | Reports the machine's own status and shuts it down or reboots it. In Wake-on-LAN mode, also wakes, checks and logs in to other devices in its network. | -Agents and devices belong to the user who created them. Each user only sees their own. +You can also [write your own agent](/docs/agents/protocol). + +**Device.** A machine you want to control: a desktop PC, server, laptop, printer or NAS. A device +record stores the device's **MAC address**, the **Wake-on-LAN port** (usually `9`), the **agent** +that wakes it, how its **status is checked**, and optionally the **Linux agent running on it** and +an **SSH login**. The agent must be in the same local network (broadcast domain) as the device. + +Agents and devices belong to the user who created them. Agents are never shared; devices can be +[shared](/docs/guides/sharing) with other users. + +## Connecting an agent + +An agent proves who it is with a credential that only the agent knows: + +1. In the dashboard, the agent's owner creates a **single-use enrollment token** + (`netignite_enroll_…`, valid for 15 minutes). The ESP32 web installer writes it into the + board's flash; for the Linux agent it's part of the setup command. +2. The agent generates a random **secret** itself and sends it with the token to + `POST /api/v1/agents/enroll`. The server stores only a hash of the secret and answers with a + token ID. +3. From then on, the agent connects to `/ws/agent` with + `Authorization: Bearer netignite_agent_:`. Its first message is a `HELLO` + that lists its **capabilities**; the server only sends commands the agent listed. + +The secret never leaves the agent and is never shown. Enrolling again (with a new token) replaces +the old credential. See [Agent Protocol](/docs/agents/protocol) for the details. ## Waking a device -1. You press **Power On** in the dashboard. The browser calls - `POST /api/devices//wakeup`. -2. The server looks up the device and its agent. If the agent isn't connected, the request fails - with **Agent is offline**. -3. The server sends a `WAKEUP` command with the device's MAC address and port to the agent. -4. The agent broadcasts a Wake-on-LAN magic packet to its subnet (the ESP32 agent sends it three - times, 100 ms apart) and replies `OK`. +1. You press **Power on**. The browser calls `POST /api/v1/devices//power-on`. +2. The server checks that you may power the device on (owner, manager or operator) and that its + agent is online and supports Wake-on-LAN. Otherwise it answers with an error such as + *Agent is offline*. +3. The server sends the agent a `WAKE` command with the device's MAC address and port. +4. The agent broadcasts the magic packet three times, 100 ms apart, to its subnet's broadcast + address and to `255.255.255.255`, and answers. 5. The device's network card receives the packet and powers the machine on. -The agent's `OK` means that the packet was sent, not that the device actually started. -Wake-on-LAN must be enabled in the device's BIOS/UEFI and network driver. See +*Wake-on-LAN packet sent* means that the packet was sent, not that the device started. +Wake-on-LAN must be enabled on the device. See [Troubleshooting](/docs/reference/troubleshooting#the-device-doesnt-wake-up) if nothing happens. ## Checking device status -The dashboard refreshes device and agent status every 20 seconds. +The server checks every device with a status check every **20 seconds** and keeps the results; +the dashboard reads them and refreshes every 20 seconds too. Checks through the same agent run one +after another, different agents work in parallel. Each check has 6 seconds. **Check now** on a +device page runs one immediately (at most every 5 seconds per device). -- **Agent status** is `online` while the agent's WebSocket connection is open. The server keeps - this state in memory and sends every connected agent a `HEARTBEAT` once a minute. -- **Device status** comes from the agent. For each device, the server sends a `GET_STATUS` - command with the device's MAC address. The ESP32 agent derives the device's IPv6 link-local - address from the MAC (the EUI-64 address) and pings it once. +| Status check | How it works | +| --- | --- | +| Off | No check. The status stays *Unknown*. | +| MAC (IPv6 link-local) | The agent pings the IPv6 link-local address derived from the MAC address (EUI-64). | +| IPv4 address / IPv6 address | The agent pings the address you enter. | +| SSH port | The agent tries a TCP connection to port 22 (or the port you enter). | +| Linux agent on the device | The device is online while the Linux agent running on it is connected. | + +For the first three, you can add a **TCP port** (for example `445` or `3389`): the device then also +counts as online when that port answers. See [Devices](/docs/guides/devices#status-checks) for +when to use which. | Status | Meaning | | --- | --- | -| `online` | The device answered the ping. | -| `offline` | The device didn't answer within one second. | -| `unknown` | The agent is offline, didn't answer within 5 seconds, or rejected the request (for example because of a malformed MAC address). | +| Online | The device answered. | +| Offline | The device didn't answer. | +| Unknown | No check is configured, or the check couldn't run: no agent, the agent is offline, didn't answer in time or doesn't support the check. The badge's tooltip shows the reason. | + +## Shutting down and rebooting + +Shutdown and reboot need one of two things: + +- **The Linux agent on the device.** The server sends it `HOST_CONTROL`, and the agent runs + `systemctl poweroff` or `systemctl reboot`. +- **An SSH login.** The device's agent logs in to the device over SSH and runs a fixed `shutdown` + command. The device's host key must have been trusted first. See [SSH](/docs/guides/ssh). -::warning -The EUI-64 status check only works when the device has IPv6 enabled, uses a MAC-derived -link-local address and answers ICMPv6 echo requests. Windows and many modern Linux distributions -use randomized IPv6 interface identifiers by default, so such devices show as `offline` even when -they are running. Waking them is not affected. -:: +If a device has both, the server uses the Linux agent while it's online. ## Where data lives | Data | Storage | | --- | --- | -| Users, agents, devices | SQLite database `data/db.sqlite` | -| Login sessions | Server memory (a restart logs everyone out) | -| Agent connection state | Server memory | -| Server settings | `config/config.json` | +| Users, sessions, API keys, agents, devices, shares, favorites, SSH logins, the firmware list | SQLite database `data/netignite.sqlite` | +| Agent credentials, session tokens, API keys | Only as hashes in the database | +| SSH passwords and private keys | Encrypted in the database with the secrets key (`config/secrets.key` or `NETIGNITE_SECRETS_KEY`) | +| ESP32 firmware releases | `data/firmware//`, downloaded from GitHub | +| Agent connections, device status, firmware update progress | Server memory: agents reconnect and statuses are checked again after a restart | Continue with [Getting Started](/docs/getting-started) to set up your own instance. diff --git a/content/docs/index.md b/content/docs/index.md index f980369..2ee950c 100644 --- a/content/docs/index.md +++ b/content/docs/index.md @@ -1,36 +1,47 @@ --- title: "Documentation" -description: "Learn how NetIgnite powers on devices from anywhere: self-host the server, connect an agent and wake your devices." +description: "Learn how NetIgnite powers on, checks and shuts down devices from anywhere: self-host the server, set up an agent and control your devices." navigation: title: Overview --- # NetIgnite Documentation -NetIgnite lets you power on computers, servers and other devices in your network from anywhere. A -small **agent** inside your network keeps a connection open to the **NetIgnite server**. When you -press **Power On** in the dashboard, the server tells the agent to send a +NetIgnite lets you power on computers, servers and other devices in your network from anywhere, +see whether they're online, and shut them down or reboot them. A small **agent** inside your +network keeps a connection open to the **NetIgnite server**. When you press **Power on** in the +dashboard, the server tells the agent to send a [Wake-on-LAN](https://en.wikipedia.org/wiki/Wake-on-LAN) magic packet to the device. ::note -NetIgnite is under active development. These docs describe the latest versions of the -[server](https://github.com/NetIgnite-project/Server/releases) and the -[ESP32 agent](https://github.com/NetIgnite-project/Agent-ESP32). Where a feature isn't available -yet, the page says so. +These docs describe **NetIgnite v2**: the server 2.x, the ESP32 firmware 2.x and the Linux agent +1.x. v1 agents can't connect to a v2 server. Coming from v1? Read +[Upgrading from v1](/docs/server/upgrading). :: ## Components | Component | What it does | Source | | --- | --- | --- | -| **Server** | Web dashboard, REST API and the WebSocket service that agents connect to. Stores users, agents and devices in SQLite. | [NetIgnite-project/Server](https://github.com/NetIgnite-project/Server) | -| **ESP32 Agent** | Firmware for an ESP32 that sits in your LAN, sends Wake-on-LAN packets and checks whether devices are online. | [NetIgnite-project/Agent-ESP32](https://github.com/NetIgnite-project/Agent-ESP32) | +| **Server** | Web dashboard, REST API and the WebSocket gateway that agents connect to. Stores users, agents and devices in SQLite. | [NetIgnite-project/Server](https://github.com/NetIgnite-project/Server) | +| **ESP32 agent** | Firmware for an ESP32 board in your LAN. Wakes devices, checks whether they're online and can shut them down over SSH. Installed from the browser and updated over the network. | [NetIgnite-project/Agent-ESP32](https://github.com/NetIgnite-project/Agent-ESP32) | +| **Linux agent** | A systemd service for a Linux machine. Reports the machine's status and shuts it down or reboots it; optionally wakes and checks other devices in its network, like the ESP32 agent. | [NetIgnite-project/Agent-Linux](https://github.com/NetIgnite-project/Agent-Linux) | | **Website** | This site, including the docs. | [NetIgnite-project/Website](https://github.com/NetIgnite-project/Website) | You can use the hosted app at [app.netignite.dev](https://app.netignite.dev/) or run your own server. All components are open source under the [AGPL-3.0](https://www.gnu.org/licenses/agpl-3.0.html). +## What you can do + +- **Power on** devices with Wake-on-LAN through an agent in their network. +- **Check** whether a device is online: by its IPv4 or IPv6 address, a TCP port, its MAC address + (IPv6 link-local), its SSH port, or the Linux agent running on it. +- **Shut down and reboot** devices through the Linux agent on the device, or over SSH. +- **Share** devices with other users as viewer, operator or manager. +- **Install the ESP32 agent from the browser** and keep it up to date with over-the-air updates. +- **Automate** with the REST API and API keys. + ## Where to start ::icon-card-grid @@ -38,7 +49,7 @@ server. All components are open source under the cards: - icon: i-lucide-workflow title: How It Works - description: Server, agents and devices, and what happens when you press Power On. + description: Server, agents and devices, and what happens when you press Power on. to: /docs/how-it-works - icon: i-lucide-rocket title: Getting Started @@ -50,15 +61,23 @@ cards: to: /docs/server/installation - icon: i-lucide-cpu title: ESP32 Agent - description: Configure, build and flash the agent firmware. + description: Install the firmware from the browser, its console and updates. to: /docs/agents/esp32 + - icon: i-lucide-terminal + title: Linux Agent + description: Install the agent as a systemd service with one command. + to: /docs/agents/linux + - icon: i-lucide-history + title: Upgrading from v1 + description: Import your v1 data and set your agents up again. + to: /docs/server/upgrading - icon: i-lucide-cable title: Agent Protocol description: The WebSocket protocol, if you want to write your own agent. to: /docs/agents/protocol - icon: i-lucide-braces title: REST API - description: Every HTTP endpoint of the server. + description: Authentication, API keys and the most important endpoints. to: /docs/reference/api --- :: diff --git a/content/docs/reference/api.md b/content/docs/reference/api.md index b342acc..19eb8e8 100644 --- a/content/docs/reference/api.md +++ b/content/docs/reference/api.md @@ -1,6 +1,6 @@ --- title: "REST API" -description: "Reference for the NetIgnite server's HTTP API: authentication, accounts, agents, devices and Wake-on-LAN." +description: "The NetIgnite server's HTTP API: authentication with logins and API keys, the response envelope, the OpenAPI reference and the most important endpoints." navigation: title: REST API --- @@ -8,290 +8,275 @@ navigation: # REST API The dashboard is built on the server's JSON API, and you can call the same endpoints from scripts -and automations. All paths are relative to your server, for example -`https://netignite.example.com/api/devices`. +and automations. The API lives under `/api/v1` on your server, for example +`https://netignite.example.com/api/v1/devices`. -## Basics +::tip +Every server includes a complete, interactive **OpenAPI reference** at `/api/docs/v1` (the +dashboard links it as **API Reference**), with all request and response schemas. The OpenAPI +document itself is at `/api/docs/v1/openapi`. Admins can turn both off with +`NETIGNITE_API_DISABLE_DOCS=true`. This page covers the basics and the most important endpoints. +:: -### Authentication +## Authentication + +Requests authenticate with a bearer token: + +```http +Authorization: Bearer +``` + +| Token | Looks like | Get it | Valid | +| --- | --- | --- | --- | +| **API key** | `netignite_apikey_…` | **Settings → API Keys** in the dashboard | Until it expires or is deleted | +| **Login (session)** | `netignite_sess_…` | `POST /api/v1/auth/login` | 7 days, or until logout | -The API uses the same session cookie as the dashboard. Log in with -[`POST /api/auth/login`](#log-in) and send the returned `session` cookie with every request. -API keys aren't available yet. +Use an API key for scripts. It works for agents, devices, firmware and (for admins) the admin +endpoints. The endpoints under `/account` (your profile, password and API keys) and +`/auth/session` and `/auth/logout` need a login token. ```bash [Terminal] -# Log in and store the session cookie -curl -c cookies.txt -H "Content-Type: application/json" \ - -d '{"username":"admin","password":""}' \ - https://netignite.example.com/api/auth/login +curl -H "Authorization: Bearer netignite_apikey_…" \ + https://netignite.example.com/api/v1/devices + +curl -X POST -H "Authorization: Bearer netignite_apikey_…" \ + https://netignite.example.com/api/v1/devices/4/power-on +``` -# Use the cookie -curl -b cookies.txt https://netignite.example.com/api/devices -curl -b cookies.txt -X POST https://netignite.example.com/api/devices/1/wakeup +To get a login token instead: + +```bash [Terminal] +curl -H "Content-Type: application/json" \ + -d '{"username":"admin","password":""}' \ + https://netignite.example.com/api/v1/auth/login ``` -Every endpoint except those under `/api/auth/` requires a session. Without one, the server answers -`401` with `No session found` or `Invalid session`. Sessions last 30 days and end when the server -restarts. +The answer's `data.token` is the login token. After 5 failed logins for a username from one address +(15 from all addresses), the server answers `429` for 5 minutes. -### Responses +## Responses -Responses share one envelope: +Every response uses the same envelope: ```json { - "status": "OK", - "message": "Devices retrieved successfully.", + "success": true, + "code": 200, + "message": "Devices retrieved", "data": [] } ``` | Field | Description | | --- | --- | -| `status` | `"OK"` on success, `"ERROR"` on failure. | -| `message` | Human-readable result. Missing on the status endpoints. | -| `data` | The result, if any. | +| `success` | `true` or `false`. | +| `code` | The HTTP status code. | +| `message` | Human-readable result. The dashboard shows error messages as they are. | +| `data` | The result; `null` if there is none. Missing on errors. | -Errors use the matching HTTP status code: `400` for invalid input, `401` without a valid session, -`404` for agents or devices that don't exist or belong to another user, `409` for conflicts, -`503` when an agent is offline, and `500` for server errors. Updates answer `201`. - -### Objects +| Status | Meaning | +| --- | --- | +| `200`, `201`, `202` | Success. `202`: a firmware update was started or queued. | +| `400` | Invalid input. | +| `401` | No or an invalid token, or the endpoint needs a login token. | +| `403` | Not allowed, for example your role on a shared device or a non-admin calling an admin endpoint. | +| `404` | Not found, or not yours. | +| `409` | Conflict, for example a duplicate username, or a device without an agent or method for the action. | +| `429` | Rate limit. | +| `502` | The agent reported an error. | +| `503` | The agent is offline. | +| `504` | The agent didn't answer in time. | + +Timestamps are Unix times in milliseconds. -```json [Agent] -{ - "id": 3, - "name": "Office ESP32", - "type": "microcontroller", - "description": "Under the desk", - "ownerID": 1 -} -``` +## Devices -| Agent field | Type | Description | -| --- | --- | --- | -| `id` | number | Agent ID, also the Connection ID. | -| `name` | string | Display name. | -| `type` | `"server"` \| `"microcontroller"` | Agent type. | -| `description` | string | Free text; may be empty. | -| `ownerID` | number | ID of the owning user. | +All endpoints return the devices you own and the devices shared with you; your role on each is in +`access_role`. -Agent objects never contain the secret. The server stores only a hash of it and returns the -secret once, when you [create an agent](#create-an-agent) or -[regenerate its secret](#regenerate-an-agent-secret). +| Method and path | Description | +| --- | --- | +| `GET /devices` | List devices. | +| `POST /devices` | Create a device. | +| `GET /devices/` | Get a device. | +| `PUT /devices/` | Change a device (owner, manager; agents: owner only). All fields optional. | +| `DELETE /devices/` | Delete a device (owner). | +| `POST /devices//power-on` | Send Wake-on-LAN (owner, manager, operator). | +| `POST /devices//shutdown` | Shut down through the Linux agent on the device or SSH (owner, manager, operator). | +| `POST /devices//reboot` | Reboot, like shutdown. | +| `POST /devices//status` | Check the status now and return it (at most every 5 seconds per device). | +| `PUT /devices//favorite` | Add to your favorites. `DELETE` removes it. | ```json [Device] { - "id": 1, + "id": 4, "name": "Gaming PC", "type": "desktop", - "description": null, - "macAddress": "AA:BB:CC:DD:EE:FF", - "port": 9, - "agentID": 3, - "ownerID": 1 + "description": "", + "mac_address": "AA:BB:CC:DD:EE:FF", + "wol_port": 9, + "agent_id": 3, + "status_method": "ipv4", + "status_address": "192.168.1.20", + "status_tcp_port": 3389, + "host_agent_id": null, + "created_at": 1792351800000, + "access_role": "owner", + "owner": { "id": 1, "username": "admin", "display_name": "Admin" }, + "agent": { "id": 3, "name": "Office ESP32", "kind": "esp32", "state": "online" }, + "host_agent": null, + "favorite": true, + "status": { "state": "online", "checked_at": 1792351812000 }, + "actions": { "power_on": true, "shutdown": false, "reboot": false } } ``` -| Device field | Type | Description | -| --- | --- | --- | -| `id` | number | Device ID. | -| `name` | string | Display name. | -| `type` | `"server"` \| `"desktop"` \| `"laptop"` \| `"printer"` \| `"nas"` | Device type. | -| `description` | string \| null | Free text. | -| `macAddress` | string | MAC address, `AA:BB:CC:DD:EE:FF` or `AA-BB-CC-DD-EE-FF`. | -| `port` | number | Wake-on-LAN UDP port, `1`–`65535` (usually `9`). | -| `agentID` | number | ID of one of your agents; it wakes this device. | -| `ownerID` | number | ID of the owning user. | - -`id` and `ownerID` are set by the server. Leave them out of request bodies. - -## Authentication - -### Log in - -`POST /api/auth/login` - -| Body field | Type | Required | -| --- | --- | --- | -| `username` | string | yes | -| `password` | string | yes | - -Sets the `session` cookie and answers `200` with `Login successful`. Wrong credentials return -`401` with `No matching credentials found`. - -### Log out - -`POST /api/auth/logout` - -Ends the session and clears the cookie. Always answers `200`. - -### Get the session - -`GET /api/auth/session` - -Returns the logged-in user, or `401` if there is no valid session. - -```json -{ - "status": "OK", - "message": "Session valid", - "data": { - "userID": 1, - "username": "admin", - "role": "admin", - "favorites": [], - "expirationTimestamp": 1792351800000 - } -} -``` - -## Account - -### Update an account - -`PUT /api/account/` - -Updates the username and password of the logged-in user. +| Field | Description | +| --- | --- | +| `type` | `server`, `desktop`, `laptop`, `printer`, `nas` or `other`. | +| `mac_address` | `AA:BB:CC:DD:EE:FF` or `AA-BB-CC-DD-EE-FF` in requests; stored with colons in uppercase. | +| `wol_port` | `1`–`65535`, default `9`. | +| `agent_id` | The agent that wakes and checks the device, or `null`. | +| `status_method` | `none` (default), `link_local`, `ipv4`, `ipv6`, `ssh` or `host_agent`. See [Status checks](/docs/guides/devices#status-checks). | +| `status_address` | Required for `ipv4` (IPv4 address), `ipv6` (IPv6 address) and `ssh` (IP address or host name). | +| `status_tcp_port` | Optional extra TCP probe; the port for `ssh` (default `22`). | +| `host_agent_id` | The Linux agent running on the device, or `null`. | +| `access_role` | `owner`, `manager`, `operator` or `viewer`. | +| `status` | `state` (`online`, `offline`, `unknown`), `checked_at` and, for `unknown`, a `reason`. | +| `actions` | Which power actions are possible right now for you. | + +Required when creating: `name`, `type` and `mac_address`. Agents must be your own; +`host_agent_id` must be a Linux agent. + +### Sharing + +Owner only, except leaving a device. + +| Method and path | Description | +| --- | --- | +| `GET /devices//shares` | List the shares: `user`, `role`, `created_at`. | +| `POST /devices//shares` | Share: `{ "user": "", "role": "viewer" \| "operator" \| "manager" }`. | +| `PUT /devices//shares/` | Change the role: `{ "role": "manager" }`. | +| `DELETE /devices//shares/` | Remove a share. With your own user ID, you leave a shared device. | -| Body field | Type | Required | Description | -| --- | --- | --- | --- | -| `username` | string | yes | New or current username. Must be unique (`409` otherwise). | -| `favorites` | number[] | yes | IDs of favorite devices. Send the current list (`[]` if none). | -| `password` | string | no | New password. Omit it or send an empty string to keep the current one. | -| `role` | `"user"` \| `"admin"` | no | See below. | +### SSH login -Answers `201` with `User Account updated successfully`. +Owner only. The password or private key is never returned. See [SSH](/docs/guides/ssh). -**Admin mode.** Admins can add `?adminMode=true` to update the user given in the path instead of -themselves. The first user (ID `1`) can also change other users' `role`, and must send `role` when -editing someone else. +| Method and path | Description | +| --- | --- | +| `GET /devices//ssh` | The login without its secret, with `authorized_keys` and `sudoers` lines. | +| `PUT /devices//ssh` | Set the login: `host` (or `null` for the status address), `port`, `username`, `os` (`posix`, `windows`), `sudo`, and `auth`: `{ "type": "generate_key" }`, `{ "type": "password", "password": "…" }` or `{ "type": "keep" }`. | +| `DELETE /devices//ssh` | Remove the login. | +| `POST /devices//ssh/test` | Let the device's agent log in and report the host key: `online`, `authenticated`, `host_key`, `trusted`, `error`. | +| `POST /devices//ssh/trust` | Trust a host key: `{ "host_key": "SHA256:…" }`. | ## Agents -### List agents - -`GET /api/agents` returns all of your agents as an array of [agent objects](#objects). - -### Create an agent +Agents are never shared: these endpoints only see your own. -`POST /api/agents` - -| Body field | Type | Required | -| --- | --- | --- | -| `name` | string | yes | -| `type` | `"server"` \| `"microcontroller"` | yes | -| `description` | string | yes (may be empty) | - -Answers `201` with the new agent's ID and its generated secret. Store the secret now: it isn't -returned again. The agent can connect right away. +| Method and path | Description | +| --- | --- | +| `GET /agents` | List agents. | +| `POST /agents` | Create an agent: `{ "name": "…", "description": "…", "kind": "esp32" \| "linux" }`. | +| `GET /agents/` | Get an agent. | +| `PUT /agents/` | Change `name` and `description`. | +| `DELETE /agents/` | Delete and disconnect. Its devices keep their settings without an agent. | +| `POST /agents//enrollments` | Create a single-use enrollment token (15 minutes): `{ "token", "expires_at" }`. | +| `POST /agents//revoke` | Revoke the credential and disconnect. | +| `POST /agents//flash-image` | ESP32: the NVS image for the web installer: `{ "version": "…", "server_url"?: "…" }` → `{ "image" (base64), "offset", "server_url", "expires_at" }`. Contains a new enrollment token. | +| `POST /agents//update` | ESP32: start a firmware update: `{ "version"?: "…" }` (default: the recommended release). | +| `POST /agents/bulk-update` | ESP32: update several agents one after another: `{ "agent_ids": [3, 5], "version"?: "…" }` → `{ "version", "queued", "skipped" }`. | +| `GET /agents//logs` | The agent's stored log lines and log level. | +| `GET /agents//info` | Ask the agent for current diagnostics. | +| `PUT /agents//device-config` | Change settings on the agent: `server_url`, `name`, `log_level`, `ip`. | +| `POST /agents//reboot` | Restart the agent. | +| `POST /agents//factory-reset` | Erase the agent's settings and revoke its credential. | +| `POST /agents/enroll` | Used by agents, without user authentication. See [Agent Protocol](/docs/agents/protocol#enrollment). | -```json +```json [Agent] { - "status": "OK", - "message": "Agent created successfully", - "data": { "id": 3, "secret": "6f1c2e9a0b7d4c3e8a5f1b2c3d4e5f60" } + "id": 3, + "name": "Office ESP32", + "description": "", + "kind": "esp32", + "state": "online", + "version": "2.0.0", + "capabilities": ["wake", "status.link_local", "status.ip", "status.host", "status.tcp", "ota", "logs", "config", "device.reboot", "device.reset", "ssh"], + "info": { "name": "Office-ESP32", "ipv4": "192.168.1.50", "rssi_dbm": -58 }, + "last_seen_at": 1792351812000, + "enrolled_at": 1792350000000, + "created_at": 1792349000000, + "update": null, + "update_available": null } ``` -### Get agent statuses - -`GET /api/agents/status?ids=1,2` - -Returns the connection status of your agents as a map from agent ID to `"online"`, `"offline"` -or `"unknown"`. Without `ids`, all of your agents are included. - -```json -{ "status": "OK", "data": { "1": "online", "2": "offline" } } -``` - -### Get an agent - -`GET /api/agents/` returns one [agent object](#objects), or `404`. - -### Update an agent - -`PUT /api/agents/` takes the same body as [Create an agent](#create-an-agent) and answers -`201`, or `404` if the agent doesn't exist. It changes only the name, type and description. - -### Regenerate an agent secret +`state` is `online`, `offline` or `unenrolled` (*Not set up*). `update` is the current or last +firmware update (`version`, `from_version`, `state`: `queued`, `starting`, `downloading`, +`restarting`, `done` or `failed`, `percent`, `error`), kept in memory for 24 hours. +`update_available` is a newer release of the admin's channel, if the agent can be updated. -`POST /api/agents//regenerate-secret` (no body) +## Firmware -Replaces the agent's secret and answers `200` with the new one, in the same shape as -[Create an agent](#create-an-agent). The old secret stops working immediately and a connected -agent is disconnected. Returns `404` if the agent doesn't exist. - -### Delete an agent - -`DELETE /api/agents/` answers `200`, or `404` if the agent doesn't exist. A connected agent is -disconnected. Devices assigned to the agent are not deleted; reassign them first. - -## Devices - -### List devices - -`GET /api/devices` returns all of your devices as an array of [device objects](#objects). Add -`?onlyFavorites=true` to return only the devices in your favorites list. - -### Create a device +| Method and path | Description | +| --- | --- | +| `GET /firmware` | The synced ESP32 releases: `{ "channel", "recommended", "releases": [...] }`, each with its flash parts, offsets and SHA-256s. | +| `GET /firmware//files/` | Download a firmware file. ESP32 agents also use it with their agent credential for updates. | -`POST /api/devices` +## Account and authentication -| Body field | Type | Required | -| --- | --- | --- | -| `name` | string | yes | -| `type` | `"server"` \| `"desktop"` \| `"laptop"` \| `"printer"` \| `"nas"` | yes | -| `description` | string | no | -| `macAddress` | string (`AA:BB:CC:DD:EE:FF` or `AA-BB-CC-DD-EE-FF`) | yes | -| `port` | integer (`1`–`65535`) | yes | -| `agentID` | number | yes | +| Method and path | Description | +| --- | --- | +| `POST /auth/login` | `{ "username", "password" }` → a login token in `data.token`. | +| `GET /auth/session` | The current login (login token only). | +| `POST /auth/logout` | End the current login. | +| `GET /auth/registration` | `{ "enabled": true \| false }`: whether public sign-up is on. | +| `POST /auth/signup` | Create an account while public sign-up is on: `{ "username", "email", "password", "display_name"? }`. | +| `POST /auth/reset-password/request` | Send a reset link to `{ "email" }` (needs email to be configured). | +| `POST /auth/reset-password` | `{ "reset_token", "new_password" }`. | +| `GET /account`, `PUT /account` | Your profile; changes need `current_password`. | +| `PUT /account/password` | `{ "current_password", "new_password" }`. Ends all your logins. | +| `DELETE /account` | Delete your account with its agents and devices. | +| `GET /account/apikeys`, `POST /account/apikeys` | List or create API keys: `{ "description", "expires_at": "7d" \| "30d" \| "90d" \| "180d" \| "365d" \| null }` → `{ "id", "token" }`. | +| `DELETE /account/apikeys/` | Delete an API key. | + +## Admin + +Admins only, with a login token or an admin's API key. + +| Method and path | Description | +| --- | --- | +| `GET /admin/users`, `POST /admin/users` | List users (`?role=`, `?search=`) or create one: `{ "username", "display_name", "email", "password", "role" }`. | +| `GET`, `PUT`, `DELETE /admin/users/` | Get, change (`display_name`, `email`, `role`, …) or delete a user. | +| `PUT /admin/users//password` | Set a user's password and end their logins. | +| `GET /admin/settings`, `PUT /admin/settings` | `{ "registration_enabled": false, "firmware_channel": "stable" \| "prerelease" }`. | +| `GET /admin/firmware` | The firmware sync state. | +| `POST /admin/firmware/sync` | Sync firmware releases from GitHub now. | -Answers `201`; `data` is the new device's ID. Invalid fields return `400` with `Invalid payload`. -An `agentID` that isn't one of your agents returns `400` with -`No matching agent found for the given AgentID`. +## Other endpoints -### Get device statuses +| Path | Description | +| --- | --- | +| `GET /api/health` | `200` while the server runs. Used by Docker's health check. | +| `GET /api/docs/v1` | The OpenAPI reference. `/api` redirects there. | +| `/ws/agent` | The agent WebSocket. See [Agent Protocol](/docs/agents/protocol). | +| `/ws/console` | The live log of an agent, for the dashboard. See [below](#the-console-websocket). | +| `GET /install/linux.sh` | The Linux agent's install script. See [Linux Agent](/docs/agents/linux#install). | -`GET /api/devices/status?ids=1,2` +### The console WebSocket -Asks each device's agent whether the device is online and returns a map from device ID to -`"online"`, `"offline"` or `"unknown"`. Without `ids`, all of your devices are checked. The -devices are checked one after another, and each check can take up to 5 seconds, so filter with -`ids` when you only need a few. +`/ws/console` streams an agent's log to a browser. The connection must come from a page on +`NETIGNITE_APP_URL` (other `Origin` headers get `403`). The first message authenticates within 10 +seconds; only the agent's owner may watch it: ```json -{ "status": "OK", "data": { "1": "online", "4": "unknown" } } +{ "type": "auth", "token": "", "agent_id": 3 } ``` -### Get a device - -`GET /api/devices/` returns one [device object](#objects), or `404`. - -### Update a device - -`PUT /api/devices/` takes the same body as [Create a device](#create-a-device) and answers -`201`, or `404` if the device doesn't exist. - -### Delete a device - -`DELETE /api/devices/` answers `200`, or `404` if the device doesn't exist. - -### Wake a device - -`POST /api/devices//wakeup` (no body) - -Sends a Wake-on-LAN command through the device's agent. - -| Status | Message | Meaning | -| --- | --- | --- | -| `200` | `Wakeup command sent` | The agent sent the magic packet. | -| `404` | `Device not found or inaccessible by user` / `Agent not found` | Unknown device, or its agent no longer exists or belongs to another user. | -| `503` | `Agent is offline` | The agent isn't connected. | -| `500` | `Failed to send wakeup command` | The agent didn't answer within 5 seconds. | -| `500` | `Wakeup command failed: …` | The agent rejected the command, for example because of an invalid MAC address. | - -## Agent WebSocket - -`/api/agent-control-service` is the WebSocket endpoint for agents, not a REST endpoint. See -[Agent Protocol](/docs/agents/protocol). +The server then sends `{ "type": "ready", "online": true }`, the stored lines as +`{ "type": "lines", "lines": [...], "reset": true, "level": "info" }`, new lines as +`{ "type": "lines", "lines": [...], "reset": false }`, `{ "type": "state", "online": false }` when the +agent disconnects, and `{ "type": "error", "message": "…" }` before closing (close codes `4401` +not authenticated, `4404` agent not found, `1008` invalid message). The token is checked again +every minute. diff --git a/content/docs/reference/development.md b/content/docs/reference/development.md index d6efd97..b82d64a 100644 --- a/content/docs/reference/development.md +++ b/content/docs/reference/development.md @@ -1,6 +1,6 @@ --- title: "Development" -description: "NetIgnite's repositories, how to run each one locally, and how to contribute." +description: "NetIgnite's repositories and branches, how to run each one locally, the tests, and how to contribute." navigation: title: Development --- @@ -13,45 +13,98 @@ licensed under the [AGPL-3.0](https://www.gnu.org/licenses/agpl-3.0.html). | Repository | Stack | | --- | --- | -| [Server](https://github.com/NetIgnite-project/Server) | Nuxt 3, Nitro (Bun preset) with WebSockets, SQLite via libsql, Bootstrap | -| [Agent-ESP32](https://github.com/NetIgnite-project/Agent-ESP32) | C++17, Arduino framework, PlatformIO | +| [Server](https://github.com/NetIgnite-project/Server) | Nuxt 4, Nuxt UI, Hono with Zod and OpenAPI, Drizzle ORM on `bun:sqlite`, Bun | +| [Agent-ESP32](https://github.com/NetIgnite-project/Agent-ESP32) | C++17, Arduino-ESP32 3.3 on ESP-IDF 5.5 (pioarduino), PlatformIO | +| [Agent-Linux](https://github.com/NetIgnite-project/Agent-Linux) | TypeScript, Bun, compiled into single binaries | | [Website](https://github.com/NetIgnite-project/Website) | Nuxt 4, Nuxt UI, Nuxt Content, Tailwind CSS, static generation | +## Branches and releases + +`main` holds the released version of each repository. Until NetIgnite v2 has its stable release, +v2 is developed on the `v2` branches of Server and Agent-ESP32, and their `main` branches hold v1. + +| Repository | Release | +| --- | --- | +| Server | The manual GitHub Actions workflow **Create Release** builds, tests, creates the GitHub release and publishes the Docker image. Stable releases (which move the image's `latest` tag) only run from `main`; pre-releases such as `2.0.0-beta.1` from any branch. | +| Agent-ESP32 | Pushing a tag `v` builds, tests and publishes a release with the flash images and `release.json`. Versions with a suffix become pre-releases. | +| Agent-Linux | Pushing a tag `v` that matches `package.json` builds the three binaries and `SHA256SUMS`. Versions with a suffix become pre-releases. | +| Website | Every push to `main` is deployed to netignite.dev. Docs for unreleased versions live on a separate branch until the release. | + +The agents and the server talk over the [agent protocol v2](/docs/agents/protocol); its source of +truth is the server (`server/lib/agents/protocol.ts`). Change the ESP32's NVS keys +(`src/Config.h`) only together with the server's web installer +(`server/lib/firmware/provisioning.ts`). + ## Server ```bash [Terminal] git clone https://github.com/NetIgnite-project/Server.git cd Server bun install +cp example.env .env bun run dev ``` -The dev server runs on `http://localhost:3000` and stores its database in `./data/`. The admin -password is printed on first start. Run the tests with `bun test`. +The dev server runs on `http://localhost:18232` with the dashboard, the API, the agent gateway and +the OpenAPI reference at `/api/docs/v1`. It stores its data in `./data/` and `./config/`; on the +first start it prints the link for the `admin` password. `example.env` contains sample SMTP +settings; remove them if you don't have a mail server. + +| Command | What it does | +| --- | --- | +| `bun test` | Runs the tests: API routes and permissions, the agent gateway with a simulated agent, the v1 import, the firmware catalog against a fake GitHub, the NVS image against Espressif's generator, OTA, console and SSH. | +| `bun run check` | Biome lint and format check. | +| `bun run typecheck` | `nuxt typecheck` and `tsc` for `server/`. | +| `bun run api-client:generate` | Regenerates the dashboard's typed API client after API changes. | +| `bun run db:generate` | Creates a Drizzle migration after schema changes. | +| `bun run build` | Production build in `.output/`, including `.output/import-v1.mjs`. | -Helper scripts in `devutils/` write directly to `./data/db.sqlite`: +To test agents without hardware, run the [Linux agent](#linux-agent) or the +[example agent](/docs/agents/protocol#example-agent) against the dev server. -- `bun run devutils/createAgent.ts` creates a test agent (secret `123456`) for user `1`. Restart - the dev server afterwards so it loads the agent. -- `bun run devutils/createTestUser.ts` creates an `admin` user with the password `123456`. It - fails if `admin` already exists, so run it before the server's first start. +## ESP32 agent -Releases are created with the manual GitHub Actions workflows **Create Release** (builds, tests -and attaches `app.tar.gz`) and **Publish Docker Images** (pushes to -`ghcr.io/netignite-project/server`). +You need [PlatformIO](https://platformio.org/) Core 6.2 or newer. Building, flashing and the serial +console are described on the [ESP32 Agent](/docs/agents/esp32#build-from-source) page. A board +flashed from PlatformIO is set up over the serial console: +`setup http://:18232 `. -## ESP32 Agent +| Command | What it does | +| --- | --- | +| `pio run -e esp32dev` | Builds the firmware. | +| `pio test -e native` | Unit tests of the parsers and encoders on your computer (needs `gcc`). | +| `python scripts/check_size.py` | Fails if the firmware takes more than 85 % of a firmware slot. | +| `pio run -e esp32dev-qemu` | The firmware without Wi-Fi, for Espressif's QEMU. | -Open the repository in VS Code with the PlatformIO extension, or use PlatformIO Core. Setup, -build and flashing are described on the [ESP32 Agent](/docs/agents/esp32) page. The code is -formatted with the included `.clang-format`. +`test/emulator/` boots the QEMU build and tests Improv, the console commands and OTA updates +(signature checks, probation, rollback) with Bun; its README has the commands. QEMU has no Wi-Fi, +so networking needs a real board. The code is formatted with the included `.clang-format`. + +## Linux agent + +```bash [Terminal] +git clone https://github.com/NetIgnite-project/Agent-Linux.git +cd Agent-Linux +bun install +bun run dev version +``` + +| Command | What it does | +| --- | --- | +| `bun run dev ` | Runs the agent from source with file watching, for example `bun run dev run --config ./agent.json`. | +| `bun test` | Unit and integration tests against a fake server and an SSH server. | +| `bun run typecheck` | TypeScript check. | +| `bun run check` | Biome. | +| `bun run compile all --no-version-tag` | Builds `build/bin/netignite-agent-linux-{x64,x64-baseline,arm64}`. | -To test protocol changes without hardware, use the [example agent](/docs/agents/protocol#example-agent). +Pair a development agent with a config outside `/etc`: +`bun run dev pair http://localhost:18232 --config ./agent.json`. `install` only works from +a compiled binary. ## Website This website, including the docs, is a static site built with the -[LeiCraftMC Style Guide](https://git.leicraftmc.de/LeiCraftMC/Style-Guides) static-site-with-docs +[LeiCraftMC Style Guide](https://github.com/LeiCraftMC/Style-Guides) static-site-with-docs template. ```bash [Terminal] @@ -62,7 +115,7 @@ bun run dev ``` The dev server runs on `http://localhost:12531`. `bun run generate` writes the static site to -`.output/public/`. +`.output/public/`; `bun test` and `bun run check` run the tests and Biome. Docs pages are Markdown files in `content/docs/`. To add one: @@ -70,10 +123,12 @@ Docs pages are Markdown files in `content/docs/`. To add one: 2. Register it in `app/data/docs.ts` so it appears in the sidebar and the previous/next links. 3. Add it to `public/static/utils/sitemap.xml`. -Every page has an **Edit this page** link that opens the file on GitHub. +Label code blocks with a file name or an extension from `app/data/code-icons.ts`, or a label without +a dot such as `[Terminal]`; a test checks this. Every page has an **Edit this page** link that +opens the file on GitHub. ## Contributing Bug reports and pull requests are welcome in the matching repository. Use [Conventional Commits](https://www.conventionalcommits.org/) for commit messages, for example -`fix: reconnect after WiFi loss`. +`fix: reconnect after Wi-Fi loss`. diff --git a/content/docs/reference/troubleshooting.md b/content/docs/reference/troubleshooting.md index 6c82fb5..8c1783f 100644 --- a/content/docs/reference/troubleshooting.md +++ b/content/docs/reference/troubleshooting.md @@ -1,122 +1,242 @@ --- title: "Troubleshooting" -description: "Solutions for common NetIgnite problems: logins, agents that don't connect, devices that don't wake, and wrong status." +description: "Solutions for common NetIgnite problems: the server doesn't start, logins, the web installer, agents that don't connect, devices that don't wake, wrong status, shutdown, updates and the v1 import." navigation: title: Troubleshooting --- # Troubleshooting +Start with the logs: the server's (`docker logs netignite-server`), the ESP32's +[serial console](/docs/agents/esp32#serial-console) or **Console** tab, and the Linux agent's +journal (`journalctl -u netignite-agent`). + +## Server + +### The server doesn't start + +| Log message | Fix | +| --- | --- | +| `Failed to read the environment variable APP_URL: …` | Set `NETIGNITE_APP_URL`; it's required. | +| `Failed to read the environment variable : Expected a boolean value ('true' or 'false')` | Booleans must be exactly `true` or `false`, not empty, `1` or `yes`. | +| `Unhandled Rejection` about a file that doesn't exist, right after `Starting NetIgnite...` | `NETIGNITE_FIRMWARE_PUBLIC_KEY` is a *path*; the file must exist inside the container, for example in the config volume. | + +### The firmware list stays empty + +The web installer says *No firmware available yet*. Under **Admin → Server Settings**, click +**Sync now** and read the result: + +- *GitHub answered 403 …* or another HTTP error: the server can't reach GitHub or hit GitHub's rate + limit. Allow outbound HTTPS, or set `NETIGNITE_GITHUB_TOKEN`. +- *Skipped … the release isn't signed, but FIRMWARE_PUBLIC_KEY requires a signature*: with + `NETIGNITE_FIRMWARE_PUBLIC_KEY` set, unsigned releases are skipped. Remove the variable or use + signed releases. +- *Skipped … the firmware speaks agent protocol …*: the release is for another server version. + +Servers without internet access can't sync firmware. Build the firmware yourself and set the board +up over the [serial console](/docs/agents/esp32#build-from-source). + ## Logging in -### The login form returns to the login page +### The login page comes back after logging in -Production builds only accept the session cookie over HTTPS (or on `localhost`). If you open the -dashboard as `http://:18232`, the browser drops the cookie and every page redirects -back to the login. Put the server behind [HTTPS](/docs/server/reverse-proxy), or open -`http://localhost:18232` on the server itself (for example through an SSH tunnel: -`ssh -L 18232:localhost:18232 `). +- **No HTTPS.** The browser drops the login cookie on plain `http://` pages other than `localhost`. + Put the server behind [HTTPS](/docs/server/reverse-proxy), or open `http://localhost:18232` on + the server itself (for example through an SSH tunnel: `ssh -L 18232:localhost:18232 `). +- **Wrong `NETIGNITE_APP_URL`.** The dashboard sends its API requests to `NETIGNITE_APP_URL`. If it + isn't the address in the browser's address bar, logins and everything else fail. Fix it and + restart the server. -### Everyone was logged out +### "Too many login attempts" -Sessions are kept in memory, so every server restart or update ends all sessions. Log in again. +After 5 failed logins for a username from one address (15 from all addresses), the server refuses +logins for that username until 5 minutes after the first attempt. Behind a reverse proxy, all +clients share the proxy's address. Wait, then try again. + +### The reset link email doesn't arrive + +Password-reset emails need [email](/docs/server/configuration#email) to be configured, and they go +to the account's email address. Imported v1 users and the first admin have placeholder addresses +until they change them. An admin can set a new password under **Admin → Users → Reset Password**. ### I lost the admin password -If `data/initial_admin_credentials` still exists and you never changed the password, it's in -there. Otherwise, set a new password directly in the database. With Docker, replace -`NEW-PASSWORD` and run: +If another admin exists, they can reset it under **Admin → Users**. If the first admin's link in +`config/initial_admin_password_reset_token.txt` was never used and is less than 7 days old, use it. +Otherwise, set a new password directly in the database. With Docker, replace `NEW-PASSWORD` and run: ```bash [Terminal] docker exec netignite-server bun -e ' const { Database } = require("bun:sqlite"); - const hash = await Bun.password.hash("NEW-PASSWORD", { algorithm: "bcrypt", cost: 12 }); - new Database("data/db.sqlite").run("UPDATE users SET password_hash = ? WHERE username = ?", [hash, "admin"]); + const hash = await Bun.password.hash("NEW-PASSWORD"); + new Database("/opt/netignite/data/netignite.sqlite").run("UPDATE users SET password_hash = ? WHERE username = ?", [hash, "admin"]); ' ``` +## Web installer + +| Problem | Fix | +| --- | --- | +| *This browser can't access USB devices* | Use Google Chrome, Microsoft Edge or Firefox 151 or newer on a computer, or set the board up over its [serial console](/docs/agents/esp32#serial-console). | +| *Installing needs HTTPS* | Open the dashboard over HTTPS (or on `localhost`). | +| The ESP32's port isn't in the list | Install the USB driver for the board's USB-to-serial chip (often CP210x or CH340), use a data cable, and close other programs that use the port, such as a serial monitor. | +| *The setup token expired* | The token is valid for 15 minutes. Click **Prepare again**. | +| Wi-Fi setup fails in the dialog | The ESP32 only supports 2.4 GHz networks. Check the password; a new network is only saved if the connection works. | +| Installed, but the agent doesn't come online | See [below](#the-agent-doesnt-come-online). | + ## Agents ### The agent doesn't come online -Check the server log (`docker logs netignite-server`) and, for the ESP32, the serial monitor. +**ESP32**: check the serial console or the web installer's **Logs & console**: -| Symptom | Cause and fix | +| Message | Cause and fix | +| --- | --- | +| `Waiting for Wi-Fi settings` | No Wi-Fi set up. Use **Change Wi-Fi** in the web installer or the `wifi` command. | +| `Connecting to Wi-Fi "…"` every 30 seconds, `Wi-Fi connection lost` | Wrong network or password, a 5 GHz-only network, or poor signal. Change the Wi-Fi with the web installer or the `wifi` command. | +| `Couldn't connect to "…"` after `wifi` or **Change Wi-Fi** | The new network didn't work; the previous one is kept. | +| `Not connected to a server yet` | No server set. Use the web installer, or `setup `. | +| `Waiting for the time` | The board can't reach NTP (UDP port 123) or DNS. HTTPS connections wait until the time is synced. | +| `Enrollment rejected: …` | The setup token was used, expired or is for a Linux agent. Create a new one and install again, or use `enroll `. | +| `Enrollment failed: …` | The server isn't reachable at the configured address. The agent retries every 30 seconds. | +| `This agent has no credential` | The credential was erased. Set the agent up again. | +| `Couldn't connect to the server, retrying in … s` | Wrong server address, the certificate isn't trusted (private CA, see [Certificates](/docs/agents/esp32#certificates)), the proxy doesn't forward `/ws/agent`, or the credential was revoked: then set the agent up again. | + +**Linux agent**: run `sudo netignite-agent status`. It checks the server (`Server check`) and the +credential (`Credential`) unless the service is running. + +| Message | Cause and fix | | --- | --- | -| ESP32 logs `Missing credentials!` | `C_CONTROL_SERVER_AUTH_ID` or `C_CONTROL_SERVER_AUTH_SECRET` is empty. | -| ESP32 logs `Websocket Connected` and right after `Websocket Disconnected!` | The server rejected the credentials. Check that the ID is the numeric **Connection ID** and that the secret is exactly the one shown when the agent was created. If you're unsure, [regenerate the secret](/docs/guides/agents#changing-the-secret) and reflash. | -| ESP32 only logs `Websocket Disconnected! Retrying in 60 seconds...` | The connection fails before the handshake: wrong host or port, `USE_SSL` doesn't match the port (`true` for `443`, `false` for a plain `18232`), or the server certificate isn't trusted (see [Certificate bundle](/docs/agents/esp32#certificate-bundle)). | -| ESP32 logs `NTP time sync timed out` | The board has no internet access, DNS doesn't work, or NTP (UDP port 123) is blocked. TLS connections fail until the time is synced; the agent keeps retrying on its own once the network works. | -| ESP32 logs `WiFi lost connection` repeatedly | Wrong SSID or password, or the network is 5 GHz only. | -| The server logs `Agent '' connected`, but the dashboard shows offline | The status refreshes every 20 seconds; use the **Ping** button to refresh it. | +| `The server doesn't accept this agent's credential (HTTP 401)`, exit code `78` | The credential was revoked or the agent deleted. Pair again with a new token: `sudo netignite-agent pair `. | +| `The server revoked this agent's credential` | Same: it was paired again elsewhere, revoked or reset in the dashboard. | +| `Another connection with this agent's credential replaced this one` | The agent runs twice, for example `run` by hand next to the service. | +| `The server didn't accept the HELLO` | The agent and the server don't speak the same protocol version. Update the agent or the server. | +| `No config at /etc/netignite/agent.json` | Pair the agent first. | -### The agent disconnects about once a minute +The server logs `Agent online (…)` when an agent connects and `Agent disconnected` when +it goes. -A reverse proxy is closing idle WebSocket connections. Raise its idle/read timeout above 60 -seconds, as shown in [HTTPS & Reverse Proxy](/docs/server/reverse-proxy). +### The agent disconnects regularly -### The ESP32 firmware doesn't build +- A reverse proxy closes idle WebSockets. Raise its timeout above 60 seconds; see + [HTTPS & Reverse Proxy](/docs/server/reverse-proxy). +- `Agent unresponsive, closing` in the server log: the agent didn't send anything for 75 + seconds. For an ESP32, check its Wi-Fi signal (`rssi_dbm` on the agent's page). -- **The certificate bundle can't be generated:** the first build installs the Python package - `cryptography` into PlatformIO's environment and needs internet access for that. See - [Certificate bundle](/docs/agents/esp32#certificate-bundle). -- **`config/config.h` not found:** copy `src/config/config.sample.h` to `src/config/config.h`. -- **Errors about `IPv6Address`, `enableIpV6` or `beginSslWithBundle`:** you're building with - Arduino-ESP32 core 3.x. Use PlatformIO's official `espressif32` platform, which ships core 2.x. +### The Console tab says "Agent offline" or "Disconnected" + +*Agent offline*: the agent isn't connected; the log appears when it is. *Disconnected*: the +browser's connection to `/ws/console` failed. Check that the proxy forwards WebSockets on +`/ws/console` and that you opened the dashboard at `NETIGNITE_APP_URL` (the server refuses other +origins). ## Waking devices +### The power button is disabled + +Its tooltip says *No online agent with Wake-on-LAN*: the device has no agent, its agent is offline, +or the agent can't send Wake-on-LAN packets (a Linux agent without +[Wake-on-LAN mode](/docs/agents/linux#wake-on-lan-mode)). Viewers of a shared device don't get the +button at all. + ### The device doesn't wake up -*Wakeup command sent* only means the agent sent the packet. If the device stays off: +*Wake-on-LAN packet sent* only means the agent sent the packet. If the device stays off: 1. **Wake-on-LAN enabled?** Check the BIOS/UEFI and the network driver settings, and disable ErP/EuP power saving. See [Preparing a device](/docs/guides/devices#preparing-a-device). 2. **Right MAC address?** Use the MAC of the **wired** network card the device is connected with, - not its WiFi adapter. + not its Wi-Fi adapter. 3. **Same network?** The agent must be in the same subnet as the device. Magic packets are broadcasts and don't cross routers or VLANs. 4. **Right port?** Most devices listen on `9`; some only react to `7`. 5. **Power state.** Some devices only wake from sleep or hibernation, not from a full shutdown. On Windows, disabling Fast Startup often helps. -6. **Test locally.** Send a magic packet from another computer in the same network (for example - with `wakeonlan` or `etherwake`). If that doesn't work either, the problem is the device, not - NetIgnite. +6. **Test locally.** Send a magic packet from another machine in the same network, for example with + `netignite-agent wol AA:BB:CC:DD:EE:FF` on a Linux agent, `wakeonlan` or `etherwake`. If that + doesn't work either, the problem is the device, not NetIgnite. + +### Error messages -### "Agent is offline" +| Message | Meaning | +| --- | --- | +| *No agent is assigned to this device* | Choose an agent in the device's settings. | +| *Agent is offline* | See [The agent doesn't come online](#the-agent-doesnt-come-online). | +| *The agent didn't answer in time* | The agent didn't answer within 5 seconds, often because its connection had just dropped. Try again. | +| *The agent doesn't support this action* | The agent can't do that, for example Wake-on-LAN on a Linux agent without Wake-on-LAN mode. | +| *Your role on this device doesn't allow power actions* | You're a viewer of this shared device. | -The device's agent isn't connected. See [The agent doesn't come online](#the-agent-doesnt-come-online). +## Device status -### "Wakeup command failed: Wakeup failed: Invalid MAC address or port" +### A running device shows as offline -The ESP32 agent only accepts MAC addresses like `AA:BB:CC:DD:EE:FF` or `AA-BB-CC-DD-EE-FF` and -ports from `1` to `65535`. Edit the device and fix the value. +- **MAC (IPv6 link-local)** only works when the device uses its MAC-based IPv6 link-local address + and answers IPv6 pings. Windows and many Linux distributions use random addresses. Switch to the + **IPv4 address** check, or change the device: + - Windows, in an elevated prompt: `netsh interface ipv6 set global randomizeidentifiers=disabled`. + - NetworkManager: `nmcli connection modify ipv6.addr-gen-mode eui64`, then reconnect. + - systemd-networkd: `IPv6LinkLocalAddressGenerationMode=eui64` in the `[Network]` section. +- **The device doesn't answer pings.** Windows blocks them by default. Add a **TCP port** the device + answers on (`445`, `3389`), or allow *File and Printer Sharing (Echo Request)* in the firewall. +- **The address changed.** Give the device a fixed address (DHCP reservation). +- **The agent can't reach the address.** The agent checks the device, not the server: the address + must be reachable from the agent's network. -### "Failed to send wakeup command" +### The status is "Unknown" -The agent didn't answer within 5 seconds, often because it was busy with status checks or its -connection had just dropped. Try again. If it keeps happening, check the agent's connection. +Hover the status badge for the reason: -## Device status +| Reason | Fix | +| --- | --- | +| *Status checks are turned off* | Choose a status check in the device's settings. | +| *No agent assigned* | Choose an agent. | +| *No status address configured* | Enter the address for the IPv4, IPv6 or SSH check. | +| *Agent is offline* | Check the agent. | +| *Agent doesn't support …* | The agent lacks the capability, for example a Linux agent without Wake-on-LAN mode. | +| *No host agent assigned* | Choose the Linux agent on the device, or another status check. | +| *Agent didn't answer in time* | The check took longer than 6 seconds. | -### A running device shows as offline +## Shutdown and reboot -The ESP32 agent pings the device's IPv6 link-local address that it derives from the MAC address -(EUI-64). The device shows as `offline` if it doesn't use that address or doesn't answer pings: +### Shut down and Reboot are missing -- **Windows** uses random interface identifiers. Switch to MAC-based ones in an elevated prompt with - `netsh interface ipv6 set global randomizeidentifiers=disabled`, and allow the firewall rule - *File and Printer Sharing (Echo Request - ICMPv6-In)*. -- **Linux with NetworkManager:** `nmcli connection modify ipv6.addr-gen-mode eui64`, - then reconnect. -- **Linux with systemd-networkd:** set `IPv6LinkLocalAddressGenerationMode=eui64` in the - `[Network]` section. -- **IPv6 disabled** on the device or a firewall that blocks ICMPv6 echo requests also causes - `offline`. +The device needs the [Linux agent on the device](/docs/agents/linux#host-mode) (online), or an +[SSH login](/docs/guides/ssh) with a trusted host key and an online agent that can log in over SSH. +Viewers don't get power actions. -Waking the device works regardless of the status. +### Shutdown requested, but nothing happens -### Every status is "unknown" +- **Linux agent on the device:** check its journal for `Host control (shutdown) failed`. Usually + polkit is missing (`apt install polkitd`, then `sudo netignite-agent install` again) or a + block inhibitor, such as a running package upgrade, holds the shutdown. +- **SSH:** errors come back in the toast: + +| Message | Fix | +| --- | --- | +| *Test the SSH login and trust the device's host key first* | Open the **SSH** tab, **Test login** and **Trust this host key**. | +| `sudo: a password is required` | The sudoers line is missing. See [SSH](/docs/guides/ssh#linux-macos-bsd). | +| `host_key_mismatch` | The device's host key changed. Test again and trust the new key if you expected the change. | +| `auth_failed` | Wrong username, password or `authorized_keys` line. | +| *The device's agent can't log in over SSH* | The device's agent doesn't support SSH, for example a Linux agent without Wake-on-LAN mode. | + +## Firmware updates + +| Message | Meaning | +| --- | --- | +| *This firmware can't be updated over the network* | The installed firmware has no update support. Install a current one with the web installer once. | +| *The current firmware isn't confirmed yet; try again shortly* | The agent just updated and is on probation until it reaches the server. | +| *The download doesn't match its SHA-256* | The download was damaged. Try again. | +| *This firmware only installs signed updates, and the update has no signature* | The agent requires signed updates; install a signed release. | +| *The update's signature is invalid* | The release was signed with another key than the one built into the agent. | +| *The update to … was rolled back* | The new firmware didn't reach the server within 5 minutes after restarting, so the agent went back. Check the server address and the certificate (a firmware with a private CA updated to an official release can't connect). | + +## Upgrading from v1 + +| Message | Fix | +| --- | --- | +| `v1 database not found: …` | Check the path given with `--from`; inside the container it's `/opt/netignite/data/db.sqlite`. | +| `Not a NetIgnite v1 database: table "…" is missing` | `--from` points at another file, for example the v2 `netignite.sqlite`. | +| `skipped user …: username already exists in v2` | Add `--merge-users`, or rename the v2 user first. | +| `skipped device …: invalid MAC address` | Add the device again in the dashboard. | -The devices' agent is offline, or it didn't answer within 5 seconds. Check the agent's status on -the **Agents** page. +See [Upgrading from v1](/docs/server/upgrading). diff --git a/content/docs/server/configuration.md b/content/docs/server/configuration.md index b5b146c..9ae22ed 100644 --- a/content/docs/server/configuration.md +++ b/content/docs/server/configuration.md @@ -1,75 +1,172 @@ --- title: "Configuration" -description: "Configure the NetIgnite server: config.json, environment variables, data paths and log levels." +description: "Configure the NetIgnite server with NETIGNITE_* environment variables: public URL, data paths, secrets, firmware sync, the Linux agent installer and email." navigation: title: Configuration --- # Configuration -The NetIgnite server is configured with a JSON file and a few environment variables. Agents and -devices are not part of the configuration. They live in the database and are managed in the -dashboard. +The NetIgnite server is configured with environment variables prefixed `NETIGNITE_`. They're read +once at startup; restart the server after changing them. Agents, devices, users and the settings +under **Server Settings** live in the database and are managed in the dashboard. + +```yaml [docker-compose.yml] +services: + netignite-server: + image: ghcr.io/netignite-project/server:latest + environment: + NETIGNITE_APP_URL: https://netignite.example.com + NETIGNITE_LOG_LEVEL: info +``` -## config.json +Running from source, put the variables into a `.env` file in the working directory; Bun loads it on +start. The repository's `example.env` lists all of them. -The server reads `config/config.json` relative to its working directory (in Docker: -`/opt/netignite/config/config.json`). If the file is missing, the server copies the sample config -there on startup. The sample is an empty object, so all settings use their defaults. +::note +Booleans must be exactly `true` or `false`. An invalid value, or a missing `NETIGNITE_APP_URL`, +stops the server at startup with `Failed to read the environment variable `. +:: -```json [config.json] -{ - "logLevel": "info" -} -``` +## Variables + +The *Default* column is what the server uses when the variable isn't set. The Docker image sets some +of them to paths inside the container (*Docker image* column). + +### General + +| Variable | Default | Docker image | Description | +| --- | --- | --- | --- | +| `NETIGNITE_APP_URL` | *required* | | The public URL of the dashboard, for example `https://netignite.example.com`, without a path. See [below](#the-public-url). | +| `NETIGNITE_LOG_LEVEL` | `info` | | `debug`, `info`, `warn`, `error` or `critical`. | +| `NETIGNITE_API_DISABLE_DOCS` | `false` | | `true` turns off the OpenAPI reference at `/api/docs/v1` and its spec. | +| `NETIGNITE_SECRETS_KEY` | *generated* | | Key that encrypts stored SSH passwords and private keys. Without it, the server generates one in `/secrets.key`. Any string; use a long random one, for example from `openssl rand -hex 32`. Changing it later makes stored SSH logins unreadable. | + +### Paths and database + +| Variable | Default | Docker image | Description | +| --- | --- | --- | --- | +| `NETIGNITE_DB_PATH` | `./data/netignite.sqlite` | `/opt/netignite/data/netignite.sqlite` | The SQLite database. | +| `NETIGNITE_DB_AUTO_MIGRATE` | `true` | `true` | Apply database migrations at startup. | +| `NETIGNITE_DB_MIGRATION_DIR` | `./drizzle/migrations` | `/opt/netignite/app/drizzle/migrations` | Where the migrations are. | +| `NETIGNITE_CONFIG_BASE_DIR` | `./config` | `/opt/netignite/config` | Directory for `secrets.key` and `initial_admin_password_reset_token.txt`. | +| `NETIGNITE_LOG_DIR` | `./data/logs` | `/opt/netignite/data/logs` | Log files of background tasks. The server's own log goes to stdout. | + +### ESP32 firmware + +| Variable | Default | Docker image | Description | +| --- | --- | --- | --- | +| `NETIGNITE_FIRMWARE_SYNC` | `true` | | Download new firmware releases from GitHub at startup and every 6 hours. `false` for servers without internet access; admins can still click **Sync now**. | +| `NETIGNITE_FIRMWARE_REPO` | `NetIgnite-project/Agent-ESP32` | | The GitHub repository (`owner/name`) whose releases are synced, for example a fork. | +| `NETIGNITE_FIRMWARE_DIR` | `./data/firmware` | `/opt/netignite/data/firmware` | Where synced releases are stored. | +| `NETIGNITE_FIRMWARE_PUBLIC_KEY` | — | | Path to a PEM file with an ECDSA P-256 public key. When set, only releases with a valid `firmware.sig` are synced. The file must exist at startup. | +| `NETIGNITE_GITHUB_TOKEN` | — | | A GitHub token for the release sync; raises GitHub's API rate limit. | + +### Linux agent installer + +| Variable | Default | Docker image | Description | +| --- | --- | --- | --- | +| `NETIGNITE_LINUX_AGENT_REPO` | `NetIgnite-project/Agent-Linux` | | GitHub repository that `/install/linux.sh` downloads the agent from. | +| `NETIGNITE_LINUX_AGENT_VERSION` | — | | A release tag such as `v1.0.0`. Without it, the script installs the newest release, pre-releases included. | -| Key | Type | Default | Description | +### Email + +Email is used for password-reset links (**Forgot password** on the login page). Without +`NETIGNITE_SMTP_HOST`, email is off; admins then reset passwords under **Admin → Users**. + +| Variable | Default | Docker image | Description | | --- | --- | --- | --- | -| `logLevel` | `"debug"` \| `"info"` \| `"warn"` \| `"error"` \| `"critical"` | `"info"` | Minimum level of log messages written to the console. | +| `NETIGNITE_SMTP_HOST` | — | | SMTP server. Setting it turns email on. | +| `NETIGNITE_SMTP_PORT` | `587` | | SMTP port. | +| `NETIGNITE_SMTP_SECURE` | `false` | | `true` for implicit TLS (usually port `465`). | +| `NETIGNITE_SMTP_USERNAME` | — | | SMTP user; without it the server doesn't log in. | +| `NETIGNITE_SMTP_PASSWORD` | — | | SMTP password. | +| `NETIGNITE_SMTP_FROM` | `"NetIgnite" ` | | Sender address. | + +### Runtime + +These aren't NetIgnite settings but are read by the runtime: + +| Variable | Default | Docker image | Description | +| --- | --- | --- | --- | +| `NITRO_PORT` (or `PORT`) | `3000` | `18232` | HTTP port. `bun run start` sets `PORT=18232`. | +| `NITRO_HOST` (or `HOST`) | all interfaces | `::` | Address the server binds to. | +| `NUXT_PUBLIC_APP_URL` | `NETIGNITE_APP_URL` | | Overrides the URL the dashboard in the browser uses. You normally don't need it. | + +To use another port with Docker, change the published port instead, for example `"8080:18232"`. + +## The public URL + +`NETIGNITE_APP_URL` must be the address users type into the browser, because: -The file is read once at startup; restart the server after changing it. Invalid JSON or an unknown -log level stops the server. +- the dashboard sends its API requests to `/api/v1`, +- the API only allows cross-origin requests from it, and the agent console (`/ws/console`) only + accepts connections from a page on it, +- password-reset links and the first admin's link point to it, +- the ESP32 web installer and the Linux agent's setup command use it as the server address for + agents (you can change the address the ESP32 uses in the installer). -## Environment variables +If users open NetIgnite under another address, the dashboard can't reach its API. Use one public +host name, at the root of the host (no sub-path), with HTTPS. -| Variable | Default | Description | +## Server settings in the dashboard + +Admins find these under **Admin → Server Settings**. They're stored in the database. + +| Setting | Default | Description | | --- | --- | --- | -| `NITRO_PORT` | `3000` (`18232` in Docker) | Port of the dashboard, API and agent WebSocket. `PORT` works as well. | -| `NITRO_HOST` | all interfaces (`0.0.0.0` in Docker) | Address the server binds to. `HOST` works as well. | -| `CONFIG_FILE_PATH` | `./config/config.json` | Location of the config file. | -| `NETIGNITE_SAMPLE_CONFIG_PATH` | `./config/config.sample.json` (`/etc/netignite/config.sample.json` in Docker) | File copied to `CONFIG_FILE_PATH` when no config exists. | +| **Public sign-up** | off | Anyone who can open the server can create an account at `/auth/signup`. See [Users & Accounts](/docs/server/users#public-sign-up). | +| **Default channel** | Stable releases | Which ESP32 firmware release is recommended in the installer and offered as an update: *Stable releases* or *Include pre-releases*. Users can still pick any synced release. | + +The same page shows the firmware sync state, the synced releases and the **Sync now** button. + +## Firmware sync -To change the port of the Docker image, change the published port instead: -`"8080:18232"`. +The server offers ESP32 firmware for the [web installer](/docs/agents/esp32#install-from-the-browser) +and for [updates over the network](/docs/guides/agents#firmware-updates). Firmware only comes from +the GitHub releases of `NETIGNITE_FIRMWARE_REPO`; there are no uploads. -## Data paths +A sync reads the repository's releases and keeps the 5 newest ones plus the newest stable release +in `NETIGNITE_FIRMWARE_DIR`. A release is used if: -All paths are relative to the working directory of the server process (`/opt/netignite` in -Docker). +- its tag is a version like `v2.0.0` or `v2.1.0-beta.1`, +- it has a `release.json` for the agent protocol 2 and all files it lists, +- every file matches the size and SHA-256 in `release.json` (files up to 4 MB), +- with `NETIGNITE_FIRMWARE_PUBLIC_KEY` set: its `firmware.sig` is a valid signature of the app image. -| Path | Content | +Skipped releases appear in the server log and in the toast after **Sync now**. Releases that drop +out of the newest ones are deleted. See [Release signing](/docs/agents/esp32#release-signing) for +how releases are signed. + +## Data paths and backup + +| Path (Docker) | Content | | --- | --- | -| `data/db.sqlite` | SQLite database with users, agents and devices. Back this file up. | -| `data/initial_admin_credentials` | Admin password generated on first start. Delete it after logging in. | -| `config/config.json` | The config file above. | +| `/opt/netignite/data/netignite.sqlite` | Database: users, sessions, API keys, agents, devices, shares, favorites, SSH logins, firmware list, settings. | +| `/opt/netignite/data/firmware/` | Synced firmware. Can be deleted; the next sync downloads it again. | +| `/opt/netignite/config/secrets.key` | Encrypts SSH passwords and private keys. Back it up with the database. | +| `/opt/netignite/config/initial_admin_password_reset_token.txt` | The first admin's password link. Delete it after use. | -The database schema is created automatically on startup. To back up the server, stop it and copy -the `data/` directory. +To back up the server, stop it and copy the `data/` and `config/` directories. ## Logging -The server logs to stdout/stderr only. With Docker, read the logs with -`docker logs netignite-server`. +The server logs to stdout and stderr. With Docker, read the log with +`docker logs netignite-server`. Lines look like this: + +```text [Server log] +[2026-10-21T09:12:03.512Z] [INFO] Agent 3 online (esp32 2.0.0) +``` | Level | Used for | | --- | --- | | `debug` | Detailed diagnostics | -| `info` | Startup steps, agent connects and disconnects | -| `warn` | Recoverable problems | -| `error` | Failed operations, WebSocket errors | +| `info` | Startup steps, agents connecting and disconnecting, enrollments, firmware syncs and updates | +| `warn` | Recoverable problems, skipped firmware releases, unresponsive agents | +| `error` | Failed operations | | `critical` | Errors that stop the server | ::note -The initial admin password is logged at level `info`. With `logLevel` set to `warn` or higher, -it is only written to `data/initial_admin_credentials`. +The first admin's password link is logged at level `info`. With `NETIGNITE_LOG_LEVEL` set to `warn` +or higher, it's only in `initial_admin_password_reset_token.txt`. :: diff --git a/content/docs/server/installation.md b/content/docs/server/installation.md index 354083e..63d2364 100644 --- a/content/docs/server/installation.md +++ b/content/docs/server/installation.md @@ -16,10 +16,11 @@ ghcr.io/netignite-project/server | Tag | Content | | --- | --- | | `latest` | The latest stable release | -| `0.1.3`, `0.1.2`, … | A specific release (see the [release list](https://github.com/NetIgnite-project/Server/releases)) | +| `2.0.0`, … | A specific release (see the [release list](https://github.com/NetIgnite-project/Server/releases)) | +| `2.1.0-beta.1`, … | Pre-releases. They never move `latest`. | Images are built for `linux/amd64` and signed with [cosign](https://github.com/sigstore/cosign) -(keyless). +(keyless). On other platforms, [run the server from source](#from-source). ## Docker Compose (recommended) @@ -31,6 +32,8 @@ services: restart: unless-stopped ports: - "18232:18232" + environment: + NETIGNITE_APP_URL: https://netignite.example.com volumes: - /opt/netignite/data:/opt/netignite/data - /opt/netignite/config:/opt/netignite/config @@ -41,9 +44,9 @@ docker compose up -d docker compose logs -f netignite-server ``` -The upstream [compose file](https://github.com/NetIgnite-project/Server/blob/main/docker/docker-compose.yml) -also mounts `/opt/netignite/logs`. The current release logs to stdout only (use `docker logs`), -so that mount is optional. +`NETIGNITE_APP_URL` is required: it's the address users open the dashboard at, and the server +stops at startup without it. All other settings have defaults; see +[Configuration](/docs/server/configuration). ## docker run @@ -52,6 +55,7 @@ docker run -d \ --name netignite-server \ --restart unless-stopped \ -p 18232:18232 \ + -e NETIGNITE_APP_URL=https://netignite.example.com \ -v /opt/netignite/data:/opt/netignite/data \ -v /opt/netignite/config:/opt/netignite/config \ ghcr.io/netignite-project/server:latest @@ -61,69 +65,95 @@ docker run -d \ | Path | Content | | --- | --- | -| `/opt/netignite/data/db.sqlite` | The SQLite database (users, agents, devices) | -| `/opt/netignite/data/initial_admin_credentials` | The generated admin password from the first start | -| `/opt/netignite/config/config.json` | Server settings; created from a sample on first start (see [Configuration](/docs/server/configuration)) | +| `/opt/netignite/data/netignite.sqlite` | The SQLite database | +| `/opt/netignite/data/firmware/` | ESP32 firmware releases downloaded from GitHub | +| `/opt/netignite/data/logs/` | Log files of background tasks | +| `/opt/netignite/config/secrets.key` | Key that encrypts stored SSH passwords and private keys, created on first use | +| `/opt/netignite/config/initial_admin_password_reset_token.txt` | The password link for the first admin | +| `/opt/netignite/app/` | The application (part of the image, not a volume) | -The container listens on port `18232` (`NITRO_PORT`) on all interfaces. The dashboard, the REST -API and the agent WebSocket (`/api/agent-control-service`) all share this port. +The container listens on port `18232` on all interfaces (IPv4 and IPv6). The dashboard, the REST +API, the OpenAPI reference (`/api/docs/v1`) and the WebSockets `/ws/agent` and `/ws/console` all +share this port. Docker checks the container's health with `GET /api/health`. + +Back up both volumes. Without `secrets.key`, stored SSH logins can't be decrypted. ## First start On first start, the server: -1. creates `config/config.json` from the bundled sample (an empty `{}`) if it doesn't exist, -2. creates the database tables, -3. creates the user `admin` with a random 64-character password, writes it to - `data/initial_admin_credentials` and prints it to the log, -4. loads the agents from the database and starts accepting agent connections. +1. creates the database and applies the migrations, +2. creates the user `admin` with a random password, and writes a link for setting its password to + `config/initial_admin_password_reset_token.txt` (and to the log), +3. starts the API and accepts agent connections, +4. downloads the newest ESP32 firmware releases from GitHub in the background (again every 6 + hours). ```bash [Terminal] -sudo cat /opt/netignite/data/initial_admin_credentials +sudo cat /opt/netignite/config/initial_admin_password_reset_token.txt ``` +The link is valid for 7 days. Open it, set a password and log in as `admin`. See +[Users & Accounts](/docs/server/users#the-initial-admin). + +The server needs outbound HTTPS to `api.github.com` and GitHub's download servers for the firmware +sync. Without it, the ESP32 web installer has no firmware to offer; see +[Configuration](/docs/server/configuration#firmware-sync). + ::warning -Log in and change the admin password right away, then delete `initial_admin_credentials`. The -server never removes the file itself. +Logging in and the ESP32 web installer need HTTPS: the browser only keeps the login cookie and +only allows USB access on HTTPS pages (or on `localhost`). Before you use the dashboard from another +machine, set up [HTTPS & a reverse proxy](/docs/server/reverse-proxy). :: -Production builds only accept the session cookie over HTTPS (or on `localhost`). Before you use -the dashboard from another machine, set up [HTTPS & a reverse proxy](/docs/server/reverse-proxy). - ## Updating -Pull the new image and recreate the container. The database and config live in the mounted -volumes and are kept. +Pull the new image and recreate the container. The database and the config live in the mounted +volumes and are kept; database migrations run automatically on startup. ```bash [Terminal] docker compose pull docker compose up -d ``` -::note -Login sessions are held in memory, so everyone has to log in again after a restart or update. -:: +Logins are stored in the database, so users stay logged in. Agents reconnect on their own within a +minute. + +Upgrading from NetIgnite v1 (`0.1.x`)? Follow [Upgrading from v1](/docs/server/upgrading). ## From source -The server requires [Bun](https://bun.sh). Node.js is not supported: the build uses Nitro's `bun` -preset and the startup code uses Bun APIs. +The server requires [Bun](https://bun.sh); it uses Bun's runtime APIs and doesn't run on Node.js. ```bash [Terminal] git clone https://github.com/NetIgnite-project/Server.git cd Server bun install bun run build -bun run .output/server/index.mjs ``` -Run the last command from the repository root: `config/` and `data/` are resolved relative to the -working directory, and the config file is created from `config/config.sample.json`. Without -`NITRO_PORT` the server listens on port `3000`; set `NITRO_PORT=18232` to match the Docker image. +Create a `.env` file in the repository root. Bun loads it when it starts the server: + +```bash [.env] +NETIGNITE_APP_URL=https://netignite.example.com +``` + +Then start the server from the repository root: -To build the image yourself, use the repository root as the build context: +```bash [Terminal] +bun run start +``` + +`bun run start` listens on port `18232` (it sets `PORT=18232`). The data paths (`./data`, +`./config`) and the database migrations (`./drizzle/migrations`) are resolved relative to the +working directory. `bun run build` also writes `.output/import-v1.mjs`, the +[v1 import](/docs/server/upgrading). + +To build the Docker image yourself, build the app first, then the image with the repository root as +the build context: ```bash [Terminal] +bun run build docker build -f docker/Dockerfile -t netignite-server . ``` diff --git a/content/docs/server/reverse-proxy.md b/content/docs/server/reverse-proxy.md index b71f040..5bb9f30 100644 --- a/content/docs/server/reverse-proxy.md +++ b/content/docs/server/reverse-proxy.md @@ -1,27 +1,38 @@ --- title: "HTTPS & Reverse Proxy" -description: "Serve the NetIgnite dashboard and the agent WebSocket over HTTPS with Caddy, nginx or Apache." +description: "Serve the NetIgnite dashboard, API and WebSockets over HTTPS with Caddy, nginx or Apache." navigation: title: HTTPS & Reverse Proxy --- # HTTPS & Reverse Proxy -The NetIgnite server speaks plain HTTP. Put it behind a reverse proxy that terminates TLS before -you use it outside `localhost`: +The NetIgnite server speaks plain HTTP on port `18232`. Put it behind a reverse proxy that +terminates TLS before you use it outside `localhost`: -- **Logins need HTTPS.** Production builds mark the `session` cookie as `Secure`. Browsers only - store it over HTTPS or on `localhost`, so logging in over `http://:18232` fails. -- **Agent credentials.** Agents send their ID and secret in an HTTP Basic `Authorization` header. - Without TLS, anyone on the path can read them. +- **Logins need HTTPS.** The dashboard stores its login token in a `Secure` cookie, which browsers + only keep on HTTPS pages or on `localhost`. Over `http://:18232`, every login ends on + the login page again. +- **The ESP32 web installer needs HTTPS.** Browsers only allow USB (Web Serial) access on HTTPS + pages or on `localhost`. +- **Credentials.** Agents send their credential in an `Authorization` header; the dashboard sends + its login token and passwords. Without TLS, anyone on the path can read them. -The dashboard, the REST API and the agent WebSocket share one port, so a single proxy rule covers -all of them. The proxy must: +Set `NETIGNITE_APP_URL` to the public HTTPS address, for example +`https://netignite.example.com` (see [Configuration](/docs/server/configuration#the-public-url)). +Serve NetIgnite at the root of its own host name; sub-paths aren't supported. -1. forward WebSocket upgrades for `/api/agent-control-service`, -2. pass the `Authorization` header through (all proxies below do this by default), -3. allow idle connections longer than 60 seconds. The server sends each agent a heartbeat once a - minute, so a 60-second idle timeout can cut agent connections. +## What the proxy must do + +The dashboard, the API and both WebSockets share one port, so a single proxy rule covers all of +them. The proxy must: + +1. forward WebSocket upgrades: `/ws/agent` (agents) and `/ws/console` (the live log of an ESP32 + agent in the dashboard), +2. pass the `Authorization`, `Host` and `Origin` headers through (the examples below do), +3. keep idle WebSockets open for at least 60 seconds. The server pings every agent every 30 + seconds. The console can be quiet for longer; the dashboard reconnects it after 5 seconds if the + proxy closes it. The examples assume NetIgnite runs on the same host at `127.0.0.1:18232`, published as `netignite.example.com`. @@ -61,9 +72,9 @@ server { proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; - # Agents may be idle for a minute between heartbeats. - proxy_read_timeout 300s; - proxy_send_timeout 300s; + # WebSockets: agents get a ping every 30 s, the console can be quiet for longer. + proxy_read_timeout 600s; + proxy_send_timeout 600s; } } ``` @@ -81,28 +92,39 @@ Requires Apache 2.4.47 or newer with `mod_proxy`, `mod_proxy_http` and `mod_ssl` SSLCertificateKeyFile /etc/letsencrypt/live/netignite.example.com/privkey.pem ProxyPreserveHost On - ProxyTimeout 300 + ProxyTimeout 600 ProxyPass / http://127.0.0.1:18232/ upgrade=websocket ProxyPassReverse / http://127.0.0.1:18232/ ``` -## Agent settings behind a proxy +## Agents behind a proxy -Point your agents at the public hostname on port `443` with TLS enabled. For the ESP32 agent: +Agents use the server address from their setup: the ESP32 web installer and the Linux agent's +setup command fill in `NETIGNITE_APP_URL`. Agents connect to `wss://` for `https://` addresses and +to `ws://` for `http://` addresses. -```cpp [config.h] -#define C_CONTROL_SERVER_HOST "netignite.example.com" -#define C_CONTROL_SERVER_PORT (uint16_t) 443 -#define C_CONTROL_SERVER_USE_SSL (bool) true -``` +- The **ESP32 agent** checks the server's certificate against Mozilla's list of public root CAs + (Let's Encrypt included). A certificate from a private CA only works with a firmware you + [build yourself](/docs/agents/esp32#build-from-source) with that CA added. +- The **Linux agent** checks the certificate against the public root CAs built into its runtime + ([Bun](https://bun.sh)). Use a certificate from a public CA such as Let's Encrypt. + +An agent in the same LAN as the server can also use a plain `http://` address such as +`http://192.168.1.10:18232`: set it with **Change server address** in the web installer, or pair +the Linux agent with it. Its credential then crosses the network unencrypted, so only do this in a +network you trust. + +## Rate limits behind a proxy -The ESP32 agent verifies the server certificate against a bundle of public root CAs (including -Let's Encrypt). Self-signed certificates or a private CA only work if you -[add your CA to the bundle](/docs/agents/esp32#certificate-bundle). +The server counts failed logins, sign-ups and agent enrollments per client IP address. It uses the +address of the TCP connection and doesn't read `X-Forwarded-For`, so behind a proxy all clients +share the proxy's address for these limits. ## Checking the setup 1. Open `https://netignite.example.com` and log in. -2. Start an agent and watch the server log for `Agent '' connected`. -3. If the agent connects and drops again after about a minute, raise the proxy's idle timeout. +2. Open `https://netignite.example.com/api/health`; it answers + `{"success": true, "code": 200, "message": "NetIgnite API is running", …}`. +3. Start an agent and watch the server log for `Agent online (…)`. +4. Open an ESP32 agent's **Console** tab: the badge shows **Live** when `/ws/console` works. diff --git a/content/docs/server/upgrading.md b/content/docs/server/upgrading.md new file mode 100644 index 0000000..0a814b2 --- /dev/null +++ b/content/docs/server/upgrading.md @@ -0,0 +1,156 @@ +--- +title: "Upgrading from v1" +description: "Move a NetIgnite v1 server to v2: back up, run v2 next to the v1 data, import users, agents and devices, and set the agents up again." +navigation: + title: Upgrading from v1 +--- + +# Upgrading from v1 + +NetIgnite v2 is a rewrite of the server and the agents. Your v1 data is imported once into the new +database; the v1 database stays untouched. Agents have to be set up again: v1 agents can't connect +to a v2 server, and ESP32 boards need the v2 firmware. + +| | v1 (`0.1.x`) | v2 | +| --- | --- | --- | +| Database | `data/db.sqlite` | `data/netignite.sqlite` (the import reads `db.sqlite`) | +| First admin | Password in `data/initial_admin_credentials` | Password link in `config/initial_admin_password_reset_token.txt` | +| Settings | `config/config.json` | `NETIGNITE_*` environment variables (`NETIGNITE_APP_URL` is required) | +| Agent connection | `/api/agent-control-service`, ID and secret from the dashboard | `/ws/agent`, credential generated by the agent at enrollment | +| ESP32 setup | `config.h`, built with PlatformIO | Web installer in the dashboard, settings in flash | +| Port, image, volumes | `18232`, `ghcr.io/netignite-project/server`, `/opt/netignite/data` and `/opt/netignite/config` | unchanged | + +## What is migrated + +| v1 | In v2 | +| --- | --- | +| Users | Imported with their username, password and role. They get a placeholder email address `@imported.netignite.invalid`. Passwords are converted to the new hash format at the first login. | +| The v1 `admin` | Merged into the v2 `admin`. If nobody has logged in as `admin` in v2 yet, it takes over the v1 password and the v2 password link stops working. | +| Agents | Imported with name and description, but **not set up** (status *Not set up*): v1 secrets can't be used in v2. *Microcontroller* agents become ESP32 agents, *Server* agents become Linux agents. | +| Devices | Imported with name, type, description, MAC address, Wake-on-LAN port and agent. Their status check is **MAC (IPv6 link-local)**, the check v1 used. Devices without an agent get no status check. | +| Favorites | Imported. | + +Not migrated: v1 agent secrets, v1 logins (users log in again), `config/config.json` (set +`NETIGNITE_LOG_LEVEL` instead) and the v1 admin password file. Devices with an invalid MAC address +are skipped and listed in the import's output. + +::steps{level="2"} + +## Back up + +Stop the v1 server and copy its data and config: + +```bash [Terminal] +docker compose down +sudo cp -a /opt/netignite /opt/netignite-v1-backup +``` + +## Start v2 next to the v1 data + +Change the image to v2 and add `NETIGNITE_APP_URL` with the address users open the dashboard at. +Keep the volumes: + +```yaml [docker-compose.yml] +services: + netignite-server: + image: ghcr.io/netignite-project/server:latest + container_name: netignite-server + restart: unless-stopped + ports: + - "18232:18232" + environment: + NETIGNITE_APP_URL: https://netignite.example.com + volumes: + - /opt/netignite/data:/opt/netignite/data + - /opt/netignite/config:/opt/netignite/config +``` + +```bash [Terminal] +docker compose pull +docker compose up -d +``` + +v2 creates its own database `data/netignite.sqlite` next to the v1 `data/db.sqlite`, and a new +`admin` with a password link. Don't log in yet if you want the v1 admin password to carry over. + +If you use a reverse proxy, check that it forwards WebSockets on `/ws/agent` and `/ws/console` +(v1 only needed `/api/agent-control-service`). See [HTTPS & Reverse Proxy](/docs/server/reverse-proxy). + +## Try the import + +A dry run imports everything inside a transaction and rolls it back, so nothing changes: + +```bash [Terminal] +docker exec netignite-server bun /opt/netignite/app/.output/import-v1.mjs \ + --from /opt/netignite/data/db.sqlite --dry-run +``` + +```text [Output] +Dry run — nothing was changed. + +Users imported: 2 +Users merged: admin +Agents imported: 2 +Devices imported: 5 +Favorites imported: 3 +Imported users got a placeholder email (…@imported.netignite.invalid) and are asked to change it after logging in. +``` + +Users whose username already exists in v2 (other than `admin`) are skipped: +`skipped user alice: username already exists in v2 (use --merge-users to merge them)`. With +`--merge-users`, such a v1 user is merged into the existing v2 user instead; the v2 user takes over +the v1 password only if nobody has logged in to it in v2 yet. + +## Import + +```bash [Terminal] +docker exec netignite-server bun /opt/netignite/app/.output/import-v1.mjs \ + --from /opt/netignite/data/db.sqlite +``` + +The import uses the container's `NETIGNITE_*` settings and writes to the running server's +database. Running it again is safe: records that were already imported are skipped. When users +were merged (normally the `admin`), the import deletes `config/initial_admin_password_reset_token.txt`. + +Running the server from source? Run the import from the repository root: +`bun scripts/import-v1.ts --from [--dry-run] [--merge-users]`. + +## Log in + +Log in as `admin` with the v1 password (or, if you logged in to v2 before the import, with the v2 +password). Every user gets the welcome page at the first login; ask your users to enter their real +email address under **Settings → General**. + +## Set up the agents again + +Open **Agents**. The imported agents show **Not set up**; their devices still point at them. + +- **ESP32 boards** need the v2 firmware once over USB: open the agent's **Install** tab and follow + the [web installer](/docs/agents/esp32#install-from-the-browser). v2 uses a new partition table + with two firmware slots, so it can't be installed over the network on a v1 board. After that, + updates come [over the network](/docs/guides/agents#firmware-updates). Your old `config.h` isn't + needed anymore: the installer sets the server and the Wi-Fi. +- **Server agents** (your own v1 agents) don't work with v2: the + [protocol](/docs/agents/protocol) changed. Install the [Linux agent](/docs/agents/linux) on the + machine instead (turn on Wake-on-LAN mode to wake other devices), or update your agent to + protocol v2. + +As each agent comes online, its devices can be woken again. + +## Check the devices + +The imported devices keep the v1 status check, which doesn't work for most Windows and many Linux +machines. Open each device and choose a better [status check](/docs/guides/devices#status-checks), +for example its IPv4 address. While you're there, consider [sharing](/docs/guides/sharing), +shutdown through the [Linux agent](/docs/agents/linux#host-mode) or over [SSH](/docs/guides/ssh). + +:: + +## Going back + +The v1 database `data/db.sqlite` is never modified. To go back, run the v1 image again (for example +`ghcr.io/netignite-project/server:0.1.3`) with the same volumes. Agents you set up for v2 don't +work with v1: flash the v1 firmware onto ESP32 boards again. + +Once you're happy with v2, you can delete `data/db.sqlite`, `data/initial_admin_credentials` and +`config/config.json`. diff --git a/content/docs/server/users.md b/content/docs/server/users.md index 91832b6..ec6d85b 100644 --- a/content/docs/server/users.md +++ b/content/docs/server/users.md @@ -1,6 +1,6 @@ --- title: "Users & Accounts" -description: "The initial admin account, login sessions, roles and password changes on a self-hosted NetIgnite server." +description: "The first admin, user management, public sign-up, roles, logins, password resets and API keys on a NetIgnite server." navigation: title: Users & Accounts --- @@ -14,44 +14,107 @@ When the server starts with an empty database, it creates one account: | Field | Value | | --- | --- | | Username | `admin` | -| Password | 64 random hex characters, written to `data/initial_admin_credentials` and printed to the log | +| Display name | Default Administrator | +| Email | `admin@netignite.dev` (a placeholder; change it) | | Role | `admin` | +| Password | Random and unknown. Set it with the link below. | -```text [initial_admin_credentials] -Username: admin -Password: 3f9c… +The server writes a password link to `config/initial_admin_password_reset_token.txt` (in Docker: +`/opt/netignite/config/…`, readable only by root) and prints it to the log: + +```text [initial_admin_password_reset_token.txt] +https://netignite.example.com/auth/reset-password?token=3f9c… ``` -Log in, open **Settings** from the user menu, set a new password and delete the credentials file. +1. Open the link within **7 days** and choose a password. +2. Log in with the username `admin` and that password. +3. The welcome page opens. Click **Complete your profile** (or go to **Settings → General**) and + enter your real email address and name. Saving changes there asks for your current password. +4. Delete `initial_admin_password_reset_token.txt`. The link stops working once it's used, but the + server doesn't remove the file. -## Changing username and password +The link uses `NETIGNITE_APP_URL`. If that was wrong at the first start, replace the address in the +link and keep the `?token=…` part. -Every user can change their own username and password under **Settings** (`/account/settings`). -Leave the password field empty to keep the current password. Usernames must be unique. +::note +If the link expired before you used it and the server has no data yet, stop the server, delete +`data/netignite.sqlite` and start it again: an empty database gets a new admin and a new link. With +data, see [I lost the admin password](/docs/reference/troubleshooting#i-lost-the-admin-password). +:: -Changing the password does not end other active sessions. +## Roles -## Sessions +There are two roles: `admin` and `user`. -- Logging in sets a `session` cookie that is valid for 30 days. The cookie is `HttpOnly` (scripts - on the page can't read it), `SameSite=Strict` and, in production builds, `Secure`. Serve the - dashboard over [HTTPS](/docs/server/reverse-proxy). -- Sessions are kept in server memory. Restarting or updating the server logs everyone out. -- **Log out** in the user menu ends the session immediately. +- Agents and devices belong to the user who created them, for both roles. Admins don't see other + users' agents or devices. Users see devices that were [shared](/docs/guides/sharing) with them. +- Admins additionally get the **Admin** section in the sidebar: **Users** and **Server Settings**. -## Roles +## Managing users -There are two roles: `admin` and `user`. +Admins manage accounts under **Admin → Users**: -- Agents and devices are private to the user who created them, for both roles. Admins don't see - other users' agents or devices. -- Through the [API](/docs/reference/api#update-an-account), admins can edit other users' usernames - and passwords. The first user (the initial `admin`, ID `1`) can also change other users' roles. +- **New User** creates an account with a username, display name, email address, password and role. +- **Edit** changes the display name, email address and role. A changed role applies to the user's + existing logins and API keys right away. +- **Reset Password** sets a new password and logs the user out everywhere. +- **Delete** removes the account with all its data: its agents, devices, shares, API keys and + logins. -## Adding users +Usernames have 5 to 40 characters: lowercase letters, digits, `.`, `_` and `-`, starting with a +letter or digit. Usernames and email addresses must be unique. -::note -The current release has no user management in the dashboard, no self-registration and no -password reset. A self-hosted server starts with the single `admin` account, which is enough to -manage all of your own agents and devices. -:: +## Public sign-up + +By default only admins create accounts. To let people register themselves, turn on +**Admin → Server Settings → Registration → Public sign-up**. The login page then shows a **Sign up** +link to `/auth/signup`, where anyone who can open the server creates an account with the role +`user`. The server accepts at most 5 sign-ups per client address and hour. + +## Logging in + +- A login is valid for **7 days**. The dashboard keeps it in the cookie + `netignite_session_token`, which requires [HTTPS](/docs/server/reverse-proxy) (or `localhost`). + With **Remember me**, the browser keeps it after it's closed. +- Logins are stored in the database, so restarting or updating the server doesn't log anyone out. +- **Log out** in the user menu ends the login on the server. +- After 5 failed attempts for a username from one address (or 15 from all addresses), further + logins for it are refused until 5 minutes after the first attempt. + +## Changing your account + +Under **Settings** in the sidebar: + +- **General**: username, display name and email address. Saving asks for your current password. +- **Security**: change your password (this logs you out everywhere) or delete your account. Deleting + your account deletes your agents and devices too. +- **API Keys**: see [below](#api-keys). + +Passwords need at least 8 characters. A password set with **Change Password**, an admin's +**Reset Password** or a reset link must also have at most 50 characters and contain an uppercase +letter, a lowercase letter, a digit and a special character. + +## Forgotten passwords + +**Forgot password?** on the login page sends a reset link to the account's email address. The link +is valid for 1 hour; one link per address is sent every 15 minutes. This needs +[email](/docs/server/configuration#email) to be configured. Without email, an admin resets the +password under **Admin → Users**. + +## API keys + +API keys let scripts and integrations use the [REST API](/docs/reference/api) as you. Create one +under **Settings → API Keys → New API Key** with a description and an expiration (7, 30, 90, 180 or +365 days, or none). The key (`netignite_apikey_…`) is shown **once**; copy it right away. Send it as +`Authorization: Bearer `. + +An API key can do what you can do with agents, devices and firmware, and admin tasks if you're an +admin. Your own account (profile, password, API keys) can only be changed with a login, not with an +API key. Delete a key under **Settings → API Keys**; it stops working immediately. Keys of a user +whose role changes get the new role. + +## Imported v1 users + +Users imported from NetIgnite v1 keep their username and password. They get a placeholder email +address (`@imported.netignite.invalid`) and should enter their real one under +**Settings → General** after their first login. See [Upgrading from v1](/docs/server/upgrading). diff --git a/nuxt.config.ts b/nuxt.config.ts index fb76169..6f127a4 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -61,7 +61,7 @@ export default defineNuxtConfig({ build: { markdown: { highlight: { - langs: ["bash", "json", "yaml", "ts", "cpp", "nginx", "apache", "http"], + langs: ["bash", "json", "yaml", "ts", "js", "ini", "nginx", "apache", "http"], }, }, }, diff --git a/public/static/utils/sitemap.xml b/public/static/utils/sitemap.xml index 6f5d5ae..9b6ed5d 100644 --- a/public/static/utils/sitemap.xml +++ b/public/static/utils/sitemap.xml @@ -21,77 +21,97 @@ https://netignite.dev/docs - 2026-10-18T00:00:00+00:00 + 2026-10-21T00:00:00+00:00 0.9 https://netignite.dev/docs/how-it-works - 2026-10-18T00:00:00+00:00 + 2026-10-21T00:00:00+00:00 0.8 https://netignite.dev/docs/getting-started - 2026-10-18T00:00:00+00:00 + 2026-10-21T00:00:00+00:00 0.8 https://netignite.dev/docs/server/installation - 2026-10-18T00:00:00+00:00 + 2026-10-21T00:00:00+00:00 0.8 https://netignite.dev/docs/server/configuration - 2026-10-18T00:00:00+00:00 + 2026-10-21T00:00:00+00:00 0.7 https://netignite.dev/docs/server/reverse-proxy - 2026-10-18T00:00:00+00:00 + 2026-10-21T00:00:00+00:00 0.7 https://netignite.dev/docs/server/users - 2026-10-18T00:00:00+00:00 + 2026-10-21T00:00:00+00:00 + 0.7 + + + https://netignite.dev/docs/server/upgrading + 2026-10-21T00:00:00+00:00 0.7 https://netignite.dev/docs/guides/dashboard - 2026-10-18T00:00:00+00:00 + 2026-10-21T00:00:00+00:00 0.7 https://netignite.dev/docs/guides/agents - 2026-10-18T00:00:00+00:00 + 2026-10-21T00:00:00+00:00 0.7 https://netignite.dev/docs/guides/devices - 2026-10-18T00:00:00+00:00 + 2026-10-21T00:00:00+00:00 + 0.7 + + + https://netignite.dev/docs/guides/sharing + 2026-10-21T00:00:00+00:00 + 0.7 + + + https://netignite.dev/docs/guides/ssh + 2026-10-21T00:00:00+00:00 0.7 https://netignite.dev/docs/agents/esp32 - 2026-10-18T00:00:00+00:00 + 2026-10-21T00:00:00+00:00 + 0.8 + + + https://netignite.dev/docs/agents/linux + 2026-10-21T00:00:00+00:00 0.8 https://netignite.dev/docs/agents/protocol - 2026-10-18T00:00:00+00:00 + 2026-10-21T00:00:00+00:00 0.7 https://netignite.dev/docs/reference/api - 2026-10-18T00:00:00+00:00 + 2026-10-21T00:00:00+00:00 0.7 https://netignite.dev/docs/reference/troubleshooting - 2026-10-18T00:00:00+00:00 + 2026-10-21T00:00:00+00:00 0.7 https://netignite.dev/docs/reference/development - 2026-10-18T00:00:00+00:00 + 2026-10-21T00:00:00+00:00 0.6 From 9daa46bf68dbac64a3d998cd1fe738dc28893a71 Mon Sep 17 00:00:00 2001 From: Linus Fischer Date: Wed, 21 Oct 2026 13:26:03 +0200 Subject: [PATCH 2/4] docs: match the fixes from the docs review The v1 admin password takeover, an unreadable firmware public key, the Linux agent reporting a refused shutdown, and one password rule everywhere. --- content/docs/agents/linux.md | 14 ++++++++------ content/docs/reference/troubleshooting.md | 9 +++++---- content/docs/server/configuration.md | 2 +- content/docs/server/upgrading.md | 12 ++++++------ content/docs/server/users.md | 6 +++--- 5 files changed, 23 insertions(+), 20 deletions(-) diff --git a/content/docs/agents/linux.md b/content/docs/agents/linux.md index d91f631..6eefce6 100644 --- a/content/docs/agents/linux.md +++ b/content/docs/agents/linux.md @@ -138,12 +138,14 @@ then: - show the machine as online while the agent is connected (status check *Linux agent on the device*), -- send `HOST_CONTROL` with `shutdown` or `reboot`. The agent answers, waits about a second and runs - `systemctl --no-ask-password poweroff` or `systemctl --no-ask-password reboot`. - -Nothing else: the server can't run commands, read files or change the configuration. A failed -`systemctl` call (polkit missing, or a block inhibitor such as a running package upgrade) is logged -in the agent's journal. +- send `HOST_CONTROL` with `shutdown` or `reboot`. The agent first asks systemd-logind whether it + may (`CanPowerOff`/`CanReboot`); if not (polkit rule missing, or a block inhibitor such as a + running package upgrade), it answers with the reason, which the dashboard shows. Otherwise it + answers, waits about a second and runs `systemctl --no-ask-password poweroff` or + `systemctl --no-ask-password reboot`. + +Nothing else: the server can't run commands, read files or change the configuration. If +`systemctl` still fails after the answer, the error is in the agent's journal. ## Wake-on-LAN mode diff --git a/content/docs/reference/troubleshooting.md b/content/docs/reference/troubleshooting.md index 8c1783f..404c667 100644 --- a/content/docs/reference/troubleshooting.md +++ b/content/docs/reference/troubleshooting.md @@ -19,7 +19,7 @@ journal (`journalctl -u netignite-agent`). | --- | --- | | `Failed to read the environment variable APP_URL: …` | Set `NETIGNITE_APP_URL`; it's required. | | `Failed to read the environment variable : Expected a boolean value ('true' or 'false')` | Booleans must be exactly `true` or `false`, not empty, `1` or `yes`. | -| `Unhandled Rejection` about a file that doesn't exist, right after `Starting NetIgnite...` | `NETIGNITE_FIRMWARE_PUBLIC_KEY` is a *path*; the file must exist inside the container, for example in the config volume. | +| `NETIGNITE_FIRMWARE_PUBLIC_KEY: can't use …` | `NETIGNITE_FIRMWARE_PUBLIC_KEY` is a *path* to a PEM public key; the file must exist inside the container, for example in the config volume. Until it can be read, every firmware sync fails with this message (also under **Admin → Server Settings**). | ### The firmware list stays empty @@ -206,9 +206,10 @@ Viewers don't get power actions. ### Shutdown requested, but nothing happens -- **Linux agent on the device:** check its journal for `Host control (shutdown) failed`. Usually - polkit is missing (`apt install polkitd`, then `sudo netignite-agent install` again) or a - block inhibitor, such as a running package upgrade, holds the shutdown. +- **Linux agent on the device:** the dashboard shows why the agent refused (from systemd-logind). + Usually polkit is missing (`apt install polkitd`, then `sudo netignite-agent install` again) or a + block inhibitor, such as a running package upgrade, holds the shutdown (`systemd-inhibit --list`). + If the request succeeded but nothing happened, check the journal for `Host control (shutdown) failed`. - **SSH:** errors come back in the toast: | Message | Fix | diff --git a/content/docs/server/configuration.md b/content/docs/server/configuration.md index 9ae22ed..a9d05f4 100644 --- a/content/docs/server/configuration.md +++ b/content/docs/server/configuration.md @@ -59,7 +59,7 @@ of them to paths inside the container (*Docker image* column). | `NETIGNITE_FIRMWARE_SYNC` | `true` | | Download new firmware releases from GitHub at startup and every 6 hours. `false` for servers without internet access; admins can still click **Sync now**. | | `NETIGNITE_FIRMWARE_REPO` | `NetIgnite-project/Agent-ESP32` | | The GitHub repository (`owner/name`) whose releases are synced, for example a fork. | | `NETIGNITE_FIRMWARE_DIR` | `./data/firmware` | `/opt/netignite/data/firmware` | Where synced releases are stored. | -| `NETIGNITE_FIRMWARE_PUBLIC_KEY` | — | | Path to a PEM file with an ECDSA P-256 public key. When set, only releases with a valid `firmware.sig` are synced. The file must exist at startup. | +| `NETIGNITE_FIRMWARE_PUBLIC_KEY` | — | | Path to a PEM file with an ECDSA P-256 public key. When set, only releases with a valid `firmware.sig` are synced. If the file can't be read, every sync fails with the reason (shown in Server Settings) and no firmware is accepted. | | `NETIGNITE_GITHUB_TOKEN` | — | | A GitHub token for the release sync; raises GitHub's API rate limit. | ### Linux agent installer diff --git a/content/docs/server/upgrading.md b/content/docs/server/upgrading.md index 0a814b2..5605856 100644 --- a/content/docs/server/upgrading.md +++ b/content/docs/server/upgrading.md @@ -25,7 +25,7 @@ to a v2 server, and ESP32 boards need the v2 firmware. | v1 | In v2 | | --- | --- | | Users | Imported with their username, password and role. They get a placeholder email address `@imported.netignite.invalid`. Passwords are converted to the new hash format at the first login. | -| The v1 `admin` | Merged into the v2 `admin`. If nobody has logged in as `admin` in v2 yet, it takes over the v1 password and the v2 password link stops working. | +| The v1 `admin` | Merged into the v2 `admin`. If nobody has set the v2 `admin` password yet (the password link is unused), it takes over the v1 password and the link stops working. | | Agents | Imported with name and description, but **not set up** (status *Not set up*): v1 secrets can't be used in v2. *Microcontroller* agents become ESP32 agents, *Server* agents become Linux agents. | | Devices | Imported with name, type, description, MAC address, Wake-on-LAN port and agent. Their status check is **MAC (IPv6 link-local)**, the check v1 used. Devices without an agent get no status check. | | Favorites | Imported. | @@ -71,7 +71,7 @@ docker compose up -d ``` v2 creates its own database `data/netignite.sqlite` next to the v1 `data/db.sqlite`, and a new -`admin` with a password link. Don't log in yet if you want the v1 admin password to carry over. +`admin` with a password link. Don't use the link yet if you want the v1 admin password to carry over. If you use a reverse proxy, check that it forwards WebSockets on `/ws/agent` and `/ws/console` (v1 only needed `/api/agent-control-service`). See [HTTPS & Reverse Proxy](/docs/server/reverse-proxy). @@ -109,16 +109,16 @@ docker exec netignite-server bun /opt/netignite/app/.output/import-v1.mjs \ ``` The import uses the container's `NETIGNITE_*` settings and writes to the running server's -database. Running it again is safe: records that were already imported are skipped. When users -were merged (normally the `admin`), the import deletes `config/initial_admin_password_reset_token.txt`. +database. Running it again is safe: records that were already imported are skipped. When the +`admin` takes over the v1 password, the import deletes `config/initial_admin_password_reset_token.txt`. Running the server from source? Run the import from the repository root: `bun scripts/import-v1.ts --from [--dry-run] [--merge-users]`. ## Log in -Log in as `admin` with the v1 password (or, if you logged in to v2 before the import, with the v2 -password). Every user gets the welcome page at the first login; ask your users to enter their real +Log in as `admin` with the v1 password (or, if you set a v2 password with the link before the +import, with that one). Every user gets the welcome page at the first login; ask your users to enter their real email address under **Settings → General**. ## Set up the agents again diff --git a/content/docs/server/users.md b/content/docs/server/users.md index ec6d85b..ae7b0a0 100644 --- a/content/docs/server/users.md +++ b/content/docs/server/users.md @@ -90,9 +90,9 @@ Under **Settings** in the sidebar: your account deletes your agents and devices too. - **API Keys**: see [below](#api-keys). -Passwords need at least 8 characters. A password set with **Change Password**, an admin's -**Reset Password** or a reset link must also have at most 50 characters and contain an uppercase -letter, a lowercase letter, a digit and a special character. +Passwords need 8 to 50 characters with an uppercase letter, a lowercase letter, a digit and a +special character, everywhere: sign-up, **Create User**, **Change Password**, an admin's **Reset +Password** and reset links. ## Forgotten passwords From fb3f2576597cde443edf35c758e6af249e061c4f Mon Sep 17 00:00:00 2001 From: Linus Fischer Date: Wed, 21 Oct 2026 21:36:21 +0200 Subject: [PATCH 3/4] docs: true marketing claims, SSH security advice and the dashboard app - Landing and features pages: drop the claims NetIgnite doesn't back up (multi-factor authentication, scheduled actions, device discovery, power monitoring, webhooks, offline mode, a 5-device limit, "thousands of IT professionals", "enterprise-grade"). List what v2 does instead: shutdown and reboot, live status, sharing roles, browser install and OTA updates, the installable app (PWA), the REST API with API keys, hosted or self-hosted. - SSH guide: prefer the Linux agent, and if SSH is used, a dedicated user that may only shut down and reboot, with a generated key; Windows needs an administrator, so a separate account and restrict,from=. - Devices and How it works: the Linux agent is the preferred way to shut down. - Dashboard guide: installing the dashboard as an app. --- app/data/features.ts | 97 ++++++++++++++++++++------------ app/pages/features.vue | 20 +++---- app/pages/index.vue | 12 ++-- content/docs/guides/dashboard.md | 16 +++++- content/docs/guides/devices.md | 5 +- content/docs/guides/ssh.md | 59 +++++++++++++++++-- content/docs/how-it-works.md | 8 ++- content/docs/index.md | 1 + 8 files changed, 154 insertions(+), 64 deletions(-) diff --git a/app/data/features.ts b/app/data/features.ts index 89199eb..1796799 100644 --- a/app/data/features.ts +++ b/app/data/features.ts @@ -24,42 +24,42 @@ export const homeFeatures: Feature[] = [ icon: "i-lucide-zap", title: "Wake-on-LAN", description: - "Power on devices remotely using Wake-on-LAN technology. Support for multiple protocols and network configurations.", + "Power on PCs, servers and NAS devices from anywhere. An agent in their network sends the Wake-on-LAN packet, so no port forwarding to your devices is needed.", tone: "amber", }, { - icon: "i-lucide-shield-check", - title: "Secure Access", + icon: "i-lucide-power", + title: "Shutdown & Reboot", description: - "Enterprise-grade security with encrypted connections, multi-factor authentication, and role-based access control.", - tone: "primary", + "Shut down and reboot Linux machines through the NetIgnite Linux agent, or other devices over SSH with a login limited to exactly these two commands.", + tone: "cyan", }, { - icon: "i-lucide-chart-line", - title: "Real-time Monitoring", + icon: "i-lucide-activity", + title: "Live Status", description: - "Monitor device status, network performance, and power consumption with real-time dashboards and alerts.", + "See which devices are online, checked every 20 seconds by IP address, TCP port, MAC address, SSH port or the Linux agent on the device.", tone: "green", }, { - icon: "i-lucide-history", - title: "Scheduled Actions", + icon: "i-lucide-users", + title: "Sharing & Roles", description: - "Automate device power management with intelligent scheduling and energy-saving policies.", - tone: "cyan", + "Share devices with family or colleagues as viewer, operator or manager. They never get access to your agents or logins.", + tone: "primary", }, { icon: "i-lucide-smartphone", - title: "Mobile App", + title: "Install as an App", description: - "PWA-Based Mobile apps for iOS and Android with full feature parity and offline capabilities.", + "Add the dashboard to the home screen of your iPhone or Android phone as an app (PWA) and power on devices on the go.", tone: "red", }, { icon: "i-lucide-puzzle", - title: "API Integration", + title: "REST API", description: - "Comprehensive REST API for custom integrations with your existing infrastructure and automation tools.", + "Automate with a REST API, API keys and an OpenAPI reference for your scripts and home automation.", tone: "slate", }, ]; @@ -70,59 +70,82 @@ export const platformFeatures: Feature[] = [ icon: "i-lucide-zap", title: "Wake-on-LAN Control", description: - "Power on offline systems remotely with structured workflows and fast response times.", + "Power on devices through an ESP32 or Linux agent in their network, from the dashboard, your phone or the API.", tone: "amber", }, { - icon: "i-lucide-shield-check", - title: "Secure Access", + icon: "i-lucide-power", + title: "Shutdown & Reboot", description: - "Protect your environment with encrypted traffic, strict authentication, and access controls.", - tone: "primary", + "Turn Linux machines off or restart them through the NetIgnite Linux agent, which can do nothing else. Other devices work over SSH with a login restricted to shutdown and reboot.", + tone: "cyan", }, { - icon: "i-lucide-chart-line", - title: "Real-time Visibility", - description: "Track status, uptime, and health signals in real time from a single dashboard.", + icon: "i-lucide-activity", + title: "Live Status", + description: + "Status checks every 20 seconds by IPv4 or IPv6 address, TCP port, MAC address (IPv6 link-local), SSH port or the Linux agent on the device.", tone: "green", }, { - icon: "i-lucide-history", - title: "Scheduled Actions", + icon: "i-lucide-users", + title: "Sharing & Roles", description: - "Automate power routines for maintenance windows, daily operations, and energy savings.", - tone: "cyan", + "Share a device as viewer (status), operator (also power actions) or manager (also settings). Agents and SSH logins stay yours.", + tone: "primary", + }, + { + icon: "i-lucide-usb", + title: "Browser Install & Updates", + description: + "Install the ESP32 agent from Chrome, Edge or Firefox over USB. Over-the-air updates are checked before they're installed and roll back automatically if they fail.", + tone: "amber", + }, + { + icon: "i-lucide-shield-check", + title: "Secure by Design", + description: + "Each agent has its own credential, stored on the server only as a hash, and only runs fixed commands: no remote shell, no file access.", + tone: "green", }, { icon: "i-lucide-smartphone", - title: "Mobile Ready", + title: "Install as an App", description: - "Use a responsive interface and PWA support to manage critical tasks while on the move.", + "A responsive dashboard you can install as an app (PWA) on iOS, Android and the desktop.", tone: "red", }, { icon: "i-lucide-puzzle", - title: "API Integration", - description: "Connect NetIgnite to your tools with a flexible REST API built for automation.", + title: "REST API", + description: + "Connect NetIgnite to your tools with a REST API, API keys and an OpenAPI reference.", tone: "slate", }, + { + icon: "i-lucide-server", + title: "Hosted or Self-Hosted", + description: + "Use the hosted app or run your own server with Docker. All of NetIgnite is open source under the AGPL-3.0.", + tone: "primary", + }, ]; /** "How It Works" steps on the landing page. */ export const setupSteps: SetupStep[] = [ { - title: "Install Agent", + title: "Set Up an Agent", description: - "Deploy our lightweight agent on your network or use our cloud-based solution for instant setup.", + "Install the ESP32 agent from your browser, or the Linux agent with one command, in the network of your devices.", }, { - title: "Discover Devices", + title: "Add Your Devices", description: - "Automatically discover and catalog all network devices with their capabilities and current status.", + "Enter each device's MAC address and choose how NetIgnite checks whether it's online.", }, { title: "Take Control", description: - "Start managing your devices remotely through our intuitive dashboard or mobile app.", + "Power devices on, shut them down and reboot them from the dashboard, the app on your phone or the API.", }, ]; diff --git a/app/pages/features.vue b/app/pages/features.vue index db827df..f81be1c 100644 --- a/app/pages/features.vue +++ b/app/pages/features.vue @@ -4,24 +4,24 @@ import { platformFeatures } from "~/data/features"; usePageSeo({ title: "Features — NetIgnite", description: - "Discover devices, trigger Wake-on-LAN power actions and keep your infrastructure available from anywhere with NetIgnite.", + "Wake-on-LAN, live status checks, shutdown and reboot, device sharing, agent updates over the network, an installable app and a REST API: what NetIgnite can do.", }); const highlightCards = [ { title: "Operational Highlights", items: [ - "Centralized overview of device state and reachability", - "Reliable Wake-on-LAN dispatch with retries and validation", - "Role-based controls for secure team operations", + "One overview of all your devices and whether they're online", + "Wake-on-LAN from an agent in the device's own network", + "Per-device roles: viewer, operator and manager", ], }, { - title: "Built for Growth", + title: "Open and Extensible", items: [ - "REST API and webhooks for automation and integrations", - "Dashboard designed for both homelabs and enterprise fleets", - "Continuous improvements with focus on reliability and usability", + "REST API with API keys and an OpenAPI reference", + "Agents for the ESP32 and for Linux, in as many networks as you like", + "Open source under the AGPL-3.0, hosted or self-hosted", ], }, ]; @@ -39,8 +39,8 @@ const highlightCards = [ Platform Features

- Everything you need to discover devices, trigger remote power actions, and keep your - infrastructure available from anywhere. + Everything you need to power devices on and off remotely, see whether they're online, + and share them with the people who need them.

- Take control of your network infrastructure with NetIgnite. Remotely manage, monitor, - and power on devices across your entire network with enterprise-grade security and - reliability. + Power on, monitor and shut down the computers in your network from anywhere. A small + agent in your LAN does the work; you use the dashboard, your phone or the API.

- Free for personal use with up to 5 devices. + Open source under the AGPL-3.0. Use the hosted app or run your own server.

@@ -97,7 +96,8 @@ const heroDevices = [

Powerful Remote Management

- Everything you need to manage your network devices remotely with confidence and security. + Everything you need to power your devices on and off remotely and see whether they're + online.

@@ -142,7 +142,7 @@ const heroDevices = [

Ready to Power Up Your Network?

- Join thousands of IT professionals who trust NetIgnite for remote device management. + Set up your first agent in minutes, on the hosted app or your own server.

Date: Wed, 21 Oct 2026 22:29:06 +0200 Subject: [PATCH 4/4] docs: prepare the docs for the 2.0.0 release - Upgrading: v1 servers on the `latest` image get v2 with 2.0.0; where their data is, the log warning, and the 0.1.3 image to stay on v1. - Linux agent installer: the newest stable release by default. --- content/docs/agents/linux.md | 7 ++++--- content/docs/server/configuration.md | 2 +- content/docs/server/upgrading.md | 9 +++++++++ 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/content/docs/agents/linux.md b/content/docs/agents/linux.md index 6eefce6..eb7f4f4 100644 --- a/content/docs/agents/linux.md +++ b/content/docs/agents/linux.md @@ -62,9 +62,10 @@ The agent shows up as online on its page. Then: - To **wake other devices**, turn on [Wake-on-LAN mode](#wake-on-lan-mode) and choose the agent as the **Agent** of those devices. -By default, the script installs the newest release, pre-releases included. The server's admin can -pin a release with `NETIGNITE_LINUX_AGENT_VERSION` or use another repository with -`NETIGNITE_LINUX_AGENT_REPO` (see [Configuration](/docs/server/configuration#linux-agent-installer)). +By default, the script installs the newest stable release (a pre-release only while there is no +stable one). The server's admin can pin a release with `NETIGNITE_LINUX_AGENT_VERSION` or use +another repository with `NETIGNITE_LINUX_AGENT_REPO` (see +[Configuration](/docs/server/configuration#linux-agent-installer)). On the machine, the variables `NETIGNITE_AGENT_VERSION` (a release tag) and `NETIGNITE_AGENT_BASE_URL` (a mirror with the release files) override that; pass them through `sudo env`: diff --git a/content/docs/server/configuration.md b/content/docs/server/configuration.md index a9d05f4..59033ef 100644 --- a/content/docs/server/configuration.md +++ b/content/docs/server/configuration.md @@ -67,7 +67,7 @@ of them to paths inside the container (*Docker image* column). | Variable | Default | Docker image | Description | | --- | --- | --- | --- | | `NETIGNITE_LINUX_AGENT_REPO` | `NetIgnite-project/Agent-Linux` | | GitHub repository that `/install/linux.sh` downloads the agent from. | -| `NETIGNITE_LINUX_AGENT_VERSION` | — | | A release tag such as `v1.0.0`. Without it, the script installs the newest release, pre-releases included. | +| `NETIGNITE_LINUX_AGENT_VERSION` | — | | A release tag such as `v1.0.0`. Without it, the script installs the newest stable release, or the newest pre-release while there is no stable one. | ### Email diff --git a/content/docs/server/upgrading.md b/content/docs/server/upgrading.md index 5605856..543b33d 100644 --- a/content/docs/server/upgrading.md +++ b/content/docs/server/upgrading.md @@ -11,6 +11,15 @@ NetIgnite v2 is a rewrite of the server and the agents. Your v1 data is imported database; the v1 database stays untouched. Agents have to be set up again: v1 agents can't connect to a v2 server, and ESP32 boards need the v2 firmware. +::warning +**Using the `latest` image?** Since 2.0.0, `ghcr.io/netignite-project/server:latest` is v2. A v1 +server that pulls it, by hand or with an auto-updater such as Watchtower, starts v2 with an empty +database next to the v1 one: users, agents and devices seem to be gone, but they're still in +`data/db.sqlite`, and the server log says *Found a NetIgnite v1 database … that hasn't been +imported*. Continue at [Try the import](#try-the-import). To stay on v1 for now, use the image +`ghcr.io/netignite-project/server:0.1.3`. +:: + | | v1 (`0.1.x`) | v2 | | --- | --- | --- | | Database | `data/db.sqlite` | `data/netignite.sqlite` (the import reads `db.sqlite`) |