← projects

homebase

A process supervisor and dashboard for the apps I self-host

GoWindowsTailscalentfy

My desktop hosts my own apps, like Job Tracker and Sentinel, and I reach them from anywhere over Tailscale. homebase is what keeps them running: it starts every app at boot, restarts anything that crashes, health-checks each one, and gives me a single dashboard with live logs and start, stop and restart buttons.

Adding a project is one entry in a JSON file. When I push changes to GitHub, one command pulls, rebuilds and redeploys an app on the desktop, or homebase itself. If something breaks, my phone gets a notification.

In Action

The homebase dashboard on my desktop showing Job Tracker and Sentinel running, with uptime, response time, memory, restarts and controls

The real dashboard on my desktop: every app's state, response time, memory, restart count and last exit, with controls and live logs.

Under the Hood

0

third-party dependencies

~1.9k

lines of Go

36

test functions

2

platforms tested: Windows, macOS

How it works

  • —One goroutine per app owns that app's process. Buttons and health checks send it requests over a channel instead of touching the process, so two restarts can never race to launch duplicate copies
  • —Each app is a small state machine: stopped, starting, running, stopping, restarting after a crash, crashed, or external
  • —External means something is already answering on the app's health URL, for example the old startup task. homebase reports it instead of starting a second copy that would fail on a port conflict
  • —The dashboard can only start and stop apps listed in the config. There's no API for running arbitrary commands, and the buttons require a custom header, so another website can't press them for you
  • —Secrets live in per-app env files outside the repo; the config itself is committed to git
{
  "name": "jobtracker",
  "command": "JobTracker.exe",
  "dir": "C:\\Users\\Admin\\apps\\jobtracker",
  "env_file": "C:\\Users\\Admin\\apps\\jobtracker\\jobtracker.env",
  "health": "http://127.0.0.1:5206/",
  "autostart": true,
  "restart_when_unhealthy": true,
  "source": {
    "repo_dir": "C:\\Users\\Admin\\projects\\job-tracker",
    "build": ["dotnet", "publish", "src\\JobTracker", "-c", "Release", "-o", "..."]
  }
}

Decisions that took more than one try

Apps outliving their supervisor

If homebase itself was force-killed, say by Task Scheduler or Task Manager, its apps kept running as orphans with no one watching them. The fix is a Windows Job Object: every app joins one job that's set to close with homebase, so Windows ends them automatically however homebase exits. I verified it on the desktop by force-killing homebase and checking that its app died with it.

The test suite runs real processes, on Windows too

The supervisor tests don't mock anything. The test binary re-runs itself as a tiny app that serves HTTP, crashes, or returns 500s, and the tests start, crash and kill it. I cross-compiled the same suite for Windows and ran it on the desktop, which is how I caught that Go refuses to run a program by relative path there.

Probing a site can get you banned by it

Sentinel, which homebase runs, used to probe for exposed files like /.env. On Parliament those paths are honeypots that ban the visitor's IP, so the first run banned my own IP address. Now any path a site treats as a trap goes in a skip list.

Deploys that can't take the apps down

Windows won't overwrite a running .exe, so an update stops the app through homebase, rebuilds, and starts it again, and a failed build brings the old version back up. homebase updates itself the same way: build the new exe, check it against the new config, and only then swap it in, keeping the previous version for a one-step rollback.