hi-server/.github/workflows/deploy-linux.yml
EUForest 5d18c7bb7d 添加 GitHub 自动部署 Linux 二进制工作流
- 新增 .github/workflows/deploy-linux.yml 支持自动构建和部署
- 添加 .goreleaser.yml 配置多平台发布
- 支持 build-only/deploy-staging/deploy-production 模式
- 包含 Docker 镜像自动构建
2025-11-24 18:10:00 +08:00

437 lines
15 KiB
YAML

name: Build and Deploy Linux Binary
on:
push:
branches: [ main, master ]
workflow_dispatch:
inputs:
mode:
description: 'Operation mode'
required: true
default: 'build-only'
type: choice
options:
- build-only
- deploy-staging
- deploy-production
- build-and-deploy
version:
description: 'Version to build (leave empty for auto)'
required: false
type: string
release:
types: [ published ]
permissions:
contents: write
packages: write
env:
BINARY_NAME: ppanel-server
jobs:
build:
name: Build Linux Binary
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
binary_name: ${{ steps.build.outputs.binary_name }}
checksum: ${{ steps.checksum.outputs.checksum }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.23.3'
cache: true
- name: Get version information
id: version
run: |
if [ "${{ github.event_name }}" = "release" ]; then
VERSION=${GITHUB_REF#refs/tags/}
elif [ -n "${{ github.event.inputs.version }}" ]; then
VERSION="${{ github.event.inputs.version }}"
else
VERSION=$(git describe --tags --always --dirty)
fi
echo "VERSION=$VERSION" >> $GITHUB_ENV
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "BUILD_TIME=$(date -u +"%Y-%m-%d %H:%M:%S")" >> $GITHUB_ENV
echo "GIT_COMMIT=${GITHUB_SHA}" >> $GITHUB_ENV
- name: Build binary
id: build
env:
CGO_ENABLED: 0
GOOS: linux
GOARCH: amd64
run: |
BINARY_NAME_WITH_VERSION="${{ env.BINARY_NAME }}-${{ env.VERSION }}-linux-amd64"
echo "binary_name=$BINARY_NAME_WITH_VERSION" >> $GITHUB_OUTPUT
go build \
-ldflags "-s -w \
-X 'github.com/perfect-panel/server/pkg/constant.Version=${{ env.VERSION }}' \
-X 'github.com/perfect-panel/server/pkg/constant.BuildTime=${{ env.BUILD_TIME }}' \
-X 'github.com/perfect-panel/server/pkg/constant.GitCommit=${{ env.GIT_COMMIT }}'" \
-o "$BINARY_NAME_WITH_VERSION" \
./ppanel.go
- name: Generate checksum
id: checksum
run: |
CHECKSUM=$(sha256sum "${{ steps.build.outputs.binary_name }}" | awk '{print $1}')
echo "checksum=$CHECKSUM" >> $GITHUB_OUTPUT
echo "$CHECKSUM ${{ steps.build.outputs.binary_name }}" > checksum.txt
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: linux-binary
path: |
${{ steps.build.outputs.binary_name }}
checksum.txt
retention-days: 30
- name: Create deployment package
run: |
mkdir -p deploy-package
cp "${{ steps.build.outputs.binary_name }}" deploy-package/ppanel-server
cp checksum.txt deploy-package/
cp -r etc deploy-package/ 2>/dev/null || echo "etc directory not found, skipping"
# Create deployment script
cat > deploy-package/deploy.sh << 'EOF'
#!/bin/bash
set -e
BINARY_NAME="ppanel-server"
DEPLOY_PATH="/opt/ppanel"
CONFIG_PATH="/app/etc/ppanel.yaml"
SERVICE_NAME="ppanel-server"
# Create directories
sudo mkdir -p $DEPLOY_PATH /app/etc /opt/ppanel/logs
# Install binary
sudo cp $BINARY_NAME $DEPLOY_PATH/ppanel-server
sudo chmod +x $DEPLOY_PATH/ppanel-server
# Copy config if needed
if [ -d "etc" ] && [ ! -f "/app/etc/ppanel.yaml" ]; then
sudo cp -r etc/* /app/etc/
fi
# Create systemd service
sudo tee /etc/systemd/system/$SERVICE_NAME.service > /dev/null << 'SERVICE_EOF'
[Unit]
Description=PPanel Server
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=$DEPLOY_PATH
ExecStart=$DEPLOY_PATH/ppanel-server run --config $CONFIG_PATH
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
SERVICE_EOF
# Reload and start service
sudo systemctl daemon-reload
sudo systemctl enable $SERVICE_NAME
sudo systemctl restart $SERVICE_NAME
echo "✅ Deployment completed!"
echo "📊 Service status: sudo systemctl status $SERVICE_NAME"
echo "📝 Logs: sudo journalctl -u $SERVICE_NAME -f"
EOF
chmod +x deploy-package/deploy.sh
tar -czf "${{ steps.build.outputs.binary_name }}-deploy.tar.gz" deploy-package/
- name: Upload deployment package
uses: actions/upload-artifact@v4
with:
name: deploy-package
path: "${{ steps.build.outputs.binary_name }}-deploy.tar.gz"
retention-days: 30
- name: Upload to GitHub Release (if release)
if: github.event_name == 'release'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release upload ${{ env.VERSION }} \
${{ steps.build.outputs.binary_name }} \
checksum.txt
- name: Create summary
run: |
echo "## 📦 Build Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Binary:** ${{ steps.build.outputs.binary_name }}" >> $GITHUB_STEP_SUMMARY
echo "**Version:** ${{ env.VERSION }}" >> $GITHUB_STEP_SUMMARY
echo "**Checksum:** \`${{ steps.checksum.outputs.checksum }}\`" >> $GITHUB_STEP_SUMMARY
echo "**Size:** $(du -h ${{ steps.build.outputs.binary_name }} | cut -f1)" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### 📥 Download" >> $GITHUB_STEP_SUMMARY
echo "- **Binary only**: Actions → Artifacts → \`linux-binary\`" >> $GITHUB_STEP_SUMMARY
echo "- **Deploy package**: Actions → Artifacts → \`deploy-package\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### 🚀 Next Steps" >> $GITHUB_STEP_SUMMARY
if [ "${{ github.event.inputs.mode }}" = "build-only" ] || [ "${{ github.event_name }}" = "push" ]; then
echo "1. Download the binary from Artifacts" >> $GITHUB_STEP_SUMMARY
echo "2. Deploy manually to your server" >> $GITHUB_STEP_SUMMARY
else
echo "1. Configure server secrets in repository settings" >> $GITHUB_STEP_SUMMARY
echo "2. The deployment will start automatically" >> $GITHUB_STEP_SUMMARY
fi
deploy-staging:
name: Deploy to Staging
runs-on: ubuntu-latest
needs: build
if: (github.event.inputs.mode == 'deploy-staging' || github.event.inputs.mode == 'build-and-deploy') && secrets.STAGING_HOST != ''
steps:
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
name: linux-binary
- name: Validate staging configuration
run: |
echo "✅ Checking staging configuration..."
if [ -z "${{ secrets.STAGING_HOST }}" ]; then
echo "❌ STAGING_HOST is not configured"
echo "Please set STAGING_HOST in repository secrets"
exit 1
fi
if [ -z "${{ secrets.STAGING_USER }}" ]; then
echo "❌ STAGING_USER is not configured"
exit 1
fi
if [ -z "${{ secrets.STAGING_SSH_KEY }}" ]; then
echo "❌ STAGING_SSH_KEY is not configured"
exit 1
fi
echo "✅ Staging configuration validated"
- name: Deploy to staging server
uses: appleboy/ssh-action@v1.0.3
env:
BINARY_NAME: ${{ needs.build.outputs.binary_name }}
VERSION: ${{ needs.build.outputs.version }}
CHECKSUM: ${{ needs.build.outputs.checksum }}
with:
host: ${{ secrets.STAGING_HOST }}
username: ${{ secrets.STAGING_USER }}
key: ${{ secrets.STAGING_SSH_KEY }}
port: ${{ secrets.STAGING_PORT || 22 }}
timeout: 300s
script: |
set -e
BINARY="${BINARY_NAME}"
DEPLOY_PATH="/opt/ppanel"
SERVICE_NAME="ppanel-server"
echo "🚀 Deploying $BINARY to staging..."
# Create directories
sudo mkdir -p $DEPLOY_PATH /app/etc /opt/ppanel/{backups,logs}
# Backup current binary
if [ -f "$DEPLOY_PATH/ppanel-server" ]; then
sudo cp "$DEPLOY_PATH/ppanel-server" "/opt/ppanel/backups/ppanel-server-$(date +%Y%m%d_%H%M%S)"
fi
# Install new binary
chmod +x $BINARY
sudo mv $BINARY $DEPLOY_PATH/ppanel-server
# Create systemd service
sudo tee /etc/systemd/system/$SERVICE_NAME.service > /dev/null << 'EOF'
[Unit]
Description=PPanel Server
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=$DEPLOY_PATH
ExecStart=$DEPLOY_PATH/ppanel-server run --config /app/etc/ppanel.yaml
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
EOF
# Restart service
sudo systemctl daemon-reload
sudo systemctl enable $SERVICE_NAME
sudo systemctl restart $SERVICE_NAME
# Check status
sleep 5
if sudo systemctl is-active --quiet $SERVICE_NAME; then
echo "✅ Staging deployment successful!"
else
echo "❌ Service failed to start"
sudo journalctl -u $SERVICE_NAME --no-pager -n 20
exit 1
fi
deploy-production:
name: Deploy to Production
runs-on: ubuntu-latest
needs: build
if: (github.event.inputs.mode == 'deploy-production' || github.event.inputs.mode == 'build-and-deploy' || github.event_name == 'release') && secrets.PRODUCTION_HOST != ''
steps:
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
name: linux-binary
- name: Validate production configuration
run: |
echo "✅ Checking production configuration..."
if [ -z "${{ secrets.PRODUCTION_HOST }}" ]; then
echo "❌ PRODUCTION_HOST is not configured"
echo "Please set PRODUCTION_HOST in repository secrets"
exit 1
fi
if [ -z "${{ secrets.PRODUCTION_USER }}" ]; then
echo "❌ PRODUCTION_USER is not configured"
exit 1
fi
if [ -z "${{ secrets.PRODUCTION_SSH_KEY }}" ]; then
echo "❌ PRODUCTION_SSH_KEY is not configured"
exit 1
fi
echo "✅ Production configuration validated"
- name: Deploy to production server
uses: appleboy/ssh-action@v1.0.3
env:
BINARY_NAME: ${{ needs.build.outputs.binary_name }}
VERSION: ${{ needs.build.outputs.version }}
CHECKSUM: ${{ needs.build.outputs.checksum }}
with:
host: ${{ secrets.PRODUCTION_HOST }}
username: ${{ secrets.PRODUCTION_USER }}
key: ${{ secrets.PRODUCTION_SSH_KEY }}
port: ${{ secrets.PRODUCTION_PORT || 22 }}
timeout: 300s
script: |
set -e
BINARY="${BINARY_NAME}"
DEPLOY_PATH="/opt/ppanel"
SERVICE_NAME="ppanel-server"
echo "🚀 Deploying $BINARY to production..."
# Create directories
sudo mkdir -p $DEPLOY_PATH /app/etc /opt/ppanel/{backups,logs}
# Backup current binary
if [ -f "$DEPLOY_PATH/ppanel-server" ]; then
sudo cp "$DEPLOY_PATH/ppanel-server" "/opt/ppanel/backups/ppanel-server-$(date +%Y%m%d_%H%M%S)"
fi
# Install new binary
chmod +x $BINARY
sudo mv $BINARY $DEPLOY_PATH/ppanel-server
# Create/update systemd service
sudo tee /etc/systemd/system/$SERVICE_NAME.service > /dev/null << 'EOF'
[Unit]
Description=PPanel Server
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=$DEPLOY_PATH
ExecStart=$DEPLOY_PATH/ppanel-server run --config /app/etc/ppanel.yaml
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
EOF
# Restart service
sudo systemctl daemon-reload
sudo systemctl enable $SERVICE_NAME
sudo systemctl restart $SERVICE_NAME
# Wait for service to be ready
sleep 10
if sudo systemctl is-active --quiet $SERVICE_NAME; then
echo "✅ Production deployment successful!"
echo "📊 Service status:"
sudo systemctl status $SERVICE_NAME --no-pager
else
echo "❌ Production deployment failed!"
echo "🔄 Attempting rollback..."
LATEST_BACKUP=$(ls -t /opt/ppanel/backups/ppanel-server-* 2>/dev/null | head -1)
if [ -n "$LATEST_BACKUP" ]; then
sudo cp "$LATEST_BACKUP" "$DEPLOY_PATH/ppanel-server"
sudo systemctl restart $SERVICE_NAME
echo "✅ Rollback completed"
fi
exit 1
fi
docker:
name: Build Docker Image
runs-on: ubuntu-latest
needs: build
if: secrets.DOCKER_USERNAME != '' && secrets.DOCKER_PASSWORD != '' && (github.event_name == 'release' || github.event.inputs.mode == 'build-and-deploy')
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64
push: true
tags: |
${{ secrets.DOCKER_USERNAME }}/ppanel-server:${{ needs.build.outputs.version }}
${{ secrets.DOCKER_USERNAME }}/ppanel-server:latest
build-args: |
VERSION=${{ needs.build.outputs.version }}
labels: |
org.opencontainers.image.version=${{ needs.build.outputs.version }}
org.opencontainers.image.revision=${{ github.sha }}