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:
@@ -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
|
||||
@@ -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 .
|
||||
@@ -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`
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
Executable
+69
@@ -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
|
||||
Executable
+28
@@ -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
|
||||
Executable
+93
@@ -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
|
||||
Reference in New Issue
Block a user