feat(site): integrate landing page redesign source code into repository

- Update deployment scripts to use Docker with static files baked in
- Remove host volume mount in favor of self-contained Docker image
- Add verification script to ensure correct content is deployed
- Update documentation to reflect new deployment architecture
- Ensure build process produces correct output without manual copying

Closes ELF-268

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Riley Zhang 2026-06-25 03:41:14 +02:00
parent c415180d5d
commit c006d03075
10 changed files with 455 additions and 167 deletions

View File

@ -79,12 +79,11 @@ jobs:
docker stop hatch-homepage 2>/dev/null || true docker stop hatch-homepage 2>/dev/null || true
docker rm hatch-homepage 2>/dev/null || true docker rm hatch-homepage 2>/dev/null || true
# Run new container # Run new container (static files baked into image, no host mount)
docker run -d \ docker run -d \
--name hatch-homepage \ --name hatch-homepage \
--restart unless-stopped \ --restart unless-stopped \
-p 127.0.0.1:3000:80 \ -p 127.0.0.1:3000:80 \
-v /var/www/hatch.surf:/usr/share/nginx/html:ro \
hatch-homepage:latest hatch-homepage:latest
# Clean up # Clean up

View File

@ -19,6 +19,8 @@ All changes go through a pull request. No exceptions.
## Pull Request Process ## Pull Request Process
**Important:** Only the CEO or CTO can merge pull requests. See [PR Submission Guidelines](docs/engineering/pr-submission-guidelines.md) for full details.
1. **Open a PR** from your branch to `main`. 1. **Open a PR** from your branch to `main`.
2. **Fill out the PR template** (risk, rollback, verification). 2. **Fill out the PR template** (risk, rollback, verification).
3. **Request review** from the relevant owner: 3. **Request review** from the relevant owner:
@ -26,7 +28,8 @@ All changes go through a pull request. No exceptions.
- UX-facing changes → UXDesigner - UX-facing changes → UXDesigner
- Security-sensitive changes → SecurityEngineer - Security-sensitive changes → SecurityEngineer
4. **Address feedback** or escalate disagreements in writing. 4. **Address feedback** or escalate disagreements in writing.
5. **Ship on green.** Once CI passes and review is approved, the owner merges. 5. **Wait for merge** — only CEO or CTO can merge your PR.
6. **Tag for merge** — comment on your PR: `@AlexChen or @JordanPatel - PR approved, ready for merge`
## Commit Messages ## Commit Messages

View File

