First of the six planned live integrations (Proxmox, Synology, Semaphore, Tailscale, Gitea, Dockhand), reusing Sloth Manager's existing Tailscale adapter logic. Generalizes the "integrations" table already scaffolded in the foundation pass into a working config-in-UI + encrypted-credentials flow, following the same pattern as the DNS providers module — an "Add integration" form only offers types with an implemented adapter (currently just Tailscale), so the framework is ready for the next five integrations without further schema/plumbing changes. - server/src/integrations/tailscale/adapter.ts: ported from Sloth Manager's backend/src/adapters/tailscale.js — listDevices/setAuthorized/deleteDevice against the Tailscale API, now config-based (tailnet + apiKey) instead of reading process.env, and returning a ping() result instead of throwing. - server/src/routes/integrations.ts: generic integration CRUD (admin) + a "test connection" endpoint, plus Tailscale-specific device routes (dashboard-and-basic-actions depth per the plan: authorize/deauthorize/ remove, gated to operator+, audit-logged). - web: an Integrations page (provider-style manage/browse split, matching the DNS page's UX) with a device table, and a live Tailscale widget on the Dashboard. Bug found and fixed while testing: hitting the Tailscale device routes on a non-Tailscale integration row crashed the ENTIRE server process, not just that request — the generic adapter registry throws for unimplemented types, and that throw happened inside an async handler with no surrounding try/catch, which Express 4 doesn't catch, so it became an unhandled rejection that (on modern Node) kills the process. Fixed at the source (check the row's type before ever constructing an adapter) and, since the same "a helper throws before any local try/catch runs" shape existed wherever a route calls into loadDnsProviderConfig/loadIntegrationConfig (both call decryptSecret, which throws if CREDENTIALS_ENCRYPTION_KEY is ever wrong/missing after data was already encrypted with a different key), added a small asyncHandler() wrapper and applied it to every route handler across every router — a single bad request should never be able to take the whole app down for every user. Verified: full build passes. Fresh HTTP-layer tests against a running server (17 checks) cover not-implemented-type rejection, missing-field validation, a real network call to api.tailscale.com with a bogus key (clean ok:false, not a crash), role gating at every tier, credential non-leakage, disabled-integration blocking, and the wrong_type case that originally crashed the server — confirmed it now returns 400 cleanly and the server stays up. Re-ran the existing DNS (13 checks) and Servers/Tasks suites afterward to confirm the asyncHandler sweep didn't regress anything — all passing. Authorizing/removing a real device still needs a real Tailscale API key to verify end-to-end. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Homelab Manager
A single dashboard for a homelab: Proxmox, Synology DSM, Semaphore, Tailscale, Gitea, and Dockhand/Docker status and basic actions, plus DNS record management, an IP address inventory (IPAM), and a secret-expiry tracker (ported from Sloth Manager) and scheduled-task tracking across Debian/Raspbian hosts (ported from Schedule Task Manager). Looks and feels like a Tabler admin dashboard. Sign-in is delegated to Authentik (OIDC), with local admin/operator/viewer roles.
Status
Built so far:
- Monorepo scaffold, Tabler-themed app shell/navigation
- Authentik OIDC login, roles (first user to sign in becomes admin), audit log
- Secrets — expiry tracking for API tokens/certs/passwords
- IP Addresses (IPAM) — inventory of IPs across vendors/locations
- DNS — zone/record management across Cloudflare, Loopia, Pi-hole, Azure DNS, cPanel, and Technitium; providers are configured in-app (not via env vars) and their credentials are encrypted at rest
- Servers & Tasks — cron/systemd tracking across Debian/Raspbian servers
via a lightweight push agent (
agent/linux/), plus manual entries for things an agent can't see (Docker jobs, backups)
Not yet built (see .claude/plans for the full delivery plan):
- Live integrations: Proxmox, Synology, Semaphore, Tailscale, Gitea, Dockhand
Requirements
- Node.js 20+
- An Authentik instance reachable from wherever this app runs
1. Set up an Authentik application
- Create an OAuth2/OpenID Provider:
- Redirect URI:
<APP_BASE_URL>/auth/callback - Scopes:
openid,email,profile
- Redirect URI:
- Create an Application using that provider, and assign the users/groups who should be able to sign in — Authentik controls who can authenticate; the app's own admin/operator/viewer roles control what they can do once in.
- Copy the provider's issuer URL, client ID, and client secret into
.env.
2. Local development
cp .env.example .env # fill in AUTHENTIK_*, SESSION_SECRET, CREDENTIALS_ENCRYPTION_KEY
npm install
npm run dev:server # http://localhost:3000 (API)
npm run dev:web # http://localhost:5173 (Vite dev server, proxies /api and /auth to :3000)
Visit http://localhost:5173 during development. Database migrations run
automatically on server start. SQLite data lands in ./data (gitignored).
Generate SESSION_SECRET and CREDENTIALS_ENCRYPTION_KEY with:
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
3. Run with Docker
cp .env.example .env
# edit .env
docker compose -f docker-compose.dev.yml up -d --build # build locally
# or, once an image is published to your registry:
docker compose up -d
The app listens on HOST_PORT (default 3000); SQLite data persists in
./data on the host.