add: github workflows

This commit is contained in:
EUForest 2025-11-24 18:01:23 +08:00
parent 22d03a100a
commit f0f29deef1

View File

@ -1,4 +1,4 @@
name: Deploy Linux Binary name: Build and Deploy Linux Binary
on: on:
push: push:
@ -9,30 +9,26 @@ on:
- '.github/workflows/deploy-linux.yml' - '.github/workflows/deploy-linux.yml'
workflow_dispatch: workflow_dispatch:
inputs: inputs:
environment: mode:
description: 'Deployment environment' description: 'Operation mode'
required: true required: true
default: 'production' default: 'build'
type: choice type: choice
options: options:
- production - build-only
- staging - deploy-staging
- testing - deploy-production
- build-and-deploy
version: version:
description: 'Version to deploy (leave empty for latest)' description: 'Version to build (leave empty for auto)'
required: false required: false
type: string type: string
force_deploy:
description: 'Force deployment even if same version'
required: false
default: false
type: boolean
release: release:
types: [ published ] types: [ published ]
permissions: permissions:
contents: read contents: write
packages: read packages: write
env: env:
BINARY_NAME: ppanel-server BINARY_NAME: ppanel-server
@ -107,38 +103,148 @@ jobs:
- name: Upload build artifacts - name: Upload build artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: binary name: linux-binary
path: | path: |
${{ steps.build.outputs.binary_name }} ${{ steps.build.outputs.binary_name }}
checksum.txt checksum.txt
retention-days: 7 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: deploy-staging:
name: Deploy to Staging name: Deploy to Staging
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: build needs: build
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master' || github.event_name == 'workflow_dispatch' if: (github.event.inputs.mode == 'deploy-staging' || github.event.inputs.mode == 'build-and-deploy') && env.STAGING_HOST != ''
environment:
name: staging
url: ${{ steps.deploy.outputs.url }}
steps: steps:
- name: Download build artifacts - name: Download build artifacts
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
name: binary 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 - name: Deploy to staging server
id: deploy
uses: appleboy/ssh-action@v1.0.3 uses: appleboy/ssh-action@v1.0.3
env: env:
BINARY_NAME: ${{ needs.build.outputs.binary_name }} BINARY_NAME: ${{ needs.build.outputs.binary_name }}
VERSION: ${{ needs.build.outputs.version }} VERSION: ${{ needs.build.outputs.version }}
CHECKSUM: ${{ needs.build.outputs.checksum }} CHECKSUM: ${{ needs.build.outputs.checksum }}
DEPLOY_PATH: ${{ env.DEPLOY_PATH }}
SERVICE_NAME: ${{ env.SERVICE_NAME }}
CONFIG_PATH: ${{ env.CONFIG_PATH }}
BACKUP_PATH: ${{ env.BACKUP_PATH }}
with: with:
host: ${{ secrets.STAGING_HOST }} host: ${{ secrets.STAGING_HOST }}
username: ${{ secrets.STAGING_USER }} username: ${{ secrets.STAGING_USER }}
@ -147,32 +253,25 @@ jobs:
timeout: 300s timeout: 300s
script: | script: |
set -e set -e
BINARY="${BINARY_NAME}"
DEPLOY_PATH="/opt/ppanel"
SERVICE_NAME="ppanel-server"
# Create necessary directories echo "🚀 Deploying $BINARY to staging..."
sudo mkdir -p $DEPLOY_PATH $BACKUP_PATH $LOG_PATH
sudo chown $USER:$USER $DEPLOY_PATH $BACKUP_PATH $LOG_PATH
# Move binary to server # Create directories
chmod +x $BINARY_NAME sudo mkdir -p $DEPLOY_PATH /app/etc /opt/ppanel/{backups,logs}
mv $BINARY_NAME $DEPLOY_PATH/
# Create symlink for easy access # Backup current binary
ln -sf $DEPLOY_PATH/$BINARY_NAME $DEPLOY_PATH/ppanel-server 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)"
# Backup current running binary if exists
if [ -f "$DEPLOY_PATH/ppanel-server-running" ]; then
cp "$DEPLOY_PATH/ppanel-server-running" "$BACKUP_PATH/ppanel-server-$(date +%Y%m%d_%H%M%S)"
fi fi
# Update running binary # Install new binary
ln -sf $DEPLOY_PATH/$BINARY_NAME $DEPLOY_PATH/ppanel-server-running chmod +x $BINARY
sudo mv $BINARY $DEPLOY_PATH/ppanel-server
# Verify binary # Create systemd service
echo "Verifying binary checksum..."
echo "$CHECKSUM $DEPLOY_PATH/$BINARY_NAME" | sha256sum -c -
$DEPLOY_PATH/ppanel-server --version
# Create/Update systemd service
sudo tee /etc/systemd/system/$SERVICE_NAME.service > /dev/null << 'EOF' sudo tee /etc/systemd/system/$SERVICE_NAME.service > /dev/null << 'EOF'
[Unit] [Unit]
Description=PPanel Server Description=PPanel Server
@ -182,68 +281,67 @@ jobs:
Type=simple Type=simple
User=root User=root
WorkingDirectory=$DEPLOY_PATH WorkingDirectory=$DEPLOY_PATH
ExecStart=$DEPLOY_PATH/ppanel-server-running run --config $CONFIG_PATH ExecStart=$DEPLOY_PATH/ppanel-server run --config /app/etc/ppanel.yaml
Restart=always Restart=always
RestartSec=5 RestartSec=5
StandardOutput=journal StandardOutput=journal
StandardError=journal StandardError=journal
SyslogIdentifier=$SERVICE_NAME
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target
EOF EOF
# Reload systemd and restart service # Restart service
sudo systemctl daemon-reload sudo systemctl daemon-reload
sudo systemctl enable $SERVICE_NAME sudo systemctl enable $SERVICE_NAME
sudo systemctl restart $SERVICE_NAME sudo systemctl restart $SERVICE_NAME
# Wait for service to be ready # Check status
sleep 5 sleep 5
if sudo systemctl is-active --quiet $SERVICE_NAME; then if sudo systemctl is-active --quiet $SERVICE_NAME; then
echo "Service started successfully" echo "✅ Staging deployment successful!"
echo "Service status:"
sudo systemctl status $SERVICE_NAME --no-pager
else else
echo "Service failed to start" echo "❌ Service failed to start"
sudo journalctl -u $SERVICE_NAME --no-pager -n 20 sudo journalctl -u $SERVICE_NAME --no-pager -n 20
exit 1 exit 1
fi fi
- name: Health check
run: |
sleep 10
curl -f ${{ secrets.STAGING_URL }}/health || {
echo "Health check failed"
exit 1
}
deploy-production: deploy-production:
name: Deploy to Production name: Deploy to Production
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [build, deploy-staging] needs: [build, deploy-staging]
if: github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.environment == 'production') if: (github.event.inputs.mode == 'deploy-production' || github.event.inputs.mode == 'build-and-deploy' || github.event_name == 'release') && env.PRODUCTION_HOST != ''
environment:
name: production
url: ${{ steps.deploy.outputs.url }}
steps: steps:
- name: Download build artifacts - name: Download build artifacts
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
name: binary 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 - name: Deploy to production server
id: deploy
uses: appleboy/ssh-action@v1.0.3 uses: appleboy/ssh-action@v1.0.3
env: env:
BINARY_NAME: ${{ needs.build.outputs.binary_name }} BINARY_NAME: ${{ needs.build.outputs.binary_name }}
VERSION: ${{ needs.build.outputs.version }} VERSION: ${{ needs.build.outputs.version }}
CHECKSUM: ${{ needs.build.outputs.checksum }} CHECKSUM: ${{ needs.build.outputs.checksum }}
DEPLOY_PATH: ${{ env.DEPLOY_PATH }}
SERVICE_NAME: ${{ env.SERVICE_NAME }}
CONFIG_PATH: ${{ env.CONFIG_PATH }}
BACKUP_PATH: ${{ env.BACKUP_PATH }}
with: with:
host: ${{ secrets.PRODUCTION_HOST }} host: ${{ secrets.PRODUCTION_HOST }}
username: ${{ secrets.PRODUCTION_USER }} username: ${{ secrets.PRODUCTION_USER }}
@ -252,54 +350,25 @@ jobs:
timeout: 300s timeout: 300s
script: | script: |
set -e set -e
BINARY="${BINARY_NAME}"
DEPLOY_PATH="/opt/ppanel"
SERVICE_NAME="ppanel-server"
# Check if service is currently healthy echo "🚀 Deploying $BINARY to production..."
if systemctl is-active --quiet $SERVICE_NAME; then
echo "Current service is running, preparing for zero-downtime deployment..."
# Download new binary # Create directories
BINARY_NEW="$BINARY_NAME.new" sudo mkdir -p $DEPLOY_PATH /app/etc /opt/ppanel/{backups,logs}
mv $BINARY_NAME $BINARY_NEW
chmod +x $BINARY_NEW
# Verify new binary # Backup current binary
echo "Verifying new binary checksum..." if [ -f "$DEPLOY_PATH/ppanel-server" ]; then
echo "$CHECKSUM $BINARY_NEW" | sha256sum -c - sudo cp "$DEPLOY_PATH/ppanel-server" "/opt/ppanel/backups/ppanel-server-$(date +%Y%m%d_%H%M%S)"
./$BINARY_NEW --version
# Test new binary configuration
./$BINARY_NEW run --config $CONFIG_PATH --check || {
echo "Configuration check failed"
exit 1
}
# Create necessary directories
sudo mkdir -p $DEPLOY_PATH $BACKUP_PATH $LOG_PATH
sudo chown $USER:$USER $DEPLOY_PATH $BACKUP_PATH $LOG_PATH
# Move to deployment directory
mv $BINARY_NEW $DEPLOY_PATH/
# Create symlink for easy access
ln -sf $DEPLOY_PATH/$BINARY_NAME $DEPLOY_PATH/ppanel-server
# Backup current running binary
if [ -f "$DEPLOY_PATH/ppanel-server-running" ]; then
cp "$DEPLOY_PATH/ppanel-server-running" "$BACKUP_PATH/ppanel-server-$(date +%Y%m%d_%H%M%S)"
fi
# Atomic update of running binary
ln -sf $DEPLOY_PATH/$BINARY_NAME $DEPLOY_PATH/ppanel-server-running.new
mv -Tf $DEPLOY_PATH/ppanel-server-running.new $DEPLOY_PATH/ppanel-server-running
else
echo "Service is not running, performing direct deployment..."
mv $BINARY_NAME $DEPLOY_PATH/
chmod +x $DEPLOY_PATH/$BINARY_NAME
ln -sf $DEPLOY_PATH/$BINARY_NAME $DEPLOY_PATH/ppanel-server-running
fi fi
# Create/Update systemd service # 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' sudo tee /etc/systemd/system/$SERVICE_NAME.service > /dev/null << 'EOF'
[Unit] [Unit]
Description=PPanel Server Description=PPanel Server
@ -309,151 +378,69 @@ jobs:
Type=simple Type=simple
User=root User=root
WorkingDirectory=$DEPLOY_PATH WorkingDirectory=$DEPLOY_PATH
ExecStart=$DEPLOY_PATH/ppanel-server-running run --config $CONFIG_PATH ExecStart=$DEPLOY_PATH/ppanel-server run --config /app/etc/ppanel.yaml
Restart=always Restart=always
RestartSec=5 RestartSec=5
StandardOutput=journal StandardOutput=journal
StandardError=journal StandardError=journal
SyslogIdentifier=$SERVICE_NAME
# Limit resources
LimitNOFILE=65536
MemoryLimit=512M
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target
EOF EOF
# Reload systemd and restart service # Restart service
sudo systemctl daemon-reload sudo systemctl daemon-reload
sudo systemctl enable $SERVICE_NAME sudo systemctl enable $SERVICE_NAME
sudo systemctl restart $SERVICE_NAME sudo systemctl restart $SERVICE_NAME
# Wait for service to be ready # Wait for service to be ready
sleep 10 sleep 10
# Check service status
if sudo systemctl is-active --quiet $SERVICE_NAME; then if sudo systemctl is-active --quiet $SERVICE_NAME; then
echo "✅ Service started successfully" echo "✅ Production deployment successful!"
echo "📊 Service status:" echo "📊 Service status:"
sudo systemctl status $SERVICE_NAME --no-pager -l sudo systemctl status $SERVICE_NAME --no-pager
echo "📝 Recent logs:"
sudo journalctl -u $SERVICE_NAME --no-pager -n 10
else else
echo "❌ Service failed to start" echo "❌ Production deployment failed!"
echo "📝 Error logs:"
sudo journalctl -u $SERVICE_NAME --no-pager -n 50
# Attempt rollback
echo "🔄 Attempting rollback..." echo "🔄 Attempting rollback..."
LATEST_BACKUP=$(ls -t $BACKUP_PATH/ppanel-server-* 2>/dev/null | head -1) LATEST_BACKUP=$(ls -t /opt/ppanel/backups/ppanel-server-* 2>/dev/null | head -1)
if [ -n "$LATEST_BACKUP" ]; then if [ -n "$LATEST_BACKUP" ]; then
cp "$LATEST_BACKUP" "$DEPLOY_PATH/ppanel-server-running" sudo cp "$LATEST_BACKUP" "$DEPLOY_PATH/ppanel-server"
sudo systemctl restart $SERVICE_NAME sudo systemctl restart $SERVICE_NAME
echo "Rollback completed" echo "Rollback completed"
fi fi
exit 1 exit 1
fi fi
- name: Health check docker:
run: | name: Build Docker Image
echo "Waiting for service to be fully ready..."
for i in {1..30}; do
if curl -f ${{ secrets.PRODUCTION_URL }}/health; then
echo "✅ Health check passed"
break
fi
echo "Attempt $i: Service not ready, waiting 10 seconds..."
sleep 10
done
- name: Deployment notification
if: always()
run: |
if [ "${{ job.status }}" = "success" ]; then
echo "🎉 Production deployment completed successfully!"
echo "Version: ${{ needs.build.outputs.version }}"
echo "Deployed to: ${{ secrets.PRODUCTION_URL }}"
else
echo "❌ Production deployment failed!"
echo "Please check the logs and rollback if necessary"
fi
rollback:
name: Rollback Production
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: deploy-production needs: build
if: failure() && github.event_name == 'workflow_dispatch' if: (secrets.DOCKER_USERNAME && secrets.DOCKER_PASSWORD) && (github.event_name == 'release' || github.event.inputs.mode == 'build-and-deploy')
environment: production
steps: steps:
- name: Rollback to previous version - name: Checkout repository
uses: appleboy/ssh-action@v1.0.3 uses: actions/checkout@v4
env:
DEPLOY_PATH: ${{ env.DEPLOY_PATH }} - name: Set up Docker Buildx
SERVICE_NAME: ${{ env.SERVICE_NAME }} uses: docker/setup-buildx-action@v3
BACKUP_PATH: ${{ env.BACKUP_PATH }}
- name: Log in to Docker Hub
uses: docker/login-action@v3
with: with:
host: ${{ secrets.PRODUCTION_HOST }} username: ${{ secrets.DOCKER_USERNAME }}
username: ${{ secrets.PRODUCTION_USER }} password: ${{ secrets.DOCKER_PASSWORD }}
key: ${{ secrets.PRODUCTION_SSH_KEY }}
port: ${{ secrets.PRODUCTION_PORT || 22 }}
script: |
set -e
echo "🔄 Rolling back to previous version..." - name: Build and push Docker image
uses: docker/build-push-action@v6
# Find latest backup
LATEST_BACKUP=$(ls -t $BACKUP_PATH/ppanel-server-* 2>/dev/null | head -1)
if [ -z "$LATEST_BACKUP" ]; then
echo "❌ No backup found for rollback"
exit 1
fi
echo "📦 Rolling back to: $(basename $LATEST_BACKUP)"
# Restore from backup
cp "$LATEST_BACKUP" "$DEPLOY_PATH/ppanel-server-running"
# Restart service
sudo systemctl restart $SERVICE_NAME
# Wait and check
sleep 10
if sudo systemctl is-active --quiet $SERVICE_NAME; then
echo "✅ Rollback completed successfully"
sudo systemctl status $SERVICE_NAME --no-pager
else
echo "❌ Rollback failed"
sudo journalctl -u $SERVICE_NAME --no-pager -n 20
exit 1
fi
cleanup:
name: Cleanup Old Artifacts
runs-on: ubuntu-latest
needs: [deploy-production]
if: always() && needs.deploy-production.result == 'success'
steps:
- name: Cleanup old binaries and backups
uses: appleboy/ssh-action@v1.0.3
env:
DEPLOY_PATH: ${{ env.DEPLOY_PATH }}
BACKUP_PATH: ${{ env.BACKUP_PATH }}
with: with:
host: ${{ secrets.PRODUCTION_HOST }} context: .
username: ${{ secrets.PRODUCTION_USER }} platforms: linux/amd64
key: ${{ secrets.PRODUCTION_SSH_KEY }} push: true
script: | tags: |
echo "🧹 Cleaning up old artifacts..." ${{ secrets.DOCKER_USERNAME }}/ppanel-server:${{ needs.build.outputs.version }}
${{ secrets.DOCKER_USERNAME }}/ppanel-server:latest
# Keep only last 5 binaries in deployment directory build-args: |
cd $DEPLOY_PATH VERSION=${{ needs.build.outputs.version }}
ls -t ppanel-server-*-linux-amd64 | tail -n +6 | xargs -r rm labels: |
org.opencontainers.image.version=${{ needs.build.outputs.version }}
# Keep only last 10 backups org.opencontainers.image.revision=${{ github.sha }}
cd $BACKUP_PATH
ls -t ppanel-server-* | tail -n +11 | xargs -r rm
echo "✅ Cleanup completed"