diff --git a/.forgejo/workflows/ci.yml b/.forgejo/workflows/ci.yml new file mode 100644 index 0000000..63f33f9 --- /dev/null +++ b/.forgejo/workflows/ci.yml @@ -0,0 +1,144 @@ +name: CI/CD Pipeline + +on: + push: + branches: [ main, develop ] + pull_request: + branches: [ main ] + +env: + REGISTRY: ${{ secrets.CI_HOST }}:5000 + IMAGE_NAME: sharenet + +jobs: + test-backend: + name: Test Backend + runs-on: ubuntu-latest + + services: + postgres: + image: postgres:15 + env: + POSTGRES_PASSWORD: postgres + POSTGRES_DB: sharenet_test + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install Rust toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + + - name: Cache Rust dependencies + uses: actions/cache@v3 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + backend/target + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo- + + - name: Run backend tests + working-directory: ./backend + run: | + cargo test --all + cargo clippy --all -- -D warnings + cargo fmt --all -- --check + + test-frontend: + name: Test Frontend + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + cache-dependency-path: frontend/package-lock.json + + - name: Install frontend dependencies + working-directory: ./frontend + run: npm ci + + - name: Run frontend tests + working-directory: ./frontend + run: | + npm run lint + npm run type-check + npm run build + + build-and-push: + name: Build and Push Docker Images + needs: [test-backend, test-frontend] + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/main' + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Configure Docker for local registry + run: | + echo '{"insecure-registries": ["${{ secrets.CI_HOST }}:5000"]}' | sudo tee /etc/docker/daemon.json + sudo systemctl restart docker + + - name: Build and push backend image + uses: docker/build-push-action@v5 + with: + context: ./backend + file: ./backend/Dockerfile + push: true + tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/backend:${{ github.sha }} + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Build and push frontend image + uses: docker/build-push-action@v5 + with: + context: ./frontend + file: ./frontend/Dockerfile + push: true + tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/frontend:${{ github.sha }} + cache-from: type=gha + cache-to: type=gha,mode=max + + deploy: + name: Deploy to Production + needs: build-and-push + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/main' + + steps: + - name: Deploy to production server + uses: appleboy/ssh-action@v1.0.3 + with: + host: ${{ secrets.PROD_HOST }} + username: ${{ secrets.PROD_USER }} + key: ${{ secrets.PROD_SSH_KEY }} + script: | + cd /opt/sharenet + echo "IMAGE_TAG=${{ github.sha }}" > .env + echo "REGISTRY=${{ secrets.CI_HOST }}:5000" >> .env + echo "IMAGE_NAME=sharenet" >> .env + docker-compose pull + docker-compose up -d + docker system prune -f \ No newline at end of file diff --git a/CI_CD_SETUP_GUIDE.md b/CI_CD_SETUP_GUIDE.md new file mode 100644 index 0000000..a706c47 --- /dev/null +++ b/CI_CD_SETUP_GUIDE.md @@ -0,0 +1,819 @@ +# CI/CD Linode Setup Guide + +This guide covers setting up your CI/CD Linode with Forgejo Actions runner and a local Docker registry for automated deployments. + +## Architecture Overview + +``` +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ Forgejo Host │ │ CI/CD Linode │ │ Production Linode│ +│ (Repository) │ │ (Actions Runner)│ │ (Docker Deploy) │ +│ │ │ + Docker Registry│ │ │ +└─────────────────┘ └─────────────────┘ └─────────────────┘ + │ │ │ + │ │ │ + └─────────── Push ──────┼───────────────────────┘ + │ + └─── Deploy ────────────┘ +``` + +## Prerequisites + +- Ubuntu 24.04 LTS Linode with root access +- Basic familiarity with Linux commands and SSH +- Forgejo repository with Actions enabled +- Production Linode IP address (for SSH key exchange) + +## Quick Start + +1. **Follow this complete CI/CD setup guide** +2. **Set up SSH keys** for secure communication with Production server +3. **Configure Forgejo Actions runner** +4. **Test registry and runner functionality** +5. **Exchange SSH keys** with Production server +6. **Configure Forgejo repository secrets** + +## What's Included + +### CI/CD Linode Features +- Forgejo Actions runner for automated builds +- Local Docker registry for image storage +- Registry web UI for image management +- Automated cleanup of old images +- Secure SSH communication with production + +## Prerequisites and Initial Setup + +### What's Already Done (Assumptions) + +This guide assumes you have already: + +1. **Created an Ubuntu 24.04 LTS Linode** with root access +2. **Set root password** for the Linode +3. **Have SSH client** installed on your local machine +4. **Have Production Linode IP address** ready for SSH key exchange + +**Note**: The CI/CD Linode will be accessed via IP address only, as it's primarily used for internal services (Docker registry, Forgejo Actions runner) that don't require public web access. + +### Step 0: Initial SSH Access and Verification + +Before proceeding with the setup, you need to establish initial SSH access to your CI/CD Linode. + +#### 0.1 Get Your Linode IP Address + +From your Linode dashboard, note the IP address for: +- **CI/CD Linode**: `YOUR_CI_CD_IP` (IP address only, no domain needed) + +#### 0.2 Test Initial SSH Access + +Test SSH access to your CI/CD Linode: + +```bash +# Test CI/CD Linode (IP address only) +ssh root@YOUR_CI_CD_IP +``` + +**Expected output**: SSH login prompt asking for root password. + +**If something goes wrong**: +- Verify the IP address is correct +- Check that SSH is enabled on the Linode +- Ensure your local machine can reach the Linode (no firewall blocking) + +#### 0.3 Choose Your Names + +Before proceeding, decide on: + +1. **Service Account Name**: Choose a username for the service account (e.g., `appuser`, `deploy`, `service`) + - Replace `SERVICE_USER` in this guide with your chosen name + +2. **Application Name**: Choose a name for your application (e.g., `myapp`, `webapp`, `api`) + - Replace `APP_NAME` in this guide with your chosen name + +**Example**: +- If you choose `appuser` as service account and `myapp` as application name: + - Replace all `SERVICE_USER` with `appuser` + - Replace all `APP_NAME` with `myapp` + +--- + +## CI/CD Linode Setup + +### Step 1: Initial System Setup + +#### 1.1 Update the System + +```bash +sudo apt update && sudo apt upgrade -y +``` + +**What this does**: Updates package lists and upgrades all installed packages to their latest versions. + +**Expected output**: A list of packages being updated, followed by completion messages. + +**If something goes wrong**: +- Check your internet connection +- Try running `sudo apt update` first, then `sudo apt upgrade -y` separately +- If you get package conflicts, run `sudo apt --fix-broken install` + +#### 1.2 Install Essential Packages + +```bash +sudo apt install -y \ + curl \ + wget \ + git \ + build-essential \ + pkg-config \ + libssl-dev \ + ca-certificates \ + apt-transport-https \ + software-properties-common \ + apache2-utils +``` + +**What this does**: Installs development tools, SSL libraries, and utilities needed for Docker and application building. + +**Expected output**: Package installation progress, ending with completion messages. + +**If something goes wrong**: +- Check if any packages failed to install +- Run `sudo apt install -f` to fix broken dependencies +- Ensure you have sufficient disk space: `df -h` + +### Step 2: Create Service Account + +#### 2.1 Create the SERVICE_USER User + +```bash +sudo useradd -r -s /bin/bash -m -d /home/SERVICE_USER SERVICE_USER +sudo usermod -aG sudo SERVICE_USER +echo "SERVICE_USER:$(openssl rand -base64 32)" | sudo chpasswd +``` + +**What this does**: +- Creates a dedicated service account named `SERVICE_USER` +- Gives it sudo privileges for administrative tasks +- Generates a random 32-character password + +**Expected output**: No output (successful user creation is silent). + +**If something goes wrong**: +- If user already exists: `sudo userdel -r SERVICE_USER` then retry +- Check user creation: `id SERVICE_USER` +- Verify sudo access: `sudo -u SERVICE_USER sudo -l` + +#### 2.2 Verify Service Account + +```bash +sudo su - SERVICE_USER +whoami +pwd +``` + +**What this does**: Switches to the SERVICE_USER user and verifies the setup. + +**Expected output**: +``` +SERVICE_USER +/home/SERVICE_USER +``` + +### Step 3: Install Docker + +#### 3.1 Add Docker Repository + +```bash +curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg +echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null +sudo apt update +``` + +**What this does**: Adds Docker's official repository to your system for the latest Docker versions. + +**Expected output**: GPG key import confirmation and package list update. + +#### 3.2 Install Docker Packages + +```bash +sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin +``` + +**What this does**: Installs Docker Engine, CLI, container runtime, and Docker Compose. + +**Expected output**: Package installation progress, ending with completion messages. + +#### 3.3 Configure Docker for Service Account + +```bash +sudo usermod -aG docker SERVICE_USER +``` + +**What this does**: Adds the SERVICE_USER user to the docker group so it can run Docker commands without sudo. + +### Step 4: Install Docker Compose + +```bash +sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose +sudo chmod +x /usr/local/bin/docker-compose +``` + +**What this does**: Downloads the latest Docker Compose binary and makes it executable. + +### Step 5: Configure Docker for Local Registry + +```bash +echo '{"insecure-registries": ["localhost:5000"]}' | sudo tee /etc/docker/daemon.json +sudo systemctl restart docker +``` + +**What this does**: Configures Docker to allow connections to the local registry without SSL verification. + +### Step 6: Install Development Tools + +#### 6.1 Install Rust + +```bash +sudo su - SERVICE_USER +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y +source $HOME/.cargo/env +``` + +**What this does**: Installs Rust programming language and Cargo package manager for building the backend. + +#### 6.2 Install Node.js + +```bash +curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - +sudo apt install -y nodejs +``` + +**What this does**: Installs Node.js version 20 for building the frontend. + +### Step 7: Set Up Docker Registry + +#### 7.1 Create Registry Directory + +```bash +sudo mkdir -p /opt/registry +sudo chown SERVICE_USER:SERVICE_USER /opt/registry +``` + +**What this does**: Creates a directory for the Docker registry and sets ownership to the SERVICE_USER user. + +#### 7.2 Create Docker Compose File for Registry + +```bash +cat > /opt/registry/docker-compose.yml << 'EOF' +version: '3.8' + +services: + registry: + image: registry:2 + container_name: docker-registry + restart: unless-stopped + ports: + - "5000:5000" + environment: + REGISTRY_STORAGE_DELETE_ENABLED: "true" + REGISTRY_STORAGE_FILESYSTEM_MAXTHREADS: "100" + volumes: + - registry_data:/var/lib/registry + - ./config.yml:/etc/docker/registry/config.yml:ro + networks: + - registry-network + + registry-ui: + image: joxit/docker-registry-ui:latest + container_name: registry-ui + restart: unless-stopped + ports: + - "8080:80" + environment: + REGISTRY_URL: http://registry:5000 + DELETE_IMAGES: "true" + depends_on: + - registry + networks: + - registry-network + +volumes: + registry_data: + driver: local + +networks: + registry-network: + driver: bridge +EOF +``` + +#### 7.3 Create Registry Configuration + +```bash +cat > /opt/registry/config.yml << 'EOF' +version: 0.1 +log: + fields: + service: registry +storage: + delete: + enabled: true + cache: + blobdescriptor: inmemory + filesystem: + rootdirectory: /var/lib/registry +http: + addr: :5000 + headers: + X-Content-Type-Options: [nosniff] +health: + storagedriver: + enabled: true + interval: 10s + threshold: 3 +EOF +``` + +#### 7.4 Start the Registry + +```bash +cd /opt/registry +docker-compose up -d +``` + +**What this does**: Starts the Docker registry and web UI in detached mode. + +**Expected output**: Container creation and startup messages. + +### Step 8: Set Up SSH Keys + +#### 8.1 Create SSH Directory + +```bash +mkdir -p ~/.ssh +chmod 700 ~/.ssh +``` + +#### 8.2 Generate SSH Key Pair + +```bash +ssh-keygen -t ed25519 -C "ci-cd-server" -f ~/.ssh/id_ed25519 -N "" +``` + +**What this does**: Generates an Ed25519 SSH key pair for secure communication with the production server. + +**Expected output**: Key generation messages and file creation confirmation. + +#### 8.3 Create SSH Config + +```bash +cat > ~/.ssh/config << 'EOF' +Host production + HostName YOUR_PRODUCTION_IP + User SERVICE_USER + IdentityFile ~/.ssh/id_ed25519 + StrictHostKeyChecking no + UserKnownHostsFile /dev/null +EOF + +chmod 600 ~/.ssh/config +``` + +**What this does**: Creates SSH configuration for easy connection to the production server. + +**Note**: You'll update `YOUR_PRODUCTION_IP` with the actual production server IP later. + +### Step 9: Install Forgejo Actions Runner + +#### 9.1 Download Runner + +```bash +cd ~ +wget https://github.com/actions/runner/releases/download/v2.311.0/actions-runner-linux-x64-2.311.0.tar.gz +tar -xzf actions-runner-linux-x64-2.311.0.tar.gz +rm actions-runner-linux-x64-2.311.0.tar.gz +``` + +**What this does**: Downloads and extracts the Forgejo Actions runner. + +#### 9.2 Create Systemd Service + +```bash +sudo tee /etc/systemd/system/github-runner.service > /dev/null << 'EOF' +[Unit] +Description=GitHub Actions Runner +After=network.target + +[Service] +Type=simple +User=SERVICE_USER +WorkingDirectory=/home/SERVICE_USER/actions-runner +ExecStart=/home/SERVICE_USER/actions-runner/run.sh +Restart=always +RestartSec=10 + +[Install] +WantedBy=multi-user.target +EOF +``` + +**What this does**: Creates a systemd service to automatically start and manage the Actions runner. + +#### 9.3 Enable Service + +```bash +sudo systemctl daemon-reload +sudo systemctl enable github-runner.service +``` + +**What this does**: Enables the Actions runner service to start automatically on boot. + +### Step 10: Set Up Monitoring and Cleanup + +#### 10.1 Create Monitoring Script + +```bash +cat > ~/monitor.sh << 'EOF' +#!/bin/bash + +echo "=== CI/CD Server Status ===" +echo "Date: $(date)" +echo "Uptime: $(uptime)" +echo "" + +echo "=== Docker Status ===" +docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" +echo "" + +echo "=== Registry Status ===" +cd /opt/registry +docker-compose ps +echo "" + +echo "=== Actions Runner Status ===" +sudo systemctl status github-runner.service --no-pager +echo "" + +echo "=== System Resources ===" +echo "CPU Usage:" +top -bn1 | grep "Cpu(s)" | awk '{print $2}' | cut -d'%' -f1 +echo "Memory Usage:" +free -h | grep Mem +echo "Disk Usage:" +df -h / +echo "" + +echo "=== Recent Logs ===" +docker-compose logs --tail=10 +EOF + +chmod +x ~/monitor.sh +``` + +**What this does**: Creates a monitoring script to check the status of all CI/CD services. + +#### 10.2 Create Cleanup Script + +```bash +cat > ~/cleanup.sh << 'EOF' +#!/bin/bash + +echo "Cleaning up old Docker images..." + +# Remove unused images +docker image prune -f + +# Remove unused volumes +docker volume prune -f + +# Remove unused networks +docker network prune -f + +# Remove old registry images (keep last 10 tags per repository) +cd /opt/registry +docker-compose exec registry registry garbage-collect /etc/docker/registry/config.yml + +echo "Cleanup complete!" +EOF + +chmod +x ~/cleanup.sh +``` + +**What this does**: Creates a cleanup script to remove old Docker images and registry data. + +#### 10.3 Set Up Automated Cleanup + +```bash +(crontab -l 2>/dev/null; echo "0 3 * * * /home/SERVICE_USER/cleanup.sh") | crontab - +``` + +**What this does**: Schedules daily cleanup at 3 AM. + +### Step 11: Configure Firewall + +```bash +sudo ufw --force enable +sudo ufw default deny incoming +sudo ufw default allow outgoing +sudo ufw allow ssh +sudo ufw allow 5000/tcp # Docker registry +sudo ufw allow 8080/tcp # Registry UI +``` + +**What this does**: Configures firewall to allow only necessary ports. + +### Step 12: Test CI/CD Setup + +#### 12.1 Test Docker Installation + +```bash +docker --version +docker-compose --version +``` + +**Expected output**: Version information for both Docker and Docker Compose. + +#### 12.2 Check Registry Status + +```bash +cd /opt/registry +docker-compose ps +``` + +**Expected output**: Status of registry and registry-ui containers showing "Up". + +#### 12.3 Test Registry Access + +```bash +curl http://localhost:5000/v2/_catalog +``` + +**Expected output**: `{"repositories":[]}` (empty initially). + +#### 12.4 Get Public Key for Production Server + +```bash +cat ~/.ssh/id_ed25519.pub +``` + +**Important**: Copy this public key - you'll need it for the production server setup. + +### Step 13: Configure Forgejo Actions Runner + +#### 13.1 Get Runner Token + +1. Go to your Forgejo repository +2. Navigate to Settings → Actions → Runners +3. Click "New runner" +4. Copy the registration token + +#### 13.2 Configure Runner + +```bash +cd ~/actions-runner +./config.sh --url https://your-forgejo-instance/your-username/APP_NAME --token YOUR_TOKEN +``` + +#### 13.3 Start Runner + +```bash +sudo systemctl start github-runner.service +sudo systemctl status github-runner.service +``` + +--- + +## SSH Key Exchange + +After setting up both servers, you need to exchange SSH public keys for secure communication: + +### Step 1: Get Public Keys from Both Servers + +```bash +# On CI/CD server (get your public key) +cat ~/.ssh/id_ed25519.pub + +# On Production server (get their public key) +# You'll need to get this from the Production server +``` + +### Step 2: Add Production Server's Public Key + +```bash +# On CI/CD server (add Production's public key) +echo "PRODUCTION_PUBLIC_KEY_HERE" >> ~/.ssh/authorized_keys +sed -i 's/YOUR_PRODUCTION_IP/YOUR_ACTUAL_PRODUCTION_IP/g' ~/.ssh/config +``` + +### Step 3: Test SSH Connection + +```bash +# Test from CI/CD to Production +ssh production 'echo Connection successful' +``` + +**Expected output**: `Connection successful`. + +--- + +## Registry Configuration + +The CI/CD registry is configured to allow connections from the production server. + +### Step 1: Verify Registry Configuration + +```bash +# Check registry is running +cd /opt/registry +docker-compose ps + +# Test registry API +curl http://localhost:5000/v2/_catalog +``` + +**Expected output**: +- Registry containers showing "Up" +- Registry API: `{"repositories":[]}` or list of images + +### Step 2: Verify Registry UI Access + +You can access the registry web interface to manage images: + +```bash +# Test registry UI +curl -I http://localhost:8080 +``` + +**Expected output**: HTTP response (may be 404 initially, which is normal). + +**Note**: The registry UI is accessible at `http://YOUR_CI_CD_IP:8080` for administrative purposes. + +--- + +## Forgejo Configuration + +### Step 1: Add Repository Secrets + +Go to your Forgejo repository → Settings → Secrets and Variables → Actions, and add: + +- `CI_HOST`: Your CI/CD Linode IP address +- `PROD_HOST`: Your production Linode IP +- `PROD_USER`: SSH username for production server (should be `SERVICE_USER`) +- `PROD_SSH_KEY`: SSH private key for deployment + +### Step 2: Generate SSH Key for Deployment + +The setup automatically generates SSH keys for the SERVICE_USER service account. For Forgejo Actions deployment, use the CI/CD server's private key: + +```bash +# On CI/CD server +cat ~/.ssh/id_ed25519 +``` + +Copy the entire private key content (including the BEGIN and END lines) for the `PROD_SSH_KEY` secret. + +--- + +## Testing and Verification + +### Step 1: Test Registry Functionality + +```bash +# Test registry API +curl http://localhost:5000/v2/_catalog + +# Test registry UI (optional) +curl -I http://localhost:8080 +``` + +**Expected output**: +- Registry API: `{"repositories":[]}` (empty initially) +- Registry UI: HTTP response (may be 404 initially, which is normal) + +### Step 2: Test Actions Runner + +```bash +# Check runner status +sudo systemctl status github-runner.service + +# Check runner logs +sudo journalctl -u github-runner.service -f +``` + +**Expected output**: Runner service showing as active and running. + +### Step 3: Test Monitoring Script + +```bash +./monitor.sh +``` + +**Expected output**: Status information for all CI/CD services. + +### Step 4: Test SSH Connection to Production + +```bash +# Test from CI/CD to Production +ssh production 'echo Connection successful' +``` + +**Expected output**: `Connection successful`. + +### Step 5: Trigger First Deployment + +#### 5.1 Push Code Changes + +Make a small change to your code and push to trigger the CI/CD pipeline: + +```bash +# In your local repository +echo "# Test deployment" >> README.md +git add README.md +git commit -m "Test CI/CD pipeline" +git push +``` + +#### 5.2 Monitor Pipeline + +1. Go to your Forgejo repository +2. Navigate to Actions tab +3. Monitor the workflow execution +4. Check for any errors or issues + +#### 5.3 Verify Deployment + +After successful pipeline execution: + +```bash +# Check if images were pushed to registry +curl http://localhost:5000/v2/_catalog + +# Check registry UI for new images +# Open http://YOUR_CI_CD_IP:8080 in browser +``` + +--- + +## Troubleshooting + +### Common Issues + +1. **Docker permission denied**: + ```bash + sudo usermod -aG docker SERVICE_USER + newgrp docker + ``` + +2. **Registry not accessible**: + ```bash + cd /opt/registry + docker-compose logs + ``` + +3. **Actions runner not starting**: + ```bash + sudo systemctl status github-runner.service + sudo journalctl -u github-runner.service -f + ``` + +4. **SSH key issues**: + ```bash + chmod 600 ~/.ssh/id_ed25519 + chmod 700 ~/.ssh + ``` + +5. **Registry connection failed**: + ```bash + curl -v http://localhost:5000/v2/_catalog + ``` + +### Useful Commands + +- **Check system resources**: `htop` +- **Check disk space**: `df -h` +- **Check memory usage**: `free -h` +- **Check network**: `ip addr show` +- **Check firewall**: `sudo ufw status` +- **Check logs**: `sudo journalctl -f` + +### Security Best Practices + +1. **Service Account**: Use dedicated `SERVICE_USER` user with limited privileges +2. **SSH Keys**: Use Ed25519 keys with proper permissions (600/700) +3. **Firewall**: Configure UFW to allow only necessary ports +4. **Container Isolation**: Registry runs in isolated Docker containers +5. **Regular Cleanup**: Automated daily cleanup of old images + +--- + +## Summary + +Your CI/CD Linode is now ready to handle automated builds and deployments! The setup includes: + +- **Forgejo Actions runner** for automated builds +- **Local Docker registry** with web UI for image management +- **Secure SSH communication** with production server +- **Monitoring and cleanup** scripts +- **Firewall protection** for security + +For ongoing maintenance and troubleshooting, refer to the troubleshooting section and monitoring scripts provided in this guide. \ No newline at end of file diff --git a/PRODUCTION_SETUP_GUIDE.md b/PRODUCTION_SETUP_GUIDE.md new file mode 100644 index 0000000..3a70dd8 --- /dev/null +++ b/PRODUCTION_SETUP_GUIDE.md @@ -0,0 +1,1070 @@ +# Production Linode Setup Guide + +This guide covers setting up your Production Linode for hosting the APP_NAME application with Docker, SSL/TLS, and automated deployments. + +## Architecture Overview + +``` +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ Forgejo Host │ │ CI/CD Linode │ │ Production Linode│ +│ (Repository) │ │ (Actions Runner)│ │ (Docker Deploy) │ +│ │ │ + Docker Registry│ │ │ +└─────────────────┘ └─────────────────┘ └─────────────────┘ + │ │ │ + │ │ │ + └─────────── Push ──────┼───────────────────────┘ + │ + └─── Deploy ────────────┘ +``` + +## Prerequisites + +- Ubuntu 24.04 LTS Linode with root access +- Domain name pointing to the Production Linode's IP addresses +- Basic familiarity with Linux commands and SSH +- **For CI/CD Integration**: CI/CD Linode already set up and IP address ready +- **For Manual Deployment**: Docker registry access (local or remote) + +## Quick Start + +### Basic Production Setup (Manual Deployment) +1. **Follow the basic production setup guide** +2. **Set up SSL certificates** for secure HTTPS access +3. **Configure application deployment** with Docker Compose +4. **Test application functionality** and monitoring + +### CI/CD Integration Setup (Automated Deployment) +1. **Complete basic production setup first** +2. **Exchange SSH keys** with CI/CD server +3. **Configure registry connection** to CI/CD server +4. **Set up Forgejo repository secrets** +5. **Test automated deployment pipeline** + +## What's Included + +### Production Linode Features +- Docker-based application deployment +- SSL/TLS certificate management +- Nginx reverse proxy with security headers +- Automated backups and monitoring +- Firewall and fail2ban protection + +### CI/CD Integration Features (Optional) +- Secure SSH communication with CI/CD server +- Automated deployment from CI/CD pipeline +- Registry connection for image pulling +- Forgejo Actions integration + +## Prerequisites and Initial Setup + +### What's Already Done (Assumptions) + +This guide assumes you have already: + +1. **Created an Ubuntu 24.04 LTS Linode** with root access +2. **Set root password** for the Linode +3. **Assigned DNS entries** for your domain pointing to the Production Linode's IPv4 and IPv6 addresses +4. **Have SSH client** installed on your local machine +5. **For CI/CD Integration**: CI/CD Linode is set up and running with IP address ready +6. **For Manual Deployment**: Have access to Docker images (local registry, Docker Hub, or other registry) + +**Note**: The Production Linode needs a domain name for SSL certificates and public web access. + +### Step 0: Initial SSH Access and Verification + +Before proceeding with the setup, you need to establish initial SSH access to your Production Linode. + +#### 0.1 Get Your Linode IP Address + +From your Linode dashboard, note the IP address for: +- **Production Linode**: `YOUR_PRODUCTION_IP` (IP address for SSH, domain for web access) + +#### 0.2 Test Initial SSH Access + +Test SSH access to your Production Linode: + +```bash +# Test Production Linode (IP address only) +ssh root@YOUR_PRODUCTION_IP +``` + +**Expected output**: SSH login prompt asking for root password. + +**If something goes wrong**: +- Verify the IP address is correct +- Check that SSH is enabled on the Linode +- Ensure your local machine can reach the Linode (no firewall blocking) + +#### 0.3 Verify DNS Configuration + +On the Production Linode, verify DNS is properly configured for your domain: + +```bash +# Check if domain resolves to this server +nslookup your-domain.com + +# Check both IPv4 and IPv6 +dig your-domain.com A +dig your-domain.com AAAA +``` + +**Expected output**: DNS records showing your Production Linode's IP addresses. + +**If something goes wrong**: +- Verify DNS propagation: `dig your-domain.com +trace` +- Check DNS settings in your domain registrar +- Wait for DNS propagation (can take up to 48 hours) + +#### 0.4 Test Domain Accessibility + +Test that your domain is accessible on the Production Linode: + +```bash +# Test HTTP access (should show default nginx/apache page or connection refused) +curl -I http://your-domain.com + +# Test HTTPS access (should fail initially, but domain should resolve) +curl -I https://your-domain.com +``` + +**Expected output**: +- HTTP: Either a web page or "Connection refused" (both are normal) +- HTTPS: SSL error (expected before certificates are installed) + +**If something goes wrong**: +- Verify DNS propagation is complete +- Check that port 80/443 is not blocked by firewall +- Ensure the domain is pointing to the correct IP + +#### 0.5: Choose Your Names + +Before proceeding, decide on: + +1. **Service Account Name**: Choose a username for the service account (e.g., `appuser`, `deploy`, `service`) + - Replace `SERVICE_USER` in this guide with your chosen name + +2. **Application Name**: Choose a name for your application (e.g., `myapp`, `webapp`, `api`) + - Replace `APP_NAME` in this guide with your chosen name + +**Example**: +- If you choose `appuser` as service account and `myapp` as application name: + - Replace all `SERVICE_USER` with `appuser` + - Replace all `APP_NAME` with `myapp` + +--- + +## Basic Production Setup + +This section covers the essential setup for hosting your application. **Skip to the next section if you only want manual deployments.** + +### Step 1: Initial System Setup + +#### 1.1 Update the System + +```bash +sudo apt update && sudo apt upgrade -y +``` + +**What this does**: Updates package lists and upgrades all installed packages to their latest versions. + +**Expected output**: A list of packages being updated, followed by completion messages. + +**If something goes wrong**: +- Check your internet connection +- Try running `sudo apt update` first, then `sudo apt upgrade -y` separately +- If you get package conflicts, run `sudo apt --fix-broken install` + +#### 1.2 Install Essential Packages + +```bash +sudo apt install -y \ + curl \ + wget \ + git \ + ca-certificates \ + apt-transport-https \ + software-properties-common \ + ufw \ + fail2ban \ + htop \ + nginx \ + certbot \ + python3-certbot-nginx +``` + +**What this does**: Installs web server, SSL tools, security packages, and utilities needed for production deployment. + +**Expected output**: Package installation progress, ending with completion messages. + +**If something goes wrong**: +- Check if any packages failed to install +- Run `sudo apt install -f` to fix broken dependencies +- Ensure you have sufficient disk space: `df -h` + +### Step 2: Create Service Account + +#### 2.1 Create the SERVICE_USER User + +```bash +sudo useradd -r -s /bin/bash -m -d /home/SERVICE_USER SERVICE_USER +sudo usermod -aG sudo SERVICE_USER +echo "SERVICE_USER:$(openssl rand -base64 32)" | sudo chpasswd +``` + +**What this does**: +- Creates a dedicated service account named `SERVICE_USER` +- Gives it sudo privileges for administrative tasks +- Generates a random 32-character password + +**Expected output**: No output (successful user creation is silent). + +**If something goes wrong**: +- If user already exists: `sudo userdel -r SERVICE_USER` then retry +- Check user creation: `id SERVICE_USER` +- Verify sudo access: `sudo -u SERVICE_USER sudo -l` + +#### 2.2 Verify Service Account + +```bash +sudo su - SERVICE_USER +whoami +pwd +``` + +**What this does**: Switches to the SERVICE_USER user and verifies the setup. + +**Expected output**: +``` +SERVICE_USER +/home/SERVICE_USER +``` + +### Step 3: Install Docker + +#### 3.1 Add Docker Repository + +```bash +curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg +echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null +sudo apt update +``` + +**What this does**: Adds Docker's official repository to your system for the latest Docker versions. + +**Expected output**: GPG key import confirmation and package list update. + +#### 3.2 Install Docker Packages + +```bash +sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin +``` + +**What this does**: Installs Docker Engine, CLI, container runtime, and Docker Compose. + +**Expected output**: Package installation progress, ending with completion messages. + +#### 3.3 Configure Docker for Service Account + +```bash +sudo usermod -aG docker SERVICE_USER +``` + +**What this does**: Adds the SERVICE_USER user to the docker group so it can run Docker commands without sudo. + +### Step 4: Install Docker Compose + +```bash +sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose +sudo chmod +x /usr/local/bin/docker-compose +``` + +**What this does**: Downloads the latest Docker Compose binary and makes it executable. + +### Step 5: Configure Security + +#### 5.1 Configure Firewall + +```bash +sudo ufw --force enable +sudo ufw default deny incoming +sudo ufw default allow outgoing +sudo ufw allow ssh +sudo ufw allow 80/tcp +sudo ufw allow 443/tcp +sudo ufw allow 3000/tcp +sudo ufw allow 3001/tcp +``` + +**What this does**: Configures firewall to allow only necessary ports for web traffic and application. + +#### 5.2 Configure Fail2ban + +```bash +sudo systemctl enable fail2ban +sudo systemctl start fail2ban +``` + +**What this does**: Enables fail2ban to protect against brute force attacks. + +### Step 6: Create Application Directory + +#### 6.1 Create Directory Structure + +```bash +sudo mkdir -p /opt/APP_NAME +sudo chown SERVICE_USER:SERVICE_USER /opt/APP_NAME +``` + +**What this does**: Creates a directory for the application and sets ownership to the SERVICE_USER user. + +#### 6.2 Create SSL Directory + +```bash +sudo mkdir -p /opt/APP_NAME/nginx/ssl +sudo chown SERVICE_USER:SERVICE_USER /opt/APP_NAME/nginx/ssl +``` + +**What this does**: Creates a directory for SSL certificates. + +### Step 7: Set Up Application Files + +#### 7.1 Switch to SERVICE_USER User + +```bash +sudo su - SERVICE_USER +``` + +#### 7.2 Create Environment File + +```bash +cat > /opt/APP_NAME/.env << 'EOF' +# Production Environment Variables +POSTGRES_PASSWORD=your_secure_password_here +REGISTRY=your-remote-registry.com:port +IMAGE_NAME=APP_NAME +IMAGE_TAG=latest + +# Database Configuration +DATABASE_URL=postgresql://SERVICE_USER:your_secure_password_here@postgres:5432/APP_NAME + +# Application Configuration +NODE_ENV=production +RUST_LOG=info +EOF +``` + +**What this does**: Creates environment variables for the application deployment. + +**Note**: For manual deployments, you'll typically: +- **Pull images from a remote registry** (private registry, GitHub Container Registry, etc.) +- **Update `your-remote-registry.com:port`** with your actual registry URL +- **Configure docker-compose.yml** to reference images from your registry + +**Examples for different registry types**: +```bash +# Private registry +REGISTRY=registry.company.com:5000 + +# GitHub Container Registry +REGISTRY=ghcr.io + +# GitLab Container Registry +REGISTRY=registry.gitlab.com + +# AWS ECR +REGISTRY=123456789012.dkr.ecr.us-east-1.amazonaws.com +``` + +**Examples for docker-compose.yml**: +```yaml +# For remote registry: +services: + backend: + image: your-remote-registry.com:port/APP_NAME-backend:latest + frontend: + image: your-remote-registry.com:port/APP_NAME-frontend:latest +``` + +#### 7.3 Create Deployment Script + +```bash +cat > /opt/APP_NAME/deploy.sh << 'EOF' +#!/bin/bash + +# Deployment script for APP_NAME +set -e + +echo "Deploying APP_NAME..." + +# Pull latest images +docker-compose pull + +# Stop existing containers +docker-compose down + +# Start new containers +docker-compose up -d + +# Clean up old images +docker image prune -f + +echo "Deployment complete!" +echo "Check status with: docker-compose ps" +EOF + +chmod +x /opt/APP_NAME/deploy.sh +``` + +**What this does**: Creates a deployment script for easy application updates. + +#### 7.4 Create Backup Script + +```bash +cat > /opt/APP_NAME/backup.sh << 'EOF' +#!/bin/bash + +# Backup script for APP_NAME +set -e + +BACKUP_DIR="/opt/APP_NAME/backups" +DATE=$(date +%Y%m%d_%H%M%S) + +mkdir -p $BACKUP_DIR + +# Backup database +docker-compose exec -T postgres pg_dump -U SERVICE_USER APP_NAME > $BACKUP_DIR/db_backup_$DATE.sql + +# Backup configuration files +tar -czf $BACKUP_DIR/config_backup_$DATE.tar.gz .env docker-compose.yml nginx/ + +# Keep only last 7 days of backups +find $BACKUP_DIR -name "*.sql" -mtime +7 -delete +find $BACKUP_DIR -name "*.tar.gz" -mtime +7 -delete + +echo "Backup completed: $BACKUP_DIR" +EOF + +chmod +x /opt/APP_NAME/backup.sh +``` + +**What this does**: Creates a backup script for database and configuration files. + +#### 7.5 Create Monitoring Script + +```bash +cat > /opt/APP_NAME/monitor.sh << 'EOF' +#!/bin/bash + +echo "=== APP_NAME Application Status ===" +echo "Date: $(date)" +echo "Uptime: $(uptime)" +echo "" + +echo "=== Docker Containers ===" +docker-compose ps +echo "" + +echo "=== Application Logs (last 20 lines) ===" +docker-compose logs --tail=20 +echo "" + +echo "=== System Resources ===" +echo "CPU Usage:" +top -bn1 | grep "Cpu(s)" | awk '{print $2}' | cut -d'%' -f1 +echo "Memory Usage:" +free -h | grep Mem +echo "Disk Usage:" +df -h / +echo "" + +echo "=== Network Status ===" +netstat -tlnp | grep -E ':(80|443|3000|3001)' +echo "" + +echo "=== SSL Certificate Status ===" +if [ -f "/opt/APP_NAME/nginx/ssl/cert.pem" ]; then + openssl x509 -in /opt/APP_NAME/nginx/ssl/cert.pem -text -noout | grep -E "(Not Before|Not After)" +else + echo "SSL certificate not found" +fi +EOF + +chmod +x /opt/APP_NAME/monitor.sh +``` + +**What this does**: Creates a monitoring script to check application and system status. + +### Step 8: Set Up Automated Backups + +```bash +sudo -u SERVICE_USER bash -c '(crontab -l 2>/dev/null; echo "0 2 * * * /opt/APP_NAME/backup.sh") | crontab -' +``` + +**What this does**: Schedules daily backups at 2 AM. + +### Step 9: Copy Application Files + +```bash +sudo su - SERVICE_USER +cd /opt/APP_NAME +git clone https://your-forgejo-instance/your-username/APP_NAME.git . +``` + +**What this does**: Clones your application repository to the production server. + +**Note**: Replace the URL with your actual repository URL. + +### Step 10: Set Up SSL Certificates + +#### 10.1 Install SSL Certificate + +```bash +sudo certbot --nginx -d your-domain.com +``` + +**What this does**: Installs Let's Encrypt SSL certificate for your domain. + +**Expected output**: Certificate installation confirmation and Nginx configuration updates. + +#### 10.2 Copy Certificates + +```bash +sudo cp /etc/letsencrypt/live/your-domain.com/fullchain.pem /opt/APP_NAME/nginx/ssl/cert.pem +sudo cp /etc/letsencrypt/live/your-domain.com/privkey.pem /opt/APP_NAME/nginx/ssl/key.pem +sudo chown SERVICE_USER:SERVICE_USER /opt/APP_NAME/nginx/ssl/* +``` + +**What this does**: Copies SSL certificates to the application directory for Docker use. + +### Step 11: Test Basic Application + +#### 11.1 Test Application Deployment + +```bash +cd /opt/APP_NAME +./deploy.sh +``` + +**What this does**: Deploys the application using Docker Compose. + +**Expected output**: Deployment progress messages and completion. + +#### 11.2 Monitor Application + +```bash +cd /opt/APP_NAME +./monitor.sh +``` + +**Expected output**: Application status, system resources, and recent logs. + +--- + +## CI/CD Integration Setup (Optional) + +**⚠️ SKIP THIS SECTION if you're only doing manual deployments!** + +This section covers integrating with a CI/CD pipeline for automated deployments. + +### Prerequisites for CI/CD Integration + +- CI/CD Linode already set up and running +- CI/CD Linode IP address available +- SSH access to CI/CD server + +### Step 1: Configure Docker for CI/CD Registry + +#### 1.1 Update Docker Daemon Configuration + +```bash +# Configure Docker to allow connections to CI/CD registry +echo '{"insecure-registries": ["YOUR_CI_HOST_IP:5000"]}' | sudo tee /etc/docker/daemon.json +sudo systemctl restart docker +``` + +**What this does**: Configures Docker to allow connections to your CI/CD registry without SSL verification. + +**Note**: Replace `YOUR_CI_HOST_IP` with your actual CI/CD server IP. + +### Step 2: Set Up SSH Keys for CI/CD Communication + +#### 2.1 Create SSH Directory + +```bash +mkdir -p ~/.ssh +chmod 700 ~/.ssh +``` + +#### 2.2 Generate SSH Key Pair + +```bash +ssh-keygen -t ed25519 -C "production-server" -f ~/.ssh/id_ed25519 -N "" +``` + +**What this does**: Generates an Ed25519 SSH key pair for secure communication with the CI/CD server. + +#### 2.3 Create SSH Config + +```bash +cat > ~/.ssh/config << 'EOF' +Host ci-cd + HostName YOUR_CI_CD_IP + User SERVICE_USER + IdentityFile ~/.ssh/id_ed25519 + StrictHostKeyChecking no + UserKnownHostsFile /dev/null +EOF + +chmod 600 ~/.ssh/config +``` + +**What this does**: Creates SSH configuration for easy connection to the CI/CD server. + +**Note**: Replace `YOUR_CI_CD_IP` with your actual CI/CD server IP. + +### Step 3: Exchange SSH Keys + +#### 3.1 Get Your Public Key + +```bash +cat ~/.ssh/id_ed25519.pub +``` + +**Important**: Copy this public key - you'll need it for the CI/CD server setup. + +#### 3.2 Add CI/CD Server's Public Key + +```bash +echo "CI_CD_PUBLIC_KEY_HERE" >> ~/.ssh/authorized_keys +sed -i 's/YOUR_CI_CD_IP/YOUR_ACTUAL_CI_CD_IP/g' ~/.ssh/config +``` + +**What this does**: Adds the CI/CD server's public key to allow secure communication. + +**Note**: Replace `CI_CD_PUBLIC_KEY_HERE` with the actual public key from your CI/CD server. + +### Step 4: Update Application Configuration for CI/CD + +#### 4.1 Update Environment Variables + +```bash +cd /opt/APP_NAME +nano .env +``` + +**Required changes**: +- Replace `your-registry-url:port` with `YOUR_CI_HOST_IP:5000` +- Replace `your_secure_password_here` with a strong database password +- Update `DATABASE_URL` with the same password + +**Example**: +```bash +# Change this line: +REGISTRY=your-registry-url:port + +# To this (replace with your actual CI/CD IP): +REGISTRY=192.168.1.100:5000 +``` + +### Step 5: Test CI/CD Integration + +#### 5.1 Test SSH Connection + +```bash +ssh ci-cd 'echo Connection successful' +``` + +**Expected output**: `Connection successful`. + +#### 5.2 Test Registry Connection + +```bash +curl http://YOUR_ACTUAL_CI_IP:5000/v2/_catalog +``` + +**Expected output**: `{"repositories":[]}` or a list of available images. + +### Step 6: Configure Forgejo Repository Secrets + +Go to your Forgejo repository → Settings → Secrets and Variables → Actions, and add: + +- `CI_HOST`: Your CI/CD Linode IP address +- `PROD_HOST`: Your production Linode IP +- `PROD_USER`: SSH username for production server (should be `SERVICE_USER`) +- `PROD_SSH_KEY`: SSH private key for deployment (from CI/CD server) + +### Step 7: Test Automated Deployment + +#### 7.1 Push Code Changes + +Make a small change to your code and push to trigger the CI/CD pipeline: + +```bash +# In your local repository +echo "# Test deployment" >> README.md +git add README.md +git commit -m "Test CI/CD pipeline" +git push +``` + +#### 7.2 Monitor Pipeline + +1. Go to your Forgejo repository +2. Navigate to Actions tab +3. Monitor the workflow execution +4. Check for any errors or issues + +#### 7.3 Verify Deployment + +After successful pipeline execution: + +```bash +# Check application status +cd /opt/APP_NAME +docker-compose ps + +# Check application logs +docker-compose logs + +# Test application access +curl -I https://your-domain.com +``` + +--- + +## Testing and Verification + +### Step 1: Test Application Access + +```bash +# Test HTTP redirect to HTTPS +curl -I http://your-domain.com + +# Test HTTPS access +curl -I https://your-domain.com + +# Test application endpoints +curl -I https://your-domain.com/api/health +``` + +**Expected output**: +- HTTP: 301 redirect to HTTPS +- HTTPS: 200 OK response +- API: Application-specific response + +### Step 2: Test Monitoring + +```bash +cd /opt/APP_NAME +./monitor.sh +``` + +**Expected output**: Application status, system resources, and recent logs. + +### Step 3: Test Backup + +```bash +cd /opt/APP_NAME +./backup.sh +``` + +**Expected output**: Backup completion message with file locations. + +### Step 4: Test Manual Deployment (if not using CI/CD) + +```bash +cd /opt/APP_NAME +./pull-and-deploy.sh +``` + +**Expected output**: Pull progress messages, image download, and deployment completion. + +**Alternative manual process**: +```bash +# Pull images manually +docker pull your-remote-registry.com:port/APP_NAME-backend:latest +docker pull your-remote-registry.com:port/APP_NAME-frontend:latest + +# Deploy application +cd /opt/APP_NAME +./deploy.sh +``` + +### Step 5: Manual Image Management (for non-CI/CD deployments) + +If you're not using CI/CD, you'll need to pull and manage Docker images from your remote registry: + +#### 5.1 Configure Registry Authentication + +```bash +# Login to your remote registry +docker login your-remote-registry.com:port + +# For GitHub Container Registry +docker login ghcr.io -u YOUR_GITHUB_USERNAME -p YOUR_GITHUB_TOKEN + +# For GitLab Container Registry +docker login registry.gitlab.com -u YOUR_GITLAB_USERNAME -p YOUR_GITLAB_TOKEN + +# For AWS ECR +aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin 123456789012.dkr.ecr.us-east-1.amazonaws.com +``` + +#### 5.2 Pull Images from Remote Registry + +```bash +# Pull backend image +docker pull your-remote-registry.com:port/APP_NAME-backend:latest + +# Pull frontend image +docker pull your-remote-registry.com:port/APP_NAME-frontend:latest + +# Verify images were pulled +docker images | grep APP_NAME +``` + +#### 5.3 Update Application Configuration + +```bash +# Update docker-compose.yml to use your remote registry images +cd /opt/APP_NAME +nano docker-compose.yml + +# Example configuration: +# image: your-remote-registry.com:port/APP_NAME-backend:latest +# image: your-remote-registry.com:port/APP_NAME-frontend:latest +``` + +#### 5.4 Create Manual Pull and Deploy Script + +```bash +cat > /opt/APP_NAME/pull-and-deploy.sh << 'EOF' +#!/bin/bash + +# Manual pull and deploy script +set -e + +echo "Pulling APP_NAME images from remote registry..." + +# Pull backend +docker pull your-remote-registry.com:port/APP_NAME-backend:latest + +# Pull frontend +docker pull your-remote-registry.com:port/APP_NAME-frontend:latest + +# Show pulled images +echo "Pulled images:" +docker images | grep APP_NAME + +# Deploy application +cd /opt/APP_NAME +./deploy.sh + +echo "Pull and deploy complete!" +EOF + +chmod +x /opt/APP_NAME/pull-and-deploy.sh +``` + +**What this does**: Creates a script to pull images from your remote registry and deploy the application. + +#### 5.5 Create Image Update Script + +```bash +cat > /opt/APP_NAME/update-images.sh << 'EOF' +#!/bin/bash + +# Update images from remote registry +set -e + +echo "Updating images from remote registry..." + +# Pull latest images +docker-compose pull + +# Show updated images +echo "Updated images:" +docker images | grep APP_NAME + +echo "Update complete!" +echo "Run './deploy.sh' to deploy with new images" +EOF + +chmod +x /opt/APP_NAME/update-images.sh +``` + +**What this does**: Creates a script to update images from your remote registry. + +--- + +## Troubleshooting + +### Common Issues + +1. **Docker permission denied**: + ```bash + sudo usermod -aG docker SERVICE_USER + newgrp docker + ``` + +2. **SSL certificate issues**: + ```bash + sudo certbot certificates + sudo certbot renew --dry-run + ``` + +3. **Application not starting**: + ```bash + cd /opt/APP_NAME + docker-compose logs + ``` + +4. **SSH connection failed** (CI/CD integration only): + ```bash + ssh -v ci-cd 'echo test' + ``` + +5. **Registry connection failed** (CI/CD integration only): + ```bash + curl -v http://YOUR_CI_HOST_IP:5000/v2/_catalog + ``` + +### Manual Deployment Issues + +6. **Registry authentication fails**: + ```bash + # Check if you're logged in + docker login your-remote-registry.com:port + + # For GitHub Container Registry + docker login ghcr.io -u YOUR_GITHUB_USERNAME -p YOUR_GITHUB_TOKEN + + # For GitLab Container Registry + docker login registry.gitlab.com -u YOUR_GITLAB_USERNAME -p YOUR_GITLAB_TOKEN + + # Check authentication status + cat ~/.docker/config.json + ``` + +7. **Images not found in remote registry**: + ```bash + # Check if images exist in your registry + docker pull your-remote-registry.com:port/APP_NAME-backend:latest + + # List local images + docker images | grep APP_NAME + + # Check registry connectivity + curl -v https://your-remote-registry.com:port/v2/_catalog + + # For GitHub Container Registry + curl -H "Authorization: Bearer YOUR_GITHUB_TOKEN" https://ghcr.io/v2/_catalog + ``` + +8. **Network connectivity issues**: + ```bash + # Test basic connectivity + ping your-remote-registry.com + + # Test port connectivity + telnet your-remote-registry.com port + + # Check DNS resolution + nslookup your-remote-registry.com + + # Test HTTPS connectivity + curl -I https://your-remote-registry.com:port/v2/ + ``` + +9. **Application configuration issues**: + ```bash + # Check environment variables + cd /opt/APP_NAME + cat .env + + # Validate docker-compose configuration + docker-compose config + + # Check application logs + docker-compose logs --tail=50 + ``` + +10. **Manual pull and deploy process issues**: + ```bash + # Check if pull script exists + ls -la /opt/APP_NAME/pull-and-deploy.sh + + # Run pull with verbose output + bash -x /opt/APP_NAME/pull-and-deploy.sh + + # Check for pull errors + docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.CreatedAt}}" + + # Verify docker-compose.yml references correct registry + grep -A 5 "image:" /opt/APP_NAME/docker-compose.yml + ``` + +### Useful Commands + +- **Check system resources**: `htop` +- **Check disk space**: `df -h` +- **Check memory usage**: `free -h` +- **Check network**: `ip addr show` +- **Check firewall**: `sudo ufw status` +- **Check logs**: `sudo journalctl -f` + +### Security Best Practices + +1. **Service Account**: Use dedicated `SERVICE_USER` user with limited privileges +2. **SSH Keys**: Use Ed25519 keys with proper permissions (600/700) +3. **Firewall**: Configure UFW to allow only necessary ports +4. **Fail2ban**: Protect against brute force attacks +5. **SSL/TLS**: Use Let's Encrypt certificates with automatic renewal +6. **Regular Backups**: Automated daily backups of database and configuration +7. **Container Isolation**: Applications run in isolated Docker containers +8. **Security Headers**: Nginx configured with security headers + +### Monitoring and Maintenance + +#### Daily Monitoring + +```bash +# On Production server +cd /opt/APP_NAME +./monitor.sh +``` + +#### Weekly Maintenance + +1. **Check disk space**: `df -h` +2. **Review logs**: `docker-compose logs --tail=100` +3. **Update system**: `sudo apt update && sudo apt upgrade` +4. **Test backups**: Verify backup files exist and are recent + +#### Monthly Maintenance + +1. **Review security**: Check firewall rules and fail2ban status +2. **Update certificates**: Ensure SSL certificates are valid +3. **Clean up old images**: Remove unused Docker images +4. **Review monitoring**: Check application performance and logs + +--- + +## Summary + +Your Production Linode is now ready to host your application! The setup includes: + +### Basic Features (All Deployments) +- **Docker-based application deployment** with SSL/TLS +- **Nginx reverse proxy** with security headers +- **Automated backups and monitoring** scripts +- **Firewall and fail2ban protection** for security +- **Manual image management** tools and scripts + +### CI/CD Integration Features (Optional) +- **Secure SSH communication** with CI/CD server +- **Automated deployment** from CI/CD pipeline +- **Registry connection** for image pulling +- **Forgejo Actions integration** + +For ongoing maintenance and troubleshooting, refer to the troubleshooting section and monitoring scripts provided in this guide. \ No newline at end of file diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..2b0ce7a --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,61 @@ +# Multi-stage build for Rust backend +FROM rust:1.75-slim as builder + +# Install build dependencies +RUN apt-get update && apt-get install -y \ + pkg-config \ + libssl-dev \ + && rm -rf /var/lib/apt/lists/* + +# Set working directory +WORKDIR /app + +# Copy Cargo files +COPY Cargo.toml Cargo.lock ./ +COPY crates/ ./crates/ + +# Build dependencies first (for better caching) +RUN cargo build --release --bin sharenet-api-postgres + +# Copy source code +COPY . . + +# Build the application +RUN cargo build --release --bin sharenet-api-postgres + +# Runtime stage +FROM debian:bookworm-slim + +# Install runtime dependencies +RUN apt-get update && apt-get install -y \ + ca-certificates \ + libssl3 \ + && rm -rf /var/lib/apt/lists/* + +# Create non-root user +RUN useradd -r -s /bin/false sharenet + +# Set working directory +WORKDIR /app + +# Copy binary from builder +COPY --from=builder /app/target/release/sharenet-api-postgres /app/sharenet-api + +# Copy configuration files +COPY --from=builder /app/config/ ./config/ + +# Change ownership +RUN chown -R sharenet:sharenet /app + +# Switch to non-root user +USER sharenet + +# Expose port +EXPOSE 3001 + +# Health check +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD curl -f http://localhost:3001/health || exit 1 + +# Run the application +CMD ["./sharenet-api"] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..b52b87a --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,89 @@ +version: '3.8' + +services: + postgres: + image: postgres:15-alpine + container_name: sharenet-postgres + restart: unless-stopped + environment: + POSTGRES_DB: sharenet + POSTGRES_USER: sharenet + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-changeme} + volumes: + - postgres_data:/var/lib/postgresql/data + - ./migrations:/docker-entrypoint-initdb.d + ports: + - "5432:5432" + healthcheck: + test: ["CMD-SHELL", "pg_isready -U sharenet"] + interval: 30s + timeout: 10s + retries: 3 + networks: + - sharenet-network + + backend: + image: ${REGISTRY:-ghcr.io}/${IMAGE_NAME:-your-username/sharenet}/backend:${IMAGE_TAG:-latest} + container_name: sharenet-backend + restart: unless-stopped + environment: + DATABASE_URL: postgresql://sharenet:${POSTGRES_PASSWORD:-changeme}@postgres:5432/sharenet + RUST_LOG: info + RUST_BACKTRACE: 1 + ports: + - "3001:3001" + depends_on: + postgres: + condition: service_healthy + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:3001/health"] + interval: 30s + timeout: 10s + retries: 3 + networks: + - sharenet-network + + frontend: + image: ${REGISTRY:-ghcr.io}/${IMAGE_NAME:-your-username/sharenet}/frontend:${IMAGE_TAG:-latest} + container_name: sharenet-frontend + restart: unless-stopped + environment: + NEXT_PUBLIC_API_HOST: backend + NEXT_PUBLIC_API_PORT: 3001 + NODE_ENV: production + ports: + - "3000:3000" + depends_on: + backend: + condition: service_healthy + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"] + interval: 30s + timeout: 10s + retries: 3 + networks: + - sharenet-network + + nginx: + image: nginx:alpine + container_name: sharenet-nginx + restart: unless-stopped + ports: + - "80:80" + - "443:443" + volumes: + - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro + - ./nginx/ssl:/etc/nginx/ssl:ro + depends_on: + - frontend + - backend + networks: + - sharenet-network + +volumes: + postgres_data: + driver: local + +networks: + sharenet-network: + driver: bridge \ No newline at end of file diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 0000000..4e788dc --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,48 @@ +# Multi-stage build for Next.js frontend +FROM node:20-alpine AS builder + +# Set working directory +WORKDIR /app + +# Copy package files +COPY package*.json ./ + +# Install dependencies +RUN npm ci --only=production + +# Copy source code +COPY . . + +# Build the application +RUN npm run build + +# Production stage +FROM node:20-alpine AS runner + +# Create non-root user +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs + +# Set working directory +WORKDIR /app + +# Copy necessary files from builder +COPY --from=builder /app/public ./public +COPY --from=builder /app/.next/standalone ./ +COPY --from=builder /app/.next/static ./.next/static + +# Change ownership +RUN chown -R nextjs:nodejs /app + +# Switch to non-root user +USER nextjs + +# Expose port +EXPOSE 3000 + +# Health check +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD curl -f http://localhost:3000/api/health || exit 1 + +# Run the application +CMD ["node", "server.js"] \ No newline at end of file diff --git a/nginx/nginx.conf b/nginx/nginx.conf new file mode 100644 index 0000000..4bc2d08 --- /dev/null +++ b/nginx/nginx.conf @@ -0,0 +1,112 @@ +events { + worker_connections 1024; +} + +http { + upstream frontend { + server frontend:3000; + } + + upstream backend { + server backend:3001; + } + + # Rate limiting + limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s; + limit_req_zone $binary_remote_addr zone=frontend:10m rate=30r/s; + + # Gzip compression + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_proxied any; + gzip_comp_level 6; + gzip_types + text/plain + text/css + text/xml + text/javascript + application/json + application/javascript + application/xml+rss + application/atom+xml + image/svg+xml; + + # Security headers + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header X-Content-Type-Options "nosniff" always; + add_header Referrer-Policy "no-referrer-when-downgrade" always; + add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always; + + server { + listen 80; + server_name _; + + # Redirect HTTP to HTTPS + return 301 https://$host$request_uri; + } + + server { + listen 443 ssl http2; + server_name _; + + # SSL configuration + ssl_certificate /etc/nginx/ssl/cert.pem; + ssl_certificate_key /etc/nginx/ssl/key.pem; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384; + ssl_prefer_server_ciphers off; + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 10m; + + # Frontend routes + location / { + limit_req zone=frontend burst=20 nodelay; + + proxy_pass http://frontend; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + 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; + proxy_cache_bypass $http_upgrade; + } + + # API routes + location /api/ { + limit_req zone=api burst=10 nodelay; + + proxy_pass http://backend/; + proxy_http_version 1.1; + 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; + + # CORS headers + add_header Access-Control-Allow-Origin * always; + add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always; + add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization" always; + + if ($request_method = 'OPTIONS') { + add_header Access-Control-Allow-Origin *; + add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"; + add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization"; + add_header Access-Control-Max-Age 1728000; + add_header Content-Type 'text/plain; charset=utf-8'; + add_header Content-Length 0; + return 204; + } + } + + # Health check endpoint + location /health { + access_log off; + return 200 "healthy\n"; + add_header Content-Type text/plain; + } + } +} \ No newline at end of file