# Cloud VM Deployment Runbook (Ubuntu 24.04)

Copy-paste walkthrough for DigitalOcean / Hetzner / Vultr. Time: ~45 minutes.
Companion checklist: deployment/SECURITY.md — tick every box as you go.

## 0. Before touching the server

- [ ] **Rotate credentials** that ever appeared in chat/screenshots:
      new Alpaca keys, new Gmail app password. Keep them ONLY in the server's
      `.env` (and your local one).
- [ ] Buy/have a domain. You'll create two DNS records later:
      `app.yourdomain.com` and `api.yourdomain.com`.
- [ ] Push the project to a **private** GitHub repo (SETUP.md step 4), or be
      ready to upload with WinSCP.

## 1. Create the VM

Provider: any. Pick **Ubuntu 24.04 LTS**, smallest tier with **2 GB RAM**
(1 GB struggles with `next build`), enable **SSH key** auth during creation
(paste your public key; on Windows: `ssh-keygen` in PowerShell, key lands in
`~\.ssh\id_ed25519.pub`). Note the server IP.

## 2. First login + hardening

```bash
ssh root@YOUR_IP

# Create app user, disable root/password SSH
adduser --disabled-password --gecos "" trader
mkdir -p /home/trader/.ssh && cp ~/.ssh/authorized_keys /home/trader/.ssh/
chown -R trader:trader /home/trader/.ssh
sed -i 's/#\?PermitRootLogin.*/PermitRootLogin no/; s/#\?PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
systemctl restart ssh

# Firewall: SSH + web only
ufw allow OpenSSH && ufw allow 80 && ufw allow 443 && ufw --force enable

# Basics
apt update && apt -y upgrade
apt -y install python3.12-venv python3-pip nginx git unattended-upgrades fail2ban
curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && apt -y install nodejs
systemctl enable --now fail2ban
```

## 3. Get the code onto the server

```bash
su - trader
git clone https://github.com/YOU/ai-trading-signal-lab.git app
# (or upload the folder with WinSCP to /home/trader/app — exclude node_modules, .venv, .next, *.db)
```

## 4. Backend setup

```bash
cd ~/app/backend
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
python -m pytest -q        # must be green on the server too

cp ../.env.example .env
nano .env
```

Set in `.env` (production values — the app REFUSES to boot if these are wrong):

```
ENV=production
REQUIRE_AUTH=true
SECRET_KEY=<fresh 48+ char random string:  openssl rand -base64 48>
FRONTEND_ORIGIN=https://app.yourdomain.com
DATA_PROVIDER=live
ALPACA_API_KEY=<NEW rotated key>
ALPACA_API_SECRET=<NEW rotated secret>
AUTO_SCAN_ENABLED=true
EMAIL_ALERTS_ENABLED=true
SMTP_USER=...  SMTP_PASSWORD=<NEW rotated app password>  ALERT_EMAIL_TO=...
```

```bash
chmod 600 .env
```

## 5. Frontend build

```bash
cd ~/app/frontend
echo "NEXT_PUBLIC_API_URL=https://api.yourdomain.com" > .env.local
npm ci && npm run build
```

## 6. Run both as services (as root)

```bash
exit   # back to root
cat > /etc/systemd/system/lab-api.service <<'EOF'
[Unit]
Description=Signal Lab API
After=network.target
[Service]
User=trader
WorkingDirectory=/home/trader/app/backend
ExecStart=/home/trader/app/backend/.venv/bin/uvicorn app.main:app --host 127.0.0.1 --port 8000
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF

cat > /etc/systemd/system/lab-web.service <<'EOF'
[Unit]
Description=Signal Lab Web
After=network.target
[Service]
User=trader
WorkingDirectory=/home/trader/app/frontend
ExecStart=/usr/bin/npx next start -p 3000
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable --now lab-api lab-web
systemctl status lab-api lab-web   # both should be "active (running)"
```

## 7. DNS + nginx + SSL

At your domain registrar: create two **A records** → your server IP:
`app.yourdomain.com` and `api.yourdomain.com`. Wait for propagation
(`ping app.yourdomain.com`).

```bash
cat > /etc/nginx/sites-available/lab <<'EOF'
limit_req_zone $binary_remote_addr zone=api:10m rate=5r/s;

server {
    server_name app.yourdomain.com;
    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}
server {
    server_name api.yourdomain.com;
    location / {
        limit_req zone=api burst=20 nodelay;
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}
EOF
ln -s /etc/nginx/sites-available/lab /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx

apt -y install certbot python3-certbot-nginx
certbot --nginx -d app.yourdomain.com -d api.yourdomain.com   # auto-SSL + redirect
```

## 8. First-run verification

1. `https://api.yourdomain.com/health` → `{"status":"ok","mode":"paper-trading-only"}`
2. `https://api.yourdomain.com/docs` → **404** (proves production mode)
3. Open `https://app.yourdomain.com` → badge shows **Live API**
4. Register YOUR account once:
   `curl -X POST https://api.yourdomain.com/api/auth/register -H "Content-Type: application/json" -d '{"email":"you@...","password":"<strong>"}'`
   Then block further signups (single-user):
   add to the api server block in nginx, above `location /`:
   `location = /api/auth/register { return 403; }` → `systemctl reload nginx`
5. Wait for market hours → signals appear on their own; email/Telegram fire.
6. On your phone: open the app URL → Add to Home Screen (PWA).

## 9. Backups + monitoring (10 minutes, don't skip)

```bash
# Nightly SQLite backup, 14-day retention
cat > /etc/cron.daily/lab-backup <<'EOF'
#!/bin/sh
mkdir -p /home/trader/backups
cp /home/trader/app/backend/trading_lab.db /home/trader/backups/trading_lab_$(date +%F).db
find /home/trader/backups -mtime +14 -delete
EOF
chmod +x /etc/cron.daily/lab-backup
```

- Free uptime monitor (UptimeRobot) on `https://api.yourdomain.com/health`,
  alert to your email.
- Periodically check `GET /api/scheduler/status` (needs your login token) —
  `last_run` should never lag more than ~30 min during market hours.

## 10. Updating the app later

```bash
ssh trader@YOUR_IP
cd ~/app && git pull
cd backend && source .venv/bin/activate && pip install -r requirements.txt && python -m pytest -q
cd ../frontend && npm ci && npm run build
exit
sudo systemctl restart lab-api lab-web
```

## Reminders

- This server is a paper-trading research tool. No live brokerage credentials,
  ever, per docs/MASTER_AUDIT_PROMPT.md.
- If anything fails to boot, `journalctl -u lab-api -n 50` shows why — the
  most common cause is intentionally strict production config (fix .env,
  don't weaken the checks).
