Compare commits

...

2 Commits

Author SHA1 Message Date
Denshooter
8d65e2d7c3 Merge branch 'production' of https://git.dk0.dev/denshooter/portfolio into production
Some checks failed
CI/CD Pipeline (Reliable & Simple) / production (push) Failing after 8m19s
CI/CD Pipeline (Simple & Reliable) / production (push) Failing after 6m15s
2025-09-16 00:30:20 +02:00
Denshooter
dca8cb8973 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
2025-09-16 00:29:23 +02:00
3 changed files with 263 additions and 14 deletions

View 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:

View File

@@ -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