982257b4c1
Migrate all user-facing links from GitHub to self-hosted Forgejo. Go module imports unchanged (separate concern).
1054 lines
21 KiB
Markdown
1054 lines
21 KiB
Markdown
# Deployment Guide
|
|
|
|
**Note**: This is my personal CV website. While the code is open-source (MIT license), this deployment guide is primarily for my own use. The site may be modified without notice.
|
|
|
|
Complete guide for deploying the CV/Resume application to various platforms and environments.
|
|
|
|
## Table of Contents
|
|
|
|
- [Introduction](#introduction)
|
|
- [Prerequisites](#prerequisites)
|
|
- [Quick Start](#quick-start)
|
|
- [Deployment Methods](#deployment-methods)
|
|
- [VPS Deployment](#vps-deployment)
|
|
- [Cloud Platforms](#cloud-platforms)
|
|
- [Manual Deployment](#manual-deployment)
|
|
- [Configuration](#configuration)
|
|
- [Post-Deployment](#post-deployment)
|
|
- [Troubleshooting](#troubleshooting)
|
|
- [Updates & Maintenance](#updates--maintenance)
|
|
|
|
---
|
|
|
|
## Introduction
|
|
|
|
This guide covers deploying the CV/Resume web application built with Go 1.25.1, HTMX, and chromedp for PDF generation. The application supports bilingual content (English/Spanish) and requires Chromium for PDF export functionality.
|
|
|
|
**What this guide covers:**
|
|
- VPS deployment with systemd
|
|
- Cloud platform deployment (Fly.io, Google Cloud Run, AWS)
|
|
- Manual deployment
|
|
- Configuration for different environments
|
|
- Security hardening and production best practices
|
|
- Monitoring, logging, and health checks
|
|
- Zero-downtime updates and rollback procedures
|
|
|
|
---
|
|
|
|
## Prerequisites
|
|
|
|
### For All Deployment Methods
|
|
- **Go 1.25.1+** (if building from source)
|
|
- **Chromium/Chrome** (for PDF generation via chromedp)
|
|
- **Git** (to clone repository)
|
|
- Basic command-line knowledge
|
|
|
|
### For Specific Methods
|
|
- **Cloud Platforms**: Account on chosen platform (Fly.io, GCP, AWS, etc.)
|
|
- **VPS**: SSH access, sudo privileges, domain name (optional)
|
|
- **Production**: SSL certificate (Let's Encrypt recommended)
|
|
|
|
### System Requirements
|
|
- **CPU**: 1 core minimum (2+ recommended)
|
|
- **RAM**: 512MB minimum (1GB+ recommended for PDF generation)
|
|
- **Disk**: 200MB for application + dependencies
|
|
- **Network**: Port 1999 (default) or your chosen port
|
|
|
|
---
|
|
|
|
## Quick Start
|
|
|
|
The fastest way to get started:
|
|
|
|
```bash
|
|
# Clone repository
|
|
git clone https://repos.txeo.club/txeo/cv-site.git
|
|
cd cv-site
|
|
|
|
# Copy environment configuration
|
|
cp .env.example .env
|
|
|
|
# Build and run
|
|
make build
|
|
GO_ENV=production ./cv-server
|
|
|
|
# Verify deployment
|
|
curl http://localhost:1999/health
|
|
```
|
|
|
|
Access the application at **http://localhost:1999**
|
|
|
|
---
|
|
|
|
## Deployment Methods
|
|
|
|
### VPS Deployment
|
|
|
|
Traditional server deployment with systemd service management.
|
|
|
|
#### 1. Systemd Service Setup
|
|
|
|
**Prerequisites**:
|
|
```bash
|
|
# Install Go 1.25.1
|
|
wget https://go.dev/dl/go1.25.1.linux-amd64.tar.gz
|
|
sudo rm -rf /usr/local/go
|
|
sudo tar -C /usr/local -xzf go1.25.1.linux-amd64.tar.gz
|
|
echo 'export PATH=$PATH:/usr/local/go/bin' >> ~/.bashrc
|
|
source ~/.bashrc
|
|
|
|
# Install Chromium
|
|
sudo apt update
|
|
sudo apt install -y chromium-browser curl git
|
|
|
|
# Verify installations
|
|
go version
|
|
chromium-browser --version
|
|
```
|
|
|
|
**Deploy application**:
|
|
```bash
|
|
# Clone repository
|
|
cd /home/txeo/Git/yo
|
|
git clone https://repos.txeo.club/txeo/cv-site.git cv
|
|
cd cv
|
|
|
|
# Build production binary
|
|
make build
|
|
|
|
# Test binary
|
|
./cv-server --version
|
|
./cv-server # Test locally, press Ctrl+C to stop
|
|
```
|
|
|
|
**Install systemd service**:
|
|
|
|
The project includes `config/systemd/cv.service`. Review and customize:
|
|
|
|
```bash
|
|
# Edit service file
|
|
nano config/systemd/cv.service
|
|
```
|
|
|
|
Ensure paths match your setup:
|
|
```ini
|
|
[Unit]
|
|
Description=CV Website Service
|
|
After=network.target
|
|
Wants=network-online.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
User=txeo
|
|
Group=txeo
|
|
WorkingDirectory=/home/txeo/Git/yo/cv
|
|
ExecStart=/home/txeo/Git/yo/cv/cv-server
|
|
|
|
Environment="GO_ENV=production"
|
|
Environment="PORT=1999"
|
|
|
|
Restart=always
|
|
RestartSec=5
|
|
StartLimitInterval=60
|
|
StartLimitBurst=3
|
|
|
|
StandardOutput=append:/var/log/cv.log
|
|
StandardError=append:/var/log/cv.log
|
|
SyslogIdentifier=cv
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
```
|
|
|
|
**Install and start**:
|
|
```bash
|
|
# Create log file
|
|
sudo touch /var/log/cv.log
|
|
sudo chown txeo:txeo /var/log/cv.log
|
|
|
|
# Install service
|
|
sudo cp config/systemd/cv.service /etc/systemd/system/
|
|
sudo systemctl daemon-reload
|
|
sudo systemctl enable cv
|
|
sudo systemctl start cv
|
|
|
|
# Check status
|
|
sudo systemctl status cv
|
|
|
|
# View logs
|
|
sudo journalctl -u cv -f
|
|
tail -f /var/log/cv.log
|
|
```
|
|
|
|
**Manage service**:
|
|
```bash
|
|
# Stop
|
|
sudo systemctl stop cv
|
|
|
|
# Restart
|
|
sudo systemctl restart cv
|
|
|
|
# Reload after config changes
|
|
sudo systemctl daemon-reload
|
|
sudo systemctl restart cv
|
|
|
|
# View logs
|
|
sudo journalctl -u cv --since "1 hour ago"
|
|
```
|
|
|
|
#### 2. Nginx Reverse Proxy Configuration
|
|
|
|
**Install Nginx**:
|
|
```bash
|
|
sudo apt install -y nginx
|
|
```
|
|
|
|
**Create Nginx configuration** (`/etc/nginx/sites-available/cv`):
|
|
```nginx
|
|
# Rate limiting zone
|
|
limit_req_zone $binary_remote_addr zone=cv_limit:10m rate=10r/s;
|
|
|
|
# Upstream backend
|
|
upstream cv_backend {
|
|
server 127.0.0.1:1999 max_fails=3 fail_timeout=30s;
|
|
}
|
|
|
|
# HTTP → HTTPS redirect
|
|
server {
|
|
listen 80;
|
|
listen [::]:80;
|
|
server_name your-domain.com;
|
|
|
|
# ACME challenge for Let's Encrypt
|
|
location /.well-known/acme-challenge/ {
|
|
root /var/www/certbot;
|
|
}
|
|
|
|
location / {
|
|
return 301 https://$server_name$request_uri;
|
|
}
|
|
}
|
|
|
|
# HTTPS server
|
|
server {
|
|
listen 443 ssl http2;
|
|
listen [::]:443 ssl http2;
|
|
server_name your-domain.com;
|
|
|
|
# SSL configuration
|
|
ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
|
|
ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;
|
|
ssl_protocols TLSv1.2 TLSv1.3;
|
|
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
|
|
ssl_prefer_server_ciphers off;
|
|
ssl_session_cache shared:SSL:10m;
|
|
ssl_session_timeout 10m;
|
|
|
|
# Security headers
|
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
add_header X-XSS-Protection "1; mode=block" always;
|
|
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
|
|
|
# Gzip compression
|
|
gzip on;
|
|
gzip_vary on;
|
|
gzip_proxied any;
|
|
gzip_comp_level 6;
|
|
gzip_types text/plain text/css text/xml application/json application/javascript application/xml+rss application/atom+xml image/svg+xml;
|
|
|
|
# Static files caching
|
|
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
|
|
proxy_pass http://cv_backend;
|
|
expires 1y;
|
|
add_header Cache-Control "public, immutable";
|
|
}
|
|
|
|
# Main proxy
|
|
location / {
|
|
# Rate limiting
|
|
limit_req zone=cv_limit burst=20 nodelay;
|
|
|
|
proxy_pass http://cv_backend;
|
|
proxy_http_version 1.1;
|
|
|
|
# Headers
|
|
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;
|
|
|
|
# Timeouts
|
|
proxy_connect_timeout 60s;
|
|
proxy_send_timeout 60s;
|
|
proxy_read_timeout 60s;
|
|
|
|
# Buffering
|
|
proxy_buffering on;
|
|
proxy_buffer_size 4k;
|
|
proxy_buffers 8 4k;
|
|
proxy_busy_buffers_size 8k;
|
|
}
|
|
|
|
# Health check endpoint (no rate limit)
|
|
location /health {
|
|
proxy_pass http://cv_backend;
|
|
access_log off;
|
|
}
|
|
|
|
# Access and error logs
|
|
access_log /var/log/nginx/cv-access.log;
|
|
error_log /var/log/nginx/cv-error.log;
|
|
}
|
|
```
|
|
|
|
**Enable configuration**:
|
|
```bash
|
|
# Enable site
|
|
sudo ln -s /etc/nginx/sites-available/cv /etc/nginx/sites-enabled/
|
|
|
|
# Test configuration
|
|
sudo nginx -t
|
|
|
|
# Reload Nginx
|
|
sudo systemctl reload nginx
|
|
```
|
|
|
|
#### 3. SSL/TLS with Let's Encrypt
|
|
|
|
```bash
|
|
# Install Certbot
|
|
sudo apt install -y certbot python3-certbot-nginx
|
|
|
|
# Obtain certificate (Nginx plugin)
|
|
sudo certbot --nginx -d your-domain.com
|
|
|
|
# Or manual with webroot
|
|
sudo certbot certonly --webroot -w /var/www/certbot -d your-domain.com
|
|
|
|
# Test auto-renewal
|
|
sudo certbot renew --dry-run
|
|
|
|
# Auto-renewal is configured via systemd timer
|
|
sudo systemctl status certbot.timer
|
|
```
|
|
|
|
**Certificate renewal cron** (if not using systemd timer):
|
|
```bash
|
|
# Add to crontab
|
|
sudo crontab -e
|
|
|
|
# Add line:
|
|
0 3 * * * certbot renew --quiet --post-hook "systemctl reload nginx"
|
|
```
|
|
|
|
#### 4. Process Management
|
|
|
|
**Makefile targets** (already included):
|
|
```bash
|
|
# Install as service
|
|
make install-service
|
|
|
|
# Update service
|
|
make update-service
|
|
```
|
|
|
|
**Manual management**:
|
|
```bash
|
|
# Check if running
|
|
ps aux | grep cv-server
|
|
|
|
# Kill process
|
|
pkill cv-server
|
|
|
|
# Start with nohup (not recommended, use systemd)
|
|
nohup ./cv-server > /var/log/cv.log 2>&1 &
|
|
```
|
|
|
|
---
|
|
|
|
### Cloud Platforms
|
|
|
|
#### Fly.io (Recommended for Quick Cloud Deployment)
|
|
|
|
**Prerequisites**: Install [flyctl](https://fly.io/docs/hands-on/install-flyctl/)
|
|
|
|
**Note**: This requires building from source on Fly.io's infrastructure.
|
|
|
|
```bash
|
|
# Login
|
|
fly auth login
|
|
|
|
# Initialize app
|
|
fly launch --no-deploy
|
|
|
|
# Configure fly.toml
|
|
cat > fly.toml << 'EOF'
|
|
app = "your-cv-app-name"
|
|
primary_region = "mad" # Madrid, Spain (choose your region)
|
|
|
|
[build]
|
|
[build.args]
|
|
GO_VERSION = "1.25.1"
|
|
|
|
[env]
|
|
PORT = "8080" # Fly.io uses 8080 internally
|
|
GO_ENV = "production"
|
|
TEMPLATE_HOT_RELOAD = "false"
|
|
|
|
[http_service]
|
|
internal_port = 8080
|
|
force_https = true
|
|
auto_stop_machines = true
|
|
auto_start_machines = true
|
|
min_machines_running = 1
|
|
processes = ["app"]
|
|
|
|
[[vm]]
|
|
cpu_kind = "shared"
|
|
cpus = 1
|
|
memory_mb = 512
|
|
|
|
[[services.http_checks]]
|
|
interval = 30000
|
|
grace_period = "10s"
|
|
method = "get"
|
|
path = "/health"
|
|
protocol = "http"
|
|
timeout = 5000
|
|
EOF
|
|
|
|
# Deploy
|
|
fly deploy
|
|
|
|
# Open in browser
|
|
fly open
|
|
|
|
# View logs
|
|
fly logs
|
|
|
|
# Scale
|
|
fly scale count 2
|
|
|
|
# SSH into VM
|
|
fly ssh console
|
|
```
|
|
|
|
#### Google Cloud Run
|
|
|
|
```bash
|
|
# Create a simple Dockerfile for Cloud Run
|
|
cat > Dockerfile.cloudrun << 'EOF'
|
|
FROM golang:1.25-alpine AS builder
|
|
RUN apk add --no-cache git
|
|
WORKDIR /app
|
|
COPY go.* ./
|
|
RUN go mod download
|
|
COPY . .
|
|
RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o cv-server .
|
|
|
|
FROM alpine:latest
|
|
RUN apk --no-cache add chromium ca-certificates
|
|
COPY --from=builder /app/cv-server /cv-server
|
|
COPY data /data
|
|
COPY templates /templates
|
|
COPY static /static
|
|
EXPOSE 8080
|
|
CMD ["/cv-server"]
|
|
EOF
|
|
|
|
# Build and push to Google Container Registry
|
|
gcloud builds submit --tag gcr.io/YOUR_PROJECT_ID/cv-server
|
|
|
|
# Deploy to Cloud Run
|
|
gcloud run deploy cv-server \
|
|
--image gcr.io/YOUR_PROJECT_ID/cv-server \
|
|
--platform managed \
|
|
--region europe-west1 \
|
|
--allow-unauthenticated \
|
|
--port 8080 \
|
|
--memory 512Mi \
|
|
--cpu 1 \
|
|
--min-instances 0 \
|
|
--max-instances 10 \
|
|
--set-env-vars GO_ENV=production,TEMPLATE_HOT_RELOAD=false,PORT=8080
|
|
|
|
# Get service URL
|
|
gcloud run services describe cv-server --region europe-west1 --format 'value(status.url)'
|
|
|
|
# View logs
|
|
gcloud logging read "resource.type=cloud_run_revision AND resource.labels.service_name=cv-server" --limit 50 --format json
|
|
```
|
|
|
|
#### AWS (EC2 or Lightsail)
|
|
|
|
For AWS deployment without containers, deploy directly to EC2 or Lightsail:
|
|
|
|
```bash
|
|
# SSH into your EC2/Lightsail instance
|
|
ssh -i your-key.pem ubuntu@your-instance-ip
|
|
|
|
# Follow VPS deployment steps above
|
|
# Install Go, Chromium, clone repo, build, and set up systemd
|
|
```
|
|
|
|
#### Railway / Render
|
|
|
|
These platforms can build from source using their build systems.
|
|
|
|
**Railway**:
|
|
1. Connect GitHub repository
|
|
2. Railway auto-detects Go project
|
|
3. Set environment variables in dashboard:
|
|
- `PORT` = 8080
|
|
- `GO_ENV` = production
|
|
4. Deploy automatically on git push
|
|
|
|
**Render** (create `render.yaml`):
|
|
```yaml
|
|
services:
|
|
- type: web
|
|
name: cv-server
|
|
env: go
|
|
buildCommand: go build -o cv-server
|
|
startCommand: ./cv-server
|
|
envVars:
|
|
- key: GO_ENV
|
|
value: production
|
|
- key: PORT
|
|
value: 10000
|
|
healthCheckPath: /health
|
|
```
|
|
|
|
---
|
|
|
|
### Manual Deployment
|
|
|
|
Build and run without services.
|
|
|
|
#### 1. Build from Source
|
|
|
|
```bash
|
|
# Clone repository
|
|
git clone https://repos.txeo.club/txeo/cv-site.git
|
|
cd cv-site
|
|
|
|
# Install dependencies
|
|
go mod download
|
|
go mod verify
|
|
|
|
# Build production binary
|
|
make build
|
|
|
|
# Or manual build with optimizations
|
|
CGO_ENABLED=0 go build -ldflags="-s -w" -o cv-server .
|
|
|
|
# Verify binary
|
|
./cv-server --version
|
|
file cv-server # Should show "statically linked"
|
|
```
|
|
|
|
#### 2. Run as Standalone Binary
|
|
|
|
```bash
|
|
# Development mode
|
|
GO_ENV=development ./cv-server
|
|
|
|
# Production mode
|
|
GO_ENV=production PORT=1999 ./cv-server
|
|
|
|
# With custom configuration
|
|
PORT=8080 TEMPLATE_HOT_RELOAD=false ./cv-server
|
|
|
|
# Background with nohup
|
|
nohup ./cv-server > app.log 2>&1 &
|
|
|
|
# Stop background process
|
|
pkill cv-server
|
|
```
|
|
|
|
#### 3. Environment Variables
|
|
|
|
Create `.env` file:
|
|
```bash
|
|
cp .env.example .env
|
|
nano .env
|
|
```
|
|
|
|
Edit:
|
|
```bash
|
|
PORT=1999
|
|
GO_ENV=production
|
|
TEMPLATE_HOT_RELOAD=false
|
|
READ_TIMEOUT=30
|
|
WRITE_TIMEOUT=30
|
|
```
|
|
|
|
Load and run:
|
|
```bash
|
|
# Load .env and run
|
|
export $(cat .env | xargs) && ./cv-server
|
|
```
|
|
|
|
---
|
|
|
|
## Configuration
|
|
|
|
### Environment Variables Reference
|
|
|
|
| Variable | Default | Description | Values |
|
|
|----------|---------|-------------|--------|
|
|
| `PORT` | `1999` | Server port | Any valid port (1-65535) |
|
|
| `HOST` | `localhost` | Bind address | `localhost`, `0.0.0.0`, IP address |
|
|
| `GO_ENV` | `development` | Environment mode | `development`, `production` |
|
|
| `TEMPLATE_HOT_RELOAD` | `true` | Auto-reload templates | `true`, `false` |
|
|
| `TEMPLATE_DIR` | `templates` | Templates directory | Path to templates |
|
|
| `PARTIALS_DIR` | `templates/partials` | Partials directory | Path to partials |
|
|
| `DATA_DIR` | `data` | CV data directory | Path to JSON files |
|
|
| `READ_TIMEOUT` | `15` | Read timeout (seconds) | Integer |
|
|
| `WRITE_TIMEOUT` | `15` | Write timeout (seconds) | Integer |
|
|
| `CHROME_BIN` | Auto-detected | Chrome binary path | Full path to chrome/chromium |
|
|
|
|
### .env File Setup
|
|
|
|
**Development** (`.env`):
|
|
```bash
|
|
PORT=1999
|
|
HOST=localhost
|
|
GO_ENV=development
|
|
TEMPLATE_HOT_RELOAD=true
|
|
READ_TIMEOUT=15
|
|
WRITE_TIMEOUT=15
|
|
```
|
|
|
|
**Production** (`.env.production`):
|
|
```bash
|
|
PORT=1999
|
|
HOST=0.0.0.0
|
|
GO_ENV=production
|
|
TEMPLATE_HOT_RELOAD=false
|
|
READ_TIMEOUT=30
|
|
WRITE_TIMEOUT=30
|
|
BASE_URL=https://your-domain.com
|
|
VERSION=1.0.0
|
|
```
|
|
|
|
### Production vs Development Settings
|
|
|
|
| Setting | Development | Production |
|
|
|---------|-------------|------------|
|
|
| `GO_ENV` | `development` | `production` |
|
|
| `TEMPLATE_HOT_RELOAD` | `true` (fast iteration) | `false` (performance) |
|
|
| `HOST` | `localhost` (local only) | `0.0.0.0` (external access) |
|
|
| `READ_TIMEOUT` | `15s` (relaxed) | `30s` (prevent slow clients) |
|
|
| `WRITE_TIMEOUT` | `15s` (relaxed) | `30s` (prevent slow responses) |
|
|
| Logging | Verbose | Structured JSON |
|
|
| Error Details | Full stack traces | Generic messages |
|
|
|
|
---
|
|
|
|
## Post-Deployment
|
|
|
|
### Health Checks
|
|
|
|
**Manual check**:
|
|
```bash
|
|
# Basic health
|
|
curl http://localhost:1999/health
|
|
|
|
# With details
|
|
curl -s http://localhost:1999/health | jq .
|
|
|
|
# Expected response:
|
|
{
|
|
"status": "healthy",
|
|
"timestamp": "2025-11-09T12:00:00Z",
|
|
"version": "1.0.0"
|
|
}
|
|
```
|
|
|
|
**Automated monitoring**:
|
|
```bash
|
|
# Simple monitoring script
|
|
cat > /usr/local/bin/cv-monitor.sh << 'EOF'
|
|
#!/bin/bash
|
|
HEALTH_URL="http://localhost:1999/health"
|
|
THRESHOLD=3
|
|
FAILURES=0
|
|
|
|
while true; do
|
|
if ! curl -sf "$HEALTH_URL" > /dev/null; then
|
|
FAILURES=$((FAILURES + 1))
|
|
if [ $FAILURES -ge $THRESHOLD ]; then
|
|
echo "Service unhealthy, restarting..."
|
|
systemctl restart cv
|
|
FAILURES=0
|
|
fi
|
|
else
|
|
FAILURES=0
|
|
fi
|
|
sleep 30
|
|
done
|
|
EOF
|
|
|
|
chmod +x /usr/local/bin/cv-monitor.sh
|
|
```
|
|
|
|
### Monitoring Setup
|
|
|
|
**External monitoring** (Uptime Robot, Pingdom, etc.):
|
|
- URL: `https://your-domain.com/health`
|
|
- Interval: 60 seconds
|
|
- Expected status: 200
|
|
- Expected response: `"healthy"`
|
|
|
|
### Log Management
|
|
|
|
**Systemd logs**:
|
|
```bash
|
|
# View all logs
|
|
sudo journalctl -u cv
|
|
|
|
# Follow logs
|
|
sudo journalctl -u cv -f
|
|
|
|
# Last 100 lines
|
|
sudo journalctl -u cv -n 100
|
|
|
|
# Since timestamp
|
|
sudo journalctl -u cv --since "2025-11-09 12:00:00"
|
|
|
|
# Logs from last boot
|
|
sudo journalctl -u cv -b
|
|
```
|
|
|
|
**Application logs**:
|
|
```bash
|
|
# View log file
|
|
tail -f /var/log/cv.log
|
|
|
|
# Last 50 lines
|
|
tail -n 50 /var/log/cv.log
|
|
|
|
# Search for errors
|
|
grep -i error /var/log/cv.log
|
|
|
|
# Log rotation
|
|
sudo nano /etc/logrotate.d/cv
|
|
```
|
|
|
|
**Logrotate configuration** (`/etc/logrotate.d/cv`):
|
|
```
|
|
/var/log/cv.log {
|
|
daily
|
|
rotate 14
|
|
compress
|
|
delaycompress
|
|
missingok
|
|
notifempty
|
|
create 0644 txeo txeo
|
|
postrotate
|
|
systemctl reload cv > /dev/null 2>&1 || true
|
|
endscript
|
|
}
|
|
```
|
|
|
|
### Backup Strategies
|
|
|
|
**1. Application Backup**:
|
|
```bash
|
|
# Backup script
|
|
cat > /usr/local/bin/cv-backup.sh << 'EOF'
|
|
#!/bin/bash
|
|
BACKUP_DIR="/backups/cv"
|
|
DATE=$(date +%Y%m%d_%H%M%S)
|
|
|
|
mkdir -p "$BACKUP_DIR"
|
|
|
|
# Backup application
|
|
tar -czf "$BACKUP_DIR/cv-app-$DATE.tar.gz" \
|
|
/home/txeo/Git/yo/cv/data \
|
|
/home/txeo/Git/yo/cv/templates \
|
|
/home/txeo/Git/yo/cv/static \
|
|
/home/txeo/Git/yo/cv/.env
|
|
|
|
# Keep only last 30 days
|
|
find "$BACKUP_DIR" -name "cv-app-*.tar.gz" -mtime +30 -delete
|
|
|
|
echo "Backup completed: $BACKUP_DIR/cv-app-$DATE.tar.gz"
|
|
EOF
|
|
|
|
chmod +x /usr/local/bin/cv-backup.sh
|
|
```
|
|
|
|
**2. Automated daily backups**:
|
|
```bash
|
|
# Add to crontab
|
|
crontab -e
|
|
|
|
# Daily at 2 AM
|
|
0 2 * * * /usr/local/bin/cv-backup.sh
|
|
```
|
|
|
|
---
|
|
|
|
## Troubleshooting
|
|
|
|
### Common Deployment Issues
|
|
|
|
#### 1. Port Already in Use
|
|
**Error**: `bind: address already in use`
|
|
|
|
**Solution**:
|
|
```bash
|
|
# Find process using port 1999
|
|
sudo lsof -i :1999
|
|
sudo netstat -tulpn | grep 1999
|
|
|
|
# Kill process
|
|
sudo kill -9 <PID>
|
|
|
|
# Or change port
|
|
PORT=8080 ./cv-server
|
|
```
|
|
|
|
#### 2. Chromium Not Found
|
|
**Error**: `chrome not found` or PDF generation fails
|
|
|
|
**Solution**:
|
|
```bash
|
|
# Install Chromium
|
|
# Ubuntu/Debian
|
|
sudo apt install -y chromium-browser
|
|
|
|
# Alpine
|
|
apk add --no-cache chromium
|
|
|
|
# Verify
|
|
which chromium-browser
|
|
chromium-browser --version
|
|
|
|
# Set environment variable
|
|
export CHROME_BIN=/usr/bin/chromium-browser
|
|
```
|
|
|
|
#### 3. Permission Denied
|
|
**Error**: `permission denied` accessing files
|
|
|
|
**Solution**:
|
|
```bash
|
|
# Fix ownership
|
|
sudo chown -R txeo:txeo /home/txeo/Git/yo/cv
|
|
|
|
# Fix permissions
|
|
chmod +x cv-server
|
|
chmod -R 755 static templates data
|
|
|
|
# Check SELinux (if applicable)
|
|
sudo setenforce 0 # Temporary
|
|
```
|
|
|
|
#### 4. Template Not Found
|
|
**Error**: `template not found` or rendering errors
|
|
|
|
**Solution**:
|
|
```bash
|
|
# Verify directory structure
|
|
ls -la templates/
|
|
ls -la data/
|
|
|
|
# Check environment variables
|
|
echo $TEMPLATE_DIR
|
|
echo $DATA_DIR
|
|
|
|
# Ensure working directory is correct
|
|
pwd
|
|
cd /home/txeo/Git/yo/cv
|
|
```
|
|
|
|
#### 5. Health Check Fails
|
|
**Error**: Service marked unhealthy
|
|
|
|
**Solution**:
|
|
```bash
|
|
# Check service is running
|
|
curl http://localhost:1999/health
|
|
|
|
# Check logs
|
|
journalctl -u cv -n 50
|
|
|
|
# Verify port is accessible
|
|
nc -zv localhost 1999
|
|
```
|
|
|
|
### Performance Tuning
|
|
|
|
**1. Go Runtime Tuning**:
|
|
```bash
|
|
# Increase max connections
|
|
export GOMAXPROCS=4
|
|
|
|
# Adjust garbage collection
|
|
export GOGC=100 # Default, lower = more frequent GC
|
|
```
|
|
|
|
**2. Nginx Tuning** (`/etc/nginx/nginx.conf`):
|
|
```nginx
|
|
worker_processes auto;
|
|
worker_rlimit_nofile 65535;
|
|
|
|
events {
|
|
worker_connections 4096;
|
|
use epoll;
|
|
multi_accept on;
|
|
}
|
|
|
|
http {
|
|
# Connection limits
|
|
keepalive_timeout 65;
|
|
keepalive_requests 100;
|
|
|
|
# Buffer sizes
|
|
client_body_buffer_size 128k;
|
|
client_max_body_size 10m;
|
|
client_header_buffer_size 1k;
|
|
large_client_header_buffers 4 8k;
|
|
}
|
|
```
|
|
|
|
**3. System Limits** (`/etc/security/limits.conf`):
|
|
```
|
|
txeo soft nofile 65536
|
|
txeo hard nofile 65536
|
|
```
|
|
|
|
### Security Hardening
|
|
|
|
**1. Firewall Configuration**:
|
|
```bash
|
|
# UFW (Ubuntu)
|
|
sudo ufw allow 22/tcp # SSH
|
|
sudo ufw allow 80/tcp # HTTP
|
|
sudo ufw allow 443/tcp # HTTPS
|
|
sudo ufw enable
|
|
|
|
# Block direct access to app port
|
|
sudo ufw deny 1999/tcp
|
|
```
|
|
|
|
**2. Fail2Ban for Nginx**:
|
|
```bash
|
|
# Install
|
|
sudo apt install -y fail2ban
|
|
|
|
# Configure
|
|
sudo nano /etc/fail2ban/jail.local
|
|
```
|
|
|
|
Add:
|
|
```ini
|
|
[nginx-http-auth]
|
|
enabled = true
|
|
|
|
[nginx-limit-req]
|
|
enabled = true
|
|
filter = nginx-limit-req
|
|
logpath = /var/log/nginx/cv-error.log
|
|
```
|
|
|
|
---
|
|
|
|
## Updates & Maintenance
|
|
|
|
### Rolling Updates
|
|
|
|
**Systemd Service**:
|
|
```bash
|
|
# Pull latest code
|
|
cd /home/txeo/Git/yo/cv
|
|
git pull origin main
|
|
|
|
# Build new binary
|
|
make build
|
|
|
|
# Test binary
|
|
./cv-server --version
|
|
|
|
# Restart service (brief downtime)
|
|
sudo systemctl restart cv
|
|
|
|
# Verify
|
|
curl http://localhost:1999/health
|
|
```
|
|
|
|
### Zero-Downtime Deployment
|
|
|
|
**Nginx + Multiple Backends**:
|
|
|
|
1. **Run two instances**:
|
|
```bash
|
|
# Start second instance on different port
|
|
PORT=1998 ./cv-server &
|
|
|
|
# Update Nginx upstream
|
|
upstream cv_backend {
|
|
server 127.0.0.1:1999 weight=1;
|
|
server 127.0.0.1:1998 weight=1;
|
|
}
|
|
|
|
# Reload Nginx
|
|
sudo systemctl reload nginx
|
|
```
|
|
|
|
2. **Deploy new version**:
|
|
```bash
|
|
# Stop first instance
|
|
pkill -f "PORT=1999"
|
|
|
|
# Start updated instance
|
|
PORT=1999 ./cv-server-new &
|
|
|
|
# Verify health
|
|
curl http://localhost:1999/health
|
|
|
|
# Stop second instance
|
|
pkill -f "PORT=1998"
|
|
```
|
|
|
|
### Rollback Procedures
|
|
|
|
**Manual rollback**:
|
|
```bash
|
|
# Git rollback
|
|
cd /home/txeo/Git/yo/cv
|
|
git log --oneline -10 # Find commit hash
|
|
git checkout abc123
|
|
|
|
# Rebuild and restart
|
|
make build
|
|
sudo systemctl restart cv
|
|
|
|
# Verify
|
|
curl http://localhost:1999/health
|
|
```
|
|
|
|
---
|
|
|
|
## Best Practices Summary
|
|
|
|
1. **Use systemd** for reliable process management
|
|
2. **Enable health checks** for monitoring
|
|
3. **Set up monitoring** to detect issues early
|
|
4. **Configure SSL/TLS** for production (Let's Encrypt is free)
|
|
5. **Use reverse proxy** (Nginx) for security and performance
|
|
6. **Implement log rotation** to prevent disk space issues
|
|
7. **Automate backups** of data, templates, and configuration
|
|
8. **Test rollback procedures** before production deployments
|
|
9. **Use environment variables** for configuration (never hardcode)
|
|
10. **Enable rate limiting** to prevent abuse
|
|
|
|
---
|
|
|
|
**For customization, see [7-CUSTOMIZATION.md](7-CUSTOMIZATION.md)**
|