Add CI/CD and production flow
Some checks are pending
Some checks are pending
This commit is contained in:
parent
a14f29423d
commit
7eee42cea8
7 changed files with 2343 additions and 0 deletions
144
.forgejo/workflows/ci.yml
Normal file
144
.forgejo/workflows/ci.yml
Normal file
|
@ -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
|
819
CI_CD_SETUP_GUIDE.md
Normal file
819
CI_CD_SETUP_GUIDE.md
Normal file
|
@ -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.
|
1070
PRODUCTION_SETUP_GUIDE.md
Normal file
1070
PRODUCTION_SETUP_GUIDE.md
Normal file
File diff suppressed because it is too large
Load diff
61
backend/Dockerfile
Normal file
61
backend/Dockerfile
Normal file
|
@ -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"]
|
89
docker-compose.yml
Normal file
89
docker-compose.yml
Normal file
|
@ -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
|
48
frontend/Dockerfile
Normal file
48
frontend/Dockerfile
Normal file
|
@ -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"]
|
112
nginx/nginx.conf
Normal file
112
nginx/nginx.conf
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue