# Infrastructure A small internal tool for tracking servers. Look up a host by **hardware ID**, **hostname**, or **asset ID** and see where it lives (site / room / position), what kind of server it is, and how it's wired into the network. Built as a single Node process that serves a vanilla-JS UI and a JSON API from one port. SQLite for storage. No auth — runs on a trusted internal network. ## Features - One search box, three searchable fields. Single match jumps straight to the host page. - Per-host **network interface** tracking — name, MAC, IPv4, CIDR, link/duplex — with format-validating regex. - Lookup tables for **sites**, **rooms**, and **server types**, managed inline from the UI. - Same JSON API for browsers and scripts; schemas validated in, serialized out. ## Run locally Requires Node 22+ (uses the built-in `node:sqlite` and `node:test`). ```sh npm install npm start ``` UI: `http://localhost:3000` · API: `http://localhost:3000/api` First boot seeds one site, one room, and a handful of server types so the dropdowns aren't empty. ## Test ```sh npm test ``` 21 tests run against an in-process Fastify instance backed by an in-memory SQLite database. ## Deploy Compose pulls the prebuilt image from the Gitea container registry: ```sh docker compose up -d ``` This pulls `gitea.thewrightserver.net/josh/infrastructure:latest` and mounts a named volume at `/app/data` for the SQLite file. To grab a new build: ```sh docker compose pull && docker compose up -d ``` ### CI [`.gitea/workflows/build.yml`](.gitea/workflows/build.yml) runs on every push to `main`: 1. Install deps and run the test suite. 2. If green, log in to `gitea.thewrightserver.net` using the `REGISTRY_TOKEN` repo secret. 3. Build the image and push two tags: `:latest` and `:`. Set `REGISTRY_TOKEN` under **Repo → Settings → Actions → Secrets** to a Personal Access Token with `write:package` scope. ## API | Method | Path | Notes | |---|---|---| | `GET` | `/api/hosts?q=…` | Substring search across `hardware_id`, `hostname`, `asset_id`. Capped at 200. | | `GET` | `/api/hosts/:id` | Fetch one by numeric ID. | | `GET` | `/api/hosts/by-hardware-id/:hwid` | Fetch by hardware ID — what the detail page uses. | | `POST` | `/api/hosts` | Create. | | `PUT` | `/api/hosts/:id` | Replace. | | `DELETE` | `/api/hosts/:id` | Hard delete (cascades to the host's interfaces). | | `GET` | `/api/interfaces?host_id=N` | List interfaces for a host. | | `GET` | `/api/interfaces/:id` | Fetch one. | | `POST` | `/api/interfaces` | Create. Required: `host_id`, `name`. Optional: `mac_address`, `ip_address`, `subnet`, `link_speed`. | | `PUT` | `/api/interfaces/:id` | Replace. | | `DELETE` | `/api/interfaces/:id` | Hard delete. | | `*` | `/api/sites[/:id]` | Site CRUD. | | `*` | `/api/rooms[/:id]` | Room CRUD (`?site_id=` filter on GET). | | `*` | `/api/server-types[/:id]` | Server-type CRUD. | Errors are uniform: `{ error, details? }` with status `400` / `404` / `409` / `500`. ## Stack - **Node 22** + `node:sqlite` + `node:test` — no `better-sqlite3`, no `vitest` - **Fastify 5** with built-in JSON Schema validation - **Vanilla HTML / CSS / JS** — no framework, no bundler ## Layout ``` src/ server.js Fastify bootstrap, /api mount, SPA fallback db.js schema, prepared statements, query API schemas.js JSON Schema definitions sqlite-errors.js maps SQLite constraint violations to 400/409 routes/ hosts, interfaces, sites, rooms, server-types public/ index.html app.css app.js tests/ *.test.js one suite per resource ```