updates
This commit is contained in:
324
DEPLOYMENT_PLAN.md
Normal file
324
DEPLOYMENT_PLAN.md
Normal file
@@ -0,0 +1,324 @@
|
||||
# Marketing App — Deployment Plan
|
||||
|
||||
## Architecture
|
||||
|
||||
| Server | Role | What runs |
|
||||
|--------|------|-----------|
|
||||
| **Server A** (Hetzner, Ubuntu 24.04) | Frontends + Express backends | Nginx, act_runner, marketing-app, hihala-dashboard, other apps |
|
||||
| **Server B** (Hetzner, Cloudron) | Data layer + Git | Cloudron, NocoDB, Gitea |
|
||||
|
||||
```
|
||||
Server B (Cloudron) Server A (Ubuntu 24.04)
|
||||
┌──────────────────────┐ ┌──────────────────────────────┐
|
||||
│ Gitea (git repos) │◄── register ─│ act_runner (Gitea runner) │
|
||||
│ NocoDB (databases) │ │ Nginx │
|
||||
│ │── webhook ──►│ marketing-app (systemd) │
|
||||
│ │ │ hihala-dashboard (systemd) │
|
||||
└──────────────────────┘ └──────────────────────────────┘
|
||||
```
|
||||
|
||||
Flow: `local dev → git push → Gitea (Server B) → triggers runner on Server A → build + deploy`
|
||||
|
||||
---
|
||||
|
||||
## Phase 0 — Secure Server A (Ubuntu 24.04)
|
||||
|
||||
### 0.1 Create non-root user (if still logging in as root)
|
||||
|
||||
```bash
|
||||
adduser fahed
|
||||
usermod -aG sudo fahed
|
||||
|
||||
mkdir -p /home/fahed/.ssh
|
||||
cp /root/.ssh/authorized_keys /home/fahed/.ssh/authorized_keys
|
||||
chown -R fahed:fahed /home/fahed/.ssh
|
||||
chmod 700 /home/fahed/.ssh
|
||||
chmod 600 /home/fahed/.ssh/authorized_keys
|
||||
```
|
||||
|
||||
Test in a **new terminal** before continuing: `ssh fahed@server-a`
|
||||
|
||||
### 0.2 Lock down SSH
|
||||
|
||||
Edit `/etc/ssh/sshd_config`:
|
||||
|
||||
```
|
||||
PermitRootLogin no
|
||||
PasswordAuthentication no
|
||||
PubkeyAuthentication yes
|
||||
MaxAuthTries 3
|
||||
AllowUsers fahed
|
||||
```
|
||||
|
||||
```bash
|
||||
sudo systemctl restart sshd
|
||||
```
|
||||
|
||||
Test in a **new terminal** before closing your current session.
|
||||
|
||||
### 0.3 Firewall (ufw)
|
||||
|
||||
```bash
|
||||
sudo ufw default deny incoming
|
||||
sudo ufw default allow outgoing
|
||||
sudo ufw allow 22/tcp # SSH
|
||||
sudo ufw allow 80/tcp # HTTP (certbot + redirect)
|
||||
sudo ufw allow 443/tcp # HTTPS
|
||||
sudo ufw enable
|
||||
sudo ufw status
|
||||
```
|
||||
|
||||
### 0.4 Automatic security updates
|
||||
|
||||
```bash
|
||||
sudo apt install -y unattended-upgrades
|
||||
sudo dpkg-reconfigure -plow unattended-upgrades
|
||||
```
|
||||
|
||||
Select **Yes**.
|
||||
|
||||
### 0.5 Fail2ban
|
||||
|
||||
```bash
|
||||
sudo apt install -y fail2ban
|
||||
sudo systemctl enable --now fail2ban
|
||||
```
|
||||
|
||||
### 0.6 Disable unused services
|
||||
|
||||
```bash
|
||||
sudo ss -tlnp
|
||||
# Disable anything unexpected:
|
||||
# sudo systemctl disable --now <service-name>
|
||||
```
|
||||
|
||||
Server B (Cloudron) handles its own security.
|
||||
|
||||
---
|
||||
|
||||
## Phase 1 — Server B (Cloudron)
|
||||
|
||||
1. Install **Gitea** from Cloudron app store → `https://gitea.yourdomain.com`
|
||||
2. Install **NocoDB** from Cloudron app store → `https://nocodb.yourdomain.com`
|
||||
3. On NocoDB: create a base for marketing-app, generate an API token
|
||||
4. On Gitea: create a repo `marketing-app`, push your local code to it
|
||||
5. On Gitea: go to **Site Admin → Runners** and copy the runner registration token
|
||||
|
||||
---
|
||||
|
||||
## Phase 2 — Server A (Ubuntu 24.04)
|
||||
|
||||
### 2.1 Install dependencies
|
||||
|
||||
```bash
|
||||
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo bash -
|
||||
sudo apt install -y nodejs nginx certbot python3-certbot-nginx
|
||||
```
|
||||
|
||||
### 2.2 Create app directory
|
||||
|
||||
```bash
|
||||
sudo mkdir -p /opt/apps/marketing-app
|
||||
sudo chown fahed:fahed /opt/apps/marketing-app
|
||||
```
|
||||
|
||||
### 2.3 Create .env file (set once, never touched by deploys)
|
||||
|
||||
```
|
||||
# /opt/apps/marketing-app/server/.env
|
||||
NOCODB_URL=https://nocodb.yourdomain.com
|
||||
NOCODB_TOKEN=<token-from-step-3>
|
||||
NOCODB_BASE_ID=<base-id-from-step-3>
|
||||
SESSION_SECRET=<generate-with: openssl rand -hex 32>
|
||||
CORS_ORIGIN=https://marketing.yourdomain.com
|
||||
NODE_ENV=production
|
||||
```
|
||||
|
||||
### 2.4 Install and register Gitea runner
|
||||
|
||||
```bash
|
||||
sudo wget https://gitea.com/gitea/act_runner/releases/latest/download/act_runner-linux-amd64 -O /usr/local/bin/act_runner
|
||||
sudo chmod +x /usr/local/bin/act_runner
|
||||
mkdir -p /opt/gitea-runner && cd /opt/gitea-runner
|
||||
act_runner register \
|
||||
--instance https://gitea.yourdomain.com \
|
||||
--token <token-from-phase-1-step-5> \
|
||||
--name server-a \
|
||||
--labels ubuntu-latest:host
|
||||
```
|
||||
|
||||
### 2.5 systemd service for the runner
|
||||
|
||||
```ini
|
||||
# /etc/systemd/system/gitea-runner.service
|
||||
[Unit]
|
||||
Description=Gitea Actions Runner
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=fahed
|
||||
WorkingDirectory=/opt/gitea-runner
|
||||
ExecStart=/usr/local/bin/act_runner daemon
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
```bash
|
||||
sudo systemctl enable --now gitea-runner
|
||||
```
|
||||
|
||||
### 2.6 systemd service for marketing-app
|
||||
|
||||
```ini
|
||||
# /etc/systemd/system/marketing-app.service
|
||||
[Unit]
|
||||
Description=Marketing App
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=fahed
|
||||
WorkingDirectory=/opt/apps/marketing-app/server
|
||||
ExecStart=/usr/bin/node server.js
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
EnvironmentFile=/opt/apps/marketing-app/server/.env
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
```bash
|
||||
sudo systemctl enable marketing-app
|
||||
```
|
||||
|
||||
### 2.7 Allow passwordless restart for deploys
|
||||
|
||||
```
|
||||
# /etc/sudoers.d/gitea-runner
|
||||
fahed ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart marketing-app
|
||||
fahed ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart hihala-dashboard
|
||||
```
|
||||
|
||||
### 2.8 DNS
|
||||
|
||||
Point `marketing.yourdomain.com` to Server A's IP.
|
||||
|
||||
### 2.9 Nginx vhost
|
||||
|
||||
```nginx
|
||||
# /etc/nginx/sites-available/marketing-app
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name marketing.yourdomain.com;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/marketing.yourdomain.com/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/marketing.yourdomain.com/privkey.pem;
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://127.0.0.1:3001;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
client_max_body_size 50M;
|
||||
}
|
||||
|
||||
location / {
|
||||
root /opt/apps/marketing-app/client/dist;
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name marketing.yourdomain.com;
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
```
|
||||
|
||||
```bash
|
||||
sudo ln -s /etc/nginx/sites-available/marketing-app /etc/nginx/sites-enabled/
|
||||
sudo certbot --nginx -d marketing.yourdomain.com
|
||||
sudo nginx -t && sudo systemctl reload nginx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 3 — CI/CD Pipeline
|
||||
|
||||
### 3.1 Add workflow file to repo
|
||||
|
||||
```yaml
|
||||
# .gitea/workflows/deploy.yml
|
||||
name: Deploy
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Build frontend
|
||||
run: cd client && npm ci && npm run build
|
||||
|
||||
- name: Deploy to app directory
|
||||
run: |
|
||||
rsync -a --delete client/dist/ /opt/apps/marketing-app/client/dist/
|
||||
rsync -a server/ /opt/apps/marketing-app/server/ \
|
||||
--exclude node_modules --exclude '*.db' --exclude uploads --exclude .env
|
||||
|
||||
- name: Install production deps
|
||||
run: cd /opt/apps/marketing-app/server && npm ci --production
|
||||
|
||||
- name: Restart service
|
||||
run: sudo systemctl restart marketing-app
|
||||
```
|
||||
|
||||
### 3.2 Add Gitea remote and push
|
||||
|
||||
```bash
|
||||
cd ~/clawd/projects/marketing-app
|
||||
git remote add gitea https://gitea.yourdomain.com/fahed/marketing-app.git
|
||||
git push gitea main
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 4 — Verify
|
||||
|
||||
1. Watch the runner: `journalctl -u gitea-runner -f`
|
||||
2. Watch the app: `journalctl -u marketing-app -f`
|
||||
3. Open `https://marketing.yourdomain.com`
|
||||
4. Test: login, create an artefact, upload a file
|
||||
|
||||
---
|
||||
|
||||
## Code changes already made (in server.js)
|
||||
|
||||
- Session secret reads from `process.env.SESSION_SECRET` with `'dev-fallback-secret'` fallback
|
||||
- CORS locks to `process.env.CORS_ORIGIN` in production, open locally
|
||||
- Cookie `secure: true` when `NODE_ENV=production`
|
||||
- `trust proxy` enabled when `NODE_ENV=production`
|
||||
- Static file serving of `client/dist/` + SPA fallback when `NODE_ENV=production`
|
||||
|
||||
---
|
||||
|
||||
## Adding another app later (e.g. hihala-dashboard)
|
||||
|
||||
Repeat phases 1.3-1.4, 2.2-2.3, 2.6, 2.8-2.9, 3.1-3.2 with:
|
||||
- Different port (3002)
|
||||
- Different subdomain (hihala.yourdomain.com)
|
||||
- Different systemd service name (hihala-dashboard)
|
||||
- Same runner handles all repos
|
||||
Reference in New Issue
Block a user