Josh Wright bcd934f5b1
All checks were successful
CI / test (pull_request) Successful in 12s
CI / build-dev (pull_request) Has been skipped
Merge branch 'dev' into feat/timezone-settings
2026-03-28 15:10:26 -04:00
2026-03-28 02:35:00 -04:00
2026-03-28 02:35:00 -04:00
2026-03-28 02:35:00 -04:00
2026-03-28 02:35:00 -04:00
2026-03-28 13:59:23 -04:00
2026-03-28 02:35:00 -04:00

Catalyst

A self-hosted infrastructure registry for homelab Proxmox environments. Track virtual machines across stacks, monitor service health, and maintain a full audit log of every configuration change.


Features

  • Dashboard — filterable, searchable instance list with state and stack badges
  • Detail pages — per-instance view with service flags, Tailscale IP, and a full change timeline
  • Audit log — every field change is recorded with before/after values and a timestamp
  • Full CRUD — add, edit, and delete instances via a clean modal interface
  • Production safeguard — only development instances can be deleted; production instances must be demoted first
  • Export / import — JSON backup and restore via the settings modal
  • REST API — every operation is a plain HTTP call
  • Persistent storage — SQLite on a Docker named volume; survives restarts and upgrades
  • Zero native dependencies — SQLite via Node's built-in node:sqlite; no compilation, no binaries

Quick start

docker compose up -d

Open http://localhost:3000.

Environment variables

Variable Default Description
PORT 3000 HTTP port the server binds to
DB_PATH data/catalyst.db Path to the SQLite database file

REST API

All endpoints are under /api. Request and response bodies are JSON.

Instances

GET /api/instances

Returns all instances sorted by name. All query parameters are optional.

Parameter Type Description
search string Partial match on name, vmid, or stack
state string Exact match: deployed, testing, degraded
stack string Exact match: production, development
GET /api/instances?search=plex&state=deployed
[
  {
    "vmid": 117,
    "name": "plex",
    "state": "deployed",
    "stack": "production",
    "tailscale_ip": "100.64.0.1",
    "atlas": 1, "argus": 1, "semaphore": 0,
    "patchmon": 1, "tailscale": 1, "andromeda": 0,
    "hardware_acceleration": 1,
    "created_at": "2024-01-15T10:30:00",
    "updated_at": "2024-03-10T14:22:00"
  }
]

GET /api/instances/stacks

Returns a sorted array of distinct stack names present in the registry.

GET /api/instances/stacks
→ ["development", "production"]

GET /api/instances/:vmid

Returns a single instance by VMID.

Status Condition
200 Instance found
400 VMID is not a valid integer
404 No instance with that VMID

GET /api/instances/:vmid/history

Returns the audit log for an instance — newest events first.

Status Condition
200 History returned (may be empty array)
400 VMID is not a valid integer
404 No instance with that VMID
[
  {
    "id": 3,
    "vmid": 117,
    "field": "state",
    "old_value": "testing",
    "new_value": "deployed",
    "changed_at": "2024-03-10T14:22:00"
  },
  {
    "id": 1,
    "vmid": 117,
    "field": "created",
    "old_value": null,
    "new_value": null,
    "changed_at": "2024-01-15T10:30:00"
  }
]

POST /api/instances

Creates a new instance. Returns the created record.

Status Condition
201 Created successfully
400 Validation error — see errors array in response
409 VMID already exists

Request body:

Field Type Required Notes
name string yes
vmid integer yes Must be > 0 and unique
state string yes deployed, testing, or degraded
stack string yes production or development
tailscale_ip string no Valid IPv4 or empty string
atlas 0|1 no
argus 0|1 no
semaphore 0|1 no
patchmon 0|1 no
tailscale 0|1 no
andromeda 0|1 no
hardware_acceleration 0|1 no

PUT /api/instances/:vmid

Replaces all fields on an existing instance. Accepts the same body shape as POST. The vmid in the body may differ from the URL — this is how you change a VMID.

Status Condition
200 Updated successfully
400 Validation error
404 No instance with that VMID
409 New VMID conflicts with an existing instance

DELETE /api/instances/:vmid

Deletes an instance. Only instances on the development stack may be deleted.

Status Condition
204 Deleted successfully
400 VMID is not a valid integer
404 No instance with that VMID
422 Instance is on the production stack

Backup

GET /api/export

Downloads a JSON backup of all instances as a file attachment.

{
  "version": 1,
  "exported_at": "2024-03-10T14:22:00.000Z",
  "instances": [ ... ]
}

POST /api/import

Replaces all instances from a JSON backup. Validates every row before committing — if any row is invalid the entire import is rejected.

Status Condition
200 Import successful — returns { "imported": N }
400 Body missing instances array, or validation errors

Development

npm install
npm test            # run all tests once
npm run test:watch  # watch mode
npm start           # start the server on :3000

Tests are split across three files:

File What it covers
tests/db.test.js SQLite data layer — CRUD, constraints, filters, history logging
tests/api.test.js HTTP API — all endpoints, status codes, error cases
tests/helpers.test.js UI helpers — esc() XSS contract, date formatting, history formatters

Versioning

Catalyst uses semantic versioning. package.json is the single source of truth.

Change Bump
Bug fix patch
New feature, backward compatible minor
Breaking change major

Pushing a tag triggers the CI pipeline: test → build → release. Docker images are tagged :x.y.z, :x.y, and :latest.

Description
A lightweight instance registry for tracking self-hosted infrastructure
Readme 700 KiB
2026-03-28 21:01:27 -04:00
Languages
JavaScript 80%
CSS 13.4%
HTML 6.3%
Dockerfile 0.3%