A self-hosted homelab machine provisioning and documentation tracker. Every time you spin up a new VM, LXC container, or physical machine (Raspberry Pi, mini PC, NAS, server…), Task Central gives you a consistent setup checklist, a place to record everything about the machine, and a one-click Obsidian-ready Markdown document for your vault.
- Machine records for VMs, LXC containers, and physical machines with identity, network, OS, and hardware fields (type-aware: VMID for VM/LXC, inventory fields for physical).
- Generated setup checklists - creating a machine copies the applicable task templates (UniFi, Pi-hole DNS, Uptime Kuma, Homarr, Caddy, backups, validation, …) into independent per-machine tasks with statuses: Pending, In Progress, Completed, Blocked (reason required), Not Applicable (excluded from progress).
- Task Templates page - manage the default checklist per machine type; "Apply new defaults" adds missing template tasks to an existing machine with a preview and no duplicates.
- Services & dependencies - record what runs on each machine (name/port/protocol/URL) and what it depends on (other tracked machines or external systems), with reverse-dependency view.
- Markdown notes with preview, per machine and per task.
- Obsidian document generation - per-type Jinja templates (sandboxed), rendered with live
machine data; copy to clipboard, download as
.md, versioned snapshots, restore/view history. Task Central never writes to your vault - the workflow is copy/paste or download by design. - Document freshness alerts - Overview, Services, Dependencies, and Notes changes flag the affected machine on its header and in Dashboard Needs Attention until its document is regenerated.
- Dashboard - summary cards, recent machines with progress, "needs attention" list.
- History - audit-style activity log per machine.
- Data management - full JSON export/import for every machine type (validated, with a record-count summary and specific validation errors), SQLite backup download, restore default templates, and a confirmed factory reset that deletes all data and credentials before returning the application to first-run setup.
- Powerful machine list - search, filters (type/host/tag/archived), sorting, pagination, duplicate/archive/delete with confirmations. Soft-delete via archive; hard delete only from the archive view.
- First-run setup wizard - creates the login username and password and configures general preferences, Telegram, and optional local AI before the first login.
- Machine reachability - machine detail headers ping the stored IP from the backend and show Online or Offline with manual refresh, hover details, and periodic checks.
- Version-aware changelog - after an update, the Dashboard shows the current release notes once automatically; the sidebar Changelog button reopens them at any time.
| Layer | Choice |
|---|---|
| Backend | Python 3.12 · FastAPI · SQLAlchemy 2 · Alembic · Pydantic v2 |
| Templates | Jinja2 (SandboxedEnvironment) |
| Database | SQLite (default) - PostgreSQL-ready via DATABASE_URL |
| Frontend | React 18 · TypeScript · Vite · Tailwind CSS v4 |
| Data/forms | TanStack Query · React Hook Form · Zod |
| Icons | Lucide |
| Tests | pytest (backend) · Vitest (frontend) |
| Deploy | Docker Compose (nginx serving the SPA + proxying /api) |
/taskcentral
├── backend/
│ ├── app/
│ │ ├── models/ # SQLAlchemy models
│ │ ├── schemas/ # Pydantic schemas
│ │ ├── routers/ # API endpoints (/api/v1)
│ │ ├── services/ # seed data, checklist logic, Jinja rendering, defaults
│ │ ├── config.py # environment-driven settings
│ │ ├── database.py # engine/session
│ │ └── main.py # FastAPI app
│ ├── alembic/ # migrations
│ ├── tests/ # pytest suite
│ ├── requirements.txt
│ └── Dockerfile
├── frontend/
│ ├── src/
│ │ ├── components/ # UI primitives, layout, machine components
│ │ ├── pages/ # Dashboard, Machines, wizard, detail tabs, templates, settings
│ │ └── lib/ # API client, TanStack Query hooks, types, utils
│ ├── nginx.conf # production proxy config
│ ├── package.json
│ └── Dockerfile
├── data/ # SQLite database (persistent volume)
├── release/ # End-user Compose bundle and lifecycle scripts
├── CHANGELOG.md # Versioned release notes shown inside the application
├── .github/workflows/ # CI and multi-architecture release publishing
├── VERSION # Current application release
├── docker-compose.yml
├── .env.example
└── README.md
Make sure the following are installed:
- Docker Engine
- Docker Compose v2
- curl
- tar
mkdir taskcentral
cd taskcentral
taskcentral_version=$(curl -fsSL https://github.com/lurry2020/taskcentral/releases/latest/download/VERSION)
curl -fLO "https://github.com/lurry2020/taskcentral/releases/download/v${taskcentral_version}/taskcentral-${taskcentral_version}.tar.gz"
tar -xzf "taskcentral-${taskcentral_version}.tar.gz"
cd "taskcentral-${taskcentral_version}"
./install.shThe installer asks for the port (default 8484), generates a unique setup access code and fallback
password, creates persistent data/ and backups/ directories, pulls pinned images, starts the
application, and waits for health checks. Keep the printed setup access code, open the printed URL,
and enter that code on the one-time /setup wizard.
Release images support Linux amd64 and arm64. Users do not need Git, Python, Node.js, or a
local compiler.
./update.sh # newest release
./update.sh 1.2.3 # specific release
./backup.sh # consistent online SQLite backup
./restore.sh taskcentral-20260101-120000.db # confirmed restore
./uninstall.sh # preserve data and configuration./update.sh also downloads and verifies the new release bundle, so Compose files, management
scripts, documentation, and container images update together. It creates a database backup before
downtime and verifies both health checks. A failed update automatically restores the previous
release files, version, and database.
cd taskcentral/taskcentral-{version}
#example: cd taskcentral/taskcentral-1.1.2
./update.shAfter a successful update, the Dashboard presents the current version's changelog once. Already-open tabs detect the new running version and display Reload Task Central. HTML entry responses are not cached, so a normal reload is sufficient; a hard refresh is not required.
See the README inside the release bundle for full operational instructions.
Developers and source-based deployments can still build locally:
cd /taskcentral
cp .env.example .env
setup_access_token=$(od -An -N32 -tx1 /dev/urandom | tr -d ' \n')
sed -i "s/^SETUP_ACCESS_TOKEN=.*/SETUP_ACCESS_TOKEN=$setup_access_token/" .env
unset setup_access_token
docker compose up -d --buildOpen http://localhost:8484. Change the port with APP_PORT in .env.
- The SQLite database lives in
./data/taskcentral.db(bind-mounted volume). - Migrations run automatically when the backend container starts.
- Default task templates, Obsidian templates, and settings are seeded on first start.
- Set
DEMO_MODE=truebefore the first start to seed a few sample machines (tagged#sample-data; remove them later via Settings → Clear sample data).
Backend (Python 3.11+):
cd backend
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements-dev.txt
alembic upgrade head # creates ../data/taskcentral.db
uvicorn app.main:app --reload # http://localhost:8000, docs at /docsFrontend:
cd frontend
npm install
npm run dev # http://localhost:5173, proxies /api to :8000cd backend && .venv/bin/python -m pytest tests # backend tests
cd frontend && npm test # vitest
cd frontend && npm run typecheck # tsc
cd frontend && npm run build # production build| Variable | Default | Description |
|---|---|---|
APP_NAME |
Task Central |
Display name (also editable in Settings) |
APP_ENV |
production |
Environment label |
APP_PORT |
8484 |
Host port for the web UI |
TASKCENTRAL_VERSION |
1.1.6 |
Installed application release |
TASKCENTRAL_RELEASE_REPOSITORY |
lurry2020/taskcentral |
Repository used to check for releases |
DATABASE_URL |
sqlite:////data/taskcentral.db |
SQLAlchemy connection string |
CORS_ORIGINS |
http://localhost:8484 |
Comma-separated allowed origins |
LOG_LEVEL |
INFO |
Backend log level |
LOG_MAX_BYTES |
5242880 |
Size at which each local log file rotates |
LOG_BACKUP_COUNT |
5 |
Number of older local log files retained |
SETUP_ACCESS_TOKEN |
None; required in production | Protects incomplete first-run setup endpoints |
SESSION_COOKIE_SECURE |
false |
Set true only when Task Central always uses HTTPS |
DATA_DIR |
/data |
Data directory inside the backend container |
DEMO_MODE |
false |
Seed sample machines on first start |
Migrations are managed with Alembic and run automatically on container start. Manual usage:
cd backend
.venv/bin/alembic upgrade head # apply
.venv/bin/alembic revision --autogenerate -m "..." # create after model changes
.venv/bin/alembic downgrade -1 # roll back one revision- Add
psycopg[binary]tobackend/requirements.txtand rebuild. - Set
DATABASE_URL=postgresql+psycopg://user:pass@host:5432/taskcentral. - Start the stack - Alembic creates the schema on the empty database.
- Move data by exporting JSON from the SQLite instance (Settings → Export) and importing it into the new instance.
Backup (any of):
- Settings → Download database backup (SQLite file), or
- Settings → Export all data (JSON) (portable across database engines), or
- Stop the stack and copy
./data/taskcentral.db.
Restore:
- JSON: Settings → Import from JSON… - the file is validated first, you see a summary, and the import replaces all current data.
- SQLite file: stop the stack, replace
./data/taskcentral.db, start again.
Factory reset:
- Settings → Data Management → Reset Application permanently deletes all application data,
settings, and login credentials, restores factory defaults, signs out, and opens
/setup. This cannot be undone; download a database backup first.
Task Central requires a login. It's a single user:
- On a new database,
/setuprequires a username and a password of at least 6 characters, then stores the username and salted PBKDF2 password hash. Setup also collects timezone/date format and optionally Telegram and local-AI settings. Skipped integrations can be configured later. - The setup-selected username and password override the
AUTH_USERNAME/AUTH_PASSWORDfallbacks. The username is matched case-insensitively; the password is case-sensitive. - Logging in creates a random opaque session stored in the database. The browser receives only an
HttpOnly,SameSite=Strictcookie, valid forAUTH_TOKEN_TTL_HOURS(seven days by default). The credential is not exposed to frontend JavaScript or stored in browser local storage. - Signing out revokes the current session on the server. Changing the username or password and resetting the application revoke every session immediately.
- Health, login, and setup status are public. First-run setup mutations require the unique setup access code while setup is incomplete and stop working after setup completes.
- Sign out from the sidebar footer (or the header on mobile).
Existing databases are automatically marked as already configured during upgrade. They do not
show /setup, and their password and application preferences are not overwritten.
If you forget the password (and have host access), reset it with:
docker compose exec backend python -m app.cli setpassword
# or non-interactively:
docker compose exec backend python -m app.cli setpassword 'NewPassw0rd!'
# revert to the AUTH_PASSWORD env value:
docker compose exec backend python -m app.cli setpassword --resetThe new password is stored (salted PBKDF2 hash) in the database and takes effect immediately; no
restart is needed. It overrides AUTH_PASSWORD until you --reset. The hash is never included in
JSON data exports. Both forms preserve the username selected during setup, and both revoke all
existing sessions. --reset clears only the stored password override.
- Login is enabled (see above), but keep the instance on a private homelab network behind your
firewall/VPN or an authenticating reverse proxy, and add HTTPS if you ever expose it. Set
SESSION_COOKIE_SECURE=trueonly after HTTPS is guaranteed end to end. - Production startup fails when
SETUP_ACCESS_TOKENis missing or weak. Release installers and updaters generate it automatically; source deployments must generate it in.env. - Local AI URLs must resolve entirely to loopback, RFC 1918 private IPv4, or IPv6 ULA addresses. Link-local, metadata, public, and ambiguous numeric address forms are blocked, redirects are not followed, and each connection is pinned to the validated address.
- nginx sends a restrictive Content Security Policy and standard browser hardening headers.
- Obsidian templates render in a sandboxed Jinja environment - no arbitrary Python.
- Markdown is sanitized (DOMPurify) before rendering in the UI.
- Generated filenames are sanitized against path traversal; import size is limited (20 MB).
Each machine type (VM / LXC / Physical) has one template, editable under Obsidian
Templates. Generating a document on a machine's Obsidian tab renders the template with
that machine's current data and stores a snapshot (versioned; old versions stay viewable).
Copy the result to your clipboard or download it as machine-name.md and drop it into your
vault - Task Central intentionally never writes to the vault itself.
Template syntax is Jinja:
{% if machine.vmid %}
VMID: {{ machine.vmid }}
{% endif %}
{% for service in services %}
- {{ service.name }}{% if service.port %}: {{ service.port }}{% endif %}
{% endfor %}machine.*: name, machine_type, host, vmid, ip_address, mac_address,
dns_record, operating_system, operating_system_version, architecture, status,
purpose, tags (list), cpu, cpu_cores, memory ("8 GB"), disk, storage_location,
gpu, network_interface, hardware_model, serial_number, asset_tag, location,
owner, created_at, deployment_date, notes (machine notes joined as Markdown).
Lists: services (name, description, port, protocol, url, is_external, notes),
dependencies (name, dependency_type, notes, is_external), checklist (title, status,
category, required, notes, completed_at), completed_tasks, pending_tasks. Plus now.
Settings control whether the checklist / completed tasks / not-applicable tasks are included. The full list with descriptions (and copy buttons) is shown next to the template editor.
Task templates define the default checklist. Each has a machine-type scope (All / VM / LXC / Physical), category, required flag, enabled flag, and sort order. When a machine is created, all enabled templates matching its type are copied into independent machine tasks - editing or deleting a template later never changes existing machines. To pull new defaults into an existing machine, use Apply new defaults on its checklist tab: it previews exactly which tasks would be added and skips anything already present.
Task Central keeps persistent, host-readable diagnostics in the project directory:
tail -n 200 logs/taskcentral.log
tail -n 200 logs/frontend.logtaskcentral.log records backend startup, application errors, integration failures, and failed
API requests. frontend.log records nginx and reverse-proxy errors. Logs rotate at 5 MiB by
default and keep five older numbered files. Change LOG_MAX_BYTES or LOG_BACKUP_COUNT in
.env if needed. Task Central does not intentionally log passwords, API keys, authentication
headers, chat prompts, or model response bodies.
| Symptom | Fix |
|---|---|
| Port 8484 already in use | Set APP_PORT in .env and docker compose up -d |
502 from the frontend |
Check logs/frontend.log, then logs/taskcentral.log |
| Database locked errors | SQLite dislikes concurrent writers; this app is single-user - check for stray processes |
| Import rejected | The JSON must come from Settings → Export ("format": "taskcentral-export") |
| Template error when generating | The template failed to render - the error message names the line; Reset to default helps |
| Frontend dev server API errors | Ensure uvicorn runs on :8000, or set VITE_API_PROXY |
The full REST API is served under /api/v1 with interactive OpenAPI docs at
http://localhost:8484/api/docs in Docker (or http://localhost:8000/docs in dev).
Health check: GET /api/v1/health.