Expand the minimal README with sections for features, local run, tests, Docker deploy via the Gitea registry, the Gitea Actions workflow, full API table (now including interfaces and lookup-by-hardware-id), stack choices, and project layout. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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).
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
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:
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:
docker compose pull && docker compose up -d
CI
.gitea/workflows/build.yml runs on every push to main:
- Install deps and run the test suite.
- If green, log in to
gitea.thewrightserver.netusing theREGISTRY_TOKENrepo secret. - Build the image and push two tags:
:latestand:<commit-sha>.
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— nobetter-sqlite3, novitest - 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