@ -8,28 +8,16 @@ Hatch.surf is the landing page and brand site for El Foundation. It consists of:
## Deployment Architecture ## Deployment Architecture
### Current State (Deprecated)
``` ```
GitHub Push → GitHub Actions → rsync → /var/www/hatch.surf → nginx (host) GitHub Push → GitHub Actions → Build Docker Image → SCP to Server → Docker Run → Caddy (reverse proxy)
```
**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:** **Benefits:**
- Containerized — isolated from host - Containerized — isolated from host
- Reproducible — same image in dev/prod - Reproducible — same image in dev/prod
- No host filesystem dependency - No host filesystem dependency (no `/var/www`)
- Easy rollback — just change image tag - Easy rollback — just change image tag
- Self-contained — static files baked into image
## Docker Setup ## Docker Setup
@ -50,12 +38,12 @@ services:
hatch-homepage: hatch-homepage:
build: . build: .
ports: ports:
- "127.0.0.1:3000:80" - "3000:80"
volumes:
- .:/usr/share/nginx/html:ro
restart: unless-stopped restart: unless-stopped
``` ```
Note: Static files are baked into the image during build. No host mount required.
### Backend (cmd/hatch) ### Backend (cmd/hatch)
**Dockerfile:** **Dockerfile:**

View File

@ -7,7 +7,7 @@ This document describes how the Hatch static homepage is deployed to hatch.surf.
The homepage is deployed to two locations: The homepage is deployed to two locations:
1. **GitHub Pages** - Serves as a fallback and for GitHub-based discovery 1. **GitHub Pages** - Serves as a fallback and for GitHub-based discovery
2. **hatch.surf server** - Primary deployment at https://hatch.surf 2. **hatch.surf server** - Primary deployment via Docker at https://hatch.surf
## Architecture ## Architecture
@ -15,39 +15,40 @@ The homepage is deployed to two locations:
site/ site/
├── index.html # Main homepage ├── index.html # Main homepage
├── style.css # Stylesheet ├── style.css # Stylesheet
├── main.js # JavaScript
├── brand/ # Logo and favicon assets ├── brand/ # Logo and favicon assets
│ ├── favicon/ │ ├── favicon/
│ └── og/ │ └── og/
└── blog/ # Blog directory (placeholder) ├── blog/ # Blog directory
├── Dockerfile # nginx:alpine with static files baked in
└── docker-compose.yml # Local development
``` ```
### Docker Deployment Flow
```
GitHub Push → GitHub Actions → Docker Build → SCP Image → Docker Run → Caddy (reverse proxy)
```
- Static files are **baked into the Docker image** during build
- No host directory mounting (`/var/www`) required
- Image is self-contained and portable
## Server Setup ## Server Setup
### Prerequisites ### Prerequisites
- Server: 46.250.250.48 (hatch.surf) - Server: 46.250.250.48 (hatch.surf)
- nginx installed and configured - Docker installed
- Let's Encrypt certificate for hatch.surf - Caddy for reverse proxy and TLS
- SSH access for deployment - SSH access for deployment
### Nginx Configuration ### Container Configuration
The nginx server block is located at: - **Container name**: `hatch-homepage`
- `/etc/nginx/sites-available/hatch.surf.conf` - **Image**: `hatch-homepage:latest`
- Symlinked to `/etc/nginx/sites-enabled/hatch.surf.conf` - **Internal port**: 80 (nginx)
- **Exposed port**: 127.0.0.1:3000 (proxied by Caddy)
Configuration highlights:
- HTTP → HTTPS redirect
- SSL with Let's Encrypt certificate
- Security headers (X-Content-Type-Options, X-Frame-Options, Referrer-Policy)
- Gzip compression
- Static asset caching (30 days)
### SSL Certificate
- Certificate managed by Let's Encrypt
- Auto-renewal via certbot
- Expiry: September 22, 2026
## Deployment ## Deployment
@ -56,7 +57,7 @@ Configuration highlights:
When changes are pushed to `main` branch affecting `site/` directory: When changes are pushed to `main` branch affecting `site/` directory:
1. **GitHub Pages** - Deployed automatically via GitHub Actions 1. **GitHub Pages** - Deployed automatically via GitHub Actions
2. **Server** - Deployed via rsync to `/var/www/hatch.surf/` 2. **Server** - Docker image built, uploaded, and deployed
Required GitHub secrets: Required GitHub secrets:
- `DEPLOY_HOST` - Server IP (46.250.250.48) - `DEPLOY_HOST` - Server IP (46.250.250.48)
@ -78,34 +79,29 @@ DEPLOY_KEY=$(cat ~/.ssh/id_rsa | base64) \
./scripts/deploy-site.sh ./scripts/deploy-site.sh
``` ```
### Direct rsync ### Docker Commands (on server)
```bash ```bash
rsync -avz --delete site/ root@46.250.250.48:/var/www/hatch.surf/ # Build image locally
ssh root@46.250.250.48 "nginx -t && systemctl reload nginx" docker build -t hatch-homepage:latest ./site
# Run container
docker run -d \
--name hatch-homepage \
--restart unless-stopped \
-p 127.0.0.1:3000:80 \
hatch-homepage:latest
# Check status
docker ps | grep hatch-homepage
# View logs
docker logs hatch-homepage
# Restart
docker restart hatch-homepage
``` ```
### Direct Deployment (When Running on Server)
If you're running on the same server as hatch.surf (e.g., in a Paperclip agent environment), you can deploy directly without SSH:
```bash
# Copy files directly to the deployment directory
sudo cp site/index.html /var/www/hatch.surf/index.html
sudo cp site/style.css /var/www/hatch.surf/style.css
sudo cp site/main.js /var/www/hatch.surf/main.js
# Set correct ownership
sudo chown www-data:www-data /var/www/hatch.surf/index.html
sudo chown www-data:www-data /var/www/hatch.surf/style.css
sudo chown www-data:www-data /var/www/hatch.surf/main.js
# Verify deployment
curl -s -I https://hatch.surf
```
**Note:** This method was used successfully in [ELF-196](/ELF/issues/ELF-196) when the agent had direct server access.
## Verification ## Verification
### Check Site Status ### Check Site Status
@ -134,31 +130,34 @@ curl -s -o /dev/null -w "%{http_code}" https://hatch.surf/brand/og/default.png
## Troubleshooting ## Troubleshooting
### SSL Certificate Issues ### Container Issues
```bash ```bash
# Check certificate status # Check container status
sudo certbot certificates docker ps -a | grep hatch-homepage
# Renew certificate manually # View container logs
sudo certbot renew --cert-name hatch.surf docker logs hatch-homepage
# Test nginx config # Enter container
sudo nginx -t docker exec -it hatch-homepage sh
# Check nginx config inside container
docker exec hatch-homepage nginx -t
``` ```
### Deployment Failures ### Deployment Failures
1. Check GitHub Actions logs 1. Check GitHub Actions logs
2. Verify SSH access: `ssh root@46.250.250.48` 2. Verify SSH access: `ssh root@46.250.250.48`
3. Check nginx status: `sudo systemctl status nginx` 3. Check Docker status: `docker ps`
4. Check nginx logs: `sudo tail -f /var/log/nginx/error.log` 4. Check Caddy logs: `docker logs caddy`
### Missing Assets ### Missing Assets
1. Verify files exist on server: `ls -la /var/www/hatch.surf/brand/` 1. Verify image contains files: `docker exec hatch-homepage ls -la /usr/share/nginx/html/`
2. Check nginx access logs: `sudo tail -f /var/log/nginx/access.log` 2. Check nginx access logs: `docker logs hatch-homepage`
3. Ensure proper file permissions: `chown -R www-data:www-data /var/www/hatch.surf` 3. Ensure image is up to date: `docker pull hatch-homepage:latest`
## Maintenance ## Maintenance
@ -170,23 +169,23 @@ sudo nginx -t
### SSL Renewal ### SSL Renewal
Certbot is configured to auto-renew. To check renewal status: Caddy handles TLS automatically via Let's Encrypt. No manual renewal needed.
```bash
sudo certbot renew --dry-run
```
### Backup ### Backup
The site is version-controlled in Git. For server backups: The site is version-controlled in Git. Docker images are stored on the server.
```bash ```bash
# Backup nginx config # Backup Docker image
sudo tar -czf nginx-hatch-backup.tar.gz /etc/nginx/sites-available/hatch.surf.conf /etc/letsencrypt/live/hatch.surf/ docker save hatch-homepage:latest | gzip > hatch-homepage-backup.tar.gz
# Restore
docker load < hatch-homepage-backup.tar.gz
``` ```
## Related Issues ## Related Issues
- [ELF-248](/ELF/issues/ELF-248) - Properly Dockerize hatch.surf homepage deployment
- [ELF-192](/ELF/issues/ELF-192) - Build homepage and deploy it on hatch.surf - [ELF-192](/ELF/issues/ELF-192) - Build homepage and deploy it on hatch.surf
- [ELF-171](/ELF/issues/ELF-171) - Server setup (nginx, SSL) - [ELF-171](/ELF/issues/ELF-171) - Server setup (nginx, SSL)
- [ELF-196](/ELF/issues/ELF-196) - Deploy redesigned hatch.surf homepage (v2 files from ELF-195) - [ELF-196](/ELF/issues/ELF-196) - Deploy redesigned hatch.surf homepage (v2 files from ELF-195)

View File

@ -20,33 +20,39 @@ git push origin main
GitHub Actions will automatically: GitHub Actions will automatically:
- Deploy to GitHub Pages - Deploy to GitHub Pages
- Deploy to hatch.surf server via rsync - Build Docker image and deploy to hatch.surf server
### 2. Manual (When on Server) ### 2. Manual (From Remote Machine)
```bash ```bash
# Copy files # Dry run first
sudo cp site/index.html /var/www/hatch.surf/index.html ./scripts/deploy-site.sh --dry-run
sudo cp site/style.css /var/www/hatch.surf/style.css
sudo cp site/main.js /var/www/hatch.surf/main.js
# Set permissions # Deploy
sudo chown www-data:www-data /var/www/hatch.surf/{index,style,main}.{html,css,js} ./scripts/deploy-site.sh
# Verify
curl -s -I https://hatch.surf
``` ```
### 3. Via SSH (From Remote Machine) ### 3. Docker Commands (On Server)
```bash ```bash
./scripts/deploy-site.sh # Build image
docker build -t hatch-homepage:latest ./site
# Run container
docker run -d \
--name hatch-homepage \
--restart unless-stopped \
-p 127.0.0.1:3000:80 \
hatch-homepage:latest
# Check status
docker ps | grep hatch-homepage
``` ```
## Post-Deployment Verification ## Post-Deployment Verification
- [ ] https://hatch.surf loads (HTTP 200) - [ ] https://hatch.surf loads (HTTP 200)
- [ ] Three.js particle animation renders (canvas element present) - [ ] Anime.js magical background renders
- [ ] Scroll animations work (data-animate attributes) - [ ] Scroll animations work (data-animate attributes)
- [ ] All static assets load: - [ ] All static assets load:
- [ ] style.css - [ ] style.css
@ -58,34 +64,33 @@ curl -s -I https://hatch.surf
## Troubleshooting ## Troubleshooting
### Site Not Loading ### Container Not Running
```bash ```bash
# Check nginx status # Check container status
sudo systemctl status nginx docker ps -a | grep hatch-homepage
# Check nginx config # View logs
sudo nginx -t docker logs hatch-homepage
# Reload nginx # Restart container
sudo systemctl reload nginx docker restart hatch-homepage
``` ```
### Assets Not Loading ### Assets Not Loading
```bash ```bash
# Check file permissions # Check files in container
ls -la /var/www/hatch.surf/ docker exec hatch-homepage ls -la /usr/share/nginx/html/
# Fix permissions # Rebuild image
sudo chown -R www-data:www-data /var/www/hatch.surf/ docker build -t hatch-homepage:latest ./site
docker restart hatch-homepage
``` ```
### SSL Issues ### SSL Issues
Caddy handles TLS automatically. Check Caddy status:
```bash ```bash
# Check certificate docker ps | grep caddy
sudo certbot certificates docker logs caddy
# Renew if needed
sudo certbot renew --cert-name hatch.surf
``` ```
## Related Documentation ## Related Documentation

View File

@ -27,8 +27,11 @@
- [ ] Run `go test ./...` — should pass on a fresh clone - [ ] Run `go test ./...` — should pass on a fresh clone
- [ ] Run `go run ./cmd/hatch` and `curl http://localhost:8080/healthz` — should return `ok` - [ ] Run `go run ./cmd/hatch` and `curl http://localhost:8080/healthz` — should return `ok`
- [ ] Run `docker compose up --build` and visit `http://localhost:8080/healthz` — should return `ok` - [ ] Run `docker compose up --build` and visit `http://localhost:8080/healthz` — should return `ok`
- [ ] Read [PR Submission Guidelines](pr-submission-guidelines.md), [Approval Workflow](approval-workflow.md), and [PR Merging Rule FAQ](pr-merging-rule-faq.md)
- [ ] Open your first PR (a README typo fix or doc improvement counts) - [ ] Open your first PR (a README typo fix or doc improvement counts)
**Important:** Only the CEO or CTO can merge pull requests. You cannot merge your own PR.
## Week 1: First Task ## Week 1: First Task
- [ ] Pick up a `good first issue` or grab a task from the backlog with CTO approval - [ ] Pick up a `good first issue` or grab a task from the backlog with CTO approval

