forked from claude-did-this/claude-hub
Add deployment workflow and scripts for self-hosted runner
- Add GitHub Actions deployment workflow for staging and production - Add deployment scripts for automated deployments - Add GitHub runner management scripts - Add staging docker-compose configuration - Enable automatic deployments on push to main (staging) and version tags (production)
This commit is contained in:
326
.github/workflows/deploy.yml
vendored
326
.github/workflows/deploy.yml
vendored
@@ -1,81 +1,281 @@
|
|||||||
name: Deploy
|
name: CI/CD Pipeline
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ main ]
|
branches:
|
||||||
|
- main
|
||||||
|
- develop
|
||||||
|
tags:
|
||||||
|
- 'v*.*.*' # Semantic versioning tags (v1.0.0, v2.1.3, etc.)
|
||||||
|
pull_request:
|
||||||
|
types: [opened, synchronize, reopened]
|
||||||
|
|
||||||
env:
|
env:
|
||||||
NODE_VERSION: '20'
|
|
||||||
REGISTRY: ghcr.io
|
REGISTRY: ghcr.io
|
||||||
IMAGE_NAME: ${{ github.repository }}
|
IMAGE_NAME: ${{ github.repository }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-and-push:
|
# ============================================
|
||||||
name: Build & Push Images
|
# CI Jobs - Run on GitHub-hosted runners
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
test:
|
||||||
|
name: Run Tests
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
node-version: [18.x, 20.x]
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
packages: write
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- uses: actions/checkout@v4
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node-version }}
|
||||||
|
cache: 'npm'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Run linter
|
||||||
|
run: npm run lint
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: npm test
|
||||||
|
|
||||||
|
- name: Run type checking
|
||||||
|
run: npm run typecheck
|
||||||
|
|
||||||
|
- name: Upload coverage
|
||||||
|
if: matrix.node-version == '20.x'
|
||||||
|
uses: codecov/codecov-action@v3
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
build:
|
||||||
uses: docker/setup-buildx-action@v3
|
name: Build Docker Image
|
||||||
|
|
||||||
- name: Log in to Container Registry
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
registry: ${{ env.REGISTRY }}
|
|
||||||
username: ${{ github.actor }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Extract metadata
|
|
||||||
id: meta
|
|
||||||
uses: docker/metadata-action@v5
|
|
||||||
with:
|
|
||||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
|
||||||
tags: |
|
|
||||||
type=ref,event=branch
|
|
||||||
type=ref,event=pr
|
|
||||||
type=sha,prefix={{branch}}-
|
|
||||||
type=raw,value=latest,enable={{is_default_branch}}
|
|
||||||
|
|
||||||
- name: Build and push main image
|
|
||||||
uses: docker/build-push-action@v6
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
file: ./Dockerfile
|
|
||||||
push: true
|
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
|
||||||
cache-from: type=gha
|
|
||||||
cache-to: type=gha,mode=max
|
|
||||||
|
|
||||||
- name: Build and push Claude Code image
|
|
||||||
uses: docker/build-push-action@v6
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
file: ./Dockerfile.claudecode
|
|
||||||
push: true
|
|
||||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-claudecode:latest
|
|
||||||
cache-from: type=gha
|
|
||||||
cache-to: type=gha,mode=max
|
|
||||||
|
|
||||||
deploy:
|
|
||||||
name: Deploy to Staging
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [build-and-push]
|
needs: test
|
||||||
|
|
||||||
|
outputs:
|
||||||
|
image-tag: ${{ steps.meta.outputs.tags }}
|
||||||
|
image-digest: ${{ steps.build.outputs.digest }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Log in to Container Registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Extract metadata
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||||
|
tags: |
|
||||||
|
type=ref,event=branch
|
||||||
|
type=ref,event=pr
|
||||||
|
type=semver,pattern={{version}}
|
||||||
|
type=semver,pattern={{major}}.{{minor}}
|
||||||
|
type=semver,pattern={{major}}
|
||||||
|
type=sha,prefix={{branch}}-
|
||||||
|
type=raw,value=staging,enable=${{ github.ref == 'refs/heads/main' }}
|
||||||
|
type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||||
|
|
||||||
|
- name: Build and push Docker image
|
||||||
|
id: build
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
|
||||||
|
security-scan:
|
||||||
|
name: Security Scanning
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: build
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Run Trivy vulnerability scanner
|
||||||
|
uses: aquasecurity/trivy-action@master
|
||||||
|
with:
|
||||||
|
image-ref: ${{ needs.build.outputs.image-tag }}
|
||||||
|
format: 'sarif'
|
||||||
|
output: 'trivy-results.sarif'
|
||||||
|
|
||||||
|
- name: Upload Trivy scan results
|
||||||
|
uses: github/codeql-action/upload-sarif@v2
|
||||||
|
with:
|
||||||
|
sarif_file: 'trivy-results.sarif'
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# CD Jobs - Run on self-hosted runners
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
deploy-staging:
|
||||||
|
name: Deploy to Staging
|
||||||
|
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
|
||||||
|
needs: [build, security-scan]
|
||||||
|
runs-on: [self-hosted, linux, x64, deployment, webhook-cd]
|
||||||
environment: staging
|
environment: staging
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- uses: actions/checkout@v4
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
- name: Create .env file for staging
|
||||||
|
run: |
|
||||||
|
cat > .env.staging << EOF
|
||||||
|
GITHUB_APP_ID_STAGING=${{ secrets.GITHUB_APP_ID_STAGING }}
|
||||||
|
GITHUB_PRIVATE_KEY_STAGING=${{ secrets.GITHUB_PRIVATE_KEY_STAGING }}
|
||||||
|
GITHUB_WEBHOOK_SECRET_STAGING=${{ secrets.GITHUB_WEBHOOK_SECRET_STAGING }}
|
||||||
|
ANTHROPIC_API_KEY_STAGING=${{ secrets.ANTHROPIC_API_KEY_STAGING }}
|
||||||
|
MCP_SERVER_URL_STAGING=${{ vars.MCP_SERVER_URL_STAGING }}
|
||||||
|
ALLOWED_ORGS_STAGING=${{ vars.ALLOWED_ORGS_STAGING }}
|
||||||
|
ALLOWED_REPOS_STAGING=${{ vars.ALLOWED_REPOS_STAGING }}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
- name: Deploy to staging
|
||||||
|
run: |
|
||||||
|
export $(cat .env.staging | xargs)
|
||||||
|
./scripts/deploy/deploy-staging.sh
|
||||||
|
|
||||||
|
- name: Clean up
|
||||||
|
if: always()
|
||||||
|
run: rm -f .env.staging
|
||||||
|
|
||||||
|
- name: Create deployment record
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
await github.rest.repos.createDeployment({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
ref: context.sha,
|
||||||
|
environment: 'staging',
|
||||||
|
required_contexts: [],
|
||||||
|
auto_merge: false,
|
||||||
|
description: 'Staging deployment from main branch'
|
||||||
|
});
|
||||||
|
|
||||||
|
- name: Notify deployment status
|
||||||
|
if: always()
|
||||||
|
uses: 8398a7/action-slack@v3
|
||||||
|
with:
|
||||||
|
status: ${{ job.status }}
|
||||||
|
text: 'Staging deployment ${{ job.status }}'
|
||||||
|
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
|
||||||
|
|
||||||
- name: Deploy notification
|
deploy-production:
|
||||||
run: |
|
name: Deploy to Production
|
||||||
echo "🚀 Deployment to staging would happen here"
|
if: startsWith(github.ref, 'refs/tags/v')
|
||||||
echo "Image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest"
|
needs: [build, security-scan]
|
||||||
# Add actual deployment logic here (e.g., update Kubernetes, docker-compose, etc.)
|
runs-on: [self-hosted, linux, x64, deployment, webhook-cd]
|
||||||
|
environment:
|
||||||
|
name: production
|
||||||
|
url: https://webhook.yourdomain.com
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Validate tag is on main branch
|
||||||
|
run: |
|
||||||
|
# Get the commit SHA that the tag points to
|
||||||
|
TAG_COMMIT=$(git rev-list -n 1 ${{ github.ref_name }})
|
||||||
|
|
||||||
|
# Check if this commit exists on main branch
|
||||||
|
if ! git branch -r --contains $TAG_COMMIT | grep -q "origin/main"; then
|
||||||
|
echo "Error: Tag ${{ github.ref_name }} is not on the main branch!"
|
||||||
|
echo "Production deployments must be tagged from the main branch."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "✓ Tag ${{ github.ref_name }} is on main branch"
|
||||||
|
|
||||||
|
- name: Extract version info
|
||||||
|
id: version
|
||||||
|
run: |
|
||||||
|
VERSION=${GITHUB_REF#refs/tags/}
|
||||||
|
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||||
|
echo "Deploying version: $VERSION"
|
||||||
|
|
||||||
|
- name: Create .env file for production
|
||||||
|
run: |
|
||||||
|
cat > .env << EOF
|
||||||
|
GITHUB_APP_ID=${{ secrets.GITHUB_APP_ID }}
|
||||||
|
GITHUB_PRIVATE_KEY=${{ secrets.GITHUB_PRIVATE_KEY }}
|
||||||
|
GITHUB_WEBHOOK_SECRET=${{ secrets.GITHUB_WEBHOOK_SECRET }}
|
||||||
|
ANTHROPIC_API_KEY=${{ secrets.ANTHROPIC_API_KEY }}
|
||||||
|
MCP_SERVER_URL=${{ vars.MCP_SERVER_URL }}
|
||||||
|
ALLOWED_ORGS=${{ vars.ALLOWED_ORGS }}
|
||||||
|
ALLOWED_REPOS=${{ vars.ALLOWED_REPOS }}
|
||||||
|
DEPLOYMENT_VERSION=${{ steps.version.outputs.version }}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
- name: Deploy to production
|
||||||
|
run: |
|
||||||
|
export $(cat .env | xargs)
|
||||||
|
./scripts/deploy/deploy-production.sh
|
||||||
|
|
||||||
|
- name: Clean up
|
||||||
|
if: always()
|
||||||
|
run: rm -f .env
|
||||||
|
|
||||||
|
- name: Create deployment record
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const deployment = await github.rest.repos.createDeployment({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
ref: context.ref,
|
||||||
|
environment: 'production',
|
||||||
|
required_contexts: [],
|
||||||
|
auto_merge: false,
|
||||||
|
description: `Production deployment ${context.ref.replace('refs/tags/', '')}`
|
||||||
|
});
|
||||||
|
|
||||||
|
await github.rest.repos.createDeploymentStatus({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
deployment_id: deployment.data.id,
|
||||||
|
state: 'success',
|
||||||
|
environment_url: 'https://webhook.yourdomain.com',
|
||||||
|
description: `Deployed version ${context.ref.replace('refs/tags/', '')}`
|
||||||
|
});
|
||||||
|
|
||||||
|
- name: Create GitHub Release
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
await github.rest.repos.createRelease({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
tag_name: context.ref.replace('refs/tags/', ''),
|
||||||
|
name: `Release ${context.ref.replace('refs/tags/', '')}`,
|
||||||
|
body: `Production deployment of ${context.ref.replace('refs/tags/', '')}`,
|
||||||
|
draft: false,
|
||||||
|
prerelease: false
|
||||||
|
});
|
||||||
|
|
||||||
|
- name: Notify deployment status
|
||||||
|
if: always()
|
||||||
|
uses: 8398a7/action-slack@v3
|
||||||
|
with:
|
||||||
|
status: ${{ job.status }}
|
||||||
|
text: 'Production deployment ${{ steps.version.outputs.version }} ${{ job.status }}'
|
||||||
|
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
|
||||||
|
fields: repo,message,commit,author,action,eventName,ref,workflow
|
||||||
74
docker-compose.staging.yml
Normal file
74
docker-compose.staging.yml
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
webhook-staging:
|
||||||
|
image: ghcr.io/YOUR_ORG/claude-github-webhook:staging
|
||||||
|
container_name: claude-webhook-staging
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "8083:3003" # External:Internal port mapping
|
||||||
|
environment:
|
||||||
|
# Node environment
|
||||||
|
NODE_ENV: staging
|
||||||
|
PORT: 3003
|
||||||
|
|
||||||
|
# GitHub App Configuration (Staging Bot: @MCPClaude-Staging)
|
||||||
|
GITHUB_APP_ID: ${GITHUB_APP_ID_STAGING}
|
||||||
|
GITHUB_PRIVATE_KEY: ${GITHUB_PRIVATE_KEY_STAGING}
|
||||||
|
GITHUB_WEBHOOK_SECRET: ${GITHUB_WEBHOOK_SECRET_STAGING}
|
||||||
|
GITHUB_BOT_NAME: MCPClaude-Staging
|
||||||
|
|
||||||
|
# Anthropic Configuration
|
||||||
|
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY_STAGING}
|
||||||
|
|
||||||
|
# MCP Server Configuration (if applicable)
|
||||||
|
MCP_SERVER_URL: ${MCP_SERVER_URL_STAGING:-}
|
||||||
|
|
||||||
|
# Security & Access Control
|
||||||
|
ALLOWED_ORGS: ${ALLOWED_ORGS_STAGING:-}
|
||||||
|
ALLOWED_REPOS: ${ALLOWED_REPOS_STAGING:-}
|
||||||
|
|
||||||
|
# Logging
|
||||||
|
LOG_LEVEL: ${LOG_LEVEL_STAGING:-info}
|
||||||
|
LOG_FORMAT: json
|
||||||
|
|
||||||
|
# Feature Flags
|
||||||
|
ENABLE_METRICS: ${ENABLE_METRICS_STAGING:-false}
|
||||||
|
ENABLE_RATE_LIMITING: ${ENABLE_RATE_LIMITING_STAGING:-true}
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
# Persist logs
|
||||||
|
- ./logs/staging:/app/logs
|
||||||
|
|
||||||
|
# Configuration files (if needed)
|
||||||
|
- ./config/staging:/app/config:ro
|
||||||
|
|
||||||
|
# GitHub App private key (alternative to env var)
|
||||||
|
# - ./keys/staging/github-app.pem:/app/github-app.pem:ro
|
||||||
|
|
||||||
|
networks:
|
||||||
|
- webhook-network
|
||||||
|
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-f", "http://localhost:3003/health"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 40s
|
||||||
|
|
||||||
|
labels:
|
||||||
|
- "com.example.environment=staging"
|
||||||
|
- "com.example.service=claude-webhook"
|
||||||
|
- "com.example.bot=MCPClaude-Staging"
|
||||||
|
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
|
labels: "environment,service,bot"
|
||||||
|
|
||||||
|
networks:
|
||||||
|
webhook-network:
|
||||||
|
driver: bridge
|
||||||
|
name: claude-webhook-staging-net
|
||||||
202
scripts/deploy/deploy-production.sh
Executable file
202
scripts/deploy/deploy-production.sh
Executable file
@@ -0,0 +1,202 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Claude Webhook Production Deployment Script
|
||||||
|
# Deploys the production environment on port 8082
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Colors
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
RED='\033[0;31m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
PURPLE='\033[0;35m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
COMPOSE_FILE="docker-compose.yml"
|
||||||
|
SERVICE_NAME="webhook"
|
||||||
|
HEALTH_CHECK_URL="http://localhost:8082/health"
|
||||||
|
MAX_HEALTH_RETRIES=30
|
||||||
|
PRODUCTION_BOT="MCPClaude"
|
||||||
|
BACKUP_DIR="/home/jonflatt/backups/webhook"
|
||||||
|
|
||||||
|
echo -e "${RED}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||||
|
echo -e "${RED} Claude Webhook - PRODUCTION Deployment${NC}"
|
||||||
|
echo -e "${RED}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||||
|
echo -e "Bot: ${GREEN}@${PRODUCTION_BOT}${NC}"
|
||||||
|
echo -e "Port: ${GREEN}8082${NC}"
|
||||||
|
echo -e "Time: ${GREEN}$(date)${NC}"
|
||||||
|
echo -e "${RED}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n"
|
||||||
|
|
||||||
|
# Function to check if service is healthy
|
||||||
|
check_health() {
|
||||||
|
local retries=0
|
||||||
|
echo -e "${YELLOW}Checking service health...${NC}"
|
||||||
|
|
||||||
|
while [ $retries -lt $MAX_HEALTH_RETRIES ]; do
|
||||||
|
if curl -f -s "$HEALTH_CHECK_URL" > /dev/null 2>&1; then
|
||||||
|
echo -e "${GREEN}✓ Service is healthy!${NC}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
retries=$((retries + 1))
|
||||||
|
echo -e " Waiting for service to start... ($retries/$MAX_HEALTH_RETRIES)"
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
echo -e "${RED}✗ Health check failed after $MAX_HEALTH_RETRIES attempts${NC}"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to create backup
|
||||||
|
create_backup() {
|
||||||
|
echo -e "${YELLOW}Creating pre-deployment backup...${NC}"
|
||||||
|
mkdir -p "$BACKUP_DIR"
|
||||||
|
|
||||||
|
# Get current container info
|
||||||
|
CONTAINER_ID=$(docker-compose -f "$COMPOSE_FILE" ps -q 2>/dev/null || echo "")
|
||||||
|
if [ -n "$CONTAINER_ID" ]; then
|
||||||
|
BACKUP_FILE="$BACKUP_DIR/backup-$(date +%Y%m%d-%H%M%S).tar.gz"
|
||||||
|
|
||||||
|
# Export container
|
||||||
|
docker export "$CONTAINER_ID" | gzip > "$BACKUP_FILE"
|
||||||
|
echo -e "${GREEN}✓ Backup created: $BACKUP_FILE${NC}"
|
||||||
|
|
||||||
|
# Keep only last 5 backups
|
||||||
|
ls -t "$BACKUP_DIR"/backup-*.tar.gz 2>/dev/null | tail -n +6 | xargs rm -f 2>/dev/null || true
|
||||||
|
else
|
||||||
|
echo -e "${YELLOW}No running container to backup${NC}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Step 1: Production safety confirmation
|
||||||
|
echo -e "${PURPLE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||||
|
echo -e "${RED}⚠️ PRODUCTION DEPLOYMENT CONFIRMATION ⚠️${NC}"
|
||||||
|
echo -e "${PURPLE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||||
|
echo -e "${YELLOW}You are about to deploy to PRODUCTION.${NC}"
|
||||||
|
echo -e "${YELLOW}This will affect the live @${PRODUCTION_BOT} bot.${NC}"
|
||||||
|
echo -e ""
|
||||||
|
read -p "Type 'DEPLOY PRODUCTION' to continue: " confirmation
|
||||||
|
|
||||||
|
if [ "$confirmation" != "DEPLOY PRODUCTION" ]; then
|
||||||
|
echo -e "${RED}Deployment cancelled.${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Step 2: Pre-deployment checks
|
||||||
|
echo -e "\n${YELLOW}[1/8] Running pre-deployment checks...${NC}"
|
||||||
|
|
||||||
|
# Check if docker-compose file exists
|
||||||
|
if [ ! -f "$COMPOSE_FILE" ]; then
|
||||||
|
echo -e "${RED}Error: $COMPOSE_FILE not found${NC}"
|
||||||
|
echo -e "${YELLOW}Please ensure you're running from the project root directory${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check Docker daemon
|
||||||
|
if ! docker info > /dev/null 2>&1; then
|
||||||
|
echo -e "${RED}Error: Docker daemon is not running${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check disk space
|
||||||
|
DISK_USAGE=$(df -h /var/lib/docker | awk 'NR==2 {print $5}' | sed 's/%//')
|
||||||
|
if [ "$DISK_USAGE" -gt 85 ]; then
|
||||||
|
echo -e "${RED}Warning: Disk usage is at ${DISK_USAGE}%${NC}"
|
||||||
|
echo -e "${YELLOW}Consider cleaning up Docker images/containers${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${GREEN}✓ Pre-deployment checks passed${NC}\n"
|
||||||
|
|
||||||
|
# Step 3: Create backup
|
||||||
|
echo -e "${YELLOW}[2/8] Creating backup...${NC}"
|
||||||
|
create_backup
|
||||||
|
echo -e "${GREEN}✓ Backup complete${NC}\n"
|
||||||
|
|
||||||
|
# Step 4: Pull latest images
|
||||||
|
echo -e "${YELLOW}[3/8] Pulling latest production images...${NC}"
|
||||||
|
docker-compose -f "$COMPOSE_FILE" pull
|
||||||
|
echo -e "${GREEN}✓ Images updated${NC}\n"
|
||||||
|
|
||||||
|
# Step 5: Graceful shutdown
|
||||||
|
echo -e "${YELLOW}[4/8] Gracefully shutting down existing container...${NC}"
|
||||||
|
docker-compose -f "$COMPOSE_FILE" stop || true
|
||||||
|
sleep 5 # Allow time for graceful shutdown
|
||||||
|
docker-compose -f "$COMPOSE_FILE" down --remove-orphans || true
|
||||||
|
echo -e "${GREEN}✓ Existing container stopped${NC}\n"
|
||||||
|
|
||||||
|
# Step 6: Start new container
|
||||||
|
echo -e "${YELLOW}[5/8] Starting new production container...${NC}"
|
||||||
|
docker-compose -f "$COMPOSE_FILE" up -d
|
||||||
|
echo -e "${GREEN}✓ Container started${NC}\n"
|
||||||
|
|
||||||
|
# Step 7: Health check
|
||||||
|
echo -e "${YELLOW}[6/8] Running health checks...${NC}"
|
||||||
|
sleep 5 # Give container time to initialize
|
||||||
|
|
||||||
|
if check_health; then
|
||||||
|
echo -e "${GREEN}✓ Health check passed${NC}\n"
|
||||||
|
else
|
||||||
|
echo -e "${RED}✗ Health check failed${NC}"
|
||||||
|
echo -e "${YELLOW}Rolling back deployment...${NC}"
|
||||||
|
|
||||||
|
# Attempt rollback
|
||||||
|
docker-compose -f "$COMPOSE_FILE" down
|
||||||
|
|
||||||
|
# Check if we have a backup to restore
|
||||||
|
LATEST_BACKUP=$(ls -t "$BACKUP_DIR"/backup-*.tar.gz 2>/dev/null | head -1)
|
||||||
|
if [ -n "$LATEST_BACKUP" ]; then
|
||||||
|
echo -e "${YELLOW}Rollback instructions:${NC}"
|
||||||
|
echo -e "1. Load backup: ${YELLOW}docker load < $LATEST_BACKUP${NC}"
|
||||||
|
echo -e "2. Start previous version manually"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Step 8: Post-deployment verification
|
||||||
|
echo -e "${YELLOW}[7/8] Post-deployment verification...${NC}"
|
||||||
|
|
||||||
|
# Check container status
|
||||||
|
CONTAINER_STATUS=$(docker-compose -f "$COMPOSE_FILE" ps -q | xargs docker inspect -f '{{.State.Status}}' 2>/dev/null || echo "not found")
|
||||||
|
if [ "$CONTAINER_STATUS" = "running" ]; then
|
||||||
|
echo -e "${GREEN}✓ Container status: Running${NC}"
|
||||||
|
else
|
||||||
|
echo -e "${RED}✗ Container status: $CONTAINER_STATUS${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Test webhook endpoint
|
||||||
|
echo -e "${YELLOW}Testing webhook endpoint...${NC}"
|
||||||
|
WEBHOOK_TEST=$(curl -s -o /dev/null -w "%{http_code}" -X POST "$HEALTH_CHECK_URL" -H "Content-Type: application/json" -d '{"test": true}' || echo "000")
|
||||||
|
if [[ "$WEBHOOK_TEST" =~ ^(200|400|401)$ ]]; then
|
||||||
|
echo -e "${GREEN}✓ Webhook endpoint responding (HTTP $WEBHOOK_TEST)${NC}"
|
||||||
|
else
|
||||||
|
echo -e "${RED}⚠️ Webhook endpoint returned unexpected status: HTTP $WEBHOOK_TEST${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Step 9: Final status
|
||||||
|
echo -e "\n${YELLOW}[8/8] Deployment summary...${NC}"
|
||||||
|
|
||||||
|
# Show resource usage
|
||||||
|
echo -e "\n${BLUE}Container Resource Usage:${NC}"
|
||||||
|
docker stats --no-stream --format "table {{.Container}}\t{{.CPUPerc}}\t{{.MemUsage}}" $(docker-compose -f "$COMPOSE_FILE" ps -q)
|
||||||
|
|
||||||
|
# Show recent logs
|
||||||
|
echo -e "\n${BLUE}Recent logs:${NC}"
|
||||||
|
docker-compose -f "$COMPOSE_FILE" logs --tail=20
|
||||||
|
|
||||||
|
# Clean up old images
|
||||||
|
echo -e "\n${YELLOW}Cleaning up old images...${NC}"
|
||||||
|
docker image prune -f
|
||||||
|
|
||||||
|
echo -e "\n${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||||
|
echo -e "${GREEN}✓ PRODUCTION deployment completed successfully!${NC}"
|
||||||
|
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||||
|
echo -e "Service URL: ${GREEN}http://localhost:8082${NC}"
|
||||||
|
echo -e "GitHub Bot: ${GREEN}@${PRODUCTION_BOT}${NC}"
|
||||||
|
echo -e "Backup: ${GREEN}${LATEST_BACKUP:-No backup created}${NC}"
|
||||||
|
echo -e "Logs: ${YELLOW}docker-compose -f $COMPOSE_FILE logs -f${NC}"
|
||||||
|
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}
|
||||||
|
|
||||||
|
${PURPLE}Remember to monitor the service for the next few minutes!${NC}"
|
||||||
121
scripts/deploy/deploy-staging.sh
Executable file
121
scripts/deploy/deploy-staging.sh
Executable file
@@ -0,0 +1,121 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Claude Webhook Staging Deployment Script
|
||||||
|
# Deploys the staging environment on port 8083
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Colors
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
RED='\033[0;31m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
COMPOSE_FILE="docker-compose.staging.yml"
|
||||||
|
SERVICE_NAME="webhook-staging"
|
||||||
|
HEALTH_CHECK_URL="http://localhost:8083/health"
|
||||||
|
MAX_HEALTH_RETRIES=30
|
||||||
|
STAGING_BOT="MCPClaude-Staging"
|
||||||
|
|
||||||
|
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||||
|
echo -e "${BLUE} Claude Webhook - Staging Deployment${NC}"
|
||||||
|
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||||
|
echo -e "Bot: ${GREEN}@${STAGING_BOT}${NC}"
|
||||||
|
echo -e "Port: ${GREEN}8083${NC}"
|
||||||
|
echo -e "Time: ${GREEN}$(date)${NC}"
|
||||||
|
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n"
|
||||||
|
|
||||||
|
# Function to check if service is healthy
|
||||||
|
check_health() {
|
||||||
|
local retries=0
|
||||||
|
echo -e "${YELLOW}Checking service health...${NC}"
|
||||||
|
|
||||||
|
while [ $retries -lt $MAX_HEALTH_RETRIES ]; do
|
||||||
|
if curl -f -s "$HEALTH_CHECK_URL" > /dev/null 2>&1; then
|
||||||
|
echo -e "${GREEN}✓ Service is healthy!${NC}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
retries=$((retries + 1))
|
||||||
|
echo -e " Waiting for service to start... ($retries/$MAX_HEALTH_RETRIES)"
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
echo -e "${RED}✗ Health check failed after $MAX_HEALTH_RETRIES attempts${NC}"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Step 1: Pre-deployment checks
|
||||||
|
echo -e "${YELLOW}[1/6] Running pre-deployment checks...${NC}"
|
||||||
|
|
||||||
|
# Check if docker-compose file exists
|
||||||
|
if [ ! -f "$COMPOSE_FILE" ]; then
|
||||||
|
echo -e "${RED}Error: $COMPOSE_FILE not found${NC}"
|
||||||
|
echo -e "${YELLOW}Please ensure you're running from the project root directory${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check Docker daemon
|
||||||
|
if ! docker info > /dev/null 2>&1; then
|
||||||
|
echo -e "${RED}Error: Docker daemon is not running${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${GREEN}✓ Pre-deployment checks passed${NC}\n"
|
||||||
|
|
||||||
|
# Step 2: Pull latest images
|
||||||
|
echo -e "${YELLOW}[2/6] Pulling latest staging images...${NC}"
|
||||||
|
docker-compose -f "$COMPOSE_FILE" pull
|
||||||
|
echo -e "${GREEN}✓ Images updated${NC}\n"
|
||||||
|
|
||||||
|
# Step 3: Stop existing container
|
||||||
|
echo -e "${YELLOW}[3/6] Stopping existing staging container...${NC}"
|
||||||
|
docker-compose -f "$COMPOSE_FILE" down --remove-orphans || true
|
||||||
|
echo -e "${GREEN}✓ Existing container stopped${NC}\n"
|
||||||
|
|
||||||
|
# Step 4: Start new container
|
||||||
|
echo -e "${YELLOW}[4/6] Starting new staging container...${NC}"
|
||||||
|
docker-compose -f "$COMPOSE_FILE" up -d
|
||||||
|
echo -e "${GREEN}✓ Container started${NC}\n"
|
||||||
|
|
||||||
|
# Step 5: Health check
|
||||||
|
echo -e "${YELLOW}[5/6] Running health checks...${NC}"
|
||||||
|
sleep 5 # Give container time to initialize
|
||||||
|
|
||||||
|
if check_health; then
|
||||||
|
echo -e "${GREEN}✓ Health check passed${NC}\n"
|
||||||
|
else
|
||||||
|
echo -e "${RED}✗ Health check failed${NC}"
|
||||||
|
echo -e "${YELLOW}Checking container logs...${NC}"
|
||||||
|
docker-compose -f "$COMPOSE_FILE" logs --tail=50
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Step 6: Post-deployment verification
|
||||||
|
echo -e "${YELLOW}[6/6] Post-deployment verification...${NC}"
|
||||||
|
|
||||||
|
# Check container status
|
||||||
|
CONTAINER_STATUS=$(docker-compose -f "$COMPOSE_FILE" ps -q | xargs docker inspect -f '{{.State.Status}}' 2>/dev/null || echo "not found")
|
||||||
|
if [ "$CONTAINER_STATUS" = "running" ]; then
|
||||||
|
echo -e "${GREEN}✓ Container status: Running${NC}"
|
||||||
|
else
|
||||||
|
echo -e "${RED}✗ Container status: $CONTAINER_STATUS${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Show resource usage
|
||||||
|
echo -e "\n${BLUE}Container Resource Usage:${NC}"
|
||||||
|
docker stats --no-stream --format "table {{.Container}}\t{{.CPUPerc}}\t{{.MemUsage}}" $(docker-compose -f "$COMPOSE_FILE" ps -q)
|
||||||
|
|
||||||
|
# Show recent logs
|
||||||
|
echo -e "\n${BLUE}Recent logs:${NC}"
|
||||||
|
docker-compose -f "$COMPOSE_FILE" logs --tail=10
|
||||||
|
|
||||||
|
echo -e "\n${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||||
|
echo -e "${GREEN}✓ Staging deployment completed successfully!${NC}"
|
||||||
|
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||||
|
echo -e "Service URL: ${GREEN}http://localhost:8083${NC}"
|
||||||
|
echo -e "GitHub Bot: ${GREEN}@${STAGING_BOT}${NC}"
|
||||||
|
echo -e "Logs: ${YELLOW}docker-compose -f $COMPOSE_FILE logs -f${NC}"
|
||||||
|
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||||
336
scripts/manage-runner.sh
Executable file
336
scripts/manage-runner.sh
Executable file
@@ -0,0 +1,336 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# GitHub Actions Runner Management Script
|
||||||
|
# Manage the webhook deployment runner service
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Colors
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
RED='\033[0;31m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
SERVICE_NAME="webhook-deployment-runner"
|
||||||
|
RUNNER_DIR="/home/jonflatt/github-actions-runner"
|
||||||
|
RUNNER_USER="jonflatt"
|
||||||
|
|
||||||
|
# Function to print usage
|
||||||
|
usage() {
|
||||||
|
echo -e "${BLUE}GitHub Actions Runner Management Tool${NC}"
|
||||||
|
echo -e "${BLUE}=====================================${NC}"
|
||||||
|
echo -e "\nUsage: $0 [command]"
|
||||||
|
echo -e "\nCommands:"
|
||||||
|
echo -e " ${GREEN}start${NC} - Start the runner service"
|
||||||
|
echo -e " ${GREEN}stop${NC} - Stop the runner service"
|
||||||
|
echo -e " ${GREEN}restart${NC} - Restart the runner service"
|
||||||
|
echo -e " ${GREEN}status${NC} - Check runner service status"
|
||||||
|
echo -e " ${GREEN}logs${NC} - View runner logs (live)"
|
||||||
|
echo -e " ${GREEN}logs-tail${NC} - View last 50 lines of logs"
|
||||||
|
echo -e " ${GREEN}update${NC} - Update runner to latest version"
|
||||||
|
echo -e " ${GREEN}config${NC} - Show runner configuration"
|
||||||
|
echo -e " ${GREEN}health${NC} - Check runner health"
|
||||||
|
echo -e " ${GREEN}jobs${NC} - Show recent job history"
|
||||||
|
echo -e " ${GREEN}cleanup${NC} - Clean up work directory"
|
||||||
|
echo -e " ${GREEN}info${NC} - Show runner information"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if running with correct permissions
|
||||||
|
check_permissions() {
|
||||||
|
if [[ $EUID -ne 0 ]] && [[ "$1" =~ ^(start|stop|restart|update)$ ]]; then
|
||||||
|
echo -e "${RED}Error: This command requires sudo privileges${NC}"
|
||||||
|
echo -e "${YELLOW}Run: sudo $0 $1${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Start the runner
|
||||||
|
start_runner() {
|
||||||
|
echo -e "${YELLOW}Starting runner service...${NC}"
|
||||||
|
systemctl start $SERVICE_NAME
|
||||||
|
sleep 2
|
||||||
|
if systemctl is-active --quiet $SERVICE_NAME; then
|
||||||
|
echo -e "${GREEN}✓ Runner started successfully${NC}"
|
||||||
|
systemctl status $SERVICE_NAME --no-pager | head -n 10
|
||||||
|
else
|
||||||
|
echo -e "${RED}✗ Failed to start runner${NC}"
|
||||||
|
systemctl status $SERVICE_NAME --no-pager
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Stop the runner
|
||||||
|
stop_runner() {
|
||||||
|
echo -e "${YELLOW}Stopping runner service...${NC}"
|
||||||
|
systemctl stop $SERVICE_NAME
|
||||||
|
echo -e "${GREEN}✓ Runner stopped${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Restart the runner
|
||||||
|
restart_runner() {
|
||||||
|
echo -e "${YELLOW}Restarting runner service...${NC}"
|
||||||
|
systemctl restart $SERVICE_NAME
|
||||||
|
sleep 2
|
||||||
|
if systemctl is-active --quiet $SERVICE_NAME; then
|
||||||
|
echo -e "${GREEN}✓ Runner restarted successfully${NC}"
|
||||||
|
systemctl status $SERVICE_NAME --no-pager | head -n 10
|
||||||
|
else
|
||||||
|
echo -e "${RED}✗ Failed to restart runner${NC}"
|
||||||
|
systemctl status $SERVICE_NAME --no-pager
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check runner status
|
||||||
|
check_status() {
|
||||||
|
echo -e "${BLUE}Runner Service Status${NC}"
|
||||||
|
echo -e "${BLUE}===================${NC}"
|
||||||
|
systemctl status $SERVICE_NAME --no-pager
|
||||||
|
|
||||||
|
echo -e "\n${BLUE}Runner Process Info${NC}"
|
||||||
|
echo -e "${BLUE}===================${NC}"
|
||||||
|
ps aux | grep -E "(Runner.Listener|run.sh)" | grep -v grep || echo "No runner processes found"
|
||||||
|
}
|
||||||
|
|
||||||
|
# View logs
|
||||||
|
view_logs() {
|
||||||
|
echo -e "${YELLOW}Viewing live logs (Ctrl+C to exit)...${NC}"
|
||||||
|
journalctl -u $SERVICE_NAME -f
|
||||||
|
}
|
||||||
|
|
||||||
|
# View last 50 lines of logs
|
||||||
|
view_logs_tail() {
|
||||||
|
echo -e "${BLUE}Last 50 lines of runner logs${NC}"
|
||||||
|
echo -e "${BLUE}===========================${NC}"
|
||||||
|
journalctl -u $SERVICE_NAME -n 50 --no-pager
|
||||||
|
}
|
||||||
|
|
||||||
|
# Update runner
|
||||||
|
update_runner() {
|
||||||
|
echo -e "${YELLOW}Updating GitHub Actions Runner...${NC}"
|
||||||
|
|
||||||
|
# Stop the service
|
||||||
|
systemctl stop $SERVICE_NAME
|
||||||
|
|
||||||
|
# Get current version
|
||||||
|
CURRENT_VERSION=$($RUNNER_DIR/bin/Runner.Listener --version 2>/dev/null | grep -oP '\d+\.\d+\.\d+' || echo "unknown")
|
||||||
|
echo -e "Current version: ${YELLOW}$CURRENT_VERSION${NC}"
|
||||||
|
|
||||||
|
# Get latest version
|
||||||
|
LATEST_VERSION=$(curl -s https://api.github.com/repos/actions/runner/releases/latest | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*/\1/')
|
||||||
|
echo -e "Latest version: ${GREEN}$LATEST_VERSION${NC}"
|
||||||
|
|
||||||
|
if [ "$CURRENT_VERSION" = "$LATEST_VERSION" ]; then
|
||||||
|
echo -e "${GREEN}✓ Runner is already up to date${NC}"
|
||||||
|
systemctl start $SERVICE_NAME
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Backup current runner
|
||||||
|
echo -e "${YELLOW}Backing up current runner...${NC}"
|
||||||
|
cd $RUNNER_DIR
|
||||||
|
tar -czf runner-backup-$(date +%Y%m%d-%H%M%S).tar.gz bin externals
|
||||||
|
|
||||||
|
# Download and extract new version
|
||||||
|
echo -e "${YELLOW}Downloading new version...${NC}"
|
||||||
|
curl -o actions-runner-linux-x64.tar.gz -L "https://github.com/actions/runner/releases/download/v${LATEST_VERSION}/actions-runner-linux-x64-${LATEST_VERSION}.tar.gz"
|
||||||
|
tar xzf ./actions-runner-linux-x64.tar.gz
|
||||||
|
rm actions-runner-linux-x64.tar.gz
|
||||||
|
|
||||||
|
# Start the service
|
||||||
|
systemctl start $SERVICE_NAME
|
||||||
|
echo -e "${GREEN}✓ Runner updated to version $LATEST_VERSION${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Show configuration
|
||||||
|
show_config() {
|
||||||
|
echo -e "${BLUE}Runner Configuration${NC}"
|
||||||
|
echo -e "${BLUE}===================${NC}"
|
||||||
|
|
||||||
|
if [ -f "$RUNNER_DIR/.runner" ]; then
|
||||||
|
echo -e "\n${GREEN}Runner Settings:${NC}"
|
||||||
|
cat "$RUNNER_DIR/.runner" | jq '.' 2>/dev/null || cat "$RUNNER_DIR/.runner"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f "$RUNNER_DIR/.credentials" ]; then
|
||||||
|
echo -e "\n${GREEN}Runner Registration:${NC}"
|
||||||
|
echo "Runner is registered (credentials file exists)"
|
||||||
|
else
|
||||||
|
echo -e "\n${RED}Runner is not configured${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "\n${GREEN}Service Configuration:${NC}"
|
||||||
|
systemctl show $SERVICE_NAME | grep -E "(LoadState|ActiveState|SubState|MainPID|Environment)"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check health
|
||||||
|
check_health() {
|
||||||
|
echo -e "${BLUE}Runner Health Check${NC}"
|
||||||
|
echo -e "${BLUE}==================${NC}"
|
||||||
|
|
||||||
|
# Check service status
|
||||||
|
if systemctl is-active --quiet $SERVICE_NAME; then
|
||||||
|
echo -e "${GREEN}✓ Service is running${NC}"
|
||||||
|
else
|
||||||
|
echo -e "${RED}✗ Service is not running${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check disk space
|
||||||
|
DISK_USAGE=$(df -h $RUNNER_DIR | awk 'NR==2 {print $5}' | sed 's/%//')
|
||||||
|
if [ "$DISK_USAGE" -lt 80 ]; then
|
||||||
|
echo -e "${GREEN}✓ Disk usage: ${DISK_USAGE}%${NC}"
|
||||||
|
else
|
||||||
|
echo -e "${RED}✗ Disk usage: ${DISK_USAGE}% (High)${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check work directory size
|
||||||
|
if [ -d "$RUNNER_DIR/_work" ]; then
|
||||||
|
WORK_SIZE=$(du -sh "$RUNNER_DIR/_work" 2>/dev/null | cut -f1)
|
||||||
|
echo -e "${BLUE}Work directory size: $WORK_SIZE${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check runner connectivity
|
||||||
|
if [ -f "$RUNNER_DIR/.runner" ]; then
|
||||||
|
GITHUB_URL=$(cat "$RUNNER_DIR/.runner" | jq -r '.gitHubUrl' 2>/dev/null || echo "")
|
||||||
|
if [ -n "$GITHUB_URL" ] && curl -s -o /dev/null -w "%{http_code}" "$GITHUB_URL" | grep -q "200"; then
|
||||||
|
echo -e "${GREEN}✓ GitHub connectivity OK${NC}"
|
||||||
|
else
|
||||||
|
echo -e "${YELLOW}⚠ Cannot verify GitHub connectivity${NC}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Show recent jobs
|
||||||
|
show_jobs() {
|
||||||
|
echo -e "${BLUE}Recent Runner Jobs${NC}"
|
||||||
|
echo -e "${BLUE}=================${NC}"
|
||||||
|
|
||||||
|
# Check for job history in work directory
|
||||||
|
if [ -d "$RUNNER_DIR/_work" ]; then
|
||||||
|
echo -e "\n${GREEN}Recent job directories:${NC}"
|
||||||
|
ls -la "$RUNNER_DIR/_work" 2>/dev/null | tail -n 10 || echo "No job directories found"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Show recent log entries
|
||||||
|
echo -e "\n${GREEN}Recent job activity:${NC}"
|
||||||
|
journalctl -u $SERVICE_NAME --since "1 hour ago" | grep -E "(Running job|Job .* completed|Completed request)" | tail -n 20 || echo "No recent job activity"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Cleanup work directory
|
||||||
|
cleanup_work() {
|
||||||
|
echo -e "${YELLOW}Cleaning up work directory...${NC}"
|
||||||
|
|
||||||
|
if [ ! -d "$RUNNER_DIR/_work" ]; then
|
||||||
|
echo -e "${GREEN}Work directory doesn't exist${NC}"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Show current size
|
||||||
|
BEFORE_SIZE=$(du -sh "$RUNNER_DIR/_work" 2>/dev/null | cut -f1)
|
||||||
|
echo -e "Current size: ${YELLOW}$BEFORE_SIZE${NC}"
|
||||||
|
|
||||||
|
# Confirm
|
||||||
|
read -p "Are you sure you want to clean the work directory? (y/N): " confirm
|
||||||
|
if [ "$confirm" != "y" ]; then
|
||||||
|
echo -e "${YELLOW}Cleanup cancelled${NC}"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Stop runner
|
||||||
|
systemctl stop $SERVICE_NAME
|
||||||
|
|
||||||
|
# Clean work directory
|
||||||
|
rm -rf "$RUNNER_DIR/_work"/*
|
||||||
|
|
||||||
|
# Start runner
|
||||||
|
systemctl start $SERVICE_NAME
|
||||||
|
|
||||||
|
echo -e "${GREEN}✓ Work directory cleaned${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Show runner info
|
||||||
|
show_info() {
|
||||||
|
echo -e "${BLUE}GitHub Actions Runner Information${NC}"
|
||||||
|
echo -e "${BLUE}=================================${NC}"
|
||||||
|
|
||||||
|
echo -e "\n${GREEN}Basic Info:${NC}"
|
||||||
|
echo -e "Service Name: ${YELLOW}$SERVICE_NAME${NC}"
|
||||||
|
echo -e "Runner Directory: ${YELLOW}$RUNNER_DIR${NC}"
|
||||||
|
echo -e "Runner User: ${YELLOW}$RUNNER_USER${NC}"
|
||||||
|
|
||||||
|
if [ -f "$RUNNER_DIR/bin/Runner.Listener" ]; then
|
||||||
|
VERSION=$($RUNNER_DIR/bin/Runner.Listener --version 2>/dev/null | grep -oP '\d+\.\d+\.\d+' || echo "unknown")
|
||||||
|
echo -e "Runner Version: ${YELLOW}$VERSION${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "\n${GREEN}System Info:${NC}"
|
||||||
|
echo -e "Hostname: ${YELLOW}$(hostname)${NC}"
|
||||||
|
echo -e "OS: ${YELLOW}$(lsb_release -d | cut -f2)${NC}"
|
||||||
|
echo -e "Kernel: ${YELLOW}$(uname -r)${NC}"
|
||||||
|
echo -e "Architecture: ${YELLOW}$(uname -m)${NC}"
|
||||||
|
|
||||||
|
echo -e "\n${GREEN}Docker Info:${NC}"
|
||||||
|
if command -v docker &> /dev/null; then
|
||||||
|
DOCKER_VERSION=$(docker --version | awk '{print $3}' | sed 's/,$//')
|
||||||
|
echo -e "Docker Version: ${YELLOW}$DOCKER_VERSION${NC}"
|
||||||
|
|
||||||
|
if groups $RUNNER_USER | grep -q docker; then
|
||||||
|
echo -e "Docker Access: ${GREEN}✓ User in docker group${NC}"
|
||||||
|
else
|
||||||
|
echo -e "Docker Access: ${RED}✗ User not in docker group${NC}"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo -e "${RED}Docker not installed${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "\n${GREEN}Labels:${NC}"
|
||||||
|
echo -e "${YELLOW}self-hosted,linux,x64,deployment,webhook-cd${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main logic
|
||||||
|
check_permissions "$1"
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
start)
|
||||||
|
start_runner
|
||||||
|
;;
|
||||||
|
stop)
|
||||||
|
stop_runner
|
||||||
|
;;
|
||||||
|
restart)
|
||||||
|
restart_runner
|
||||||
|
;;
|
||||||
|
status)
|
||||||
|
check_status
|
||||||
|
;;
|
||||||
|
logs)
|
||||||
|
view_logs
|
||||||
|
;;
|
||||||
|
logs-tail)
|
||||||
|
view_logs_tail
|
||||||
|
;;
|
||||||
|
update)
|
||||||
|
update_runner
|
||||||
|
;;
|
||||||
|
config)
|
||||||
|
show_config
|
||||||
|
;;
|
||||||
|
health)
|
||||||
|
check_health
|
||||||
|
;;
|
||||||
|
jobs)
|
||||||
|
show_jobs
|
||||||
|
;;
|
||||||
|
cleanup)
|
||||||
|
cleanup_work
|
||||||
|
;;
|
||||||
|
info)
|
||||||
|
show_info
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
usage
|
||||||
|
;;
|
||||||
|
esac
|
||||||
91
scripts/setup/setup-github-runner.sh
Executable file
91
scripts/setup/setup-github-runner.sh
Executable file
@@ -0,0 +1,91 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Setup GitHub Actions self-hosted runner for claude-github-webhook
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
RUNNER_DIR="/home/jonflatt/github-actions-runner"
|
||||||
|
RUNNER_VERSION="2.324.0"
|
||||||
|
REPO_URL="https://github.com/intelligence-assist/claude-github-webhook"
|
||||||
|
RUNNER_NAME="claude-webhook-runner"
|
||||||
|
RUNNER_LABELS="self-hosted,linux,x64,claude-webhook"
|
||||||
|
|
||||||
|
echo "🚀 Setting up GitHub Actions self-hosted runner..."
|
||||||
|
|
||||||
|
# Create runner directory
|
||||||
|
mkdir -p "$RUNNER_DIR"
|
||||||
|
cd "$RUNNER_DIR"
|
||||||
|
|
||||||
|
# Download runner if not exists
|
||||||
|
if [ ! -f "actions-runner-linux-x64-${RUNNER_VERSION}.tar.gz" ]; then
|
||||||
|
echo "📦 Downloading runner v${RUNNER_VERSION}..."
|
||||||
|
curl -o "actions-runner-linux-x64-${RUNNER_VERSION}.tar.gz" -L \
|
||||||
|
"https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-linux-x64-${RUNNER_VERSION}.tar.gz"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Extract runner
|
||||||
|
echo "📂 Extracting runner..."
|
||||||
|
tar xzf "./actions-runner-linux-x64-${RUNNER_VERSION}.tar.gz"
|
||||||
|
|
||||||
|
# Install dependencies if needed
|
||||||
|
echo "🔧 Installing dependencies..."
|
||||||
|
sudo ./bin/installdependencies.sh || true
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "⚠️ IMPORTANT: You need to get a runner registration token from GitHub!"
|
||||||
|
echo ""
|
||||||
|
echo "1. Go to: https://github.com/intelligence-assist/claude-github-webhook/settings/actions/runners/new"
|
||||||
|
echo "2. Copy the registration token"
|
||||||
|
echo "3. Run the configuration command below with your token:"
|
||||||
|
echo ""
|
||||||
|
echo "cd $RUNNER_DIR"
|
||||||
|
echo "./config.sh --url $REPO_URL --token YOUR_TOKEN_HERE --name $RUNNER_NAME --labels $RUNNER_LABELS --unattended --replace"
|
||||||
|
echo ""
|
||||||
|
echo "4. After configuration, install as a service:"
|
||||||
|
echo "sudo ./svc.sh install"
|
||||||
|
echo "sudo ./svc.sh start"
|
||||||
|
echo ""
|
||||||
|
echo "5. Check status:"
|
||||||
|
echo "sudo ./svc.sh status"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Create systemd service file for the runner
|
||||||
|
cat > "$RUNNER_DIR/actions.runner.service" << 'EOF'
|
||||||
|
[Unit]
|
||||||
|
Description=GitHub Actions Runner (claude-webhook-runner)
|
||||||
|
After=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=jonflatt
|
||||||
|
WorkingDirectory=/home/jonflatt/github-actions-runner
|
||||||
|
ExecStart=/home/jonflatt/github-actions-runner/run.sh
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=5
|
||||||
|
KillMode=process
|
||||||
|
KillSignal=SIGTERM
|
||||||
|
StandardOutput=journal
|
||||||
|
StandardError=journal
|
||||||
|
SyslogIdentifier=github-runner
|
||||||
|
|
||||||
|
# Security settings
|
||||||
|
NoNewPrivileges=true
|
||||||
|
PrivateTmp=true
|
||||||
|
ProtectSystem=strict
|
||||||
|
ProtectHome=read-only
|
||||||
|
ReadWritePaths=/home/jonflatt/github-actions-runner
|
||||||
|
ReadWritePaths=/home/jonflatt/n8n/claude-repo
|
||||||
|
ReadWritePaths=/var/run/docker.sock
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "📄 Systemd service file created at: $RUNNER_DIR/actions.runner.service"
|
||||||
|
echo ""
|
||||||
|
echo "Alternative: Use systemd directly instead of ./svc.sh:"
|
||||||
|
echo "sudo cp $RUNNER_DIR/actions.runner.service /etc/systemd/system/github-runner-claude.service"
|
||||||
|
echo "sudo systemctl daemon-reload"
|
||||||
|
echo "sudo systemctl enable github-runner-claude"
|
||||||
|
echo "sudo systemctl start github-runner-claude"
|
||||||
Reference in New Issue
Block a user