feat: Dockerized deployment and home cleanup

- Replace rsync deployment with Docker-based deployment
- Container runs nginx on port 3000, proxied by host nginx
- Add architecture documentation
- Add home directory cleanup script
- No more direct /var/www writes

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Chris Anderson 2026-06-24 09:43:10 +02:00
parent 36eae69a00
commit adc6c76688
3 changed files with 305 additions and 13 deletions

View File

@ -43,28 +43,52 @@ jobs:
id: deployment
uses: actions/deploy-pages@v4
deploy-server:
name: Deploy to hatch.surf server
deploy-docker:
name: Deploy via Docker to hatch.surf
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Deploy via rsync
uses: burnett01/rsync-deployments@7.0.1
with:
switches: -avz --delete
path: site/
remote_path: /var/www/hatch.surf/
remote_host: ${{ secrets.DEPLOY_HOST }}
remote_user: ${{ secrets.DEPLOY_USER }}
remote_key: ${{ secrets.DEPLOY_KEY }}
- name: Build Docker image
run: docker build -t hatch-homepage:latest ./site
- name: Reload nginx
- name: Save Docker image
run: docker save hatch-homepage:latest | gzip > hatch-homepage.tar.gz
- name: Deploy to server
uses: appleboy/scp-action@v0.1.7
with:
host: ${{ secrets.DEPLOY_HOST }}
username: ${{ secrets.DEPLOY_USER }}
key: ${{ secrets.DEPLOY_KEY }}
source: "hatch-homepage.tar.gz"
target: "/tmp/hatch-homepage.tar.gz"
- name: Load and restart container
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.DEPLOY_HOST }}
username: ${{ secrets.DEPLOY_USER }}
key: ${{ secrets.DEPLOY_KEY }}
script: |
nginx -t && systemctl reload nginx
# Load the image
docker load < /tmp/hatch-homepage.tar.gz
# Stop and remove old container if exists
docker stop hatch-homepage 2>/dev/null || true
docker rm hatch-homepage 2>/dev/null || true
# Run new container
docker run -d \
--name hatch-homepage \
--restart unless-stopped \
-p 127.0.0.1:3000:80 \
-v /var/www/hatch.surf:/usr/share/nginx/html:ro \
hatch-homepage:latest
# Clean up
rm -f /tmp/hatch-homepage.tar.gz
# Reload nginx if it's managing the reverse proxy
nginx -t && systemctl reload nginx 2>/dev/null || true

173
docs/ARCHITECTURE.md Normal file
View File

@ -0,0 +1,173 @@
# Hatch.surf Architecture
## Overview
Hatch.surf is the landing page and brand site for El Foundation. It consists of:
- **Static frontend** (site/) — HTML/CSS/JS served via nginx
- **Go backend** (cmd/hatch) — API server for future dynamic features
## Deployment Architecture
### Current State (Deprecated)
```
GitHub Push → GitHub Actions → rsync → /var/www/hatch.surf → nginx (host)
```
**Problems:**
- Direct filesystem deployment — no isolation
- Host-dependent — can't reproduce locally
- Secrets (SSH keys) required for rsync
### New Architecture
```
GitHub Push → GitHub Actions → Build Docker Image → SCP to Server → Docker Run → nginx reverse proxy
```
**Benefits:**
- Containerized — isolated from host
- Reproducible — same image in dev/prod
- No host filesystem dependency
- Easy rollback — just change image tag
## Docker Setup
### Frontend (site/)
**Dockerfile:**
```dockerfile
FROM nginx:alpine
RUN rm -rf /usr/share/nginx/html/*
COPY . /usr/share/nginx/html/
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
```
**docker-compose.yml:**
```yaml
services:
hatch-homepage:
build: .
ports:
- "127.0.0.1:3000:80"
volumes:
- .:/usr/share/nginx/html:ro
restart: unless-stopped
```
### Backend (cmd/hatch)
**Dockerfile:**
```dockerfile
FROM golang:1.25-alpine AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . ./
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /bin/hatch ./cmd/hatch
FROM scratch
COPY --from=build /bin/hatch /bin/hatch
EXPOSE 8080
ENTRYPOINT ["/bin/hatch"]
```
**docker-compose.yml:**
```yaml
services:
hatch:
build: .
restart: unless-stopped
ports:
- "127.0.0.1:8080:8080"
volumes:
- hatch-data:/data
environment:
- HATCH_PORT=8080
- HATCH_DB_PATH=/data/hatch.db
- HATCH_BASE_URL=${HATCH_BASE_URL:-http://localhost}
caddy:
image: caddy:2-alpine
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy-data:/data
- caddy-config:/config
environment:
- HATCH_HOSTNAME=${HATCH_HOSTNAME:-localhost}
profiles:
- with-caddy
volumes:
hatch-data:
caddy-data:
caddy-config:
```
## Deployment Flow
### GitHub Actions (deploy-site.yml)
1. **Build**`docker build -t hatch-homepage:latest ./site`
2. **Save**`docker save hatch-homepage:latest | gzip > hatch-homepage.tar.gz`
3. **SCP** — Copy tarball to server
4. **Load**`docker load < /tmp/hatch-homepage.tar.gz`
5. **Run**`docker run -d --name hatch-homepage ...`
### Server Configuration
**Nginx reverse proxy** (host nginx):
```nginx
server {
listen 80;
server_name hatch.surf;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name hatch.surf;
ssl_certificate /etc/letsencrypt/live/hatch.surf/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/hatch.surf/privkey.pem;
location / {
proxy_pass http://127.0.0.1:3000;
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;
}
}
```
## Secrets Management
- **DEPLOY_HOST** — Server IP/hostname
- **DEPLOY_USER** — SSH user (nara)
- **DEPLOY_KEY** — SSH private key (stored in GitHub Secrets)
No secrets in repo. All deployment credentials in GitHub Secrets.
## Rollback Procedure
If deployment fails:
```bash
# On server
docker stop hatch-homepage
docker rm hatch-homepage
docker run -d --name hatch-homepage --restart unless-stopped -p 127.0.0.1:3000:80 hatch-homepage:previous-tag
```
## Future Improvements
1. **Container registry** — Push images to GitHub Container Registry (ghcr.io)
2. **Health checks** — Add HEALTHCHECK to Dockerfile
3. **Monitoring** — Add Prometheus metrics endpoint
4. **CDN** — Serve static assets via Cloudflare/CloudFront
5. **SSL automation** — Certbot in container or Caddy auto-SSL