View File

@ -1,5 +1,5 @@
#!/bin/bash #!/bin/bash
# Deploy static site to hatch.surf server # Deploy static site to hatch.surf server via Docker
# Usage: ./scripts/deploy-site.sh [--dry-run] # Usage: ./scripts/deploy-site.sh [--dry-run]
# #
# Environment variables required: # Environment variables required:
@ -7,19 +7,21 @@
# DEPLOY_USER - SSH user (e.g., root) # DEPLOY_USER - SSH user (e.g., root)
# DEPLOY_KEY - SSH private key (base64 encoded, optional if using ssh-agent) # DEPLOY_KEY - SSH private key (base64 encoded, optional if using ssh-agent)
# #
# The script deploys the contents of the site/ directory to /var/www/hatch.surf # This script builds a Docker image with static files baked in and deploys it.
# No /var/www mounting required - the image is self-contained.
set -euo pipefail set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
SITE_DIR="$REPO_ROOT/site" SITE_DIR="$REPO_ROOT/site"
REMOTE_DIR="/var/www/hatch.surf"
# Configuration # Configuration
DEPLOY_HOST="${DEPLOY_HOST:-46.250.250.48}" DEPLOY_HOST="${DEPLOY_HOST:-46.250.250.48}"
DEPLOY_USER="${DEPLOY_USER:-root}" DEPLOY_USER="${DEPLOY_USER:-root}"
DEPLOY_KEY="${DEPLOY_KEY:-}" DEPLOY_KEY="${DEPLOY_KEY:-}"
CONTAINER_NAME="hatch-homepage"
IMAGE_NAME="hatch-homepage:latest"
# Parse arguments # Parse arguments
DRY_RUN=false DRY_RUN=false
@ -59,6 +61,10 @@ if [ ! -f "$SITE_DIR/index.html" ]; then
error "index.html not found in $SITE_DIR" error "index.html not found in $SITE_DIR"
fi fi
if [ ! -f "$SITE_DIR/Dockerfile" ]; then
error "Dockerfile not found in $SITE_DIR"
fi
# Setup SSH key if provided # Setup SSH key if provided
SSH_OPTS="-o StrictHostKeyChecking=no -o ConnectTimeout=10" SSH_OPTS="-o StrictHostKeyChecking=no -o ConnectTimeout=10"
if [ -n "$DEPLOY_KEY" ]; then if [ -n "$DEPLOY_KEY" ]; then
@ -68,25 +74,106 @@ if [ -n "$DEPLOY_KEY" ]; then
SSH_OPTS="$SSH_OPTS -i $SSH_KEY_FILE" SSH_OPTS="$SSH_OPTS -i $SSH_KEY_FILE"
fi fi
# Deploy
DEPLOY_TARGET="${DEPLOY_USER}@${DEPLOY_HOST}" DEPLOY_TARGET="${DEPLOY_USER}@${DEPLOY_HOST}"
if [ "$DRY_RUN" = true ]; then if [ "$DRY_RUN" = true ]; then
warn "Dry run - would deploy to $DEPLOY_TARGET:$REMOTE_DIR" warn "Dry run - would build and deploy Docker image to $DEPLOY_TARGET"
rsync -avz --delete --dry-run $SSH_OPTS "$SITE_DIR/" "$DEPLOY_TARGET:$REMOTE_DIR/" warn "Image: $IMAGE_NAME"
warn "Container: $CONTAINER_NAME"
else else
log "Deploying to $DEPLOY_TARGET:$REMOTE_DIR" image_content=""
rsync -avz --delete $SSH_OPTS "$SITE_DIR/" "$DEPLOY_TARGET:$REMOTE_DIR/" log "Building Docker image..."
docker build --no-cache -t "$IMAGE_NAME" "$SITE_DIR"
# Reload nginx # Verify the built image contains correct content
log "Reloading nginx..." log "Verifying built image content..."
ssh $SSH_OPTS "$DEPLOY_TARGET" "nginx -t && systemctl reload nginx" image_content=$(docker run --rm "$IMAGE_NAME" cat /usr/share/nginx/html/index.html 2>/dev/null | head -20)
if echo "$image_content" | grep -q "Hatch.*Self-hostable HTTP request inspector"; then
log "Built image contains correct content"
else
error "Built image may contain stale content!"
echo "Expected: Hatch - Self-hostable HTTP request inspector"
echo "Got: $image_content"
exit 1
fi
log "Saving Docker image..."
docker save "$IMAGE_NAME" | gzip > /tmp/hatch-homepage.tar.gz
log "Uploading image to $DEPLOY_TARGET..."
scp $SSH_OPTS /tmp/hatch-homepage.tar.gz "$DEPLOY_TARGET:/tmp/hatch-homepage.tar.gz"
log "Deploying container on $DEPLOY_TARGET..."
ssh $SSH_OPTS "$DEPLOY_TARGET" << 'ENDSSH'
# Backup current image before loading new one
if docker images hatch-homepage:latest -q | grep -q .; then
echo "Backing up current image as hatch-homepage:backup..."
docker tag hatch-homepage:latest hatch-homepage:backup 2>/dev/null || true
fi
# 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 (static files baked into image, no host mount)
docker run -d \
--name hatch-homepage \
--restart unless-stopped \
-p 127.0.0.1:3000:80 \
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
ENDSSH
# Clean up local temp file
rm -f /tmp/hatch-homepage.tar.gz
log "Deployment complete!" log "Deployment complete!"
log "Site live at https://hatch.surf"
# Run verification to ensure correct content is being served
log "Running deployment verification..."
if "$SCRIPT_DIR/verify-deployment.sh" --host "$DEPLOY_HOST" --user "$DEPLOY_USER" ${DEPLOY_KEY:+--key "$DEPLOY_KEY"}; then
log "Verification passed - correct content is being served"
log "Site live at https://hatch.surf"
else
error "Deployment verification failed!"
warn "Rolling back deployment..."
# Rollback: restore previous container if backup exists
ssh $SSH_OPTS "$DEPLOY_TARGET" << 'ROLLBACK'
# Check if we have a backup image
if docker images hatch-homepage:backup -q | grep -q .; then
echo "Restoring from backup image..."
docker stop hatch-homepage 2>/dev/null || true
docker rm hatch-homepage 2>/dev/null || true
docker run -d \
--name hatch-homepage \
--restart unless-stopped \
-p 127.0.0.1:3000:80 \
hatch-homepage:backup
echo "Rollback complete"
else
echo "No backup image available for rollback"
echo "Manual intervention required"
exit 1
fi
ROLLBACK
error "Deployment failed verification and rollback attempted"
error "Please check the server manually"
exit 1
fi
fi fi
# Cleanup SSH key if created # Cleanup SSH key if created
if [ -n "$SSH_KEY_FILE" ]; then if [ -n "${SSH_KEY_FILE:-}" ]; then
rm -f "$SSH_KEY_FILE" rm -f "$SSH_KEY_FILE"
fi fi

