# HostSeba NOC — VPS deployment guide

Audience: HostSeba operator deploying to a fresh Ubuntu 22.04+ / Debian 12+
VPS ($5-10/mo from DigitalOcean / Vultr / Hetzner / local BD provider).

What this stack gives you that cPanel didn't:

| Capability | cPanel (Phase 1) | VPS (Phase 2) |
|---|---|---|
| Realtime WebSocket | Pusher SaaS ($) or polling | **Self-hosted Soketi (free)** |
| Cache / Queue / Session | database driver (slow) | **Redis (fast)** |
| Long-polling | 20s cap (PHP killer) | **50s** |
| Persistent workers | cron-restarted | `docker restart unless-stopped` |
| Custom ports | Blocked | Bind any port |
| Resource limits | Shared CPU/RAM | Dedicated |
| One-command deploy | Multi-step | **`./vps-deploy.sh upgrade`** |

---

## 1. Prep the VPS (one time)

```bash
# 1.1 Connect
ssh root@<vps-ip>

# 1.2 Create a deploy user (don't run app as root)
adduser hsap
usermod -aG sudo hsap
mkdir /home/hsap/.ssh && cp ~/.ssh/authorized_keys /home/hsap/.ssh/
chown -R hsap:hsap /home/hsap/.ssh && chmod 700 /home/hsap/.ssh

# 1.3 Install docker + compose
apt-get update
apt-get install -y ca-certificates curl gnupg lsb-release
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" > /etc/apt/sources.list.d/docker.list
apt-get update
apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

usermod -aG docker hsap     # so hsap can run docker without sudo

# 1.4 Firewall — only 22 / 80 / 443 open
ufw default deny incoming
ufw default allow outgoing
ufw allow 22/tcp
ufw allow 80/tcp
ufw allow 443/tcp
ufw enable

# 1.5 Switch to deploy user from here on
exit
ssh hsap@<vps-ip>
```

---

## 2. Get the repo

```bash
cd ~
git clone <YOUR_REPO_URL> noc
# OR, if private repo: rsync from your laptop:
#   rsync -avz --exclude=node_modules --exclude=vendor --exclude=.git \
#       ~/dev/noc/ hsap@<vps-ip>:~/noc/

cd ~/noc/backend/hostseba-noc-backend/deploy/vps
cp .env.vps.example .env
nano .env                          # fill in everything; see template comments
```

Critical fields in `.env`:
- `APP_URL` — must match nginx.conf server_name (default: `noc.hostseba.com`)
- `DB_PASSWORD`, `DB_ROOT_PASSWORD` — strong random strings
- `PUSHER_APP_KEY`, `PUSHER_APP_SECRET` — generate with `openssl rand -hex 24` / `-hex 32`
- `ANTHROPIC_API_KEY` — your secret AI key

---

## 3. nginx config — set your domain

Edit `nginx.conf`:
```
server_name noc.hostseba.com;          # change to your real domain
ssl_certificate     /etc/letsencrypt/live/noc.hostseba.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/noc.hostseba.com/privkey.pem;
```

---

## 4. Get an SSL cert (one time)

Point your domain's A record at the VPS IP first, wait for DNS to propagate.

```bash
sudo apt-get install -y certbot
sudo mkdir -p /var/www/letsencrypt
sudo certbot certonly --webroot -w /var/www/letsencrypt \
    -d noc.hostseba.com --non-interactive --agree-tos -m ops@hostseba.com
```

