Deploy #27
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }} | |
# Parameter database-password | |
DATABASE_PASSWORD=${{ secrets.DATABASE_PASSWORD }} | |
# Container image name for safeturned-api | |
SAFETURNED_API_IMAGE=ghcr.io/safeturned/api:latest | |
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) | |
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 |