216
scripts/verify-deployment.sh Executable file
View File

@ -0,0 +1,216 @@
#!/bin/bash
# verify-deployment.sh - Verify deployed content matches expected site
# Usage: ./scripts/verify-deployment.sh [--host HOST] [--user USER] [--key KEY]
#
# This script verifies that the deployed site is serving the correct content
# by checking for expected markers and comparing checksums.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
SITE_DIR="$REPO_ROOT/site"
# Default configuration
DEPLOY_HOST="${DEPLOY_HOST:-46.250.250.48}"
DEPLOY_USER="${DEPLOY_USER:-root}"
DEPLOY_KEY="${DEPLOY_KEY:-}"
CONTAINER_NAME="hatch-homepage"
EXPECTED_TITLE="Hatch — Self-hostable HTTP request inspector + mocker"
EXPECTED_DESCRIPTION="Hatch is a self-hostable HTTP request inspector and mocker"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
log() {
echo -e "${GREEN}${NC} $1"
}
warn() {
echo -e "${YELLOW}${NC} $1"
}
error() {
echo -e "${RED}${NC} $1" >&2
return 1
}
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
--host)
DEPLOY_HOST="$2"
shift 2
;;
--user)
DEPLOY_USER="$2"
shift 2
;;
--key)
DEPLOY_KEY="$2"
shift 2
;;
*)
echo "Unknown option: $1"
exit 1
;;
esac
done
# Setup SSH key if provided
SSH_OPTS="-o StrictHostKeyChecking=no -o ConnectTimeout=10"
if [ -n "$DEPLOY_KEY" ]; then
SSH_KEY_FILE=$(mktemp)
echo "$DEPLOY_KEY" | base64 -d > "$SSH_KEY_FILE"
chmod 600 "$SSH_KEY_FILE"
SSH_OPTS="$SSH_OPTS -i $SSH_KEY_FILE"
fi
DEPLOY_TARGET="${DEPLOY_USER}@${DEPLOY_HOST}"
# Function to verify container is running
verify_container_running() {
local container_status
container_status=$(ssh $SSH_OPTS "$DEPLOY_TARGET" "docker inspect -f '{{.State.Status}}' $CONTAINER_NAME 2>/dev/null || echo 'not_found'")
if [ "$container_status" = "running" ]; then
log "Container $CONTAINER_NAME is running"
return 0
else
error "Container $CONTAINER_NAME is not running (status: $container_status)"
return 1
fi
}
# Function to verify content via HTTP
verify_content() {
local response
local http_code
# Wait for container to be ready
sleep 2
# Fetch the page content
response=$(ssh $SSH_OPTS "$DEPLOY_TARGET" "curl -s -o /dev/null -w '%{http_code}' http://localhost:3000/ 2>/dev/null || echo '000'")
http_code="$response"
if [ "$http_code" != "200" ]; then
error "HTTP request failed with status code: $http_code"
return 1
fi
log "HTTP endpoint returns 200 OK"
# Fetch actual content
local content
content=$(ssh $SSH_OPTS "$DEPLOY_TARGET" "curl -s http://localhost:3000/ 2>/dev/null")
# Check for expected title
if echo "$content" | grep -q "$EXPECTED_TITLE"; then
log "Expected title found: '$EXPECTED_TITLE'"
else
error "Expected title not found. Possible stale content detected!"
echo "Expected: $EXPECTED_TITLE"
echo "Got content snippet:"
echo "$content" | head -20
return 1
fi
# Check for expected description
if echo "$content" | grep -q "$EXPECTED_DESCRIPTION"; then
log "Expected description found"
else
warn "Expected description not found in content"
fi
# Check for Next.js template markers (sign of stale content)
if echo "$content" | grep -qi "next.js\|create-next-app\|get started by editing"; then
error "STALE CONTENT DETECTED: Found Next.js template markers!"
return 1
else
log "No Next.js template markers found (good)"
fi
return 0
}
# Function to verify static assets
verify_assets() {
local css_response
local js_response
# Check CSS file
css_response=$(ssh $SSH_OPTS "$DEPLOY_TARGET" "curl -s -o /dev/null -w '%{http_code}' http://localhost:3000/style.css 2>/dev/null || echo '000'")
if [ "$css_response" = "200" ]; then
log "CSS asset accessible (HTTP 200)"
else
warn "CSS asset returned HTTP $css_response"
fi
# Check JS file
js_response=$(ssh $SSH_OPTS "$DEPLOY_TARGET" "curl -s -o /dev/null -w '%{http_code}' http://localhost:3000/main.js 2>/dev/null || echo '000'")
if [ "$js_response" = "200" ]; then
log "JS asset accessible (HTTP 200)"
else
warn "JS asset returned HTTP $js_response"
fi
return 0
}
# Function to verify container logs for errors
verify_logs() {
local logs
logs=$(ssh $SSH_OPTS "$DEPLOY_TARGET" "docker logs --tail 10 $CONTAINER_NAME 2>&1")
if echo "$logs" | grep -qi "error\|fatal\|panic"; then
warn "Found error entries in container logs:"
echo "$logs" | grep -i "error\|fatal\|panic" | head -5
return 1
else
log "No critical errors in container logs"
return 0
fi
}
# Main verification
main() {
echo "=== Deployment Verification ==="
echo "Host: $DEPLOY_HOST"
echo "Container: $CONTAINER_NAME"
echo ""
local exit_code=0
# Run all verification checks
verify_container_running || exit_code=1
verify_content || exit_code=1
verify_assets || exit_code=1
verify_logs || exit_code=1
echo ""
if [ $exit_code -eq 0 ]; then
echo -e "${GREEN}=== All verification checks passed ===${NC}"
else
echo -e "${RED}=== Verification FAILED ===${NC}"
echo "Possible actions:"
echo " 1. Check container logs: ssh $DEPLOY_TARGET 'docker logs $CONTAINER_NAME'"
echo " 2. Rebuild and redeploy: ./scripts/deploy-site.sh"
echo " 3. Rollback to previous version if available"
fi
# Cleanup SSH key if created
if [ -n "${SSH_KEY_FILE:-}" ]; then
rm -f "$SSH_KEY_FILE"
fi
return $exit_code
}
# Run main function
main "$@"

