Install guide
VPS Control Room
A mobile-first PWA dashboard for driving a single VPS through a web browser. Multi-pane terminals (up to 24 concurrent ptys), AI-agent launchers, host telemetry, and shell-allowlist actions — all behind one shared secret on a Tailscale-only domain.
Three install paths
🤖
AI-assisted
~20 min
npx rahman-cr ai claudeWalk through every step with Claude / Codex / Gemini
⚡
One-line
~10 min
npx rahman-cr install --vps … --domain …All values ready, want minimum prompts
🛠️
Manual
~30 min
See Phase 5 → C belowWant to read each step before running it
Phase 0
Local prereqs (your laptop)
Run on your laptop, not the VPS.
npx rahman-cr doctorConfirms Node 18+, ssh, git, and openssl are all in PATH.
Also have ready: an SSH key (~/.ssh/id_ed25519.pub) and a password manager — you'll need to store two 32-char secrets at the end.
# generate an SSH key if you don't have one yet
ssh-keygen -t ed25519 -C "your-email"Phase 1
VPS provisioning
Bring your own VPS. Tested providers: Hostinger, DigitalOcean, Vultr, Hetzner. Minimum Ubuntu 22.04, 1 GB RAM, 5 GB disk, 1 vCPU. Recommended Ubuntu 24.04 LTS, 2 GB+ RAM.
After provisioning, confirm you have the public IPv4 and can SSH in.
Phase 2
Push SSH key, kill password auth
Get rid of password auth before doing anything else.
# from your laptop
ssh-copy-id user@<vps-ip>
ssh user@<vps-ip> 'echo ok' # should print: ok (no password prompt)Recommended: disable password auth on the VPS afterward.
# on the VPS
sudo sed -i 's/^#*PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
sudo sed -i 's/^#*PubkeyAuthentication.*/PubkeyAuthentication yes/' /etc/ssh/sshd_config
sudo systemctl restart sshdPhase 3
Tailscale on the VPS
The dashboard is designed for Tailscale-only access. The reverse proxy binds to the Tailscale interface; the public IP never sees the dashboard.
3.1 Generate a Tailscale auth key
Open the keys admin page and click Generate auth key:
- Reusable: No
- Ephemeral: No
- Pre-authorized: Yes
- Tags:
tag:server
Copy the tskey-auth-…string — you'll paste it in the next step.
3.2 Install Tailscale on the VPS
ssh user@<vps-ip>
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up --authkey=tskey-auth-XXXX --hostname=control-room
tailscale ip -4 # → 100.x.y.z
exit3.3 Note your tailnet hostname
Your dashboard URL will be control-room.<tailnet>.ts.net. Find your tailnet name at:
Phase 4 (optional)
Custom DNS
Skip this phase if you're happy using .ts.net. Otherwise, create an A record pointing your custom subdomain at the Tailscale 100.x IP.
4.1 Manual via your DNS provider
| Type | Name | Value | TTL |
|---|---|---|---|
| A | control | 100.x.y.z | 300 |
4.2 Hostinger API (one curl)
Generate a token at developers.hostinger.com first.
# replace YOUR_TOKEN, yourdomain.com, and 100.x.y.z
curl -X POST https://developers.hostinger.com/api/dns/v1/zones/yourdomain.com/records \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"type":"A","name":"control","content":"100.x.y.z","ttl":300}'4.3 Verify
dig +short control.yourdomain.com # → 100.x.y.z⚠️ Do NOT set the A record to your public IP. That defeats the threat model — Traefik binds to Tailscale only, so a public record just leaks the VPS IP.
Phase 5
Pick an install path
Three paths, same end state. Run from your laptop.
A. AI-assisted
The CLI prints a structured prompt + copies it to your clipboard. Paste into Claude / Codex / Gemini. The AI triggers the /sc-all skill if available and walks the remaining phases.
npx rahman-cr ai claude # or: codex | geminiB. One-line (non-interactive)
SSHs in, installs Node 22 + Tailscale (optional), clones the repo, generates two 32-char secrets, writes .env.local (chmod 600), runs install-systemd.sh and deploy.sh main, then verifies. Login secret prints once at the end — save it.
# already on tailnet
npx rahman-cr install \
--vps user@<ip> \
--domain control-room.<tailnet>.ts.net
# fresh VPS not on tailnet yet
npx rahman-cr install \
--vps user@<ip> \
--domain control-room.<tailnet>.ts.net \
--tailscale-key tskey-auth-XXXXC. Manual
Walk every step yourself. Good for learning the architecture.
ssh user@<vps-ip>
mkdir -p ~/projects && cd ~/projects
git clone https://github.com/rahmanef63/control-room.git vps-control-room
cd vps-control-room
cp .env.example .env.local
$EDITOR .env.local # set CONTROL_ROOM_SECRET, _SESSION_SECRET, NEXT_PUBLIC_APP_HOST, _APP_URL
chmod 600 .env.local
npm --prefix frontend install
npm --prefix agent install
npm --prefix cli install
sudo bash scripts/install-systemd.sh
bash scripts/deploy.sh mainGenerate the two secrets locally with openssl rand -hex 32 (twice — they must be different).
Phase 6
Verify
6.1 systemd services
ssh user@<vps-ip> 'systemctl is-active vps-control-room-agent vps-control-room-frontend'
# expect:
# active
# active6.2 Health endpoint
ssh user@<vps-ip> 'curl -s http://127.0.0.1:4001/health'
# expect: {"ok":true,...}6.3 Browser login
Open https://control-room.<tailnet>.ts.net (or your custom domain). Paste CONTROL_ROOM_SECRET from your password manager. Spawn a terminal, type whoami, expect your VPS user.
6.4 Install as a PWA
- iOS Safari: Share → Add to Home Screen
- Android Chrome: ⋮ → Install app
Reference
API endpoints (AI / automation)
The AI prompt embeds this catalog. Listed here for direct reference.
Tailscale
- Docs: tailscale.com/api
- Auth:
Bearer ${TAILSCALE_API_KEY} POST/api/v2/tailnet/-/keys — create auth keyGET/api/v2/tailnet/-/devices — list devices
Hostinger
- Docs: developers.hostinger.com
- Auth:
Bearer ${HOSTINGER_API_TOKEN} GET/api/vps/v1/virtual-machines — list VPSPOST/api/dns/v1/zones/{domain}/records — create DNS record
Dokploy (optional)
- Auth header:
x-api-key: ${DOKPLOY_API_KEY} POST${DOKPLOY_API_URL}/api/application.createPOST${DOKPLOY_API_URL}/api/application.deployPOST${DOKPLOY_API_URL}/api/domain.create
GitHub
- Docs: docs.github.com/rest
- Generate a PAT: github.com/settings/tokens
POSThttps://api.github.com/user/repos — create repoPOST/repos/{owner}/{repo}/keys — create deploy key
Operations
Day-2: update, rollback, rotate
Deploy an update
ssh user@<vps-ip>
cd ~/projects/vps-control-room
git pull origin main
bash scripts/deploy.sh mainRollback (one-shot)
cd ~/projects/vps-control-room/frontend
mv .next .next-broken
mv .next-previous .next
sudo systemctl restart vps-control-room-frontendRotate secrets
# on the VPS
NEW=$(openssl rand -hex 32)
NEW_SESSION=$(openssl rand -hex 32)
sed -i "s/^CONTROL_ROOM_SECRET=.*/CONTROL_ROOM_SECRET=$NEW/" .env.local
sed -i "s/^CONTROL_ROOM_SESSION_SECRET=.*/CONTROL_ROOM_SESSION_SECRET=$NEW_SESSION/" .env.local
sudo systemctl restart vps-control-room-agent vps-control-room-frontend
echo "new login: $NEW"
echo "(save in password manager, then clear scrollback)"Skill
/sc-all anchor
The AI prompt auto-loads the sc-all skill if you have it installed at ~/.claude/skills/sc-all/ (or the equivalent for Codex / Gemini). /sc-all orchestrates:
- GitHub repo ensure (private fork) + push
- Dokploy project + application creation
- Self-hosted Convex deploy (skipped — Control Room is terminal-only)
- DNS record creation
- Deploy poll until done
For Control Room, /sc-all skips Convex but reuses the GitHub + Dokploy + DNS phases. The rahman-cr install one-liner also reuses this sequencing.
Troubleshoot
Common symptoms
| Symptom | Likely cause | Fix |
|---|---|---|
| ssh: connection refused | VPS firewall blocking port 22 | Open port 22 in provider firewall |
| tailscale up hangs | Auth key expired or wrong tags | Regenerate at admin.tailscale.com/settings/keys |
| dig returns nothing | DNS not propagated | Wait 5 min, try dig +trace |
| Login page says invalid | Secret mismatch | Re-check .env.local on the VPS |
| White dashboard after deploy | Build failed silently | journalctl -u vps-control-room-frontend |
| systemctl shows failed | Wrong WorkingDirectory | Re-run scripts/install-systemd.sh from repo root |