Add GitHub Actions deployment workflow

- Add deployment workflow with test, build, and deploy jobs
- Add testing workflow for PRs
- Add deployment scripts (deploy, healthcheck, rollback)
- Add systemd service configuration
- Update Makefile with CI/CD targets
- Add comprehensive deployment documentation
This commit is contained in:
root
2025-10-30 12:19:57 +00:00
parent ee354d1d35
commit 5e38292d2e
9 changed files with 1849 additions and 10 deletions
+207
View File
@@ -0,0 +1,207 @@
name: Deploy CV Site to Production
on:
push:
branches:
- main
workflow_dispatch:
inputs:
skip_tests:
description: 'Skip tests'
required: false
default: false
type: boolean
env:
GO_VERSION: '1.25'
APP_NAME: 'cv-server'
SERVICE_NAME: 'cv'
DEPLOY_PATH: '/home/txeo/Git/yo/cv'
HEALTH_URL: 'https://juan.andres.morenorub.io/health'
jobs:
test:
name: Run Tests
runs-on: ubuntu-latest
if: ${{ !inputs.skip_tests }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
cache: true
- name: Download dependencies
run: go mod download
- name: Run tests
run: |
go test -v -race -coverprofile=coverage.txt -covermode=atomic ./...
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
file: ./coverage.txt
fail_ci_if_error: false
build:
name: Build Application
runs-on: ubuntu-latest
needs: test
if: always() && (needs.test.result == 'success' || needs.test.result == 'skipped')
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
cache: true
- name: Build binary
run: |
mkdir -p build
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
-ldflags="-w -s -X main.version=${{ github.sha }}" \
-o build/${{ env.APP_NAME }} \
.
- name: Compress binary
run: |
cd build
tar -czf ${{ env.APP_NAME }}-${{ github.sha }}.tar.gz ${{ env.APP_NAME }}
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: cv-server-binary
path: build/${{ env.APP_NAME }}-${{ github.sha }}.tar.gz
retention-days: 30
deploy:
name: Deploy to Production
runs-on: ubuntu-latest
needs: build
environment:
name: production
url: ${{ env.HEALTH_URL }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Download artifact
uses: actions/download-artifact@v4
with:
name: cv-server-binary
path: ./build
- name: Setup SSH
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key
ssh-keyscan -p ${{ secrets.SSH_PORT || 22 }} ${{ secrets.SSH_HOST }} >> ~/.ssh/known_hosts
- name: Deploy to Server
env:
SSH_KEY: ~/.ssh/deploy_key
SSH_USER: ${{ secrets.SSH_USER }}
SSH_HOST: ${{ secrets.SSH_HOST }}
SSH_PORT: ${{ secrets.SSH_PORT || 22 }}
run: |
# Extract artifact
cd build
tar -xzf ${{ env.APP_NAME }}-${{ github.sha }}.tar.gz
# Upload binary to server
scp -i $SSH_KEY -P $SSH_PORT ${{ env.APP_NAME }} \
$SSH_USER@$SSH_HOST:${{ env.DEPLOY_PATH }}/${{ env.APP_NAME }}.new
# Upload deployment script
scp -i $SSH_KEY -P $SSH_PORT ../scripts/deploy.sh \
$SSH_USER@$SSH_HOST:${{ env.DEPLOY_PATH }}/
# Sync static assets, templates, and data
echo "📦 Syncing assets..."
rsync -avz -e "ssh -i $SSH_KEY -p $SSH_PORT" \
--exclude '.git' \
--exclude 'build' \
--exclude 'node_modules' \
--exclude '.env' \
../static/ ../templates/ ../data/ \
$SSH_USER@$SSH_HOST:${{ env.DEPLOY_PATH }}/ 2>/dev/null || echo "Note: Some directories may not exist"
# Execute deployment on server
ssh -i $SSH_KEY -p $SSH_PORT $SSH_USER@$SSH_HOST << 'ENDSSH'
cd ${{ env.DEPLOY_PATH }}
# Make scripts executable
chmod +x deploy.sh 2>/dev/null || true
chmod +x ${{ env.APP_NAME }}.new
# Export environment for deploy script
export DEPLOY_PATH="${{ env.DEPLOY_PATH }}"
# Run deployment
./deploy.sh ${{ env.APP_NAME }} ${{ env.SERVICE_NAME }}
ENDSSH
- name: Health check
run: |
echo "Waiting for application to start..."
sleep 10
# Check service status
ssh -i ~/.ssh/deploy_key -p ${{ secrets.SSH_PORT || 22 }} \
${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} \
"systemctl is-active ${{ env.SERVICE_NAME }}"
# HTTP health check
RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" ${{ env.HEALTH_URL }})
if [ "$RESPONSE" = "200" ]; then
echo "✅ Health check passed (HTTP $RESPONSE)"
else
echo "❌ Health check failed (HTTP $RESPONSE)"
exit 1
fi
- name: Send notification
if: always()
run: |
if [ -z "${{ secrets.SLACK_WEBHOOK }}" ]; then
echo "No Slack webhook configured, skipping notification"
exit 0
fi
STATUS="${{ job.status }}"
COLOR="good"
if [ "$STATUS" != "success" ]; then
COLOR="danger"
fi
curl -X POST "${{ secrets.SLACK_WEBHOOK }}" \
-H "Content-Type: application/json" \
-d "{
\"attachments\": [{
\"color\": \"$COLOR\",
\"title\": \"CV Site Deployment $STATUS\",
\"text\": \"Deployment to production completed\",
\"fields\": [
{\"title\": \"Branch\", \"value\": \"${{ github.ref_name }}\", \"short\": true},
{\"title\": \"Commit\", \"value\": \"${{ github.sha }}\", \"short\": true},
{\"title\": \"Environment\", \"value\": \"production\", \"short\": true}
]
}]
}" || true
- name: Cleanup SSH keys
if: always()
run: |
rm -f ~/.ssh/deploy_key
+44
View File
@@ -0,0 +1,44 @@
name: Test CV Site
on:
pull_request:
branches: [main, develop]
push:
branches: [main, develop]
jobs:
test:
name: Test on Go ${{ matrix.go-version }}
runs-on: ubuntu-latest
strategy:
matrix:
go-version: ['1.24', '1.25']
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
cache: true
- name: Install dependencies
run: go mod download
- name: Run linter
uses: golangci/golangci-lint-action@v3
with:
version: latest
skip-cache: false
skip-pkg-cache: false
skip-build-cache: false
- name: Run tests
run: |
go test -v -race -coverprofile=coverage.txt ./...
- name: Build
run: |
go build -v .
+320
View File
@@ -0,0 +1,320 @@
# Deployment Setup Guide
This guide will help you configure GitHub Actions for automatic deployment of your CV site.
## Overview
The deployment system is now fully configured with:
- ✅ GitHub Actions workflows (`.github/workflows/`)
- ✅ Deployment scripts (`scripts/`)
- ✅ Systemd service configuration (`config/systemd/cv.service`)
- ✅ Updated Makefile with CI/CD targets
## Required GitHub Secrets
To enable automatic deployment, you need to configure these secrets in your GitHub repository:
Go to: **Settings → Secrets and variables → Actions → New repository secret**
### Essential Secrets
| Secret Name | Value | Description |
|------------|-------|-------------|
| `SSH_PRIVATE_KEY` | Your private SSH key | Used to connect to the production server |
| `SSH_HOST` | `localhost` or your server IP | Production server hostname |
| `SSH_USER` | `txeo` | SSH username for deployment |
| `SSH_PORT` | `22` (default) | SSH port (optional if using default) |
### Optional Secrets
| Secret Name | Description |
|------------|-------------|
| `SLACK_WEBHOOK` | Slack webhook URL for deployment notifications |
## Setting Up SSH Key for GitHub Actions
Since this machine is both the development and production server, you need to set up SSH access for GitHub Actions to deploy here.
### Option 1: Deploy to localhost (Recommended for same-machine setup)
If GitHub Actions will run on the same machine, you can use localhost:
```bash
# Generate a deployment key (reusable for all projects)
ssh-keygen -t ed25519 -C "github-actions-deployment" -f ~/.ssh/github-deploy -N ""
# Add the public key to authorized_keys
cat ~/.ssh/github-deploy.pub >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
# Copy the private key to add to GitHub Secrets (use this for ALL projects)
cat ~/.ssh/github-deploy
# Copy the entire output (including BEGIN and END lines)
```
**GitHub Secrets Configuration:**
- `SSH_PRIVATE_KEY`: (paste the private key output from above)
- `SSH_HOST`: `localhost` or `127.0.0.1`
- `SSH_USER`: `txeo`
- `SSH_PORT`: `22`
### Option 2: Deploy from GitHub hosted runners
If using GitHub's hosted runners to deploy to this server:
```bash
# Generate a deployment key (reusable for all projects)
ssh-keygen -t ed25519 -C "github-actions-deployment" -f ~/.ssh/github-deploy -N ""
# Add the public key to authorized_keys
cat ~/.ssh/github-deploy.pub >> ~/.ssh/authorized_keys
# Get your public IP address
curl ifconfig.me
# Copy the private key (use this for ALL projects)
cat ~/.ssh/github-deploy
```
**GitHub Secrets Configuration:**
- `SSH_PRIVATE_KEY`: (paste the private key output)
- `SSH_HOST`: (your public IP from `curl ifconfig.me`)
- `SSH_USER`: `txeo`
- `SSH_PORT`: `22`
## Reusing Secrets Across Multiple Projects
**Important**: If you have multiple projects deploying to the same server, use **one shared SSH key** for all of them.
### Why One Shared Key?
**Advantages:**
- **Simpler management** - One key to rotate/update instead of multiple
- **Same access level** - All projects deploy as the same user anyway
- **Easier setup** - Generate once, reuse everywhere
- **Standard practice** - CI/CD systems typically use one deploy key per server
**You don't need separate keys because:**
- All projects deploy to the same server
- All projects use the same user account (`txeo`)
- Separation is already handled by different deployment paths and services
### Shared Secrets (Same for All 3+ Projects)
Copy these exact same values to all your project repositories:
- `SSH_PRIVATE_KEY` - The `~/.ssh/github-deploy` key you generated once
- `SSH_HOST` - Same server (localhost or your IP)
- `SSH_USER` - Same user (`txeo`)
- `SSH_PORT` - Same SSH port (`22`)
- `SLACK_WEBHOOK` - Same notification channel (optional)
### Project-Specific Configuration
Only change these in each project's `.github/workflows/deploy.yml` (in the `env` section):
| Project | APP_NAME | SERVICE_NAME | DEPLOY_PATH | PORT |
|---------|----------|--------------|-------------|------|
| CV Site | cv-server | cv | /home/txeo/Git/yo/cv | 1999 |
| Project 2 | project2-server | project2 | /home/txeo/Git/yo/project2 | 2000 |
| Project 3 | project3-server | project3 | /home/txeo/Git/yo/project3 | 2001 |
### Setup Process
1. **Generate SSH key once** on the server:
```bash
ssh-keygen -t ed25519 -C "github-actions-deployment" -f ~/.ssh/github-deploy -N ""
cat ~/.ssh/github-deploy.pub >> ~/.ssh/authorized_keys
```
2. **Copy the key** for use in all projects:
```bash
cat ~/.ssh/github-deploy
```
3. **Add to each repository's GitHub Secrets** (same values):
- Repository 1 (cv) → Add secrets
- Repository 2 (project2) → Add **same** secrets
- Repository 3 (project3) → Add **same** secrets
4. **Customize each workflow** by editing only the `env` section in `deploy.yml`
## Service Configuration
The systemd service configuration has been created at:
- **Template**: `config/systemd/cv.service`
- **Active service**: `/etc/systemd/system/cv.service`
### Update the Active Service (if needed)
If you want to use the new service template with better security settings:
```bash
# Update the systemd service
make update-service
# Or manually:
sudo cp config/systemd/cv.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl restart cv
sudo systemctl status cv
```
## Testing the Deployment Locally
Before pushing to GitHub, you can test the deployment scripts locally:
```bash
# Build the binary
make ci-build
# Test the deployment script
cd build
tar -czf cv-server-test.tar.gz cv-server
cd ..
mv build/cv-server-test.tar.gz ./cv-server.new
chmod +x scripts/deploy.sh
./scripts/deploy.sh cv-server cv
# Check service status
systemctl status cv
# Test health check
make health-check
```
## Deployment Workflow
Once configured, the deployment works automatically:
1. **Push to main branch** → Triggers deployment
2. **Run tests** → Ensures code quality
3. **Build binary** → Creates production binary
4. **Deploy to server** → Uploads and installs new version
5. **Health check** → Verifies deployment success
6. **Send notification** → (if Slack webhook configured)
### Manual Deployment
You can also trigger deployment manually:
1. Go to **Actions** tab in GitHub
2. Select **Deploy CV Site to Production**
3. Click **Run workflow**
4. Choose options (e.g., skip tests)
5. Click **Run workflow**
## Rollback
If a deployment fails or you need to rollback:
```bash
# View available backups
./scripts/rollback.sh cv-server cv
# Rollback to the most recent version
./scripts/rollback.sh cv-server cv latest
# Rollback to specific version (e.g., version #2)
./scripts/rollback.sh cv-server cv 2
```
## Monitoring
### Check Service Status
```bash
# Service status
systemctl status cv
# View logs
journalctl -u cv -f
# View recent logs
journalctl -u cv -n 50 --no-pager
```
### Check Application Health
```bash
# Local health check
curl http://localhost:1999/health
# Public health check
curl https://juan.andres.morenorub.io/health
# Or use the make target
make health-check
```
## File Structure
```
cv/
├── .github/
│ └── workflows/
│ ├── deploy.yml # Main deployment workflow
│ └── test.yml # Testing workflow
├── scripts/
│ ├── deploy.sh # Deployment script
│ ├── healthcheck.sh # Health check script
│ └── rollback.sh # Rollback script
├── config/
│ └── systemd/
│ └── cv.service # Systemd service template
├── backups/ # (created during deployment)
│ └── cv-server.* # Binary backups
└── Makefile # Build and deployment targets
```
## Troubleshooting
### Deployment Fails with SSH Error
```bash
# Test SSH connection
ssh -p 22 txeo@localhost "echo 'SSH works'"
# Check SSH key permissions
ls -la ~/.ssh/github-deploy
# Should show: -rw------- (600)
```
### Service Won't Start
```bash
# Check service logs
sudo journalctl -u cv -n 50
# Test binary manually
./cv-server
# Check port availability
sudo netstat -tlnp | grep 1999
```
### Health Check Fails
```bash
# Check if service is running
systemctl is-active cv
# Test health endpoint
curl -v http://localhost:1999/health
# Check firewall
sudo ufw status
```
## Next Steps
1. **Add GitHub Secrets** as documented above
2. **Push to main branch** to trigger first deployment
3. **Monitor the deployment** in GitHub Actions tab
4. **Verify deployment** by checking https://juan.andres.morenorub.io
## Support
For issues or questions:
- Check GitHub Actions logs in the **Actions** tab
- Review systemd logs: `journalctl -u cv -f`
- Review the main deployment guide: `GITHUB_ACTIONS_DEPLOYMENT_GUIDE.md`
+996
View File
@@ -0,0 +1,996 @@
# GitHub Actions Deployment Guide for CV Site (Go Project)
> **Target Project**: `cv-site` (Go application)
> **Repository**: `github.com/juanatsap/cv-site`
> **Deployment**: VM-based deployment (similar to La Porra architecture)
> **Reference**: Based on La Porra's GitHub Actions workflows
---
## Table of Contents
1. [Overview](#overview)
2. [Required GitHub Secrets](#required-github-secrets)
3. [Workflow Files Structure](#workflow-files-structure)
4. [Main Deployment Workflow](#main-deployment-workflow)
5. [CDN Integration (Optional)](#cdn-integration-optional)
6. [Documentation Deployment](#documentation-deployment)
7. [Build Artifacts](#build-artifacts)
8. [Environment Configuration](#environment-configuration)
9. [Testing Strategy](#testing-strategy)
10. [Deployment Checklist](#deployment-checklist)
---
## Overview
This guide provides a complete GitHub Actions setup for deploying a Go-based application to a VM server. The workflow is based on La Porra's proven deployment patterns but adapted for Go applications.
### Key Components
- **CI/CD Pipeline**: Automated build, test, and deployment
- **Artifact Management**: Binary compilation and distribution
- **CDN Integration**: CloudFlare for static assets (optional)
- **Health Checks**: Post-deployment validation
- **Notifications**: Deployment status alerts
---
## Required GitHub Secrets
Configure these secrets in your repository settings (`Settings → Secrets and variables → Actions`):
### Essential Secrets
| Secret Name | Description | Example/Notes |
|-------------|-------------|---------------|
| `SSH_PRIVATE_KEY` | Private SSH key for VM access | Generate with `ssh-keygen -t ed25519` |
| `SSH_HOST` | VM server hostname or IP | `your-vm.example.com` or `192.168.1.100` |
| `SSH_USER` | SSH username for deployment | `deployer` or `ubuntu` |
| `SSH_PORT` | SSH port (if non-standard) | `22` (default) or custom port |
| `DEPLOY_PATH` | Application directory on VM | `/var/www/cv-site` or `/opt/cv-site` |
### Optional Secrets (if using CDN)
| Secret Name | Description | Required For |
|-------------|-------------|--------------|
| `CLOUDFLARE_API_TOKEN` | CloudFlare API token | CDN deployment |
| `CLOUDFLARE_ZONE_ID` | CloudFlare zone identifier | CDN deployment |
| `SLACK_WEBHOOK` | Slack webhook URL for notifications | Team notifications |
### How to Generate SSH Keys
```bash
# On your local machine, generate a key pair
ssh-keygen -t ed25519 -C "github-actions-cv-site" -f ~/.ssh/cv-site-deploy
# Copy the public key to your VM
ssh-copy-id -i ~/.ssh/cv-site-deploy.pub user@your-vm.example.com
# Add the PRIVATE key content to GitHub Secrets
cat ~/.ssh/cv-site-deploy
# Copy the entire output (including -----BEGIN and -----END lines)
```
---
## Workflow Files Structure
Create these files in your repository:
```
cv-site/
├── .github/
│ └── workflows/
│ ├── deploy.yml # Main deployment workflow
│ ├── cdn-deploy.yml # CDN optimization (optional)
│ └── test.yml # CI testing workflow
├── scripts/
│ ├── deploy.sh # Deployment script for VM
│ ├── healthcheck.sh # Post-deployment validation
│ └── rollback.sh # Rollback to previous version
├── config/
│ └── systemd/
│ └── cv-site.service # Systemd service file
└── Makefile # Build targets
```
---
## Main Deployment Workflow
### File: `.github/workflows/deploy.yml`
```yaml
name: Deploy CV Site to VM
on:
push:
branches:
- main
- production
workflow_dispatch:
inputs:
environment:
description: 'Deployment environment'
required: true
default: 'production'
type: choice
options:
- production
- staging
env:
GO_VERSION: '1.22'
APP_NAME: 'cv-site'
BUILD_DIR: './build'
jobs:
test:
name: Run Tests
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
cache: true
- name: Download dependencies
run: go mod download
- name: Run tests
run: |
go test -v -race -coverprofile=coverage.txt -covermode=atomic ./...
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
file: ./coverage.txt
fail_ci_if_error: false
build:
name: Build Application
runs-on: ubuntu-latest
needs: test
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
cache: true
- name: Build binary
run: |
mkdir -p ${{ env.BUILD_DIR }}
# Build for Linux AMD64 (typical VM architecture)
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
-ldflags="-w -s -X main.Version=${{ github.sha }} -X main.BuildTime=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
-o ${{ env.BUILD_DIR }}/${{ env.APP_NAME }} \
./cmd/${{ env.APP_NAME }}
- name: Compress binary
run: |
cd ${{ env.BUILD_DIR }}
tar -czf ${{ env.APP_NAME }}-${{ github.sha }}.tar.gz ${{ env.APP_NAME }}
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: cv-site-binary
path: ${{ env.BUILD_DIR }}/${{ env.APP_NAME }}-${{ github.sha }}.tar.gz
retention-days: 30
deploy:
name: Deploy to VM
runs-on: ubuntu-latest
needs: build
environment:
name: ${{ github.event.inputs.environment || 'production' }}
url: https://cv.example.com
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Download artifact
uses: actions/download-artifact@v4
with:
name: cv-site-binary
path: ./build
- name: Setup SSH
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key
ssh-keyscan -p ${{ secrets.SSH_PORT || 22 }} ${{ secrets.SSH_HOST }} >> ~/.ssh/known_hosts
- name: Deploy to VM
env:
SSH_KEY: ~/.ssh/deploy_key
SSH_USER: ${{ secrets.SSH_USER }}
SSH_HOST: ${{ secrets.SSH_HOST }}
SSH_PORT: ${{ secrets.SSH_PORT || 22 }}
DEPLOY_PATH: ${{ secrets.DEPLOY_PATH }}
run: |
# Extract artifact
cd build
tar -xzf ${{ env.APP_NAME }}-${{ github.sha }}.tar.gz
# Upload binary to VM
scp -i $SSH_KEY -P $SSH_PORT ${{ env.APP_NAME }} \
$SSH_USER@$SSH_HOST:$DEPLOY_PATH/${{ env.APP_NAME }}.new
# Upload deployment script
scp -i $SSH_KEY -P $SSH_PORT ../scripts/deploy.sh \
$SSH_USER@$SSH_HOST:$DEPLOY_PATH/
# Execute deployment on VM
ssh -i $SSH_KEY -p $SSH_PORT $SSH_USER@$SSH_HOST << 'ENDSSH'
cd ${{ secrets.DEPLOY_PATH }}
# Make scripts executable
chmod +x deploy.sh
chmod +x ${{ env.APP_NAME }}.new
# Run deployment script
./deploy.sh ${{ env.APP_NAME }}
ENDSSH
- name: Health check
run: |
echo "Waiting for application to start..."
sleep 10
# Check if service is running
ssh -i ~/.ssh/deploy_key -p ${{ secrets.SSH_PORT || 22 }} \
${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} \
"systemctl is-active ${{ env.APP_NAME }}"
# HTTP health check
RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" https://cv.example.com/health)
if [ "$RESPONSE" = "200" ]; then
echo "✅ Health check passed (HTTP $RESPONSE)"
else
echo "❌ Health check failed (HTTP $RESPONSE)"
exit 1
fi
- name: Send notification
if: always()
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
run: |
STATUS="${{ job.status }}"
COLOR="good"
if [ "$STATUS" != "success" ]; then
COLOR="danger"
fi
curl -X POST "$SLACK_WEBHOOK" \
-H "Content-Type: application/json" \
-d "{
\"attachments\": [{
\"color\": \"$COLOR\",
\"title\": \"CV Site Deployment $STATUS\",
\"text\": \"Deployment to VM completed\",
\"fields\": [
{\"title\": \"Branch\", \"value\": \"${{ github.ref_name }}\", \"short\": true},
{\"title\": \"Commit\", \"value\": \"${{ github.sha }}\", \"short\": true},
{\"title\": \"Environment\", \"value\": \"${{ github.event.inputs.environment || 'production' }}\", \"short\": true}
]
}]
}" || true
- name: Cleanup SSH keys
if: always()
run: |
rm -f ~/.ssh/deploy_key
```
---
## CDN Integration (Optional)
### File: `.github/workflows/cdn-deploy.yml`
```yaml
name: CloudFlare CDN Deployment
on:
push:
branches:
- main
paths:
- 'static/**'
- 'assets/**'
workflow_dispatch:
inputs:
purge_cache:
description: 'Purge CDN cache'
required: false
default: 'true'
type: choice
options:
- 'true'
- 'false'
jobs:
deploy-cdn:
name: Deploy Static Assets to CDN
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup SSH
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key
ssh-keyscan -p ${{ secrets.SSH_PORT || 22 }} ${{ secrets.SSH_HOST }} >> ~/.ssh/known_hosts
- name: Sync static assets to VM
run: |
rsync -avz -e "ssh -i ~/.ssh/deploy_key -p ${{ secrets.SSH_PORT || 22 }}" \
--delete \
./static/ \
${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }}:${{ secrets.DEPLOY_PATH }}/static/
- name: Configure CloudFlare
if: secrets.CLOUDFLARE_API_TOKEN != ''
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ZONE_ID: ${{ secrets.CLOUDFLARE_ZONE_ID }}
run: |
# Enable WebP
curl -X PATCH "https://api.cloudflare.com/client/v4/zones/${CLOUDFLARE_ZONE_ID}/settings/webp" \
-H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \
-H "Content-Type: application/json" \
--data '{"value":"on"}'
# Enable Brotli compression
curl -X PATCH "https://api.cloudflare.com/client/v4/zones/${CLOUDFLARE_ZONE_ID}/settings/brotli" \
-H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \
-H "Content-Type: application/json" \
--data '{"value":"on"}'
- name: Purge CDN cache
if: github.event.inputs.purge_cache == 'true' || github.event_name == 'push'
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ZONE_ID: ${{ secrets.CLOUDFLARE_ZONE_ID }}
run: |
curl -X POST "https://api.cloudflare.com/client/v4/zones/${CLOUDFLARE_ZONE_ID}/purge_cache" \
-H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \
-H "Content-Type: application/json" \
--data '{"purge_everything":true}'
- name: Cleanup
if: always()
run: rm -f ~/.ssh/deploy_key
```
---
## Build Artifacts
### What Gets Built
For a Go application, the main artifact is the **compiled binary**:
1. **Binary**: `cv-site` (or `cv-site.exe` for Windows)
2. **Archive**: `cv-site-{commit-sha}.tar.gz`
3. **Metadata**: Version info embedded during build
### Build Flags Explained
```bash
CGO_ENABLED=0 # Static binary (no C dependencies)
GOOS=linux # Target OS
GOARCH=amd64 # Target architecture
-ldflags="-w -s # Strip debug info (smaller binary)
-X main.Version=$SHA # Embed git commit
-X main.BuildTime=$DATE" # Embed build timestamp
```
### Artifact Storage
- **GitHub Actions Artifacts**: 30 days retention
- **VM Server**: `/opt/cv-site/releases/{version}/`
- **Backup**: Previous 5 versions kept for rollback
---
## Deployment Scripts
### File: `scripts/deploy.sh`
```bash
#!/bin/bash
set -e
APP_NAME="${1:-cv-site}"
DEPLOY_PATH="${DEPLOY_PATH:-/opt/cv-site}"
SERVICE_NAME="cv-site"
echo "🚀 Starting deployment of $APP_NAME"
# Create backup of current version
if [ -f "$DEPLOY_PATH/$APP_NAME" ]; then
echo "📦 Backing up current version..."
cp "$DEPLOY_PATH/$APP_NAME" "$DEPLOY_PATH/$APP_NAME.backup"
fi
# Move new binary into place
echo "📥 Installing new version..."
mv "$DEPLOY_PATH/$APP_NAME.new" "$DEPLOY_PATH/$APP_NAME"
chmod +x "$DEPLOY_PATH/$APP_NAME"
# Restart service
echo "🔄 Restarting service..."
sudo systemctl restart "$SERVICE_NAME"
# Wait for service to start
sleep 3
# Check service status
if sudo systemctl is-active --quiet "$SERVICE_NAME"; then
echo "✅ Service started successfully"
# Remove backup after successful deployment
rm -f "$DEPLOY_PATH/$APP_NAME.backup"
else
echo "❌ Service failed to start - rolling back"
# Rollback to backup
if [ -f "$DEPLOY_PATH/$APP_NAME.backup" ]; then
mv "$DEPLOY_PATH/$APP_NAME.backup" "$DEPLOY_PATH/$APP_NAME"
sudo systemctl restart "$SERVICE_NAME"
echo "⚠️ Rolled back to previous version"
fi
exit 1
fi
echo "🎉 Deployment completed successfully"
```
### File: `scripts/healthcheck.sh`
```bash
#!/bin/bash
set -e
HEALTH_URL="${HEALTH_URL:-http://localhost:8080/health}"
MAX_RETRIES=30
RETRY_DELAY=2
echo "🏥 Running health check..."
for i in $(seq 1 $MAX_RETRIES); do
if curl -sf "$HEALTH_URL" > /dev/null; then
echo "✅ Health check passed (attempt $i)"
exit 0
fi
echo "⏳ Waiting for service... (attempt $i/$MAX_RETRIES)"
sleep $RETRY_DELAY
done
echo "❌ Health check failed after $MAX_RETRIES attempts"
exit 1
```
---
## Environment Configuration
### Systemd Service File
Create: `config/systemd/cv-site.service`
```ini
[Unit]
Description=CV Site Go Application
After=network.target
[Service]
Type=simple
User=www-data
Group=www-data
WorkingDirectory=/opt/cv-site
ExecStart=/opt/cv-site/cv-site
Restart=always
RestartSec=5s
# Environment variables
Environment="PORT=8080"
Environment="GIN_MODE=release"
EnvironmentFile=/opt/cv-site/.env
# Security hardening
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/opt/cv-site/data
# Resource limits
LimitNOFILE=65536
MemoryMax=512M
[Install]
WantedBy=multi-user.target
```
### Installation on VM
```bash
# Copy service file
sudo cp config/systemd/cv-site.service /etc/systemd/system/
# Reload systemd
sudo systemctl daemon-reload
# Enable service
sudo systemctl enable cv-site
# Start service
sudo systemctl start cv-site
# Check status
sudo systemctl status cv-site
```
---
## Testing Strategy
### File: `.github/workflows/test.yml`
```yaml
name: Test CV Site
on:
pull_request:
branches: [main, develop]
push:
branches: [main, develop]
jobs:
test:
name: Test on Go ${{ matrix.go-version }}
runs-on: ubuntu-latest
strategy:
matrix:
go-version: ['1.21', '1.22']
steps:
- uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
cache: true
- name: Install dependencies
run: go mod download
- name: Run linter
uses: golangci/golangci-lint-action@v3
with:
version: latest
- name: Run tests
run: |
go test -v -race -coverprofile=coverage.txt ./...
- name: Build
run: |
go build -v ./...
```
---
## Makefile
Create a `Makefile` for local development and CI consistency:
```makefile
.PHONY: build test deploy clean help
APP_NAME := cv-site
BUILD_DIR := ./build
GO_VERSION := 1.22
VERSION := $(shell git describe --tags --always --dirty)
BUILD_TIME := $(shell date -u +%Y-%m-%dT%H:%M:%SZ)
LDFLAGS := -ldflags="-w -s -X main.Version=$(VERSION) -X main.BuildTime=$(BUILD_TIME)"
## help: Display this help message
help:
@echo "Available targets:"
@sed -n 's/^##//p' ${MAKEFILE_LIST} | column -t -s ':' | sed -e 's/^/ /'
## build: Build the application binary
build:
@echo "🔨 Building $(APP_NAME)..."
@mkdir -p $(BUILD_DIR)
CGO_ENABLED=0 go build $(LDFLAGS) -o $(BUILD_DIR)/$(APP_NAME) ./cmd/$(APP_NAME)
@echo "✅ Build complete: $(BUILD_DIR)/$(APP_NAME)"
## test: Run all tests
test:
@echo "🧪 Running tests..."
go test -v -race -coverprofile=coverage.txt ./...
## lint: Run linter
lint:
@echo "🔍 Running linter..."
golangci-lint run
## clean: Clean build artifacts
clean:
@echo "🧹 Cleaning..."
rm -rf $(BUILD_DIR)
go clean
## run: Run the application locally
run: build
@echo "🚀 Starting $(APP_NAME)..."
$(BUILD_DIR)/$(APP_NAME)
## deploy: Build and deploy (for CI use)
deploy: build
@echo "📦 Deploying..."
# Deployment handled by GitHub Actions
```
---
## Deployment Checklist
### Pre-Deployment
- [ ] **GitHub Secrets configured**
- [ ] `SSH_PRIVATE_KEY` added
- [ ] `SSH_HOST` set
- [ ] `SSH_USER` set
- [ ] `DEPLOY_PATH` configured
- [ ] **VM Server prepared**
- [ ] SSH access configured
- [ ] Deploy user created with sudo privileges
- [ ] Application directory created (`/opt/cv-site`)
- [ ] Systemd service installed
- [ ] Firewall rules configured (port 8080)
- [ ] **Code repository ready**
- [ ] Workflow files in `.github/workflows/`
- [ ] Deployment scripts in `scripts/`
- [ ] Systemd service file in `config/systemd/`
- [ ] Tests passing locally
### First Deployment
1. **Push to GitHub**: `git push origin main`
2. **Monitor Actions**: Go to `Actions` tab in GitHub
3. **Check logs**: Verify each step completes
4. **Test deployment**: Visit your site URL
5. **Verify service**: SSH to VM and run `systemctl status cv-site`
### Post-Deployment
- [ ] **Health check passes**
- [ ] **Service running**: `systemctl is-active cv-site`
- [ ] **Logs clean**: `journalctl -u cv-site -n 50`
- [ ] **Backup created**: Previous version backed up
- [ ] **Rollback tested**: Verify rollback script works
---
## Troubleshooting
### Common Issues
#### 1. SSH Connection Failed
```bash
# Test SSH connection manually
ssh -i ~/.ssh/cv-site-deploy user@your-vm.example.com
# Check SSH key format
head -n 1 ~/.ssh/cv-site-deploy
# Should show: -----BEGIN OPENSSH PRIVATE KEY-----
```
#### 2. Binary Won't Execute
```bash
# Check binary architecture
file /opt/cv-site/cv-site
# Should show: ELF 64-bit LSB executable, x86-64
# Check permissions
ls -la /opt/cv-site/cv-site
chmod +x /opt/cv-site/cv-site
```
#### 3. Service Fails to Start
```bash
# Check service status
sudo systemctl status cv-site
# View logs
sudo journalctl -u cv-site -n 100 --no-pager
# Test binary manually
sudo -u www-data /opt/cv-site/cv-site
```
#### 4. Health Check Timeout
```bash
# Test health endpoint locally on VM
curl http://localhost:8080/health
# Check if port is listening
sudo netstat -tlnp | grep 8080
# Check firewall
sudo ufw status
```
---
## Advanced Features
### Blue-Green Deployment
Modify `deploy.sh` to support zero-downtime deployments:
```bash
#!/bin/bash
# Blue-Green deployment strategy
BLUE_PORT=8080
GREEN_PORT=8081
CURRENT_PORT=$(cat /opt/cv-site/current_port)
NEW_PORT=$([[ $CURRENT_PORT == $BLUE_PORT ]] && echo $GREEN_PORT || echo $BLUE_PORT)
# Start new version on alternate port
PORT=$NEW_PORT /opt/cv-site/cv-site.new &
# Wait and health check
sleep 5
curl -f http://localhost:$NEW_PORT/health || exit 1
# Switch nginx upstream
sudo sed -i "s/localhost:$CURRENT_PORT/localhost:$NEW_PORT/" /etc/nginx/sites-enabled/cv-site
sudo systemctl reload nginx
# Stop old version
pkill -f "cv-site.*$CURRENT_PORT"
# Update current port
echo $NEW_PORT > /opt/cv-site/current_port
```
### Rollback Strategy
```bash
#!/bin/bash
# scripts/rollback.sh
DEPLOY_PATH="/opt/cv-site"
BACKUP_PATH="$DEPLOY_PATH/releases"
# List available versions
echo "Available versions:"
ls -1t $BACKUP_PATH | head -5
# Rollback to previous version
PREVIOUS=$(ls -1t $BACKUP_PATH | head -1)
cp "$BACKUP_PATH/$PREVIOUS/cv-site" "$DEPLOY_PATH/cv-site"
sudo systemctl restart cv-site
echo "Rolled back to: $PREVIOUS"
```
---
## Performance Optimization
### Binary Size Reduction
```bash
# Use UPX compression (optional)
upx --best --lzma build/cv-site
# Result: ~70% size reduction
# Before: 15MB → After: 4.5MB
```
### Caching Strategy
Add to workflow for faster builds:
```yaml
- name: Cache Go modules
uses: actions/cache@v3
with:
path: |
~/go/pkg/mod
~/.cache/go-build
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
```
---
## Security Best Practices
1. **SSH Key Security**
- Use Ed25519 keys (stronger than RSA)
- Never commit private keys
- Rotate keys every 90 days
2. **Service Hardening**
- Run as non-root user (`www-data`)
- Use systemd security features
- Limit file system access
3. **Secret Management**
- Use GitHub Encrypted Secrets
- Never log sensitive data
- Use environment files on VM
4. **Network Security**
- Restrict SSH to GitHub Actions IPs (if possible)
- Use firewall rules
- Enable fail2ban
---
## Monitoring & Logging
### Add Logging to Deployment
```yaml
- name: Log deployment
run: |
echo "Deployment started at $(date)" >> /var/log/cv-site/deploy.log
echo "Commit: ${{ github.sha }}" >> /var/log/cv-site/deploy.log
echo "Branch: ${{ github.ref_name }}" >> /var/log/cv-site/deploy.log
```
### Application Monitoring
```go
// Add to your Go app for health checks
func healthHandler(c *gin.Context) {
c.JSON(200, gin.H{
"status": "healthy",
"version": Version,
"uptime": time.Since(startTime).String(),
})
}
```
---
## Summary
### Key Differences from La Porra
| Aspect | La Porra (Node/Bun) | CV Site (Go) |
|--------|---------------------|--------------|
| **Runtime** | Node.js/Bun | Native binary |
| **Build** | `bun run build` | `go build` |
| **Artifact** | /dist directory | Single binary |
| **Dependencies** | node_modules | Statically linked |
| **Deployment** | File sync | Binary upload |
| **Service** | PM2/systemd | systemd |
### Deployment Flow
```
┌─────────────┐
│ Push to Git │
└──────┬──────┘
v
┌─────────────────┐
│ GitHub Actions │
│ - Test │
│ - Build Binary │
│ - Create Artifact│
└──────┬──────────┘
v
┌─────────────────┐
│ Upload to VM │
│ via SSH/SCP │
└──────┬──────────┘
v
┌─────────────────┐
│ Execute deploy.sh│
│ - Backup old │
│ - Install new │
│ - Restart service│
└──────┬──────────┘
v
┌─────────────────┐
│ Health Check │
│ - HTTP test │
│ - Service status│
└──────┬──────────┘
v
┌─────────────────┐
│ Send Notification│
└─────────────────┘
```
---
## Quick Start Commands
```bash
# 1. Clone repository
git clone https://github.com/juanatsap/cv-site.git
cd cv-site
# 2. Create workflow files
mkdir -p .github/workflows
# Copy workflow YAML files from this guide
# 3. Create deployment scripts
mkdir -p scripts config/systemd
# Copy scripts from this guide
# 4. Add secrets to GitHub
# Go to Settings → Secrets and variables → Actions
# Add all required secrets
# 5. Test locally
make test
make build
# 6. Deploy
git add .
git commit -m "Add GitHub Actions deployment"
git push origin main
```
---
## Additional Resources
- **GitHub Actions Documentation**: https://docs.github.com/en/actions
- **Go Build Documentation**: https://pkg.go.dev/cmd/go#hdr-Compile_packages_and_dependencies
- **Systemd Service Files**: https://www.freedesktop.org/software/systemd/man/systemd.service.html
- **SSH Key Management**: https://docs.github.com/en/authentication/connecting-to-github-with-ssh
---
**Last Updated**: January 2025
**Maintained By**: Development Team
**Support**: Create an issue in the repository
+38 -1
View File
@@ -1,4 +1,4 @@
.PHONY: run build test clean dev prod docker-build docker-run
.PHONY: run build test clean dev prod docker-build docker-run ci-test ci-build health-check install-service update-service
# Development
dev:
@@ -57,6 +57,36 @@ docker-run:
@echo "🐳 Running Docker container..."
docker run -p 1999:1999 cv-server:latest
# CI/CD Targets
ci-test:
@echo "🧪 Running CI tests..."
go test -v -race -coverprofile=coverage.txt -covermode=atomic ./...
@echo "✓ CI tests complete"
ci-build:
@echo "🔨 Building for CI/CD..."
mkdir -p build
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o build/cv-server .
@echo "✓ CI build complete: build/cv-server"
health-check:
@echo "🏥 Running health check..."
@./scripts/healthcheck.sh
install-service:
@echo "📦 Installing systemd service..."
sudo cp config/systemd/cv.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable cv
@echo "✓ Service installed and enabled"
update-service:
@echo "🔄 Updating systemd service..."
sudo cp config/systemd/cv.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl restart cv
@echo "✓ Service updated and restarted"
# Help
help:
@echo "Available commands:"
@@ -69,3 +99,10 @@ help:
@echo " make clean - Remove build artifacts"
@echo " make docker-build - Build Docker image"
@echo " make docker-run - Run Docker container"
@echo ""
@echo "CI/CD commands:"
@echo " make ci-test - Run tests for CI pipeline"
@echo " make ci-build - Build binary for CI/CD"
@echo " make health-check - Check service health"
@echo " make install-service - Install systemd service"
@echo " make update-service - Update and restart service"
+45
View File
@@ -0,0 +1,45 @@
[Unit]
Description=CV Website Service
After=network.target
Wants=network-online.target
[Service]
Type=simple
User=txeo
Group=txeo
WorkingDirectory=/home/txeo/Git/yo/cv
ExecStart=/home/txeo/Git/yo/cv/cv-server
# Environment variables
Environment="GO_ENV=production"
Environment="PORT=1999"
Environment="HOST=0.0.0.0"
Environment="BASE_URL=https://juan.andres.morenorub.io"
Environment="TEMPLATE_HOT_RELOAD=false"
EnvironmentFile=-/home/txeo/Git/yo/cv/.env
# Security hardening
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=false
ReadWritePaths=/home/txeo/Git/yo/cv/data
ReadOnlyPaths=/home/txeo/Git/yo/cv
# Resource limits
LimitNOFILE=65536
MemoryMax=512M
# Restart policy
Restart=always
RestartSec=5
StartLimitInterval=60
StartLimitBurst=3
# Logging
StandardOutput=journal
StandardError=journal
SyslogIdentifier=cv
[Install]
WantedBy=multi-user.target
+69
View File
@@ -0,0 +1,69 @@
#!/bin/bash
set -e
APP_NAME="${1:-cv-server}"
SERVICE_NAME="${2:-cv}"
DEPLOY_PATH="${DEPLOY_PATH:-/home/txeo/Git/yo/cv}"
echo "🚀 Starting deployment of $APP_NAME"
echo "📍 Deploy path: $DEPLOY_PATH"
echo "🔧 Service: $SERVICE_NAME"
# Create backup of current version
if [ -f "$DEPLOY_PATH/$APP_NAME" ]; then
echo "📦 Backing up current version..."
BACKUP_DIR="$DEPLOY_PATH/backups"
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
cp "$DEPLOY_PATH/$APP_NAME" "$BACKUP_DIR/$APP_NAME.$TIMESTAMP"
# Keep only last 5 backups
ls -t "$BACKUP_DIR" | tail -n +6 | xargs -I {} rm -f "$BACKUP_DIR/{}"
echo "✓ Backup created: $APP_NAME.$TIMESTAMP"
fi
# Move new binary into place
echo "📥 Installing new version..."
mv "$DEPLOY_PATH/$APP_NAME.new" "$DEPLOY_PATH/$APP_NAME"
chmod +x "$DEPLOY_PATH/$APP_NAME"
# Restart service
echo "🔄 Restarting service..."
sudo systemctl restart "$SERVICE_NAME"
# Wait for service to start
sleep 3
# Check service status
if sudo systemctl is-active --quiet "$SERVICE_NAME"; then
echo "✅ Service started successfully"
# Get service status
echo ""
echo "📊 Service Status:"
sudo systemctl status "$SERVICE_NAME" --no-pager -l | head -15
echo ""
echo "🎉 Deployment completed successfully"
exit 0
else
echo "❌ Service failed to start - rolling back"
# Rollback to backup
LATEST_BACKUP=$(ls -t "$BACKUP_DIR" 2>/dev/null | head -1)
if [ -n "$LATEST_BACKUP" ]; then
echo "⏪ Rolling back to: $LATEST_BACKUP"
cp "$BACKUP_DIR/$LATEST_BACKUP" "$DEPLOY_PATH/$APP_NAME"
sudo systemctl restart "$SERVICE_NAME"
if sudo systemctl is-active --quiet "$SERVICE_NAME"; then
echo "⚠️ Rolled back to previous version successfully"
else
echo "❌ Rollback failed - manual intervention required"
fi
else
echo "❌ No backup found - manual intervention required"
fi
exit 1
fi
+28
View File
@@ -0,0 +1,28 @@
#!/bin/bash
set -e
HEALTH_URL="${HEALTH_URL:-http://localhost:1999/health}"
MAX_RETRIES=30
RETRY_DELAY=2
echo "🏥 Running health check on $HEALTH_URL"
for i in $(seq 1 $MAX_RETRIES); do
if curl -sf "$HEALTH_URL" > /dev/null 2>&1; then
echo "✅ Health check passed (attempt $i/$MAX_RETRIES)"
# Show health response
RESPONSE=$(curl -s "$HEALTH_URL")
echo ""
echo "📊 Health Status:"
echo "$RESPONSE" | jq . 2>/dev/null || echo "$RESPONSE"
exit 0
fi
echo "⏳ Waiting for service... (attempt $i/$MAX_RETRIES)"
sleep $RETRY_DELAY
done
echo "❌ Health check failed after $MAX_RETRIES attempts"
exit 1
+93
View File
@@ -0,0 +1,93 @@
#!/bin/bash
set -e
APP_NAME="${1:-cv-server}"
SERVICE_NAME="${2:-cv}"
DEPLOY_PATH="${DEPLOY_PATH:-/home/txeo/Git/yo/cv}"
BACKUP_DIR="$DEPLOY_PATH/backups"
echo "⏪ Rollback Script for $APP_NAME"
echo "📍 Deploy path: $DEPLOY_PATH"
echo "🔧 Service: $SERVICE_NAME"
echo ""
# Check if backup directory exists
if [ ! -d "$BACKUP_DIR" ]; then
echo "❌ No backup directory found at $BACKUP_DIR"
exit 1
fi
# List available versions
echo "📦 Available backup versions:"
ls -lht "$BACKUP_DIR" | tail -n +2 | head -5 | awk '{print NR". "$9" ("$6" "$7" "$8")"}'
echo ""
# If no argument provided, show usage
if [ -z "$3" ]; then
echo "Usage: $0 <app_name> <service_name> <version_number>"
echo "Example: $0 cv-server cv 1"
echo ""
echo "Or to rollback to the most recent version:"
echo " $0 cv-server cv latest"
exit 0
fi
VERSION_ARG="$3"
# Determine which backup to use
if [ "$VERSION_ARG" = "latest" ]; then
BACKUP_FILE=$(ls -t "$BACKUP_DIR" | head -1)
echo "🔍 Selected: latest version ($BACKUP_FILE)"
else
BACKUP_FILE=$(ls -t "$BACKUP_DIR" | sed -n "${VERSION_ARG}p")
echo "🔍 Selected: version #$VERSION_ARG ($BACKUP_FILE)"
fi
if [ -z "$BACKUP_FILE" ]; then
echo "❌ Version not found"
exit 1
fi
# Confirm rollback
echo ""
echo "⚠️ About to rollback to: $BACKUP_FILE"
read -p "Continue? (yes/no): " CONFIRM
if [ "$CONFIRM" != "yes" ]; then
echo "❌ Rollback cancelled"
exit 0
fi
# Create backup of current version before rollback
if [ -f "$DEPLOY_PATH/$APP_NAME" ]; then
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
cp "$DEPLOY_PATH/$APP_NAME" "$BACKUP_DIR/$APP_NAME.pre-rollback.$TIMESTAMP"
echo "✓ Current version backed up as: $APP_NAME.pre-rollback.$TIMESTAMP"
fi
# Perform rollback
echo "⏪ Rolling back..."
cp "$BACKUP_DIR/$BACKUP_FILE" "$DEPLOY_PATH/$APP_NAME"
chmod +x "$DEPLOY_PATH/$APP_NAME"
# Restart service
echo "🔄 Restarting service..."
sudo systemctl restart "$SERVICE_NAME"
# Wait for service to start
sleep 3
# Check service status
if sudo systemctl is-active --quiet "$SERVICE_NAME"; then
echo "✅ Rollback successful - service is running"
echo ""
echo "📊 Service Status:"
sudo systemctl status "$SERVICE_NAME" --no-pager -l | head -15
exit 0
else
echo "❌ Service failed to start after rollback"
echo ""
echo "🔍 Service logs:"
sudo journalctl -u "$SERVICE_NAME" -n 20 --no-pager
exit 1
fi