Compare commits

..

No commits in common. "0f7492f6b42abe3d437864717c4c748e0f2a7c0f" and "79e74b4810d04d149aeb9765170900efa50cb6ed" have entirely different histories.

6 changed files with 19 additions and 342 deletions

1
.gitignore vendored
View File

@ -26,4 +26,3 @@ yarn-error.log*
# typescript # typescript
*.tsbuildinfo *.tsbuildinfo
next-env.d.ts next-env.d.ts
out.backup.*

View File

@ -1,200 +0,0 @@
# Hatch Landing Page — Deployment Guide
## Overview
The Hatch landing page (`hatch.surf`) is a **static HTML** site deployed via **Docker** on the production server.
**Key principle**: All changes MUST go through the git repository and automated deployment. Never edit files directly on the server or use `/var/www`.
---
## Repository
**Location**: `/home/nara/apps/web/`
**Structure**:
```
out/ # Static files served by nginx
index.html # Main landing page
style.css # Styles
main.js # Anime.js animations
brand/favicon/ # Favicon assets
Dockerfile # Docker build configuration (static files only)
docker-compose.yml # Container orchestration
deploy.sh # Deployment script
```
---
## Deployment Flow
### Automatic (Recommended)
1. Push changes to `main` branch
2. Gitea webhook triggers `gitea-webhook.py`
3. Script runs `deploy.sh` automatically
4. Docker image rebuilds and container restarts
### Manual
```bash
cd /home/nara/apps/web
./deploy.sh
```
**What `deploy.sh` does**:
1. `git pull origin main` — fetches latest changes
2. `docker build -t hatch-web:latest .` — rebuilds image
3. Stops and removes old container
4. `docker compose up -d` — starts new container
5. Verifies deployment with health check
---
## Docker Setup
**Container**: `hatch-web`
**Port**: `8080` (mapped to host)
**Internal**: nginx serving static files on port 80
**Network**: `hatch-network`
**Check status**:
```bash
docker ps | grep hatch-web
docker logs hatch-web
```
**Restart without rebuild**:
```bash
docker compose -f /home/nara/apps/web/docker-compose.yml restart
```
---
## Making Changes
### 1. Edit the static files
```bash
cd /home/nara/apps/web
# Edit landing page
vim out/index.html
# Edit styles
vim out/style.css
# Edit animations
vim out/main.js
```
### 2. Test locally (optional)
```bash
# Serve files locally
cd out && python3 -m http.server 3000
# Visit http://localhost:3000
```
### 3. Commit and push
```bash
git add .
git commit -m "Description of changes"
git push origin main
```
### 4. Deploy
Either wait for webhook or run manually:
```bash
./deploy.sh
```
### 5. Verify
```bash
curl -s http://localhost:8080 | head -20
# Or visit https://hatch.surf
```
---
## Common Mistakes to Avoid
**DON'T**: Edit files in `/var/www/html/`
**DON'T**: Edit files directly inside the Docker container
**DON'T**: Work on static HTML/CSS files in a workspace without committing
**DON'T**: Copy files to the server without using git
**DO**: Edit files in `/home/nara/apps/web/out/`
**DO**: Commit changes to git
**DO**: Use `deploy.sh` for deployment
**DO**: Test locally before pushing
---
## Rollback
If a deployment causes issues:
```bash
cd /home/nara/apps/web
# View recent commits
git log --oneline -10
# Revert to previous version
git revert HEAD
git push origin main
# Redeploy
./deploy.sh
```
Previous builds are backed up in `out.backup.*` directories.
---
## Cache Headers
**HTML pages**: `no-cache, must-revalidate` — browsers always revalidate, ensuring fresh content after deploys.
**Static assets** (CSS/JS/images): `public, immutable` with 1-year expiry.
**Nginx config**: Built into the Dockerfile.
If users report seeing old content after deployment:
1. Verify the container is running: `docker ps | grep hatch-web`
2. Check the HTML has new content: `curl -s http://localhost:8080 | head -5`
3. Ask user to hard refresh: Ctrl+Shift+R (Chrome/Firefox) or Cmd+Shift+R (Mac)
---
## Troubleshooting
**Container not starting**:
```bash
docker logs hatch-web
docker compose -f /home/nara/apps/web/docker-compose.yml up -d
```
**Changes not showing**:
1. Verify commit pushed: `git log --oneline -1`
2. Check container was rebuilt: `docker images | grep hatch-web`
3. Force rebuild: `cd /home/nara/apps/web && docker build -t hatch-web:latest . && docker compose up -d`
**Port conflict**:
```bash
lsof -i :8080
# Kill conflicting process or change port in docker-compose.yml
```
---
## Contact
- **DevOps**: Jing Yang (Sam Lee)
- **CTO**: Jordan Patel
- **Repository**: `/home/nara/apps/web/`
- **Live site**: https://hatch.surf

