Mac mini cluster management system for browser-controlled macOS VMs.
- Frontend (React + TypeScript): browser console for inventory, terminal, and remote desktop.
- Server (Go): control plane that serves the frontend, validates Cloudflare Access JWTs, and brokers proxy sessions.
- Worker (Swift): daemon that runs on each Mac mini host, manages Tart VMs, and relays VNC/SSH traffic through the server.
┌─────────────────────┐
│ Server (Go) │
│ Linux / Public │
│ │
│ Web UI (:5496) │
│ REST API │
│ Postgres │
│ Proxy relay │
└──────────┬──────────┘
│ HTTPS / WebSocket via Cloudflare
┌──────────────┼──────────────┐
│ │ │
┌────────▼───┐ ┌──────▼─────┐ ┌─────▼──────┐
│ Mac mini 1 │ │ Mac mini 2 │ │ Mac mini N │
│ Worker │ │ Worker │ │ Worker │
│ ┌──┐┌──┐ │ │ ┌──┐┌──┐ │ │ ┌──┐┌──┐ │
│ │VM││VM│ │ │ └──┘└──┘ │ │ └──┘└──┘ │
│ └──┘└──┘ │ └────────────┘ └────────────┘
└────────────┘
Install frontend dependencies and build everything:
make frontend-install
make all
make testCreate local configuration files from the examples:
cp server-config.example.yaml server-config.yaml
cp deploy/config.example.yaml deploy/config.yaml
cp install/config.example.yaml install/config.yaml
cp .env.example .envGenerate production tokens before deploying:
openssl rand -hex 32 # admin_token
openssl rand -hex 32 # enrollment_tokenFor a Docker Compose deployment, fill in deploy/config.yaml and .env, then run:
docker compose up --build -dFor a local server build:
cd frontend && pnpm install && pnpm build
cd ../server
go build -o minicontrol-server ./cmd/minicontrol-server
./minicontrol-server --config ../server-config.yamlOn a Mac mini host, put the enrollment token in a local file and run the installer from your worker-facing hostname:
printf '%s\n' '<enrollment_token>' > ~/mini-control-enroll.key
chmod 600 ~/mini-control-enroll.key
sudo bash -c "$(curl -fsSL https://mini-control.example.com/install.sh)"For local development:
cd worker
swift build
.build/debug/MiniControlWorker --config ../install/config.yamlProduction routing is expected to use two hostnames behind Cloudflare Tunnel:
minis.example.com: browser UI, protected by Cloudflare Access.mini-control.example.com: install script, worker APIs, release upload/download, and token-authenticated machine traffic.
compose.yml expects:
deploy/config.yaml: real server configuration, copied fromdeploy/config.example.yaml..env: containsCLOUDFLARED_TOKEN.deploy/mnt/pgdata: PostgreSQL data.deploy/mnt/releases: uploaded worker binaries.
Real configuration files and runtime data are ignored by Git.
Use the release script instead of manually uploading binaries:
CODE_SIGN_IDENTITY="Developer ID Application: Your Name (TEAMID)" \
NOTARY_KEYCHAIN_PROFILE="notary-profile" \
./scripts/build-and-publish.sh \
--server https://mini-control.example.com \
--token <admin_token> \
--version <VERSION>The script builds the Swift worker, signs it, optionally notarizes it, and uploads the raw binary to:
PUT /api/v1/releases/upload/<VERSION>
Do not commit real tokens, tunnel credentials, host passwords, private keys, or production configuration files. Browser entry should be protected by Cloudflare Access; the origin validates the Access JWT before serving member endpoints.
See docs/api.md for the REST API.
This project is licensed under the MIT License. See LICENSE and THIRD_PARTY_NOTICES.md.