View File

@ -22,46 +22,39 @@ site/
## Deployment ## Deployment
The site is automatically deployed to GitHub Pages via the `deploy-site.yml` workflow when changes are pushed to the `main` branch. The site is deployed via Docker to the hatch.surf server. Static files are baked into the Docker image — no host directory mounting required.
### GitHub Pages Setup ### Docker Deployment
1. Go to repository Settings → Pages The GitHub Actions workflow (`deploy-site.yml`) automatically:
2. Source: Deploy from a branch 1. Builds a Docker image with static files baked in
3. Branch: `main`, folder: `/site` 2. Pushes the image to the server
4. Custom domain: `hatch.surf` 3. Restarts the container
### Manual Deployment
```bash
# From repo root
./scripts/deploy-site.sh
# Dry run
./scripts/deploy-site.sh --dry-run
```
### DNS Configuration ### DNS Configuration
To point `hatch.surf` to GitHub Pages: To point `hatch.surf` to your server:
1. Add these DNS records: 1. Add A record pointing to your server IP
- Type: A, Name: @, Value: 185.199.108.153 2. Enable HTTPS via Caddy or Let's Encrypt
- Type: A, Name: @, Value: 185.199.109.153
- Type: A, Name: @, Value: 185.199.110.153
- Type: A, Name: @, Value: 185.199.111.153
- Type: AAAA, Name: @, Value: 2606:50c0:8000::153
- Type: AAAA, Name: @, Value: 2606:50c0:8001::153
- Type: AAAA, Name: @, Value: 2606:50c0:8002::153
- Type: AAAA, Name: @, Value: 2606:50c0:8003::153
2. Enable HTTPS in GitHub Pages settings ### Architecture
### 301 Redirect from GitHub README The deployment uses:
- **Docker image**: nginx:alpine with static files baked in
Add this to the README.md or as a GitHub Pages redirect: - **No host mounts**: Image is self-contained, no `/var/www` dependency
- **Caddy**: Optional reverse proxy for TLS termination
```html - **Port 3000**: Internal port, proxied by Caddy on 80/443
<!-- In site/index.html or a dedicated redirect page -->
<meta http-equiv="refresh" content="0; url=/blog/why-we-are-building-hatch/">
```
Or use a JavaScript redirect for better SEO:
```javascript
// In a script tag or separate JS file
window.location.replace('/blog/why-we-are-building-hatch/');
```
## Local Development ## Local Development

View File

@ -3,9 +3,4 @@ services:
build: . build: .
ports: ports:
- "3000:80" - "3000:80"
volumes:
- .:/usr/share/nginx/html:ro
environment:
- NGINX_HOST=localhost
- NGINX_PORT=80
restart: unless-stopped restart: unless-stopped