Auto-renewal (add to root's crontab):
```
0 3 * * * certbot renew --quiet --post-hook "cd /home/hsap/noc/backend/hostseba-noc-backend/deploy/vps && docker compose exec nginx nginx -s reload"
```

---

## 5. First boot

```bash
cd ~/noc/backend/hostseba-noc-backend/deploy/vps
chmod +x vps-deploy.sh
./vps-deploy.sh fresh
```

What `fresh` does, in order:
1. Builds the Docker image (multi-stage: composer + npm + php-fpm)
2. Brings up MariaDB, Redis, Soketi
3. Generates APP_KEY into .env if blank
4. Runs migrations (~30 tables)
5. Caches config / routes / views for prod
6. Brings up nginx + app + scheduler + queue
7. Hits `/health` to verify

Takes ~3-5 min on a $5 VPS.

After completion:
- https://noc.hostseba.com/ → redirects to /noc/ dashboard
- https://noc.hostseba.com/ai/ → AI Admin Portal
- https://noc.hostseba.com/health → JSON ok

---

## 6. Day-to-day operations

```bash
cd ~/noc/backend/hostseba-noc-backend/deploy/vps

# Deploy new code from main:
./vps-deploy.sh upgrade            # pull + rebuild + migrate + restart

# Watch logs:
./vps-deploy.sh logs               # tail -f the Laravel log

# Stop the stack (keeps data):
./vps-deploy.sh down

# Full reset — DELETES DATABASE:
./vps-deploy.sh nuke               # asks for "NUKE" confirmation
```

Other useful commands (run inside `deploy/vps/`):

```bash
# Hop into the app container:
docker compose exec app sh

# Run an artisan command:
docker compose exec app php artisan tinker

# DB shell:
docker compose exec mariadb mariadb -u noc -p"$DB_PASSWORD" hostseba_noc

# Redis CLI:
docker compose exec redis redis-cli

# Restart just one service:
docker compose restart queue

# See what's running:
docker compose ps
```

---

## 7. First agent install

Generate a registration token from the dashboard (Servers → "Add Server"),
then on the target server:

```bash
TOKEN="paste-here"
curl -fsSL https://noc.hostseba.com/install.sh | sudo bash -s -- --token "$TOKEN"
```

`install.sh` route auto-bakes the public URL into the installer, so this
"just works" without any host-specific config.

---

## 8. Common VPS pitfalls

### `docker compose build` runs out of memory
On $5 VPS (~1 GB RAM), the frontend build (Vite + node) can OOM. Two fixes:
- Build the image locally on your laptop, push to a private registry, pull on VPS
- Add 2 GB swap on the VPS:
  ```bash
  sudo fallocate -l 2G /swapfile
  sudo chmod 600 /swapfile
  sudo mkswap /swapfile && sudo swapon /swapfile
  echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
  ```

### Soketi connection drops every 60s in the browser
nginx `proxy_read_timeout` too low. The config ships with 3600s; if you
customized nginx.conf, verify the `/app/` block keeps it at 3600s.

### `docker compose exec app php artisan migrate` says "no migrations table"
First-time install — use `./vps-deploy.sh fresh` instead, it handles the
ordering. Or run manually with `--force`:
```
docker compose exec app php artisan migrate --force
```

### nginx serves the wrong cert
Certbot renewal needs nginx reload. Add the post-hook in step 4 above.
You can test renewal without actually renewing:
```bash
sudo certbot renew --dry-run
```

### "Pusher" auth fails — frontend can't connect
The PUSHER_APP_KEY in `.env` MUST match what was baked into the frontend
build. If you changed the key, rebuild:
```bash
docker compose build --no-cache app
./vps-deploy.sh upgrade
```

### Backups
The `mariadb-data` and `storage` named volumes hold everything important.
Quick nightly backup:
```bash
0 2 * * * cd ~/noc/backend/hostseba-noc-backend/deploy/vps \
  && docker compose exec -T mariadb mysqldump -u root -p"$DB_ROOT_PASSWORD" --all-databases \
  | gzip > ~/backups/db-$(date +\%F).sql.gz \
  && find ~/backups -name 'db-*.sql.gz' -mtime +14 -delete
```

For offsite, rsync `~/backups/` to S3 / Backblaze B2 weekly.

---

## 9. Scaling beyond a single VPS

When THIS VPS bottlenecks (typical signs: queue lag > 30s, redis memory full,
DB CPU pinned), the move is:

1. **DB out**: move MariaDB to a managed DB service (DigitalOcean, RDS).
   Edit `.env` → `DB_HOST=db.<provider>.com`, remove `mariadb` service.
2. **Soketi out**: move Soketi to its own $5 VPS or DigitalOcean App Platform.
3. **App replicas**: bump `app` service `deploy.replicas: 3` (Docker Swarm or
   k3s) + nginx load-balance.

The codebase needs NO changes for any of this — everything's already
provider-agnostic via env vars.

---

## 10. Migrating from cPanel (Phase 1 → Phase 2)

If you currently run on cPanel and want to switch:

```bash
# On cPanel — dump DB:
mysqldump -u <user> -p hostsebau_noc | gzip > noc-db-dump.sql.gz

# On VPS — fresh deploy + load dump:
./vps-deploy.sh fresh
gunzip -c noc-db-dump.sql.gz \
  | docker compose exec -T mariadb mariadb -u root -p"$DB_ROOT_PASSWORD" hostseba_noc
docker compose exec app php artisan cache:clear

# Re-point DNS to VPS IP. Agents posting to noc.hostseba.com auto-follow.
```

No agent re-installation needed — the agent.conf BACKEND_URL is just a
hostname, and DNS handles the redirect transparently.
