# Fogbreak Hetzner Cloud Deployment Guide

**Last Updated:** March 29, 2026
**Target:** Ubuntu 24.04 LTS on Hetzner Cloud VPS
**Migration From:** Bluehost shared hosting

---

## Overview

This guide walks through migrating Fogbreak from Bluehost shared hosting to a Hetzner Cloud VPS. The automated setup script (`setup-server.sh`) installs and configures everything in one idempotent run.

**What gets installed:**
- PHP 8.3 + FPM
- MySQL 8.0 + PostgreSQL 16
- Nginx with SSL (Let's Encrypt)
- Redis, Node.js 22 LTS, Ollama, Python 3.12
- Security hardening (UFW, fail2ban, auto-updates)
- GitHub auto-deploy webhook
- Cron job scheduler

**Time required:** 15–20 minutes for fresh install

---

## Prerequisites

1. **Hetzner Cloud VPS**
   - Ubuntu 24.04 LTS (fresh install)
   - Minimum: 4 vCPU, 8 GB RAM, 80 GB SSD (recommended for production)
   - Domain DNS pointing to server IP

2. **GitHub Access**
   - Repository: `fogbreak-io/fogbreak` (private)
   - Personal Access Token with `repo` scope (optional but recommended)

3. **Database Dump from Bluehost**
   - Export existing `thinkwho_portal` database as SQL
   - Location: `/path/to/dump.sql`

4. **SSH Access**
   - Root or sudoer account on Hetzner box
   - Can run: `sudo bash setup-server.sh`

---

## Step 1: Prepare Hetzner VPS

### 1.1 Boot Fresh Server

From Hetzner Cloud Console:
1. Create new VPS → Ubuntu 24.04 LTS
2. Note the root password (will be emailed)
3. Copy IP address

### 1.2 Initial SSH Access

```bash
ssh root@<IP>
```

First login, change root password:
```bash
passwd
```

### 1.3 Update Hostname (Optional)

```bash
hostnamectl set-hostname fogbreak-production
```

---

## Step 2: Download & Run Setup Script

### 2.1 Clone or Download Script

Option A: Clone from GitHub
```bash
git clone https://github.com/fogbreak-io/fogbreak.git /tmp/fogbreak-repo
cd /tmp/fogbreak-repo
```

Option B: Download script directly
```bash
curl -O https://raw.githubusercontent.com/fogbreak-io/fogbreak/main/deploy/setup-server.sh
chmod +x setup-server.sh
```

### 2.2 Run Script with Arguments

```bash
sudo bash setup-server.sh \
  --domain=fogbreak.io \
  --github-repo=fogbreak-io/fogbreak \
  --github-token=ghp_xxxxxxxxxxxx \
  --db-password="$(openssl rand -base64 32)" \
  --db-dump=/path/to/thinkwho_portal_dump.sql
```

**Arguments:**
- `--domain` — Your domain (e.g., fogbreak.io)
- `--github-repo` — GitHub repo path (e.g., fogbreak-io/fogbreak)
- `--github-token` — GitHub Personal Access Token (optional, but recommended for private repos)
- `--db-password` — Strong MySQL password for `fogbreak_app` user
- `--db-dump` — Path to existing database SQL dump (optional; can import later)

### 2.3 Monitor Installation

The script prints color-coded status messages:
- **[INFO]** — Informational step
- **[✓]** — Completed successfully
- **[WARN]** — Non-critical issue (script continues)
- **[ERROR]** — Critical failure (script exits)

Expected output: ~100 lines, 15–20 minutes

---

## Step 3: Post-Installation Configuration

### 3.1 Verify Services Are Running

```bash
systemctl status nginx
systemctl status php8.3-fpm
systemctl status mysql
systemctl status redis-server
```

### 3.2 Update Configuration Files

#### Edit config.php

```bash
sudo nano /var/www/fogbreak.io/api/config.php
```

Update placeholders:
- Database credentials
- Domain name
- SMTP credentials (Gmail or cPanel)
- API keys (Google OAuth, third-party integrations)

#### Create .env File

```bash
sudo cp /var/www/fogbreak.io/.env.example /var/www/fogbreak.io/.env
sudo nano /var/www/fogbreak.io/.env
```

Set all values:
```env
DB_HOST=localhost
DB_NAME=fogbreak
DB_USER=fogbreak_app
DB_PASS=your_generated_password_here

DOMAIN=fogbreak.io
MAIL_HOST=mail.fogbreak.io
MAIL_PASSWORD=your_smtp_password

GITHUB_TOKEN=ghp_xxxx
GITHUB_WEBHOOK_SECRET=$(openssl rand -base64 32)

CRON_SECRET=$(openssl rand -base64 32)
```

#### Fix File Permissions

```bash
sudo chown -R www-data:www-data /var/www/fogbreak.io
sudo chmod 750 /var/www/fogbreak.io
sudo chmod 640 /var/www/fogbreak.io/api/config.php
sudo chmod 640 /var/www/fogbreak.io/.env
```

### 3.3 Verify SSL Certificate

```bash
ls -la /etc/letsencrypt/live/fogbreak.io/
```

If missing, request manually:
```bash
sudo certbot certonly --nginx -d fogbreak.io -d www.fogbreak.io
```

### 3.4 Test Application

```bash
curl -I https://fogbreak.io/
# Should return 200 OK
```

Access in browser: `https://fogbreak.io/`

---

## Step 4: Set Up GitHub Auto-Deploy

### 4.1 Get Webhook Secret

From .env file:
```bash
grep GITHUB_WEBHOOK_SECRET /var/www/fogbreak.io/.env
```

### 4.2 Configure GitHub Webhook

In GitHub repository → Settings → Webhooks:

1. Click **Add webhook**
2. Set:
   - **Payload URL:** `https://fogbreak.io/deploy/webhook.php`
   - **Content type:** `application/json`
   - **Secret:** Paste webhook secret from Step 4.1
   - **Events:** "Just the push event"
3. Click **Add webhook**

### 4.3 Test Webhook

Make a test push to main branch:
```bash
git log --oneline -1
# Should show recent commit
```

Check webhook logs:
```bash
tail -f /var/log/fogbreak/webhook.log
```

---

## Step 5: Cron Job Verification

### 5.1 Check Cron Configuration

```bash
cat /etc/cron.d/fogbreak-cron
```

### 5.2 Monitor Cron Execution

Cron runs every 15 minutes. Check logs:
```bash
tail -f /var/log/fogbreak/cron.log
```

Expected output:
```
{"status":"success","jobs_executed":8,"timestamp":"2026-03-29T14:15:00Z"}
```

### 5.3 Trigger Cron Manually (Testing)

```bash
curl -X POST \
  -H "Authorization: Bearer MTH_PORTAL_2026" \
  https://fogbreak.io/api/cron.php
```

---

## Step 6: Import Legacy Data (If Needed)

### 6.1 If You Have a Database Dump

```bash
# If dump wasn't imported during setup
sudo mysql fogbreak < /path/to/thinkwho_portal_dump.sql

# Verify import
mysql -u fogbreak_app -p -e "SELECT COUNT(*) FROM fogbreak.clients;"
```

### 6.2 Manual Data Migration

If you need to migrate data post-setup:

```bash
# 1. Export from Bluehost
mysqldump -u bluehost_user -p thinkwho_portal > backup.sql

# 2. Copy to Hetzner
scp backup.sql root@<HETZNER_IP>:/tmp/

# 3. Import
sudo mysql fogbreak < /tmp/backup.sql

# 4. Verify
sudo mysql -e "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA='fogbreak';" | wc -l
```

---

## Step 7: Prepare for AI Stack (Instruction 03)

When ready to implement self-hosted AI:

### 7.1 Pull Ollama Models

```bash
# Download reasoning model (large ~40GB)
ollama pull llama2:70b

# Download chat model (small ~4GB)
ollama pull mistral:7b
```

### 7.2 Verify Ollama Running

```bash
curl http://localhost:11434/api/tags
```

### 7.3 Deploy FastAPI AI Proxy

(Instructions in `claude-code-instructions/03-SELF-HOSTED-AI.md`)

Create Python venv and start service:
```bash
cd /var/www/fogbreak.io
python3.12 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
```

Enable service:
```bash
sudo systemctl enable fogbreak-ai-proxy
sudo systemctl start fogbreak-ai-proxy
```

---

## Monitoring & Maintenance

### Log Files to Watch

```bash
# Web server
tail -f /var/log/nginx/fogbreak-access.log
tail -f /var/log/nginx/fogbreak-error.log

# PHP errors
tail -f /var/log/php/fogbreak-error.log

# Cron execution
tail -f /var/log/fogbreak/cron.log

# Auto-deployment
tail -f /var/log/fogbreak/webhook.log

# System auth
tail -f /var/log/auth.log
```

### System Health

```bash
# Disk space
df -h

# Memory usage
free -h

# Process monitor
htop

# Service status
systemctl status nginx php8.3-fpm mysql redis-server postgresql

# Network
ss -tlnp | grep LISTEN
```

### Database Backups

**Daily automatic backup (via cron):**

```bash
# Add to /etc/cron.daily/fogbreak-backup
#!/bin/bash
mysqldump -u fogbreak_app -pPASSWORD fogbreak | \
  gzip > /backups/fogbreak-$(date +%Y%m%d-%H%M%S).sql.gz
```

Or use `mysqldump`:
```bash
mysqldump -u fogbreak_app -p fogbreak > backup-$(date +%Y%m%d).sql
```

### SSL Certificate Renewal

Auto-renewal is enabled via Certbot timer:
```bash
sudo systemctl status certbot.timer
```

Manual renewal:
```bash
sudo certbot renew --dry-run
sudo certbot renew
```

---

## Troubleshooting

### Issue: "Domain not found" or 404

**Check:**
1. DNS is pointing to server IP: `nslookup fogbreak.io`
2. Nginx config exists: `sudo nginx -t`
3. Files are in place: `ls -la /var/www/fogbreak.io/`

**Fix:**
```bash
sudo systemctl restart nginx
```

### Issue: PHP-FPM Errors (500 Errors)

**Check:**
```bash
tail -f /var/log/php/fogbreak-error.log
```

**Common causes:**
- Missing extension: `php -m | grep pdo`
- Permission denied: `ls -la /var/www/fogbreak.io/api/`
- Database connection: `mysql -u fogbreak_app -p -e "SELECT 1;"`

**Fix:**
```bash
sudo systemctl restart php8.3-fpm
```

### Issue: Cron Jobs Not Running

**Check:**
```bash
ps aux | grep cron
cat /var/log/fogbreak/cron.log
```

**Fix:**
```bash
sudo systemctl restart cron
sudo /etc/cron.d/fogbreak-cron  # Verify syntax
```

### Issue: GitHub Webhook Failing

**Check:**
```bash
tail -f /var/log/fogbreak/webhook.log
```

**Verify webhook endpoint:**
```bash
curl -I https://fogbreak.io/deploy/webhook.php
# Should return 403 (no valid signature) or 200
```

**Resend webhook in GitHub:**
- Repo → Settings → Webhooks → Select webhook → Redeliver

### Issue: SSL Certificate Not Found

**Check:**
```bash
ls /etc/letsencrypt/live/fogbreak.io/
```

**Request certificate:**
```bash
sudo certbot certonly --nginx -d fogbreak.io -d www.fogbreak.io
```

**Or use standalone:**
```bash
sudo certbot certonly --standalone -d fogbreak.io
```

---

## Production Checklist

Before going live:

- [ ] Domain DNS pointing to server IP
- [ ] SSL certificate installed and auto-renewal working
- [ ] `config.php` fully configured with all credentials
- [ ] `.env` file created and secrets set
- [ ] Database imported and tables verified
- [ ] Nginx serving 200 OK on HTTPS
- [ ] Cron jobs running every 15 minutes
- [ ] GitHub webhook configured and tested
- [ ] Logs being written (access, error, cron)
- [ ] UFW firewall active (ports 22, 80, 443)
- [ ] fail2ban protecting SSH
- [ ] Automatic security updates enabled
- [ ] Daily database backups configured
- [ ] Email system tested (SMTP, Gmail IMAP)
- [ ] API endpoints tested (curl /api/admin.php?action=status)

---

## Next Steps

After successful deployment:

1. **Read CLAUDE.md** — Full project context and operational rules
2. **Review ARCHITECTURE.html** — System design and component interactions
3. **Run instruction 01: Geographic Extraction** — Remove hardcoded Monterey references
4. **Run instruction 02: Database Redesign** — MySQL → PostgreSQL with RLS
5. **Run instruction 03: Self-Hosted AI** — Ollama + FastAPI proxy

---

## Support

See `CLAUDE.md` in project root for full operational documentation.

For issues:
- Check logs in `/var/log/fogbreak/` and `/var/log/nginx/`
- Verify service status: `systemctl status [service]`
- Review error messages in config.php and .env
- Test connectivity: `curl -v https://fogbreak.io/api/admin.php`

---

**Last Updated:** March 29, 2026