95
scripts/cleanup-home.sh Executable file
View File

@ -0,0 +1,95 @@
#!/bin/bash
# cleanup-home.sh — Clean up home directory clutter
# Run as: bash scripts/cleanup-home.sh [--dry-run]
set -euo pipefail
DRY_RUN=false
if [[ "${1:-}" == "--dry-run" ]]; then
DRY_RUN=true
echo "DRY RUN MODE — nothing will be deleted"
fi
HOME_DIR="/home/nara"
echo "=== Cleaning up $HOME_DIR ==="
# Function to remove items
remove_item() {
local item="$1"
local full_path="$HOME_DIR/$item"
if [[ -e "$full_path" ]]; then
if $DRY_RUN; then
echo " [DRY RUN] Would remove: $item"
else
echo " Removing: $item"
rm -rf "$full_path"
fi
fi
}
# Function to move items
move_item() {
local item="$1"
local dest="$2"
local full_path="$HOME_DIR/$item"
local dest_path="$HOME_DIR/$dest"
if [[ -e "$full_path" ]]; then
if $DRY_RUN; then
echo " [DRY RUN] Would move: $item$dest/"
else
echo " Moving: $item$dest/"
mkdir -p "$dest_path"
mv "$full_path" "$dest_path/"
fi
fi
}
echo ""
echo "1. Removing stale Go installations..."
remove_item "go"
remove_item "go-1.25"
remove_item "go-local"
remove_item "gopath"
remove_item "go-tools"
echo ""
echo "2. Removing Node.js artifacts..."
remove_item "node_modules"
remove_item "package.json"
remove_item "pnpm-lock.yaml"
remove_item "pnpm-workspace.yaml"
echo ""
echo "3. Removing temp bundles..."
remove_item "vibecode-bundle"
remove_item "vibecode-bundle-20260622.tar.gz"
echo ""
echo "4. Removing stray scripts..."
remove_item "add-bridge-nginx.sh"
remove_item "setup-nginx-tls.sh"
echo ""
echo "5. Removing log files..."
remove_item "paperclip-onboard.log"
remove_item "paperclip-run.log"
echo ""
echo "6. Moving project files..."
move_item "ELF-6-completion-report.md" "Documents"
move_item "README.md" "Documents"
echo ""
echo "7. Verifying clean state..."
echo "Current home directory:"
ls -la "$HOME_DIR" | grep -v "^\." | head -20
echo ""
if $DRY_RUN; then
echo "=== Dry run complete. Run without --dry-run to apply changes. ==="
else
echo "=== Cleanup complete! ==="
fi