diff --git a/.scripts/.deploy-config.production b/.scripts/.deploy-config.production new file mode 100644 index 0000000..42ebc09 --- /dev/null +++ b/.scripts/.deploy-config.production @@ -0,0 +1,33 @@ +# Production Deployment Configuration +# This file contains credentials/endpoints - ADD TO .gitignore! + +# Deployment method: ssh, sftp, http, docker, s3, gcs +DEPLOY_METHOD="ssh" + +# SSH/SFTP Configuration +DEPLOY_HOST="${DOCS_DEPLOY_HOST:-docs.vapora.io}" +DEPLOY_USER="${DOCS_DEPLOY_USER:-docs}" +DEPLOY_PATH="${DOCS_DEPLOY_PATH:-/var/www/vapora-docs}" + +# HTTP Configuration (if using HTTP deployment) +DEPLOY_ENDPOINT="${DOCS_DEPLOY_ENDPOINT:-https://docs.vapora.io/api/deploy}" +DEPLOY_TOKEN="${DOCS_DEPLOY_TOKEN:-}" + +# AWS S3 Configuration +AWS_BUCKET="${AWS_DOCS_BUCKET:-vapora-docs-prod}" +AWS_REGION="${AWS_REGION:-us-east-1}" +CLOUDFRONT_DISTRIBUTION="${CLOUDFRONT_DIST_PROD:-}" + +# Google Cloud Storage Configuration +GCS_BUCKET="${GCS_DOCS_BUCKET:-vapora-docs-prod}" + +# Docker Registry Configuration +DEPLOY_REGISTRY="${DOCKER_REGISTRY:-registry.vapora.io}" + +# Retention policy +BACKUP_RETENTION_DAYS=30 + +# Notification settings +NOTIFY_ON_SUCCESS="true" +NOTIFY_ON_FAILURE="true" +NOTIFICATION_WEBHOOK="${DOCS_NOTIFICATION_WEBHOOK:-}" diff --git a/.scripts/.deploy-config.staging b/.scripts/.deploy-config.staging new file mode 100644 index 0000000..aa04e42 --- /dev/null +++ b/.scripts/.deploy-config.staging @@ -0,0 +1,30 @@ +# Staging Deployment Configuration +# Staging environment - lower security, fast iteration + +DEPLOY_METHOD="ssh" + +# SSH/SFTP Configuration +DEPLOY_HOST="${DOCS_DEPLOY_HOST_STAGING:-staging-docs.vapora.io}" +DEPLOY_USER="${DOCS_DEPLOY_USER_STAGING:-docs-staging}" +DEPLOY_PATH="${DOCS_DEPLOY_PATH_STAGING:-/var/www/vapora-docs-staging}" + +# HTTP Configuration +DEPLOY_ENDPOINT="${DOCS_DEPLOY_ENDPOINT_STAGING:-https://staging-docs.vapora.io/api/deploy}" +DEPLOY_TOKEN="${DOCS_DEPLOY_TOKEN_STAGING:-}" + +# AWS S3 Configuration +AWS_BUCKET="${AWS_DOCS_BUCKET_STAGING:-vapora-docs-staging}" +AWS_REGION="${AWS_REGION:-us-east-1}" + +# Google Cloud Storage Configuration +GCS_BUCKET="${GCS_DOCS_BUCKET_STAGING:-vapora-docs-staging}" + +# Docker Registry Configuration +DEPLOY_REGISTRY="${DOCKER_REGISTRY:-registry.vapora.io}" + +# Retention policy (shorter for staging) +BACKUP_RETENTION_DAYS=7 + +# Notification settings +NOTIFY_ON_SUCCESS="false" +NOTIFY_ON_FAILURE="true" diff --git a/.scripts/deploy-docs.sh b/.scripts/deploy-docs.sh new file mode 100755 index 0000000..767c662 --- /dev/null +++ b/.scripts/deploy-docs.sh @@ -0,0 +1,362 @@ +#!/bin/bash +# Deploy mdBook documentation to custom server +# Usage: deploy-docs.sh [environment] +# Environments: staging, production, custom + +set -euo pipefail + +# ============================================================================ +# Configuration +# ============================================================================ + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +DOCS_DIR="$REPO_ROOT/docs" +BUILD_DIR="$DOCS_DIR/book" +TIMESTAMP=$(date +%Y%m%d_%H%M%S) +LOG_FILE="/tmp/docs-deploy-$TIMESTAMP.log" + +# Default environment +ENVIRONMENT="${1:-production}" + +# ============================================================================ +# Logging +# ============================================================================ + +log() { + echo "[$(date +'%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE" +} + +error() { + echo "[ERROR] $*" | tee -a "$LOG_FILE" >&2 + exit 1 +} + +success() { + echo "[✓] $*" | tee -a "$LOG_FILE" +} + +# ============================================================================ +# Environment Configuration +# ============================================================================ + +load_config() { + local env=$1 + local config_file="$SCRIPT_DIR/.deploy-config.$env" + + if [ ! -f "$config_file" ]; then + error "Configuration file not found: $config_file" + fi + + log "Loading configuration for environment: $env" + source "$config_file" + + # Validate required variables + local required_vars=("DEPLOY_HOST" "DEPLOY_USER" "DEPLOY_PATH" "DEPLOY_METHOD") + for var in "${required_vars[@]}"; do + if [ -z "${!var:-}" ]; then + error "Missing required configuration: $var" + fi + done + + success "Configuration loaded" +} + +# ============================================================================ +# Pre-Flight Checks +# ============================================================================ + +preflight_checks() { + log "Running preflight checks..." + + # Check if mdBook is built + if [ ! -d "$BUILD_DIR" ]; then + error "Build directory not found: $BUILD_DIR" + fi + + if [ ! -f "$BUILD_DIR/index.html" ]; then + error "Built documentation not found: $BUILD_DIR/index.html" + fi + + # Verify essential files + local required_files=("index.html" "print.html" "css/general.css" "js/book.js") + for file in "${required_files[@]}"; do + if [ ! -f "$BUILD_DIR/$file" ]; then + error "Missing essential file: $file" + fi + done + + # Check connectivity + case $DEPLOY_METHOD in + ssh|sftp) + if ! ssh -o ConnectTimeout=5 "$DEPLOY_USER@$DEPLOY_HOST" "echo ok" > /dev/null 2>&1; then + error "Cannot connect to $DEPLOY_USER@$DEPLOY_HOST via SSH" + fi + ;; + http) + if ! curl -sf "$DEPLOY_ENDPOINT/health" > /dev/null 2>&1; then + log "⚠ Deployment endpoint health check failed (non-blocking)" + fi + ;; + esac + + success "Preflight checks passed" +} + +# ============================================================================ +# Deployment Methods +# ============================================================================ + +deploy_ssh() { + log "Deploying via SSH to $DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_PATH" + + # Create backup on remote + ssh "$DEPLOY_USER@$DEPLOY_HOST" \ + "mkdir -p $DEPLOY_PATH/backups && \ + [ -d $DEPLOY_PATH/current ] && \ + mv $DEPLOY_PATH/current $DEPLOY_PATH/backups/backup_$TIMESTAMP || true" + + # Upload files + rsync -avz \ + --delete \ + --exclude '.gitignore' \ + --exclude 'CNAME' \ + "$BUILD_DIR/" \ + "$DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_PATH/current/" + + # Update symlink (atomic) + ssh "$DEPLOY_USER@$DEPLOY_HOST" \ + "ln -sfT $DEPLOY_PATH/current $DEPLOY_PATH/docs && \ + chmod -R 755 $DEPLOY_PATH/current" + + success "SSH deployment completed" +} + +deploy_sftp() { + log "Deploying via SFTP to $DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_PATH" + + # Create SFTP batch commands + local sftp_batch=$(mktemp) + cat > "$sftp_batch" << SFTP_CMDS +cd $DEPLOY_PATH +-mkdir backups +-mkdir current +mput -r $BUILD_DIR/* current/ +quit +SFTP_CMDS + + # Execute SFTP batch + sftp -b "$sftp_batch" "$DEPLOY_USER@$DEPLOY_HOST" + rm "$sftp_batch" + + success "SFTP deployment completed" +} + +deploy_http() { + log "Deploying via HTTP to $DEPLOY_ENDPOINT" + + # Create temporary tarball + local tar_file=$(mktemp --suffix=.tar.gz) + tar -czf "$tar_file" -C "$BUILD_DIR" . + + # Upload via HTTP + local response=$(curl -s -w "\n%{http_code}" \ + -X POST \ + -H "Authorization: Bearer $DEPLOY_TOKEN" \ + -F "archive=@$tar_file" \ + "$DEPLOY_ENDPOINT/deploy") + + local http_code=$(echo "$response" | tail -n1) + local body=$(echo "$response" | head -n-1) + + rm "$tar_file" + + if [ "$http_code" != "200" ] && [ "$http_code" != "201" ]; then + error "HTTP deployment failed (HTTP $http_code): $body" + fi + + success "HTTP deployment completed (HTTP $http_code)" +} + +deploy_docker() { + log "Deploying via Docker to $DEPLOY_REGISTRY" + + local image="$DEPLOY_REGISTRY/vapora-docs:$ENVIRONMENT-$TIMESTAMP" + local dockerfile=$(mktemp) + + # Generate Dockerfile + cat > "$dockerfile" << 'EOF' +FROM nginx:alpine +COPY . /usr/share/nginx/html +EXPOSE 80 +CMD ["nginx", "-g", "daemon off;"] +EOF + + # Build image + log "Building Docker image: $image" + docker build -f "$dockerfile" -t "$image" "$BUILD_DIR" + docker tag "$image" "$DEPLOY_REGISTRY/vapora-docs:$ENVIRONMENT-latest" + + # Push image + log "Pushing image to registry" + docker push "$image" + docker push "$DEPLOY_REGISTRY/vapora-docs:$ENVIRONMENT-latest" + + rm "$dockerfile" + + log "Container image available: $image" + success "Docker deployment completed" +} + +deploy_s3() { + log "Deploying to S3: s3://$AWS_BUCKET/$DEPLOY_PATH" + + aws s3 sync \ + "$BUILD_DIR/" \ + "s3://$AWS_BUCKET/$DEPLOY_PATH/" \ + --delete \ + --region "${AWS_REGION:-us-east-1}" \ + --cache-control "public, max-age=300" + + # Invalidate CloudFront if configured + if [ -n "${CLOUDFRONT_DISTRIBUTION:-}" ]; then + log "Invalidating CloudFront distribution" + aws cloudfront create-invalidation \ + --distribution-id "$CLOUDFRONT_DISTRIBUTION" \ + --paths "/$DEPLOY_PATH/*" \ + --region "${AWS_REGION:-us-east-1}" + fi + + success "S3 deployment completed" +} + +deploy_gcs() { + log "Deploying to Google Cloud Storage: gs://$GCS_BUCKET/$DEPLOY_PATH" + + gsutil -m rsync -r -d "$BUILD_DIR/" "gs://$GCS_BUCKET/$DEPLOY_PATH/" + + # Set cache control + gsutil -m setmeta -h "Cache-Control:public, max-age=300" \ + "gs://$GCS_BUCKET/$DEPLOY_PATH/**" + + success "GCS deployment completed" +} + +# ============================================================================ +# Post-Deployment Verification +# ============================================================================ + +verify_deployment() { + log "Verifying deployment..." + + case $DEPLOY_METHOD in + ssh|sftp) + # Check if files exist on remote + ssh "$DEPLOY_USER@$DEPLOY_HOST" \ + "[ -f $DEPLOY_PATH/docs/index.html ] && echo 'Files verified' || exit 1" + ;; + http) + # Hit deployment endpoint + if ! curl -sf "$DEPLOY_ENDPOINT/health" > /dev/null; then + log "⚠ Health check failed (deployment may still be in progress)" + fi + ;; + docker) + log "Docker image deployed: $image" + ;; + s3) + # Verify S3 object + aws s3 ls "s3://$AWS_BUCKET/$DEPLOY_PATH/index.html" \ + --region "${AWS_REGION:-us-east-1}" > /dev/null + ;; + gcs) + # Verify GCS object + gsutil stat "gs://$GCS_BUCKET/$DEPLOY_PATH/index.html" > /dev/null + ;; + esac + + success "Deployment verified" +} + +# ============================================================================ +# Rollback +# ============================================================================ + +rollback() { + log "Rolling back deployment..." + + case $DEPLOY_METHOD in + ssh) + if [ -d "$DEPLOY_PATH/backups/backup_$TIMESTAMP" ]; then + ssh "$DEPLOY_USER@$DEPLOY_HOST" \ + "ln -sfT $DEPLOY_PATH/backups/backup_$TIMESTAMP $DEPLOY_PATH/docs" + success "Rollback completed" + else + error "No backup available for rollback" + fi + ;; + *) + error "Rollback not implemented for deployment method: $DEPLOY_METHOD" + ;; + esac +} + +# ============================================================================ +# Main +# ============================================================================ + +main() { + log "Starting documentation deployment" + log "Environment: $ENVIRONMENT" + log "Repository: $REPO_ROOT" + log "Build directory: $BUILD_DIR" + log "Log file: $LOG_FILE" + + # Load configuration + load_config "$ENVIRONMENT" + + # Preflight checks + preflight_checks + + # Deploy based on method + case $DEPLOY_METHOD in + ssh) + deploy_ssh + ;; + sftp) + deploy_sftp + ;; + http) + deploy_http + ;; + docker) + deploy_docker + ;; + s3) + deploy_s3 + ;; + gcs) + deploy_gcs + ;; + *) + error "Unknown deployment method: $DEPLOY_METHOD" + ;; + esac + + # Verify deployment + verify_deployment + + success "Documentation deployment completed successfully" + log "Summary:" + log " Environment: $ENVIRONMENT" + log " Method: $DEPLOY_METHOD" + log " Build size: $(du -sh "$BUILD_DIR" | cut -f1)" + log " Timestamp: $TIMESTAMP" + log " Log: $LOG_FILE" +} + +# Handle errors +trap 'error "Deployment failed at line $LINENO"' ERR + +# Run main function +main "$@" diff --git a/.scripts/deployment_quick_start.md b/.scripts/deployment_quick_start.md new file mode 100644 index 0000000..00bc018 --- /dev/null +++ b/.scripts/deployment_quick_start.md @@ -0,0 +1,282 @@ +# Deployment Quick Start Guide + +Get your mdBook documentation deployed in minutes. + +## Choose Your Deployment Method + +### 🔄 Option 1: SSH/SFTP (Recommended for Most) + +**Best for**: Private servers, simple setup, full control + +**Setup Time**: 10 minutes + +```bash +# 1. Create SSH key pair (if needed) +ssh-keygen -t ed25519 -f ~/.ssh/vapora-docs -N "" + +# 2. SSH into your server and set up docs user +ssh user@your-server.com +sudo useradd -m -d /var/www/docs -s /bin/bash docs +sudo mkdir -p /var/www/docs/.ssh +sudo chmod 700 /var/www/docs/.ssh +exit + +# 3. Add public key to server +cat ~/.ssh/vapora-docs.pub | ssh user@your-server.com \ + "sudo -u docs tee -a /var/www/docs/.ssh/authorized_keys" + +# 4. Test connection +ssh -i ~/.ssh/vapora-docs docs@your-server.com ls + +# 5. Add GitHub secrets +# Settings → Secrets and variables → Actions → New repository secret +# DOCS_DEPLOY_METHOD = ssh +# DOCS_DEPLOY_HOST = your-server.com +# DOCS_DEPLOY_USER = docs +# DOCS_DEPLOY_PATH = /var/www/docs +# DOCS_DEPLOY_KEY = [base64 encode: cat ~/.ssh/vapora-docs | base64] +``` + +### ☁️ Option 2: AWS S3 (Best for Scale) + +**Best for**: High availability, CDN, minimal ops + +**Setup Time**: 5 minutes + +```bash +# 1. Create IAM user +aws iam create-user --user-name vapora-docs-deployer +aws iam create-access-key --user-name vapora-docs-deployer + +# 2. Create S3 bucket +aws s3 mb s3://vapora-docs --region us-east-1 + +# 3. Attach policy (copy from docs/CUSTOM_DEPLOYMENT_SERVER.md) + +# 4. Add GitHub secrets +# DOCS_DEPLOY_METHOD = s3 +# AWS_ACCESS_KEY_ID = [from step 1] +# AWS_SECRET_ACCESS_KEY = [from step 1] +# AWS_DOCS_BUCKET = vapora-docs +# AWS_REGION = us-east-1 +``` + +### 🌐 Option 3: GitHub Pages (Simplest) + +**Best for**: Public documentation, zero server ops + +**Setup Time**: 2 minutes + +```bash +# 1. Go to Settings → Pages +# 2. Select Source: GitHub Actions +# 3. Save +# 4. Push any docs/ change +# Done! Automatic deployment in 1-2 minutes +``` + +--- + +## Minimal SSH Setup (Step-by-Step) + +### On Your Server + +```bash +# 1. Create docs directory +mkdir -p /var/www/docs +cd /var/www/docs + +# 2. Set up for web serving +sudo apt-get install nginx -y # or yum install nginx + +# 3. Create nginx config +sudo tee /etc/nginx/sites-available/docs > /dev/null << 'EOF' +server { + listen 80; + server_name docs.your-domain.com; + root /var/www/docs; + + location / { + index index.html; + try_files $uri $uri/ =404; + } + + location ~* \.(js|css|fonts)$ { + expires 1h; + } +} +EOF + +# 4. Enable site +sudo ln -s /etc/nginx/sites-available/docs /etc/nginx/sites-enabled/ +sudo nginx -t && sudo systemctl reload nginx +``` + +### On Your Local Machine + +```bash +# 1. Create SSH key (if needed) +ssh-keygen -t ed25519 -f ~/.ssh/vapora-docs -N "" + +# 2. Copy key to server +ssh-copy-id -i ~/.ssh/vapora-docs user@your-server.com + +# 3. Test +ssh -i ~/.ssh/vapora-docs user@your-server.com + +# 4. Base64 encode key for GitHub +cat ~/.ssh/vapora-docs | base64 -w0 > /tmp/key.b64 +cat /tmp/key.b64 # Copy this +``` + +### In GitHub + +Go to **Settings** → **Secrets and variables** → **Actions** + +Create these secrets: + +| Name | Value | +|------|-------| +| `DOCS_DEPLOY_METHOD` | `ssh` | +| `DOCS_DEPLOY_HOST` | `your-server.com` | +| `DOCS_DEPLOY_USER` | `user` | +| `DOCS_DEPLOY_PATH` | `/var/www/docs` | +| `DOCS_DEPLOY_KEY` | Paste base64-encoded key | + +--- + +## Test Your Setup + +### Local Test + +```bash +# Build locally +cd docs +mdbook build + +# Test deployment script +bash ../.scripts/deploy-docs.sh production +``` + +### GitHub Actions Test + +```bash +# Make a test commit +git add docs/README.md +git commit -m "test: trigger deployment" +git push origin main + +# Watch Actions tab +# Actions → mdBook Publish & Sync → View run +``` + +--- + +## Verify Deployment + +### Check SSH Deployment + +```bash +# SSH into server +ssh -i ~/.ssh/vapora-docs user@your-server.com + +# Verify files +ls -lah /var/www/docs/ +cat /var/www/docs/index.html | head -20 + +# Check nginx logs +sudo tail -f /var/log/nginx/access.log +``` + +### Check S3 Deployment + +```bash +aws s3 ls s3://vapora-docs/ + +# Or visit via CloudFront +https://docs-cdn.cloudfront.net/ +``` + +### Check GitHub Pages + +```bash +# Visit the URL shown in Settings → Pages +https://username.github.io/vapora/ +``` + +--- + +## Common Issues & Fixes + +### SSH Connection Fails + +```bash +# Test SSH connection +ssh -vvv -i ~/.ssh/vapora-docs user@your-server.com + +# Check key permissions +ls -la ~/.ssh/vapora-docs +chmod 600 ~/.ssh/vapora-docs +chmod 700 ~/.ssh + +# Add server to known_hosts +ssh-keyscan your-server.com >> ~/.ssh/known_hosts +``` + +### Deployment Script Can't Find Files + +```bash +# Verify mdBook built successfully +cd docs +mdbook build +ls -la book/index.html +``` + +### Files Not Appearing on Website + +```bash +# Check nginx root directory +sudo nginx -T | grep root + +# Verify permissions +sudo ls -la /var/www/docs/ + +# Check nginx logs +sudo tail -f /var/log/nginx/error.log +``` + +### GitHub Actions Fails + +1. Go to **Actions** → Failed workflow +2. Expand **Deploy to Custom Server** step +3. Look for error message +4. Common issues: + - Secret not set correctly + - SSH key not base64 encoded + - Server not reachable + +--- + +## Next Steps + +1. Choose deployment method above +2. Follow setup instructions +3. Test locally: `bash .scripts/deploy-docs.sh production` +4. Push test commit to trigger GitHub Actions +5. Monitor Actions tab +6. Verify deployment succeeded +7. Check your documentation site + +--- + +## Need Help? + +- Full guide: See `docs/CUSTOM_DEPLOYMENT_SERVER.md` +- Workflow reference: See `.github/WORKFLOWS.md` +- Deployment script: `.scripts/deploy-docs.sh` +- GitHub Secrets: Repository → Settings → Secrets and variables → Actions + +--- + +**Last Updated**: 2026-01-12 +**Estimated Setup Time**: 10-15 minutes