Skip to content

Updates

Updates #28

Workflow file for this run

name: Deploy
on:
push:
branches: [ master ]
workflow_dispatch:
env:
DOCKER_REGISTRY: ghcr.io
IMAGE_NAME: safeturned/api
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout code with submodules
uses: actions/checkout@v4
with:
submodules: recursive
token: ${{ secrets.GITHUB_TOKEN }}
- name: Force initialize submodule
run: |
echo "=== Force initializing FileChecker submodule ==="
git submodule add --force https://github.com/Safeturned/FileChecker.git FileChecker || true
git submodule update --init --recursive
echo "=== Checking submodule status ==="
git submodule status
echo "=== FileChecker directory contents ==="
ls -la FileChecker/
echo "=== FileChecker/src directory contents ==="
ls -la FileChecker/src/
- name: Setup .NET
uses: actions/setup-dotnet@v4
env:
DOTNET_NOLOGO: true
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
DOTNET_CLI_TELEMETRY_OPTOUT: true
with:
dotnet-version: 9.*
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Install Aspire CLI
run: dotnet tool install --global Aspire.Cli
- name: Publish with Aspire
run: |
aspire publish -o ./publish/aspire
- name: Debug Aspire output
run: |
echo "=== Aspire publish output ==="
ls -la ./publish/aspire/
echo "=== Docker compose content ==="
cat ./publish/aspire/docker-compose.yaml
- name: Login to GHCR
run: |
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.repository_owner }} --password-stdin
- name: Find and tag Docker image
run: |
# Look for the built image with various possible names
BUILT_IMAGE=$(docker images --format "{{.Repository}}:{{.Tag}}" | grep -E "(safeturned-api|safeturned-api)" | head -n 1)
if [ -z "$BUILT_IMAGE" ]; then
# Try to find any image with safeturned in the name
BUILT_IMAGE=$(docker images --format "{{.Repository}}:{{.Tag}}" | grep safeturned | head -n 1)
fi
if [ -z "$BUILT_IMAGE" ]; then
# List all images for debugging
echo "Available Docker images:"
docker images
echo "❌ No built image found"
exit 1
fi
echo "Found built image: $BUILT_IMAGE"
# Tag for GHCR
docker tag "$BUILT_IMAGE" "ghcr.io/safeturned/api:latest"
docker tag "$BUILT_IMAGE" "ghcr.io/safeturned/api:v${{ github.run_number }}"
echo "✅ Image tagged successfully"
- name: Push Docker image
run: |
# Push both latest and versioned tags
docker push "ghcr.io/safeturned/api:latest"
docker push "ghcr.io/safeturned/api:v${{ github.run_number }}"
echo "✅ Images pushed successfully"
- name: Create deployment package
run: |
mkdir -p ./deploy
# Copy Aspire generated files
cp ./publish/aspire/docker-compose.yaml ./deploy/
cp ./publish/aspire/.env ./deploy/ 2>/dev/null || true
# Update the .env file with our values
cat > ./deploy/.env << EOF
# Default container port for safeturned-api
SAFETURNED_API_PORT=${{ secrets.SAFETURNED_API_PORT }}
DATABASE_PASSWORD=${{ secrets.DATABASE_PASSWORD }}
SAFETURNED_API_IMAGE=ghcr.io/safeturned/api:latest
ASPNETCORE_ENVIRONMENT=${{ secrets.ASPNETCORE_ENVIRONMENT }}
Hangfire__DashboardPath=${{ secrets.HANGFIRE_DASHBOARD_PATH }}
Hangfire__Username=${{ secrets.HANGFIRE_USERNAME }}
Hangfire__Password=${{ secrets.HANGFIRE_PASSWORD }}
Sentry__Dsn=${{ secrets.SENTRY_DSN }}
EOF
# Create override to use registry image and expose port
cat > ./deploy/docker-compose.override.yaml << EOF
services:
safeturned-api:
image: ghcr.io/safeturned/api:latest
ports:
- "8081:8081" # Map host port to container port (both use SAFETURNED_API_PORT)
environment:
# Environment
- ASPNETCORE_ENVIRONMENT=\${ASPNETCORE_ENVIRONMENT}
- Hangfire__DashboardPath=\${Hangfire__DashboardPath}
- Hangfire__Username=\${Hangfire__Username}
- Hangfire__Password=\${Hangfire__Password}
- Sentry__Dsn=\${Sentry__Dsn}
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8081/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
EOF
# Create deployment script with resolved variables
cat > ./deploy/deploy.sh << 'EOF'
#!/bin/bash
set -e
echo "Starting deployment..."
# Set default environment variables
export DATABASE_PASSWORD=${DATABASE_PASSWORD:-"safeturned_password"}
export SAFETURNED_API_PORT=${SAFETURNED_API_PORT:-"${{ secrets.SAFETURNED_API_PORT }}"}
echo "Environment variables set:"
echo "DATABASE_PASSWORD: $DATABASE_PASSWORD"
echo "SAFETURNED_API_PORT: $SAFETURNED_API_PORT"
# Stop existing containers
docker compose down || true
# Pull the latest image from registry
echo "Pulling latest image from registry..."
docker pull ghcr.io/safeturned/api:latest || echo "Image pull failed, will build locally"
# Start with Aspire's docker-compose
docker compose up -d
# Wait for services to be healthy
echo "Waiting for services to be healthy..."
timeout 120 bash -c 'until docker compose ps | grep -q "healthy"; do sleep 10; echo "Still waiting for healthy services..."; done' || echo "Warning: Some services may not be healthy"
# Show final status
echo "Final container status:"
docker compose ps
echo "Deployment completed successfully!"
echo "API is available on port $SAFETURNED_API_PORT for Cloudflare Tunnel"
EOF
chmod +x ./deploy/deploy.sh
echo "Deployment package created in ./deploy/"
- name: Debug deployment files
run: |
echo "=== Debugging deployment files ==="
echo "Current directory: $(pwd)"
echo "Deploy directory contents:"
ls -la ./deploy/
echo "Deploy.sh contents:"
cat ./deploy/deploy.sh
echo "=== Testing file copy ==="
echo "Files to be copied:"
ls -la ./deploy/*
- name: Copy files to server
uses: appleboy/scp-action@v1
with:
host: ${{ secrets.SERVER_HOST }}
port: ${{ secrets.SSH_PORT }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
source: "./deploy/"
target: "/opt/safeturned"
strip_components: 1
- name: Debug server files
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.SERVER_HOST }}
port: ${{ secrets.SSH_PORT }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
echo "=== Debugging server files ==="
echo "Current directory: $(pwd)"
echo "Opt directory contents:"
ls -la /opt/
echo "Safeturned directory contents:"
ls -la /opt/safeturned/ || echo "Directory does not exist"
echo "=== Checking if deploy.sh exists ==="
if [ -f "/opt/safeturned/deploy/deploy.sh" ]; then
echo "✅ deploy.sh exists"
ls -la /opt/safeturned/deploy/deploy.sh
else
echo "❌ deploy.sh does not exist"
echo "Files in /opt/safeturned/:"
ls -la /opt/safeturned/ || echo "Directory is empty"
fi
echo "=== Checking deploy subdirectory ==="
if [ -d "/opt/safeturned/deploy" ]; then
echo "✅ deploy directory exists"
echo "Files in deploy directory:"
ls -la /opt/safeturned/deploy/
if [ -f "/opt/safeturned/deploy/deploy.sh" ]; then
echo "✅ deploy.sh exists in deploy directory"
ls -la /opt/safeturned/deploy/deploy.sh
else
echo "❌ deploy.sh does not exist in deploy directory"
fi
else
echo "❌ deploy directory does not exist"
fi
echo "=== Detailed file structure ==="
echo "Tree structure of /opt/safeturned:"
find /opt/safeturned -type f -name "*.yaml" -o -name "*.yml" -o -name "*.env" -o -name "*.sh" | sort
- name: Deploy to server
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.SERVER_HOST }}
port: ${{ secrets.SSH_PORT }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
echo "=== Starting deployment ==="
echo "Current directory: $(pwd)"
# Check if we're in the right directory
if [ -f "/opt/safeturned/deploy.sh" ]; then
echo "✅ Found deploy.sh in root directory"
cd /opt/safeturned
# Verify docker-compose files exist
echo "Checking for docker-compose files:"
ls -la *.yaml *.yml .env 2>/dev/null || echo "No docker-compose files found"
./deploy.sh
elif [ -f "/opt/safeturned/deploy/deploy.sh" ]; then
echo "✅ Found deploy.sh in deploy directory"
cd /opt/safeturned/deploy
# Verify docker-compose files exist
echo "Checking for docker-compose files:"
ls -la *.yaml *.yml .env 2>/dev/null || echo "No docker-compose files found"
./deploy.sh
else
echo "❌ deploy.sh not found"
echo "Available files:"
ls -la /opt/safeturned/
if [ -d "/opt/safeturned/deploy" ]; then
echo "Files in deploy directory:"
ls -la /opt/safeturned/deploy/
fi
exit 1
fi
- name: Debug deployment status
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.SERVER_HOST }}
port: ${{ secrets.SSH_PORT }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
echo "=== Debugging deployment status ==="
cd /opt/safeturned
echo "=== Current directory and files ==="
pwd
ls -la
echo "=== Docker compose files ==="
echo "docker-compose.yaml exists: $([ -f "docker-compose.yaml" ] && echo "YES" || echo "NO")"
echo "docker-compose.override.yaml exists: $([ -f "docker-compose.override.yaml" ] && echo "YES" || echo "NO")"
echo ".env exists: $([ -f ".env" ] && echo "YES" || echo "NO")"
echo "=== Docker compose.yaml content ==="
cat docker-compose.yaml || echo "File not found"
echo "=== Docker compose.override.yaml content ==="
cat docker-compose.override.yaml || echo "File not found"
echo "=== .env content ==="
cat .env || echo "File not found"
echo "=== Container Status ==="
docker compose ps
echo "=== Container Logs ==="
docker compose logs safeturned-api
echo "=== Port Check ==="
netstat -tlnp | grep 8081 || echo "Port 8081 not listening"
echo "=== Test API from host ==="
curl -v http://localhost:8081/health || echo "API not responding"
echo "=== Test API from container network ==="
docker compose exec safeturned-api wget --no-verbose --tries=1 --spider http://localhost:8081/health || echo "Internal health check failed"
echo "=== Cloudflare Tunnel Configuration ==="
echo "For Cloudflare Tunnel, use: http://deploy-safeturned-api-1:8081"
#- name: Create Release
# if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main'
# uses: actions/create-release@v1
# env:
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# with:
# tag_name: v${{ github.run_number }}
# release_name: Release v${{ github.run_number }}
# body: |
# ## Changes
# - Built from commit: ${{ github.sha }}
#
# draft: false
# prerelease: false