diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..b94b192 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,50 @@ +{ + "mcpServers": { + "nuxt": { + "type": "url", + "url": "https://nuxt.com/mcp" + }, + "nuxt-ui": { + "type": "url", + "url": "https://ui.nuxt.com/mcp" + } + }, + "commands": { + "api-client": { + "description": "Regenerate the typed API client", + "prompt": "Regenerate and verify the generated API client for this full-stack Nuxt project.\n\n1. Run `bun run api-client:generate` (scripts/api-client-generate.ts boots the API in-process, reads its OpenAPI spec, and patches the generated files — no dev server needed).\n2. Do not hand-edit generated `*.gen.ts` files under `app/api-client/`.\n3. If generation fails, diagnose whether the Hono backend or the OpenAPI route is the issue.\n4. Summarize the outcome and any follow-up needed." + }, + "db": { + "description": "Run Drizzle migrations", + "prompt": "Run the Drizzle migration flow for this project.\n\n1. Run `bun run db:generate` to generate migrations from `server/lib/db/schema.ts` into `drizzle/migrations/`.\n2. Run `bun run db:migrate` to apply them (the app also auto-migrates on startup when `NETIGNITE_DB_AUTO_MIGRATE` is true — the default).\n3. Summarize what was generated or applied." + }, + "verify": { + "description": "Run the repo verification flow", + "prompt": "Run the standard verification flow for this Nuxt 4 + Bun project.\n\n1. Run `bun run typecheck` (runs `nuxt typecheck` then `tsc -p ./tsconfig/tsconfig.typecheck.json`).\n2. Run `bun test`.\n3. Summarize any failures or warnings.\n4. If this command was triggered after a code change, fix issues that are clearly caused by that change before summarizing." + }, + "test": { + "description": "Run the test suite", + "prompt": "Run the test suite for this Nuxt 4 + Bun project.\n\n1. Run `bun test`.\n2. If tests fail, diagnose and fix the root cause.\n3. Summarize pass/fail status and any fixes applied." + }, + "typecheck": { + "description": "Run the TypeScript type checker", + "prompt": "Run the full typecheck flow for this Nuxt 4 + Bun project.\n\n1. Run `bun run typecheck` which runs `nuxt typecheck` then `tsc -p ./tsconfig/tsconfig.typecheck.json`.\n2. If there are type errors, diagnose and fix them before summarizing.\n3. Report the pass/fail status and what was fixed, if anything." + }, + "dev": { + "description": "Start or inspect the dev setup", + "prompt": "Work on the local development setup for this project.\n\n1. If needed, install dependencies with `bun install`.\n2. Use `bun run dev` for the dev server on port 18232.\n3. If there is a dev-server issue, inspect the Nuxt config, runtime env, and Bun-specific wiring before making changes.\n4. Summarize what you started, changed, or found." + } + }, + "fileScan": { + "exclude": [ + ".git/**", + "node_modules/**", + ".nuxt/**", + ".output/**", + ".nitro/**", + ".data/**", + ".cache/**", + "dist/**" + ] + } +} diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..b3c8484 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,7 @@ +# LF everywhere (Biome formats with LF; keeps Windows checkouts clean with core.autocrlf). +* text=auto eol=lf + +*.png binary +*.ico binary +*.woff2 binary +*.bin binary diff --git a/.github/renovate.json b/.github/renovate.json new file mode 100644 index 0000000..344c613 --- /dev/null +++ b/.github/renovate.json @@ -0,0 +1,3 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json" +} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..74b6d61 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,52 @@ +name: CI + +on: + push: + branches: [main, v2] + pull_request: + workflow_dispatch: + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + - run: bun install --frozen-lockfile + - run: bun run check:ci + - run: bun run typecheck + - run: bun test + + # Builds the Docker image like the release does and checks that it starts. + image: + needs: test + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@v4 + - uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + - run: bun install --frozen-lockfile + - run: bun run build + - run: docker build -f docker/Dockerfile -t netignite-server:ci . + - name: Smoke test + run: | + docker run -d --name netignite -p 18232:18232 -e NETIGNITE_APP_URL=http://localhost:18232 netignite-server:ci + for i in $(seq 1 45); do + if curl -fsS http://localhost:18232/api/health; then break; fi + sleep 2 + done + echo + curl -fsS http://localhost:18232/api/health + # Migrations ran and the initial admin was created + docker exec netignite test -f /opt/netignite/config/initial_admin_password_reset_token.txt + # The dashboard is served + curl -fsS -o /dev/null http://localhost:18232/auth/login + # The v1 import tool is in the image + docker exec netignite bun /opt/netignite/app/.output/import-v1.mjs --help + - name: Container log + if: always() + run: docker logs netignite || true diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 60d5706..1b7a6b4 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -38,6 +38,22 @@ jobs: id-token: write steps: + # A stable image moves `latest`, which self-hosted servers pull: only main may publish one. + - name: Check version + env: + VERSION: ${{ inputs.version }} + PRE_RELEASE: ${{ inputs.pre_release }} + REF: ${{ github.ref }} + run: | + if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-(alpha|beta|rc|pre)\.[0-9]+)?$ ]]; then + echo "::error::Invalid version '$VERSION'. Use x.y.z or x.y.z-beta.n." + exit 1 + fi + if [[ "$PRE_RELEASE" != "true" && "$REF" != "refs/heads/main" ]]; then + echo "::error::Only main can publish a stable image (tag 'latest'). Publish from $REF as a pre-release." + exit 1 + fi + - name: Checkout repository uses: actions/checkout@v4 @@ -48,10 +64,10 @@ jobs: - name: Install dependencies shell: bash - run: bun install + run: bun install --frozen-lockfile - #- name: Build Nuxt application - # run: bun run build + - name: Build Nuxt application + run: bun run build - name: Setup Docker id: docker-setup diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 64b6ba2..19628d2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,76 +1,99 @@ name: Create Release +# Creates a GitHub release of the branch the workflow runs on and publishes its Docker image. +# Pre-releases (a version like 2.0.0-beta.1, or the "pre-release" box) can run from any branch and +# only push the versioned image tag. Stable releases must run from main; they also move `latest`. + on: workflow_dispatch: inputs: version: - description: 'Release version Format: x.y.z-[alpha|beta|pre].n' + description: "Version: x.y.z, or x.y.z-alpha.n / -beta.n / -rc.n / -pre.n for a pre-release" required: true release_name: - description: 'Release title' + description: "Release title" required: true release_notes: - description: 'Release notes or changelog' + description: "Release notes or changelog" required: true prerelease: - description: 'Is this a pre-release?' + description: "Is this a pre-release? (Versions with a suffix always are.)" required: false default: false type: boolean jobs: - build: + check: runs-on: ubuntu-latest - + outputs: + prerelease: ${{ steps.check.outputs.prerelease }} steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Bun - uses: oven-sh/setup-bun@v2 - with: - bun-version: latest - - - name: Install dependencies - run: bun install - - - name: Test code - run: bun test - - - name: Build Nuxt application - run: bun run build - - - name: Create Archive of .output - run: | - tar -czf app.tar.gz .output + - id: check + env: + VERSION: ${{ inputs.version }} + PRERELEASE: ${{ inputs.prerelease }} + REF: ${{ github.ref }} + run: | + if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-(alpha|beta|rc|pre)\.[0-9]+)?$ ]]; then + echo "::error::Invalid version '$VERSION'. Use x.y.z or x.y.z-beta.n." + exit 1 + fi + prerelease=false + if [[ "$PRERELEASE" == "true" || "$VERSION" == *-* ]]; then prerelease=true; fi + if [[ "$prerelease" == "false" && "$REF" != "refs/heads/main" ]]; then + echo "::error::Stable releases move the image's 'latest' tag and can only be created from main. Use a pre-release version (e.g. $VERSION-beta.1) on $REF." + exit 1 + fi + echo "prerelease=$prerelease" >> "$GITHUB_OUTPUT" + echo "Releasing v$VERSION from $REF (pre-release: $prerelease)" - - name: Upload build artifacts - uses: actions/upload-artifact@v4 - with: - name: app - path: ./app.tar.gz - retention-days: 1 # Set to 1 day for temporary storage + build: + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + - run: bun install --frozen-lockfile + - run: bun test + - run: bun run build + # The server applies the database migrations on startup, so they belong with the build. + - run: tar -czf app.tar.gz .output drizzle/migrations + - uses: actions/upload-artifact@v4 + with: + name: app + path: ./app.tar.gz + retention-days: 1 release: - needs: build + needs: [check, build] runs-on: ubuntu-latest - permissions: contents: write - steps: - - name: Download build artifacts - uses: actions/download-artifact@v4 - with: - name: app + - uses: actions/download-artifact@v4 + with: + name: app + - uses: ncipollo/release-action@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + tag: v${{ inputs.version }} + # Tag the commit this workflow runs on, not the default branch. + commit: ${{ github.sha }} + name: ${{ inputs.release_name }} + body: ${{ inputs.release_notes }} + prerelease: ${{ needs.check.outputs.prerelease }} + artifacts: ./app.tar.gz - - name: Create GitHub Release - id: create_release - uses: ncipollo/release-action@v1 - with: - token: ${{ secrets.GITHUB_TOKEN }} - tag: v${{ github.event.inputs.version }} - name: ${{ github.event.inputs.release_name }} - body: ${{ github.event.inputs.release_notes }} - prerelease: ${{ github.event.inputs.prerelease }} - artifacts: ./app.tar.gz \ No newline at end of file + docker: + needs: [check, release] + permissions: + contents: read + packages: write + id-token: write + uses: ./.github/workflows/docker-publish.yml + with: + version: ${{ inputs.version }} + pre_release: ${{ needs.check.outputs.prerelease }} + secrets: inherit diff --git a/.gitignore b/.gitignore index 194dd79..7c359a8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,29 +1,52 @@ -# Nuxt dev/build outputs +# Node +node_modules + +# Dev / Build / compile output .output .data .nuxt .nitro .cache -dist +build/ +out/ +dist/ +*.tgz -# Node dependencies -node_modules +# Drizzle artifacts +.drizzle + +# TypeScript +*.tsbuildinfo + +# Coverage and test reports +coverage +*.lcov +junit.xml # Logs logs *.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* -# Misc +# Editor / OS .DS_Store .fleet .idea -# Local env files +# Environment .env .env.* !.env.example +#Tests +tmp-data-* + +# Backend data & config (keep examples) +data/ +!app/data/ config/* -!config/*sample* +!config/*.example.json -data/ \ No newline at end of file +.claude/settings.local.json \ No newline at end of file diff --git a/.vscode/mcp.json b/.vscode/mcp.json new file mode 100644 index 0000000..f36ea74 --- /dev/null +++ b/.vscode/mcp.json @@ -0,0 +1,12 @@ +{ + "servers": { + "nuxt": { + "type": "http", + "url": "https://nuxt.com/mcp" + }, + "nuxt-ui": { + "type": "http", + "url": "https://ui.nuxt.com/mcp" + } + } +} diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..5c9a1a3 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,81 @@ +# AGENTS.md — operating manual for AI coding agents + +You are working in the NetIgnite server: a LeiCraftMC full-stack Nuxt app (Nuxt frontend + Hono +backend in `server/`) plus the WebSocket gateway that NetIgnite agents connect to. +Follow the LeiCraftMC Style Guides: https://github.com/LeiCraftMC/Style-Guides. + +## Must read + +- [docs/00-overview.md](https://github.com/LeiCraftMC/Style-Guides/blob/main/docs/00-overview.md) +- [docs/01-project-structure.md](https://github.com/LeiCraftMC/Style-Guides/blob/main/docs/01-project-structure.md) — the full-stack Nuxt shape +- [docs/04-backend-hono.md](https://github.com/LeiCraftMC/Style-Guides/blob/main/docs/04-backend-hono.md) — Mounting Hono in Nitro +- [docs/05-api-contract.md](https://github.com/LeiCraftMC/Style-Guides/blob/main/docs/05-api-contract.md) +- [docs/06-frontend-nuxt.md](https://github.com/LeiCraftMC/Style-Guides/blob/main/docs/06-frontend-nuxt.md) +- [docs/07-state-and-data.md](https://github.com/LeiCraftMC/Style-Guides/blob/main/docs/07-state-and-data.md) + +## Non-negotiable + +- Nuxt 4 `app/` srcDir; Tailwind v4 CSS-first; no `tailwind.config.js`. Dark-only (no toggle). +- The Hono backend lives in `server/` and owns `/api` — no `Bun.serve`, no `Main.main()`; init in + `server/plugins/startup.ts`. +- Every API response uses the `{ success, code, message, data }` envelope via `APIResponse.*` + (errors omit `data`). +- Validate with `hono-openapi`'s validator (`import { validator as zValidator } from "hono-openapi"`); routes in `server/lib/api/versions/v/routes//{index.ts, model.ts}`. +- Auth uses opaque bearer tokens (`__:`, `Bun.password`-hashed) — not JWT. + See docs/10-auth.md. +- Frontend API access only through `useAPI`; global state via `AbstractStore` over `useState`; + component-local form/UI state with `reactive()`/`ref()` is fine. +- Lucide icons only (`i-lucide-*`). Never hand-edit `*.gen.ts` under `app/api-client/` — + regenerate with `bun run api-client:generate`. +- `nitro.rollupConfig.external: ["bun:sqlite"]` in `nuxt.config.ts`. WebSocket routes + (`server/routes/ws/`) require `nitro.experimental.websocket = true` (Bun preset). +- Format with Biome before finishing. Conventional Commits. + +## NetIgnite specifics + +- **Port 18232** in dev and prod — a deliberate exception to the style guide's 12xxx rule, kept from + v1 so existing compose files, proxies and agents keep working. +- **Agents** connect to `/ws/agent` (`server/routes/ws/agent.ts` → `server/lib/agents/`). The + protocol is documented in `server/lib/agents/protocol.ts`; commands are only sent if the agent's + HELLO listed the capability. Agent credentials are `netignite_agent_:`; the + secret is generated by the agent at enrollment (`server/lib/agents/auth.ts`) and never shown. +- **Devices** are owned by one user and shared per device with a role (viewer/operator/manager). + Check access only through `DevicePermissions` (`server/lib/devices/permissions.ts`). Agents are + never shared. +- Device status is polled server-side (`DeviceStatusService`) and cached; routes read the cache. +- **Transactions:** drizzle's bun-sqlite `transaction()` is synchronous and commits at the first + `await` inside its callback. Do async work (password hashing) first and keep transaction + callbacks synchronous (`.run()`, `.get()`, `.all()`) when atomicity matters. +- SQLite foreign keys are enabled in `DB.init` (`PRAGMA foreign_keys = ON`); `onDelete` rules rely + on it. +- `scripts/import-v1.ts` imports a NetIgnite v1 database (bundled into `.output/import-v1.mjs` by + `bun run build`). +- **Firmware** (`server/lib/firmware/`): `FirmwareCatalog` syncs Agent-ESP32 GitHub releases + (`release.json` + flash images, SHA-256 and optional ECDSA signature checks) into + `NETIGNITE_FIRMWARE_DIR`; no uploads. `NVSImage` writes ESP-IDF NVS v2 images byte-identical to + Espressif's `nvs_partition_gen.py` (fixtures in `tests/fixtures/nvs/`); `ProvisioningImage` only + writes the keys a release lists in `release.json` → `nvs.keys`. The firmware reads these NVS + keys, so change them together with Agent-ESP32's `src/Config.h`. +- **Web installer** (`app/pages/dashboard/agents/[agent_id]/install.vue`): imports ESP Web Tools' + prebuilt bundle `esp-web-tools/dist/web/install-button.js`; the package's ES modules need an + older `@material/web` than npm resolves. All manifest parts are `blob:` URLs, so the enrollment + token never appears in a URL. +- **OTA** (`server/lib/firmware/ota.ts`): `OTA_BEGIN` sends a path on this server; the agent + downloads `/api/v1/firmware//files/` with its own agent credential (the auth + middleware treats agent tokens as unauthenticated; only that route accepts them). Progress is + in memory; HELLO with the new version (or `info.ota_failed_version` after a rollback) ends an + update. +- **Console** (`server/lib/agents/console.ts`, `/ws/console`): the browser authenticates with its + first message (`{type:"auth", token, agent_id}`), owner only, Origin must be NETIGNITE_APP_URL; + the session is checked again every minute. +- **SSH** (`server/lib/ssh/`): logins are owner-only and write-only; secrets go through + `Secrets` (`server/lib/utils/secrets.ts`) and are only sent to the device's agent in + `SSH_CHECK`/`SSH_EXEC`. `SSH_EXEC` needs a trusted host key; changing host or port un-trusts it. + `DeviceActions.hostControl` prefers the device's host agent, then SSH. +- **Enrollment** takes the agent's `kind`; a token of another kind gets 409 and stays valid. +- Run Biome's autofix only on the files you changed: on the whole tree it rewrites `@ts-ignore` to + `@ts-expect-error`, which breaks `nuxt typecheck`. +- Tests: `tests/netignite.test.ts` (API + gateway with `tests/helpers/fake-agent.ts`), + `tests/v1-import.test.ts`, `tests/firmware.test.ts` (catalog against a fake GitHub, flash image), + `tests/nvs.test.ts`, `tests/agent-control.test.ts` (OTA, console, device control, install + script) and `tests/ssh.test.ts`. diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..3941306 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,102 @@ +# Changelog + +## 2.0.0 (2026-10-21) + +The first stable release of NetIgnite v2: the server rewritten from scratch, with new agents for +the ESP32 (firmware 2.0.0) and for Linux (Agent-Linux 1.0.0). It includes everything from +2.0.0-beta.1 and 2.0.0-beta.2 below. + +### Upgrading from v1 (0.1.x) + +- **The `latest` image is now v2.** A v1 server that pulls it starts v2 with an empty database next + to the v1 one. The v1 users, agents and devices stay in `data/db.sqlite` until you import them, + and the server log says so on every start. To stay on v1 for now, use + `ghcr.io/netignite-project/server:0.1.3`. +- Follow the [upgrade guide](https://netignite.dev/docs/server/upgrading): back up, start v2 with + the same volumes plus `NETIGNITE_APP_URL`, run `import-v1`, then set up the agents again. +- v1 agents can't connect to v2. Install the ESP32 firmware 2.0.0 once over USB with the + dashboard's web installer; replace v1 "server" agents with the Linux agent. + +### What's new since v1 + +- Wake-on-LAN through an ESP32 or Linux agent, and status checks by MAC (IPv6 link-local), IPv4, + IPv6, TCP port, SSH port or the Linux agent on the device. +- Shutdown and reboot through the Linux agent on the device (recommended), or over SSH with a login + restricted to these two commands. +- Sharing devices with other users as viewer, operator or manager. +- ESP32 web installer, firmware updates over the network with automatic rollback, and a console. +- Linux agent with a one-line installer. +- Installable as an app (PWA); REST API with API keys and an OpenAPI reference at `/api/docs/v1`. + +### Changes since 2.0.0-beta.2 + +- On start, the server warns when it finds a v1 database (`data/db.sqlite`) that hasn't been + imported, with the import command. +- The Linux agent install script installs the newest stable Agent-Linux release, and a pre-release + only while there is none (before: the newest release, pre-releases included). + +## 2.0.0-beta.2 (2026-10-21) + +### New + +- **ESP32 web installer.** An ESP32 agent's **Install** tab flashes the firmware from the browser + (ESP Web Tools; Chrome, Edge or Firefox 151+ over HTTPS) together with the agent's setup data: + the server URL, its name and a single-use enrollment token. Wi-Fi is entered in the browser and + goes to the ESP32 only. Reinstalling or changing Wi-Fi works the same way. +- **Firmware catalog.** The server downloads ESP32 firmware from the + [Agent-ESP32](https://github.com/NetIgnite-project/Agent-ESP32) GitHub releases on start and every + 6 hours and checks every file's SHA-256 (and the signature with `NETIGNITE_FIRMWARE_PUBLIC_KEY`). + Admins pick the stable or pre-release channel and can sync manually under Server Settings. +- **Firmware updates over the network** for ESP32 agents with firmware 2.0.0-beta.2 or newer, one + agent or several one after another. A new firmware that doesn't reach the server within + 5 minutes goes back to the previous one, and the dashboard shows the failed update. +- **Console** tab for ESP32 agents: live log, log level, server address, host name and IP settings + (tried before they are kept), restart and factory reset. +- **Linux agent installer:** a Linux agent's page shows + `curl -fsSL https:///install/linux.sh | sudo sh -s -- `, which downloads the + [Agent-Linux](https://github.com/NetIgnite-project/Agent-Linux) binary for the machine's CPU, + checks it against `SHA256SUMS`, pairs it and installs the systemd service. +- **SSH shutdown and reboot.** A device's **SSH** tab (owner only) stores a login: an Ed25519 key + pair the server generates (with ready-made `authorized_keys` and `sudoers` lines that allow only + shutdown and reboot) or a password. The device's agent logs in; host keys are trusted on first + use. Passwords and keys are stored encrypted and never returned by the API. The tab recommends + the Linux agent instead where it runs, asks for a dedicated user that may only shut down and + reboot, and warns about `root` and administrator logins (Windows, which only shuts down for + administrators, gets its own advice). +- **Installable as an app (PWA):** web app manifest, icons and a service worker that only shows an + offline page when the server can't be reached. + +### Changed + +- Enrollment takes the agent's kind and refuses a token of another kind (409) without using it up. +- The ESP32 setup data writes the agent's name as a valid host name ("Garage ESP32" → "Garage-ESP32"). + +### Configuration + +New, all optional: `NETIGNITE_FIRMWARE_SYNC`, `NETIGNITE_FIRMWARE_REPO`, `NETIGNITE_FIRMWARE_DIR` +(Docker: `/opt/netignite/data/firmware`), `NETIGNITE_FIRMWARE_PUBLIC_KEY`, `NETIGNITE_GITHUB_TOKEN`, +`NETIGNITE_LINUX_AGENT_REPO`, `NETIGNITE_LINUX_AGENT_VERSION`, `NETIGNITE_SECRETS_KEY` (default: a +key generated in `config/secrets.key`; back it up with the database). + +### Upgrading from 2.0.0-beta.1 + +The database migrations run on start. ESP32 agents with firmware 2.0.0-beta.1 keep working but +can't be updated over the network or log in over SSH: install firmware 2.0.0-beta.2 once with the +web installer. + +## 2.0.0-beta.1 (2026-10-20) + +The server rewritten on the LeiCraftMC full-stack Nuxt template (Nuxt 4, Hono, Drizzle on SQLite, +Bun). Not compatible with v1 agents. + +- Dashboard: overview with favorites, devices, agents, account settings, admin area. +- Agents (ESP32 firmware v2, Linux agent) enroll with a single-use token and generate their own + secret; protocol v2 over `/ws/agent`. +- Devices: Wake-on-LAN, status checks by MAC (IPv6 link-local), IPv4, IPv6, SSH port or a Linux + agent running on the device; shutdown and reboot through that agent. +- Sharing devices with other users as viewer, operator or manager. +- Users: initial admin through a password-reset link, optional public sign-up, API keys, OpenAPI + reference at `/api/docs/v1`. +- One-time import of a v1 database (`import-v1`). +- Port 18232 and the `/opt/netignite/data` and `/opt/netignite/config` volumes as in v1; the + database is `data/netignite.sqlite`. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..35e3425 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,43 @@ +# CLAUDE.md — Claude Code specifics + +Read `AGENTS.md` first. This file adds Claude-Code-specific notes. + +## Slash commands + +Defined in `.claude/settings.json`: + +- `/api-client` — regenerate the typed API client (boots the API in-process; no dev server needed). +- `/db` — run Drizzle migrations. +- `/verify` — typecheck + tests. +- `/typecheck` — `bun run typecheck` (`nuxt typecheck` + `tsc`, includes `server/`). +- `/test` — `bun test`. +- `/dev` — start/inspect the dev setup. + +## MCP servers + +`mcpServers` in `.claude/settings.json` and `.vscode/mcp.json` both register `nuxt` and `nuxt-ui`. + +## Backend in `server/` + +This is the full-stack shape: Hono lives in `server/lib/api`, mounted at `/api` by +`server/routes/api/[...].ts`, and initialized by `server/plugins/startup.ts`. After changing a +backend route, regenerate the client with `bun run api-client:generate`. Never hand-edit +`app/api-client/*.gen.ts`. + +## Frontend conventions + +- Route map and access rules: see the header of `app/middleware/auth.global.ts` and the Frontend + section of `README.md`. The template is deliberately rich; delete unused parts instead of + working around them. +- Reference components by their Nuxt auto-import names (`LayoutHeader`, `ImgAppLogo`, + `DashboardDataTable`, `FormDateRangePicker`, …). Don't add explicit imports that only the + `