josh 5881383689
All checks were successful
Build / test (push) Successful in 9m29s
Build / build (push) Successful in 22s
Build / release (push) Successful in 2s
version bump
2026-03-28 01:08:00 -04:00
2026-03-27 23:51:03 -04:00
2026-03-28 01:08:00 -04:00
2026-03-27 22:57:13 -04:00
2026-03-27 22:57:13 -04:00
2026-03-27 23:51:03 -04:00
2026-03-28 00:45:02 -04:00
2026-03-27 23:51:03 -04:00
2026-03-27 23:11:28 -04:00
2026-03-27 22:57:13 -04:00
2026-03-27 23:11:28 -04:00
2026-03-27 23:51:03 -04:00
2026-03-28 01:08:00 -04:00
2026-03-28 01:08:00 -04:00
2026-03-27 23:51:03 -04:00

Catalyst

A lightweight instance registry for tracking self-hosted infrastructure. No backend, no framework — just a browser, a SQLite database compiled to WebAssembly, and a static file server. :)

Structure

index.html        Entry point
css/app.css       Styles
js/
  config.js       Service definitions and seed data
  db.js           Data layer
  ui.js           Rendering, modals, notifications
  app.js          Router

Data layer

All reads and writes go through five functions in js/db.js. This is the boundary that would be replaced when wiring Catalyst to a real backend — nothing else in the codebase touches data directly.

getInstances(filters?)

Returns an array of instances, sorted by name. All filters are optional.

getInstances()
getInstances({ search: 'plex' })
getInstances({ state: 'degraded' })
getInstances({ stack: 'production' })
getInstances({ search: 'home', state: 'deployed', stack: 'production' })

search matches against name, vmid, and stack.

getInstance(vmid)

Returns a single instance by VMID, or null if not found.

getInstance(137) // → { id, name, vmid, state, stack, ...services, createdAt, updatedAt }

getDistinctStacks()

Returns a sorted array of unique stack names present in the registry. Used to populate the stack filter dynamically.

getDistinctStacks() // → ['development', 'production']

createInstance(data)

Inserts a new instance. Returns { ok: true } on success or { ok: false, error } on failure (e.g. duplicate VMID).

createInstance({
  name: 'plex',
  vmid: 117,
  state: 'deployed',        // 'deployed' | 'testing' | 'degraded'
  stack: 'production',
  tailscale_ip: '100.64.0.1',
  atlas: 1,
  argus: 1,
  semaphore: 0,
  patchmon: 1,
  tailscale: 1,
  andromeda: 0,
  hardware_acceleration: 1,
})

updateInstance(id, data)

Updates an existing instance by internal id. Accepts the same shape as createInstance. Returns { ok: true } or { ok: false, error }.

deleteInstance(id)

Deletes an instance by internal id. Only instances on the development stack can be deleted — this is enforced in the UI before deleteInstance is ever called.


Instance shape

Field Type Notes
id integer Internal autoincrement ID
vmid integer Unique. Used as the public identifier and in URLs (/instance/137)
name string Display name
state string deployed, testing, or degraded
stack string production or development
tailscale_ip string Optional
atlas 0 | 1
argus 0 | 1
semaphore 0 | 1
patchmon 0 | 1
tailscale 0 | 1
andromeda 0 | 1
hardware_acceleration 0 | 1
createdAt ISO string Set on insert
updatedAt ISO string Updated on every write

Versioning

Catalyst uses semantic versioning. The version in package.json is the source of truth and must match the release tag.

Change Bump Example
Bug fix patch 1.0.01.0.1
New feature, backward compatible minor 1.0.01.1.0
Breaking change major 1.0.02.0.0

Cutting a release

1. Bump the version in package.json

"version": "1.1.0"

2. Commit, tag, and push

git add package.json
git commit -m "chore: release v1.1.0"
git tag v1.1.0
git push && git push --tags

Pushing the tag triggers the full pipeline: tests → build → release.

  • The image is tagged :1.1.0, :1.1, and :latest in the Gitea registry
  • A Gitea release is created at v1.1.0 with the image reference in the release notes

Pushes to main without a tag still run tests and build a :latest image — no release is created.

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%