View File

@ -1,11 +1,24 @@
# Production - Static landing page # Stage 1: Build
FROM node:20-alpine AS builder
WORKDIR /app
# Install dependencies
COPY package.json package-lock.json ./
RUN npm ci
# Build the application
COPY . .
RUN npm run build
# Stage 2: Production
FROM nginx:alpine AS production FROM nginx:alpine AS production
# Copy custom nginx config # Copy custom nginx config
COPY nginx.conf /etc/nginx/conf.d/default.conf COPY nginx.conf /etc/nginx/conf.d/default.conf
# Copy static files from build context # Copy static export (everything is in /app/out)
COPY out/ /usr/share/nginx/html COPY --from=builder /app/out /usr/share/nginx/html
# Expose port (internal only, no SSL) # Expose port (internal only, no SSL)
EXPOSE 80 EXPOSE 80

View File

@ -1,28 +0,0 @@
# Stage 1: Build
FROM node:20-alpine AS builder
WORKDIR /app
# Install dependencies
COPY package.json package-lock.json ./
RUN npm ci
# Build the application
COPY . .
RUN npm run build
# Stage 2: Production
FROM nginx:alpine AS production
# Copy custom nginx config
COPY nginx.conf /etc/nginx/conf.d/default.conf
# Copy static export (everything is in /app/out)
COPY --from=builder /app/out /usr/share/nginx/html
# Expose port (internal only, no SSL)
EXPOSE 80
# Health check
HEALTHCHECK --interval=30s --timeout=3s \
CMD wget -qO- http://localhost:80/ || exit 1

View File

@ -10,11 +10,10 @@ services:
networks: networks:
- hatch-network - hatch-network
healthcheck: healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost/ || exit 1"] test: ["CMD", "wget", "-qO-", "http://localhost:80/"]
interval: 30s interval: 30s
timeout: 10s timeout: 3s
retries: 5 retries: 3
start_period: 30s
networks: networks:
hatch-network: hatch-network:

View File

@ -1,106 +0,0 @@
#!/usr/bin/env python3
"""
Gitea webhook server for hatch.surf deployment
Listens on port 9000 for POST requests from Gitea
"""
import http.server
import json
import subprocess
import logging
from pathlib import Path
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('/home/nara/logs/gitea-webhook.log'),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
DEPLOY_SCRIPT = Path(__file__).parent / "deploy.sh"
class WebhookHandler(http.server.BaseHTTPRequestHandler):
def do_POST(self):
"""Handle POST request from Gitea webhook"""
content_length = int(self.headers.get('Content-Length', 0))
body = self.rfile.read(content_length) if content_length > 0 else b''
try:
payload = json.loads(body) if body else {}
event = self.headers.get('X-Gitea-Event', 'unknown')
logger.info(f"Received webhook: {event}")
logger.info(f"Payload: {json.dumps(payload, indent=2)}")
# Only deploy on push to main branch
if event == 'push':
ref = payload.get('ref', '')
if ref == 'refs/heads/main':
logger.info("Push to main branch - triggering deployment...")
self._deploy()
else:
logger.info(f"Ignoring push to {ref}")
else:
logger.info(f"Ignoring event: {event}")
self.send_response(200)
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write(json.dumps({"status": "ok"}).encode())
except Exception as e:
logger.error(f"Error processing webhook: {e}")
self.send_response(500)
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write(json.dumps({"status": "error", "message": str(e)}).encode())
def _deploy(self):
"""Run the deployment script"""
try:
logger.info(f"Running deploy script: {DEPLOY_SCRIPT}")
result = subprocess.run(
[str(DEPLOY_SCRIPT)],
capture_output=True,
text=True,
timeout=300 # 5 minute timeout
)
if result.returncode == 0:
logger.info("Deployment successful!")
logger.info(f"Output: {result.stdout}")
else:
logger.error(f"Deployment failed with code {result.returncode}")
logger.error(f"Stdout: {result.stdout}")
logger.error(f"Stderr: {result.stderr}")
except subprocess.TimeoutExpired:
logger.error("Deployment timed out after 5 minutes")
except Exception as e:
logger.error(f"Error running deployment: {e}")
def do_GET(self):
"""Health check endpoint"""
self.send_response(200)
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write(json.dumps({"status": "healthy"}).encode())
def log_message(self, format, *args):
"""Override to use our logger"""
logger.info(format % args)
def main():
server = http.server.HTTPServer(('0.0.0.0', 9000), WebhookHandler)
logger.info("Starting webhook server on port 9000...")
try:
server.serve_forever()
except KeyboardInterrupt:
logger.info("Shutting down webhook server...")
server.shutdown()
if __name__ == '__main__':
main()