Files
workspace/tools/coolify-deploy.sh

256 lines
7.7 KiB
Bash
Executable File

#!/bin/bash
# coolify-deploy.sh — Build, push, and deploy to Coolify
# Usage: coolify-deploy.sh [project-dir] [app-uuid]
#
# Examples:
# coolify-deploy.sh # interactive, lists known apps
# coolify-deploy.sh ~/projects/coinex-dashboard # auto-detect app from .coolify
# coolify-deploy.sh ~/projects/coinex-dashboard fcs04o8w... # explicit app UUID
# coolify-deploy.sh --status fcs04o8w... # check latest deploy status
# coolify-deploy.sh --skip-build ~/projects/coinex-dashboard # push + deploy only
set -euo pipefail
COOLIFY_HOST="${COOLIFY_HOST:-192.168.86.44}"
COOLIFY_PORT="${COOLIFY_PORT:-8000}"
COOLIFY_URL="http://${COOLIFY_HOST}:${COOLIFY_PORT}/api/v1"
COOLIFY_TOKEN="${COOLIFY_TOKEN:-}"
CREDS_FILE="$HOME/.openclaw/workspace/.credentials/coolify.env"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m'
log() { echo -e "${BLUE}[deploy]${NC} $*"; }
ok() { echo -e "${GREEN}[ ok ]${NC} $*"; }
warn() { echo -e "${YELLOW}[ warn ]${NC} $*"; }
fail() { echo -e "${RED}[FAILED]${NC} $*"; exit 1; }
# Load token
load_token() {
if [ -z "$COOLIFY_TOKEN" ]; then
if [ -f "$CREDS_FILE" ]; then
source "$CREDS_FILE"
COOLIFY_TOKEN="${COOLIFY_API_TOKEN:-${COOLIFY_TOKEN:-}}"
fi
fi
[ -z "$COOLIFY_TOKEN" ] && fail "No COOLIFY_TOKEN. Set env var or create $CREDS_FILE with COOLIFY_API_TOKEN=..."
}
api() {
local method="$1" path="$2"
shift 2
curl -sf -X "$method" \
-H "Authorization: Bearer $COOLIFY_TOKEN" \
-H "Content-Type: application/json" \
"$COOLIFY_URL$path" "$@" 2>/dev/null
}
# Save/load app UUID mapping
save_app_config() {
local dir="$1" uuid="$2"
echo "$uuid" > "$dir/.coolify-app"
echo "$uuid"
}
load_app_config() {
local dir="$1"
if [ -f "$dir/.coolify-app" ]; then
cat "$dir/.coolify-app"
fi
}
# --- Commands ---
cmd_status() {
local app_uuid="$1"
load_token
log "Checking deployments for app $app_uuid..."
local deploys
deploys=$(api GET "/applications/$app_uuid/deployments" 2>/dev/null) || fail "Could not fetch deployments"
echo "$deploys" | python3 -c "
import json, sys
data = json.load(sys.stdin)
deploys = data if isinstance(data, list) else data.get('data', data.get('deployments', []))
for d in deploys[:5]:
status = d.get('status', '?')
commit = d.get('commit', '?')[:8]
created = d.get('created_at', '?')[:19]
uuid = d.get('deployment_uuid', '?')[:12]
icon = '✅' if status == 'finished' else '❌' if status == 'failed' else '⏳' if status == 'in_progress' else '❓'
print(f'{icon} {created} {status:<12} {commit} {uuid}')
" 2>/dev/null || echo "$deploys" | python3 -m json.tool 2>/dev/null || echo "$deploys"
}
cmd_deploy() {
local project_dir="$1"
local app_uuid="$2"
local skip_build="${3:-false}"
load_token
cd "$project_dir"
local project_name
project_name=$(basename "$project_dir")
# Step 1: Build
if [ "$skip_build" = "false" ]; then
log "Building $project_name..."
# Clean previous build
rm -rf .next 2>/dev/null || true
if npm run build 2>&1 | tee /tmp/coolify-build.log | tail -5; then
ok "Build succeeded"
else
fail "Build failed. Full log: /tmp/coolify-build.log"
fi
else
warn "Skipping build (--skip-build)"
fi
# Step 2: Git commit & push
log "Committing and pushing..."
if git diff --quiet && git diff --cached --quiet; then
warn "No changes to commit — pushing existing commits"
else
git add -A
local msg
msg=$(git log --oneline -1 2>/dev/null | cut -d' ' -f2-)
git commit -m "deploy: ${msg:-update}" --quiet 2>/dev/null || true
fi
if git push --quiet 2>&1; then
ok "Pushed to $(git remote get-url origin 2>/dev/null | sed 's/.*@//' | sed 's/\.git$//')"
else
fail "Git push failed"
fi
# Step 3: Trigger Coolify deploy
log "Triggering Coolify deployment..."
local result
result=$(api POST "/applications/$app_uuid/start") || fail "Could not trigger deployment"
local deploy_uuid
deploy_uuid=$(echo "$result" | python3 -c "import json,sys; print(json.load(sys.stdin).get('deployment_uuid',''))" 2>/dev/null)
if [ -z "$deploy_uuid" ]; then
fail "No deployment UUID returned: $result"
fi
ok "Deployment queued: $deploy_uuid"
# Step 4: Poll for completion
log "Waiting for deployment..."
local attempts=0
local max_attempts=40 # 40 * 10s = ~6.5 min
local status=""
while [ $attempts -lt $max_attempts ]; do
sleep 10
attempts=$((attempts + 1))
status=$(api GET "/deployments/$deploy_uuid" | python3 -c "import json,sys; print(json.load(sys.stdin).get('status','unknown'))" 2>/dev/null)
case "$status" in
finished)
echo ""
ok "✅ Deployment successful! ($((attempts * 10))s)"
# Show app URL
local app_info
app_info=$(api GET "/applications/$app_uuid" 2>/dev/null)
local fqdn
fqdn=$(echo "$app_info" | python3 -c "import json,sys; print(json.load(sys.stdin).get('fqdn',''))" 2>/dev/null)
[ -n "$fqdn" ] && ok "Live at: $fqdn"
return 0
;;
failed)
echo ""
fail "❌ Deployment failed after $((attempts * 10))s. Check Coolify UI at http://$COOLIFY_HOST:$COOLIFY_PORT"
;;
*)
printf "\r${CYAN}[ .. ]${NC} Status: %-15s (%ds)" "$status" "$((attempts * 10))"
;;
esac
done
echo ""
fail "Deployment timed out after $((max_attempts * 10))s (status: $status)"
}
# --- Main ---
SKIP_BUILD=false
STATUS_ONLY=false
PROJECT_DIR=""
APP_UUID=""
# Parse args
while [ $# -gt 0 ]; do
case "$1" in
--skip-build) SKIP_BUILD=true; shift ;;
--status) STATUS_ONLY=true; shift ;;
--help|-h)
echo "Usage: coolify-deploy.sh [options] [project-dir] [app-uuid]"
echo ""
echo "Options:"
echo " --skip-build Push and deploy without building locally"
echo " --status UUID Check deployment status for an app"
echo " -h, --help Show this help"
echo ""
echo "If app-uuid is omitted, reads from .coolify-app in project dir."
echo "Token from \$COOLIFY_TOKEN env or $CREDS_FILE"
exit 0
;;
*)
if [ -z "$PROJECT_DIR" ]; then
PROJECT_DIR="$1"
else
APP_UUID="$1"
fi
shift
;;
esac
done
# Status check mode
if [ "$STATUS_ONLY" = "true" ]; then
[ -z "$PROJECT_DIR" ] && fail "Usage: coolify-deploy.sh --status <app-uuid>"
cmd_status "$PROJECT_DIR"
exit 0
fi
# Resolve project dir
if [ -z "$PROJECT_DIR" ]; then
if [ -f "package.json" ]; then
PROJECT_DIR="$(pwd)"
else
fail "No project directory specified and not in a Node.js project"
fi
fi
PROJECT_DIR=$(cd "$PROJECT_DIR" && pwd)
[ -f "$PROJECT_DIR/package.json" ] || fail "$PROJECT_DIR is not a Node.js project (no package.json)"
# Resolve app UUID
if [ -z "$APP_UUID" ]; then
APP_UUID=$(load_app_config "$PROJECT_DIR")
fi
if [ -z "$APP_UUID" ]; then
fail "No app UUID. Pass it as argument or create $PROJECT_DIR/.coolify-app with the UUID"
fi
# Save for next time
save_app_config "$PROJECT_DIR" "$APP_UUID" > /dev/null
# Deploy
cmd_deploy "$PROJECT_DIR" "$APP_UUID" "$SKIP_BUILD"