Fix CI/CD: Switch from Gitea Actions to Woodpecker CI workflow
- Disabled ci-cd-fast.yml (Gitea Actions syntax) - Added ci-cd-woodpecker.yml (proper Woodpecker CI syntax) - Fixed environment variable and secret access - Should resolve deployment issues with missing variables
This commit is contained in:
232
.gitea/workflows/ci-cd-woodpecker.yml
Normal file
232
.gitea/workflows/ci-cd-woodpecker.yml
Normal file
@@ -0,0 +1,232 @@
|
||||
name: CI/CD Pipeline (Woodpecker)
|
||||
|
||||
when:
|
||||
event: push
|
||||
branch: production
|
||||
|
||||
steps:
|
||||
build:
|
||||
image: node:20-alpine
|
||||
commands:
|
||||
- echo "🚀 Starting CI/CD Pipeline"
|
||||
- echo "📋 Step 1: Installing dependencies..."
|
||||
- npm ci --prefer-offline --no-audit
|
||||
- echo "🔍 Step 2: Running linting..."
|
||||
- npm run lint
|
||||
- echo "🧪 Step 3: Running tests..."
|
||||
- npm run test
|
||||
- echo "🏗️ Step 4: Building application..."
|
||||
- npm run build
|
||||
- echo "🔒 Step 5: Running security scan..."
|
||||
- npm audit --audit-level=high || echo "⚠️ Some vulnerabilities found, but continuing..."
|
||||
volumes:
|
||||
- node_modules:/app/node_modules
|
||||
|
||||
docker-build:
|
||||
image: docker:latest
|
||||
commands:
|
||||
- echo "🐳 Building Docker image..."
|
||||
- docker build -t portfolio-app:latest .
|
||||
- docker tag portfolio-app:latest portfolio-app:$(date +%Y%m%d-%H%M%S)
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
|
||||
deploy:
|
||||
image: docker:latest
|
||||
commands:
|
||||
- echo "🚀 Deploying application..."
|
||||
|
||||
# Verify secrets and variables
|
||||
- echo "🔍 Verifying secrets and variables..."
|
||||
- |
|
||||
if [ -z "$NEXT_PUBLIC_BASE_URL" ]; then
|
||||
echo "❌ NEXT_PUBLIC_BASE_URL variable is missing!"
|
||||
exit 1
|
||||
fi
|
||||
if [ -z "$MY_EMAIL" ]; then
|
||||
echo "❌ MY_EMAIL variable is missing!"
|
||||
exit 1
|
||||
fi
|
||||
if [ -z "$MY_INFO_EMAIL" ]; then
|
||||
echo "❌ MY_INFO_EMAIL variable is missing!"
|
||||
exit 1
|
||||
fi
|
||||
if [ -z "$MY_PASSWORD" ]; then
|
||||
echo "❌ MY_PASSWORD secret is missing!"
|
||||
exit 1
|
||||
fi
|
||||
if [ -z "$MY_INFO_PASSWORD" ]; then
|
||||
echo "❌ MY_INFO_PASSWORD secret is missing!"
|
||||
exit 1
|
||||
fi
|
||||
if [ -z "$ADMIN_BASIC_AUTH" ]; then
|
||||
echo "❌ ADMIN_BASIC_AUTH secret is missing!"
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ All required secrets and variables are present"
|
||||
|
||||
# Check if current container is running
|
||||
- |
|
||||
if docker ps -q -f name=portfolio-app | grep -q .; then
|
||||
echo "📊 Current container is running, proceeding with zero-downtime update"
|
||||
CURRENT_CONTAINER_RUNNING=true
|
||||
else
|
||||
echo "📊 No current container running, doing fresh deployment"
|
||||
CURRENT_CONTAINER_RUNNING=false
|
||||
fi
|
||||
|
||||
# Ensure database and redis are running
|
||||
- echo "🔧 Ensuring database and redis are running..."
|
||||
- docker compose up -d postgres redis
|
||||
- sleep 10
|
||||
|
||||
# Deploy with zero downtime
|
||||
- |
|
||||
if [ "$CURRENT_CONTAINER_RUNNING" = "true" ]; then
|
||||
echo "🔄 Performing rolling update..."
|
||||
|
||||
# Generate unique container name
|
||||
TIMESTAMP=$(date +%s)
|
||||
TEMP_CONTAINER_NAME="portfolio-app-temp-$TIMESTAMP"
|
||||
echo "🔧 Using temporary container name: $TEMP_CONTAINER_NAME"
|
||||
|
||||
# Clean up any existing temporary containers
|
||||
echo "🧹 Cleaning up any existing temporary containers..."
|
||||
docker rm -f portfolio-app-new portfolio-app-temp-* portfolio-app-backup || true
|
||||
|
||||
# Find and remove any containers with portfolio-app in the name (except the main one)
|
||||
EXISTING_CONTAINERS=$(docker ps -a --format "table {{.Names}}" | grep "portfolio-app" | grep -v "^portfolio-app$" || true)
|
||||
if [ -n "$EXISTING_CONTAINERS" ]; then
|
||||
echo "🗑️ Removing existing portfolio-app containers:"
|
||||
echo "$EXISTING_CONTAINERS"
|
||||
echo "$EXISTING_CONTAINERS" | xargs -r docker rm -f || true
|
||||
fi
|
||||
|
||||
# Also clean up any stopped containers
|
||||
docker container prune -f || true
|
||||
|
||||
# Start new container with unique temporary name
|
||||
docker run -d \
|
||||
--name $TEMP_CONTAINER_NAME \
|
||||
--restart unless-stopped \
|
||||
--network portfolio_net \
|
||||
-e NODE_ENV=$NODE_ENV \
|
||||
-e LOG_LEVEL=$LOG_LEVEL \
|
||||
-e DATABASE_URL=postgresql://portfolio_user:portfolio_pass@postgres:5432/portfolio_db?schema=public \
|
||||
-e REDIS_URL=redis://redis:6379 \
|
||||
-e NEXT_PUBLIC_BASE_URL="$NEXT_PUBLIC_BASE_URL" \
|
||||
-e NEXT_PUBLIC_UMAMI_URL="$NEXT_PUBLIC_UMAMI_URL" \
|
||||
-e NEXT_PUBLIC_UMAMI_WEBSITE_ID="$NEXT_PUBLIC_UMAMI_WEBSITE_ID" \
|
||||
-e MY_EMAIL="$MY_EMAIL" \
|
||||
-e MY_INFO_EMAIL="$MY_INFO_EMAIL" \
|
||||
-e MY_PASSWORD="$MY_PASSWORD" \
|
||||
-e MY_INFO_PASSWORD="$MY_INFO_PASSWORD" \
|
||||
-e ADMIN_BASIC_AUTH="$ADMIN_BASIC_AUTH" \
|
||||
portfolio-app:latest
|
||||
|
||||
# Wait for new container to be ready
|
||||
echo "⏳ Waiting for new container to be ready..."
|
||||
sleep 15
|
||||
|
||||
# Health check new container
|
||||
for i in {1..20}; do
|
||||
if docker exec $TEMP_CONTAINER_NAME curl -f http://localhost:3000/api/health > /dev/null 2>&1; then
|
||||
echo "✅ New container is healthy!"
|
||||
break
|
||||
fi
|
||||
echo "⏳ Health check attempt $i/20..."
|
||||
sleep 3
|
||||
done
|
||||
|
||||
# Stop old container
|
||||
echo "🛑 Stopping old container..."
|
||||
docker stop portfolio-app || true
|
||||
docker rm portfolio-app || true
|
||||
|
||||
# Rename new container
|
||||
docker rename $TEMP_CONTAINER_NAME portfolio-app
|
||||
|
||||
# Update port mapping
|
||||
docker stop portfolio-app
|
||||
docker rm portfolio-app
|
||||
|
||||
# Start with correct port
|
||||
docker run -d \
|
||||
--name portfolio-app \
|
||||
--restart unless-stopped \
|
||||
--network portfolio_net \
|
||||
-p 3000:3000 \
|
||||
-e NODE_ENV=$NODE_ENV \
|
||||
-e LOG_LEVEL=$LOG_LEVEL \
|
||||
-e DATABASE_URL=postgresql://portfolio_user:portfolio_pass@postgres:5432/portfolio_db?schema=public \
|
||||
-e REDIS_URL=redis://redis:6379 \
|
||||
-e NEXT_PUBLIC_BASE_URL="$NEXT_PUBLIC_BASE_URL" \
|
||||
-e NEXT_PUBLIC_UMAMI_URL="$NEXT_PUBLIC_UMAMI_URL" \
|
||||
-e NEXT_PUBLIC_UMAMI_WEBSITE_ID="$NEXT_PUBLIC_UMAMI_WEBSITE_ID" \
|
||||
-e MY_EMAIL="$MY_EMAIL" \
|
||||
-e MY_INFO_EMAIL="$MY_INFO_EMAIL" \
|
||||
-e MY_PASSWORD="$MY_PASSWORD" \
|
||||
-e MY_INFO_PASSWORD="$MY_INFO_PASSWORD" \
|
||||
-e ADMIN_BASIC_AUTH="$ADMIN_BASIC_AUTH" \
|
||||
portfolio-app:latest
|
||||
|
||||
echo "✅ Rolling update completed!"
|
||||
else
|
||||
echo "🆕 Fresh deployment..."
|
||||
docker compose up -d
|
||||
fi
|
||||
|
||||
# Wait for container to be ready
|
||||
- echo "⏳ Waiting for container to be ready..."
|
||||
- sleep 15
|
||||
|
||||
# Health check
|
||||
- |
|
||||
echo "🏥 Performing health check..."
|
||||
for i in {1..40}; do
|
||||
if curl -f http://localhost:3000/api/health > /dev/null 2>&1; then
|
||||
echo "✅ Application is healthy!"
|
||||
break
|
||||
fi
|
||||
echo "⏳ Health check attempt $i/40..."
|
||||
sleep 3
|
||||
done
|
||||
|
||||
# Final verification
|
||||
- echo "🔍 Final health verification..."
|
||||
- docker ps --filter "name=portfolio-app" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
|
||||
- |
|
||||
if curl -f http://localhost:3000/api/health; then
|
||||
echo "✅ Health endpoint accessible"
|
||||
else
|
||||
echo "❌ Health endpoint not accessible"
|
||||
exit 1
|
||||
fi
|
||||
- |
|
||||
if curl -f http://localhost:3000/ > /dev/null; then
|
||||
echo "✅ Main page is accessible"
|
||||
else
|
||||
echo "❌ Main page is not accessible"
|
||||
exit 1
|
||||
fi
|
||||
- echo "✅ Deployment successful!"
|
||||
|
||||
# Cleanup
|
||||
- docker image prune -f
|
||||
- docker system prune -f
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
environment:
|
||||
- NODE_ENV
|
||||
- LOG_LEVEL
|
||||
- NEXT_PUBLIC_BASE_URL
|
||||
- NEXT_PUBLIC_UMAMI_URL
|
||||
- NEXT_PUBLIC_UMAMI_WEBSITE_ID
|
||||
- MY_EMAIL
|
||||
- MY_INFO_EMAIL
|
||||
- MY_PASSWORD
|
||||
- MY_INFO_PASSWORD
|
||||
- ADMIN_BASIC_AUTH
|
||||
|
||||
volumes:
|
||||
node_modules:
|
||||
45
sync-env.ps1
45
sync-env.ps1
@@ -11,27 +11,43 @@ if (!(Get-Command bw -ErrorAction SilentlyContinue)) {
|
||||
}
|
||||
|
||||
# Check status
|
||||
$status = bw status | ConvertFrom-Json
|
||||
$statusOutput = bw status 2>$null
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host "Error: Failed to get Bitwarden status" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
$status = $statusOutput | ConvertFrom-Json
|
||||
|
||||
if ($status.status -eq "unauthenticated") {
|
||||
Write-Host "Please login to Bitwarden:"
|
||||
bw login
|
||||
$status = bw status | ConvertFrom-Json
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host "Error: Failed to login to Bitwarden" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
$statusOutput = bw status 2>$null
|
||||
$status = $statusOutput | ConvertFrom-Json
|
||||
}
|
||||
|
||||
# Unlock if needed
|
||||
if ($status.status -eq "locked") {
|
||||
Write-Host "Unlocking vault..."
|
||||
$env:BW_SESSION = bw unlock --raw
|
||||
Write-Host "Please enter your Bitwarden master password:"
|
||||
$sessionKey = bw unlock --raw
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host "Error: Failed to unlock vault" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
$env:BW_SESSION = $sessionKey
|
||||
}
|
||||
|
||||
# Sync
|
||||
Write-Host "Syncing with Bitwarden..."
|
||||
bw sync | Out-Null
|
||||
bw sync 2>$null | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host "Error: Failed to sync with Bitwarden" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
# CHANGE THIS to your Bitwarden item name
|
||||
$itemName = "portfolio-env"
|
||||
@@ -39,24 +55,25 @@ $itemName = "portfolio-env"
|
||||
Write-Host "Fetching environment variables..."
|
||||
|
||||
# Get item
|
||||
$item = bw get item $itemName 2>$null | ConvertFrom-Json
|
||||
|
||||
if (!$item) {
|
||||
$itemOutput = bw get item $itemName 2>$null
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host "Error: Could not find item '$itemName' in Bitwarden" -ForegroundColor Red
|
||||
Write-Host "Make sure you have an item with this exact name in your vault"
|
||||
exit 1
|
||||
}
|
||||
$item = $itemOutput | ConvertFrom-Json
|
||||
|
||||
# Get notes
|
||||
$notes = $item.notes
|
||||
|
||||
if (!$notes) {
|
||||
# Get notes and process them
|
||||
if (!$item.notes) {
|
||||
Write-Host "Error: No notes found in item '$itemName'" -ForegroundColor Red
|
||||
Write-Host "Add your environment variables to the notes field"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Create .env file
|
||||
$notes | Out-File -FilePath ".env" -Encoding UTF8 -NoNewline
|
||||
# Process notes - handle escaped newlines and other JSON formatting
|
||||
$notes = $item.notes -replace '\\n', "`n" -replace '\\r', "`r" -replace '\\t', "`t"
|
||||
|
||||
Write-Host "✓ Created .env file successfully!" -ForegroundColor Green
|
||||
# Create .env file
|
||||
$envPath = Join-Path (Get-Location) ".env"
|
||||
$notes | Out-File -FilePath $envPath -Encoding UTF8 -NoNewline
|
||||
Write-Host "Created .env file successfully at: $envPath" -ForegroundColor Green
|
||||
|
||||
Reference in New Issue
Block a user