fix(ci): trigger workflow on main branch to enable :latest tag

Changes:
- Create Gitea workflow for ai-stack-deployer
- Trigger on main branch (default branch)
- Use oussamadouhou + REGISTRY_TOKEN for authentication
- Build from ./Dockerfile

This enables :latest tag creation via {{is_default_branch}}.

Tags created:
- git.app.flexinit.nl/oussamadouhou/ai-stack-deployer:latest
- git.app.flexinit.nl/oussamadouhou/ai-stack-deployer:<sha>

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Oussama Douhou
2026-01-09 23:33:39 +01:00
commit 19845880e3
46 changed files with 9875 additions and 0 deletions

176
scripts/claude-session.sh Executable file
View File

@@ -0,0 +1,176 @@
#!/bin/bash
# scripts/claude-session.sh
# Description: Manage persistent Claude Code sessions for AI Stack Deployer
# Usage: bash scripts/claude-session.sh [list|delete <name>|help]
# Configuration
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
# Store sessions in user's home directory, NOT in project root
SESSION_DIR="$HOME/.claude/sessions/ai-stack-deployer"
mkdir -p "$SESSION_DIR"
# Claude Code built-in session storage
CLAUDE_BUILTIN_DIR="$HOME/.claude/projects/-home-odouhou-locale-projects-ai-stack-deployer"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
MAGENTA='\033[0;35m'
NC='\033[0m' # No Color
# Helper functions
function success() {
echo -e "${GREEN}$1${NC}"
}
function error() {
echo -e "${RED}$1${NC}"
return 1
}
function info() {
echo -e "${BLUE} $1${NC}"
}
function warning() {
echo -e "${YELLOW}⚠️ $1${NC}"
}
# List all sessions
function list_sessions() {
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " AI Stack Deployer Claude Code Sessions"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
local has_custom=false
local has_builtin=false
# List custom sessions
if [ "$(ls -A $SESSION_DIR 2>/dev/null)" ]; then
has_custom=true
echo -e "${MAGENTA}📁 Custom Persistent Sessions${NC}"
echo ""
for session_file in "$SESSION_DIR"/*.session; do
if [ -f "$session_file" ]; then
source "$session_file"
local name=$(basename "$session_file" .session)
echo -e "${BLUE}Session:${NC} $name"
echo " ID: $CLAUDE_SESSION_ID"
echo " Started: $CLAUDE_SESSION_START"
echo " Last used: ${CLAUDE_SESSION_LAST_USED:-Never}"
echo ""
fi
done
fi
# List Claude Code built-in sessions
if [ -d "$CLAUDE_BUILTIN_DIR" ] && [ "$(ls -A $CLAUDE_BUILTIN_DIR/*.jsonl 2>/dev/null)" ]; then
has_builtin=true
if [ "$has_custom" = true ]; then
echo ""
fi
echo -e "${MAGENTA}🤖 Claude Code Built-in Sessions${NC}"
echo ""
for jsonl_file in "$CLAUDE_BUILTIN_DIR"/*.jsonl; do
if [ -f "$jsonl_file" ]; then
local session_id=$(basename "$jsonl_file" .jsonl)
local file_size=$(du -h "$jsonl_file" | cut -f1)
local created=$(stat -c %y "$jsonl_file" 2>/dev/null | cut -d'.' -f1 || stat -f "%Sm" -t "%Y-%m-%d %H:%M:%S" "$jsonl_file" 2>/dev/null)
local modified=$(stat -c %y "$jsonl_file" 2>/dev/null | cut -d'.' -f1 || stat -f "%Sm" -t "%Y-%m-%d %H:%M:%S" "$jsonl_file" 2>/dev/null)
echo -e "${BLUE}Session ID:${NC} $session_id"
echo " Size: $file_size"
echo " Created: $created"
echo " Last modified: $modified"
echo ""
fi
done
fi
if [ "$has_custom" = false ] && [ "$has_builtin" = false ]; then
warning "No sessions found"
echo ""
info "Custom sessions: $SESSION_DIR"
info "Built-in sessions: $CLAUDE_BUILTIN_DIR"
fi
}
# Delete a session
function delete_session() {
local SESSION_NAME="$1"
local SESSION_FILE="$SESSION_DIR/$SESSION_NAME.session"
if [ ! -f "$SESSION_FILE" ]; then
error "Session not found: $SESSION_NAME"
return 1
fi
source "$SESSION_FILE"
echo ""
warning "Delete session: $SESSION_NAME"
echo " ID: $CLAUDE_SESSION_ID"
echo " Started: $CLAUDE_SESSION_START"
echo ""
read -p "Are you sure? (yes/no): " -r REPLY
if [ "$REPLY" == "yes" ]; then
rm "$SESSION_FILE"
success "Session deleted: $SESSION_NAME"
else
info "Deletion cancelled"
fi
}
# Show help
function show_help() {
echo "Usage: bash scripts/claude-session.sh [command]"
echo ""
echo "Commands:"
echo " list - List all sessions"
echo " delete <name> - Delete a session"
echo " help - Show this help"
echo ""
echo "Environment variables set:"
echo " CLAUDE_SESSION_ID - Persistent session UUID"
echo " CLAUDE_SESSION_NAME - Session name"
echo " CLAUDE_SESSION_START - When session was created"
echo " CLAUDE_SESSION_LAST_USED - Last usage timestamp"
echo " CLAUDE_SESSION_PROJECT - Project name (ai-stack-deployer)"
echo " CLAUDE_SESSION_MCP_GROUP - MCP group ID (project_ai_stack_deployer)"
echo ""
echo "Examples:"
echo " bash scripts/claude-session.sh list"
echo " bash scripts/claude-session.sh delete feature-mcp-tools"
echo ""
echo "To start a session:"
echo " ./scripts/claude-start.sh feature-name"
}
# Main logic
case "${1:-}" in
list)
list_sessions
;;
delete)
if [ -z "${2:-}" ]; then
error "Usage: $0 delete <session-name>"
exit 1
fi
delete_session "$2"
;;
help|--help|-h)
show_help
;;
*)
show_help
;;
esac

201
scripts/claude-start.sh Executable file
View File

@@ -0,0 +1,201 @@
#!/bin/bash
# scripts/claude-start.sh
# Description: Start Claude Code with persistent session management for AI Stack Deployer
# Usage: ./scripts/claude-start.sh [session-name] [additional-flags]
set -e # Exit on error
set -u # Exit on undefined variable
# Configuration
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$PROJECT_ROOT"
# Store sessions in user's home directory, NOT in project root
SESSION_DIR="$HOME/.claude/sessions/ai-stack-deployer"
mkdir -p "$SESSION_DIR"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
MAGENTA='\033[0;35m'
NC='\033[0m' # No Color
# Helper functions
function success() {
echo -e "${GREEN}$1${NC}"
}
function error() {
echo -e "${RED}$1${NC}"
exit 1
}
function info() {
echo -e "${BLUE} $1${NC}"
}
function warning() {
echo -e "${YELLOW}⚠️ $1${NC}"
}
function header() {
echo ""
echo -e "${MAGENTA}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${MAGENTA}$1${NC}"
echo -e "${MAGENTA}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
}
# Generate UUID v4
function generate_uuid() {
if command -v uuidgen &> /dev/null; then
uuidgen | tr '[:upper:]' '[:lower:]'
elif command -v python3 &> /dev/null; then
python3 -c "import uuid; print(str(uuid.uuid4()))"
else
# Fallback: use random + timestamp
echo "$(date +%s)-$(( RANDOM % 100000 ))-$(( RANDOM % 100000 ))-$(( RANDOM % 100000 ))"
fi
}
# Get or create session
function get_or_create_session() {
local SESSION_NAME="${1:-$(date +%Y%m%d-%H%M)}"
local SESSION_FILE="$SESSION_DIR/$SESSION_NAME.session"
# Check if session exists
if [ -f "$SESSION_FILE" ]; then
# Load existing session
source "$SESSION_FILE"
info "Resuming session: ${MAGENTA}$SESSION_NAME${NC}"
info "Session ID: ${BLUE}$CLAUDE_SESSION_ID${NC}"
info "Started: $CLAUDE_SESSION_START"
# Calculate session age
if command -v python3 &> /dev/null; then
SESSION_AGE=$(python3 -c "
from datetime import datetime
start = datetime.strptime('$CLAUDE_SESSION_START', '%Y-%m-%d %H:%M:%S')
age = datetime.now() - start
days = age.days
hours = age.seconds // 3600
print(f'{days}d {hours}h')
" 2>/dev/null || echo "N/A")
info "Age: $SESSION_AGE"
fi
# Update last used timestamp
echo "export CLAUDE_SESSION_LAST_USED=\"$(date +%Y-%m-%d\ %H:%M:%S)\"" >> "$SESSION_FILE"
else
# Create new session
CLAUDE_SESSION_ID=$(generate_uuid)
CLAUDE_SESSION_NAME="$SESSION_NAME"
CLAUDE_SESSION_START="$(date +%Y-%m-%d\ %H:%M:%S)"
CLAUDE_SESSION_LAST_USED="$(date +%Y-%m-%d\ %H:%M:%S)"
# Save session
cat > "$SESSION_FILE" << SESSIONEOF
# Claude Code Session: $SESSION_NAME
# Created: $CLAUDE_SESSION_START
export CLAUDE_SESSION_ID="$CLAUDE_SESSION_ID"
export CLAUDE_SESSION_NAME="$SESSION_NAME"
export CLAUDE_SESSION_START="$CLAUDE_SESSION_START"
export CLAUDE_SESSION_LAST_USED="$CLAUDE_SESSION_LAST_USED"
export CLAUDE_SESSION_PROJECT="ai-stack-deployer"
export CLAUDE_SESSION_MCP_GROUP="project_ai_stack_deployer"
SESSIONEOF
success "Created new session: ${MAGENTA}$SESSION_NAME${NC}"
info "Session ID: ${BLUE}$CLAUDE_SESSION_ID${NC}"
info "Session file: $SESSION_FILE"
fi
# Export variables
export CLAUDE_SESSION_ID
export CLAUDE_SESSION_NAME
export CLAUDE_SESSION_START
export CLAUDE_SESSION_LAST_USED
export CLAUDE_SESSION_PROJECT="ai-stack-deployer"
export CLAUDE_SESSION_MCP_GROUP="project_ai_stack_deployer"
}
# Main script logic
function main() {
local SESSION_NAME="${1:-$(date +%Y%m%d-%H%M)}"
shift || true # Remove first argument if it exists
local ADDITIONAL_FLAGS="$@"
header "AI Stack Deployer - Claude Code Session Manager"
# Get or create session
get_or_create_session "$SESSION_NAME"
# Display helpful information
echo ""
info "Starting Claude Code with persistent session..."
echo ""
warning "Session Environment Variables:"
echo " CLAUDE_SESSION_ID=$CLAUDE_SESSION_ID"
echo " CLAUDE_SESSION_NAME=$CLAUDE_SESSION_NAME"
echo " CLAUDE_SESSION_PROJECT=$CLAUDE_SESSION_PROJECT"
echo " CLAUDE_SESSION_MCP_GROUP=$CLAUDE_SESSION_MCP_GROUP"
echo ""
info "Permission Mode: ${CLAUDE_PERMISSION_MODE:-bypassPermissions} (set CLAUDE_PERMISSION_MODE to override)"
echo ""
info "Quick Commands:"
echo " 🔧 Dev server: bun run dev"
echo " 🚀 MCP server: bun run mcp"
echo " ✅ Type check: bun run typecheck"
echo " 🧪 Test clients: bun run src/test-clients.ts"
echo ""
info "Memory Management:"
echo " 🧠 Search: graphiti-memory_search_memory_facts({query: '...', group_ids: ['project_ai_stack_deployer']})"
echo " 💾 Store: graphiti-memory_add_memory({name: '...', episode_body: '...', group_id: 'project_ai_stack_deployer'})"
echo ""
# Start Claude Code with session ID
header "Starting Claude Code"
echo ""
# Build Claude Code command
CLAUDE_CMD="claude --session-id \"$CLAUDE_SESSION_ID\""
# Add permission mode (default: bypassPermissions, override with CLAUDE_PERMISSION_MODE env var)
PERMISSION_MODE="${CLAUDE_PERMISSION_MODE:-bypassPermissions}"
CLAUDE_CMD="$CLAUDE_CMD --permission-mode $PERMISSION_MODE"
# Add additional flags if provided
if [ -n "$ADDITIONAL_FLAGS" ]; then
CLAUDE_CMD="$CLAUDE_CMD $ADDITIONAL_FLAGS"
fi
info "Command: $CLAUDE_CMD"
echo ""
# Execute Claude Code
eval $CLAUDE_CMD
# After Claude Code exits
echo ""
header "Session Ended"
success "Session complete: $CLAUDE_SESSION_NAME"
info "Session ID: $CLAUDE_SESSION_ID"
echo ""
warning "Don't forget to store your learnings in Graphiti Memory!"
echo "Example:"
echo ""
echo "graphiti-memory_add_memory({"
echo " name: \"Session: $CLAUDE_SESSION_NAME - $(date +%Y-%m-%d)\","
echo " episode_body: \"Accomplished: [...]. Decisions: [...]. Issues: [...].\","
echo " group_id: \"project_ai_stack_deployer\""
echo "})"
echo ""
}
# Run main function
main "$@"

386
scripts/deploy-to-dokploy.sh Executable file
View File

@@ -0,0 +1,386 @@
#!/usr/bin/env bash
#
# AI Stack Deployer - Automated Deployment Script
# Deploys the AI Stack Deployer application to Dokploy
#
# Usage: ./scripts/deploy-to-dokploy.sh [options]
# Options:
# --registry REGISTRY Docker registry to push to (default: local Docker)
# --domain DOMAIN Domain name for deployment (default: portal.ai.flexinit.nl)
# --project-name NAME Dokploy project name (default: ai-stack-deployer-portal)
# --skip-build Skip Docker build (use existing image)
# --skip-test Skip local testing
#
set -euo pipefail
# Configuration
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="${SCRIPT_DIR}/.."
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
LOG_FILE="${PROJECT_ROOT}/deployment-${TIMESTAMP}.log"
# Default values
REGISTRY=""
DOMAIN="portal.ai.flexinit.nl"
PROJECT_NAME="ai-stack-deployer-portal"
APP_NAME="ai-stack-deployer-web"
IMAGE_NAME="ai-stack-deployer"
SKIP_BUILD=false
SKIP_TEST=false
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Logging functions
log_info() {
echo -e "${GREEN}[INFO]${NC} $*" | tee -a "${LOG_FILE}"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $*" | tee -a "${LOG_FILE}"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $*" | tee -a "${LOG_FILE}"
}
log_step() {
echo -e "\n${GREEN}==>${NC} $*\n" | tee -a "${LOG_FILE}"
}
# Error handler
error_exit() {
log_error "$1"
exit 1
}
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
--registry)
REGISTRY="$2"
shift 2
;;
--domain)
DOMAIN="$2"
shift 2
;;
--project-name)
PROJECT_NAME="$2"
shift 2
;;
--skip-build)
SKIP_BUILD=true
shift
;;
--skip-test)
SKIP_TEST=true
shift
;;
*)
echo "Unknown option: $1"
exit 1
;;
esac
done
log_info "Deployment started at ${TIMESTAMP}"
log_info "Target domain: ${DOMAIN}"
log_info "Project name: ${PROJECT_NAME}"
cd "${PROJECT_ROOT}"
# Load environment variables
if [ ! -f .env ]; then
error_exit ".env file not found. Please create it from .env.example"
fi
source .env
if [ -z "${DOKPLOY_API_TOKEN}" ]; then
error_exit "DOKPLOY_API_TOKEN not set in .env"
fi
if [ -z "${DOKPLOY_URL}" ]; then
error_exit "DOKPLOY_URL not set in .env"
fi
# Phase 1: Pre-deployment checks
log_step "Phase 1: Pre-deployment Verification"
log_info "Checking Dokploy API connectivity..."
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
-H "x-api-key: ${DOKPLOY_API_TOKEN}" \
"${DOKPLOY_URL}/api/project.all" || echo "000")
if [ "${HTTP_CODE}" != "200" ]; then
error_exit "Dokploy API unreachable or unauthorized (HTTP ${HTTP_CODE})"
fi
log_info "✓ Dokploy API accessible"
# Check Docker
if ! command -v docker &> /dev/null; then
error_exit "Docker not installed"
fi
# Try docker with sg if needed
if ! docker ps &> /dev/null; then
if sg docker -c "docker ps" &> /dev/null; then
log_info "Using 'sg docker' for Docker commands"
DOCKER_CMD="sg docker -c"
else
error_exit "Docker not accessible. Check permissions."
fi
else
DOCKER_CMD=""
fi
log_info "✓ Docker accessible"
# Phase 2: Build Docker image
if [ "${SKIP_BUILD}" = false ]; then
log_step "Phase 2: Building Docker Image"
log_info "Building image: ${IMAGE_NAME}:${TIMESTAMP}"
${DOCKER_CMD} docker build \
-t "${IMAGE_NAME}:${TIMESTAMP}" \
-t "${IMAGE_NAME}:latest" \
. 2>&1 | tee -a "${LOG_FILE}" || error_exit "Docker build failed"
# Get image info
IMAGE_ID=$(${DOCKER_CMD} docker images -q "${IMAGE_NAME}:latest")
IMAGE_SIZE=$(${DOCKER_CMD} docker images "${IMAGE_NAME}:latest" --format "{{.Size}}")
log_info "✓ Image built successfully"
log_info " Image ID: ${IMAGE_ID}"
log_info " Size: ${IMAGE_SIZE}"
else
log_warn "Skipping build (--skip-build specified)"
fi
# Phase 3: Local testing
if [ "${SKIP_TEST}" = false ]; then
log_step "Phase 3: Local Container Testing"
log_info "Starting test container..."
# Stop existing test container if any
${DOCKER_CMD} docker stop ai-stack-deployer-test 2>/dev/null || true
${DOCKER_CMD} docker rm ai-stack-deployer-test 2>/dev/null || true
# Start test container
${DOCKER_CMD} docker run -d \
--name ai-stack-deployer-test \
-p 3001:3000 \
--env-file .env \
"${IMAGE_NAME}:latest" || error_exit "Failed to start test container"
log_info "Waiting for container to be healthy..."
sleep 5
# Test health endpoint
if ! curl -sf http://localhost:3001/health > /dev/null; then
${DOCKER_CMD} docker logs ai-stack-deployer-test | tail -20
${DOCKER_CMD} docker stop ai-stack-deployer-test
${DOCKER_CMD} docker rm ai-stack-deployer-test
error_exit "Health check failed"
fi
log_info "✓ Container healthy"
# Cleanup
${DOCKER_CMD} docker stop ai-stack-deployer-test
${DOCKER_CMD} docker rm ai-stack-deployer-test
log_info "✓ Local testing complete"
else
log_warn "Skipping local test (--skip-test specified)"
fi
# Phase 4: Registry push (if specified)
if [ -n "${REGISTRY}" ]; then
log_step "Phase 4: Pushing to Registry"
FULL_IMAGE_NAME="${REGISTRY}/${IMAGE_NAME}:latest"
log_info "Tagging image for registry: ${FULL_IMAGE_NAME}"
${DOCKER_CMD} docker tag "${IMAGE_NAME}:latest" "${FULL_IMAGE_NAME}"
log_info "Pushing to registry..."
${DOCKER_CMD} docker push "${FULL_IMAGE_NAME}" || error_exit "Failed to push to registry"
log_info "✓ Image pushed to registry"
IMAGE_FOR_DOKPLOY="${FULL_IMAGE_NAME}"
else
log_warn "No registry specified - Dokploy must have access to local Docker"
IMAGE_FOR_DOKPLOY="${IMAGE_NAME}:latest"
fi
# Phase 5: Deploy to Dokploy
log_step "Phase 5: Deploying to Dokploy"
# Check if project exists
log_info "Checking for existing project..."
EXISTING_PROJECT=$(curl -s \
-H "x-api-key: ${DOKPLOY_API_TOKEN}" \
"${DOKPLOY_URL}/api/project.all" | \
jq -r ".[]? | select(.name==\"${PROJECT_NAME}\") | .projectId" || echo "")
if [ -n "${EXISTING_PROJECT}" ]; then
log_info "✓ Found existing project: ${EXISTING_PROJECT}"
PROJECT_ID="${EXISTING_PROJECT}"
else
log_info "Creating new project..."
CREATE_PROJECT_RESPONSE=$(curl -s -X POST \
-H "x-api-key: ${DOKPLOY_API_TOKEN}" \
-H "Content-Type: application/json" \
"${DOKPLOY_URL}/api/project.create" \
-d "{
\"name\": \"${PROJECT_NAME}\",
\"description\": \"Self-service portal for deploying AI stacks\"
}") || error_exit "Failed to create project"
PROJECT_ID=$(echo "${CREATE_PROJECT_RESPONSE}" | jq -r '.project.projectId // empty')
if [ -z "${PROJECT_ID}" ]; then
log_error "API Response: ${CREATE_PROJECT_RESPONSE}"
error_exit "Failed to extract project ID"
fi
log_info "✓ Created project: ${PROJECT_ID}"
fi
# Create/Update application
log_info "Creating application..."
# Prepare environment variables
ENV_VARS="DOKPLOY_URL=${DOKPLOY_URL}
DOKPLOY_API_TOKEN=${DOKPLOY_API_TOKEN}
PORT=3000
HOST=0.0.0.0
STACK_DOMAIN_SUFFIX=${STACK_DOMAIN_SUFFIX:-ai.flexinit.nl}
STACK_IMAGE=${STACK_IMAGE:-git.app.flexinit.nl/oussamadouhou/oh-my-opencode-free:latest}
RESERVED_NAMES=${RESERVED_NAMES:-admin,api,www,root,system,test,demo,portal}"
CREATE_APP_RESPONSE=$(curl -s -X POST \
-H "x-api-key: ${DOKPLOY_API_TOKEN}" \
-H "Content-Type: application/json" \
"${DOKPLOY_URL}/api/application.create" \
-d "{
\"name\": \"${APP_NAME}\",
\"projectId\": \"${PROJECT_ID}\",
\"dockerImage\": \"${IMAGE_FOR_DOKPLOY}\",
\"env\": \"${ENV_VARS}\"
}") || error_exit "Failed to create application"
APP_ID=$(echo "${CREATE_APP_RESPONSE}" | jq -r '.application.applicationId // .applicationId // empty')
if [ -z "${APP_ID}" ]; then
log_error "API Response: ${CREATE_APP_RESPONSE}"
error_exit "Failed to extract application ID"
fi
log_info "✓ Created application: ${APP_ID}"
# Configure domain
log_info "Configuring domain: ${DOMAIN}"
curl -s -X POST \
-H "x-api-key: ${DOKPLOY_API_TOKEN}" \
-H "Content-Type: application/json" \
"${DOKPLOY_URL}/api/domain.create" \
-d "{
\"domain\": \"${DOMAIN}\",
\"applicationId\": \"${APP_ID}\",
\"https\": true,
\"port\": 3000
}" || log_warn "Domain configuration may have failed (might already exist)"
log_info "✓ Domain configured"
# Deploy application
log_info "Triggering deployment..."
DEPLOY_RESPONSE=$(curl -s -X POST \
-H "x-api-key: ${DOKPLOY_API_TOKEN}" \
-H "Content-Type: application/json" \
"${DOKPLOY_URL}/api/application.deploy" \
-d "{
\"applicationId\": \"${APP_ID}\"
}") || error_exit "Failed to trigger deployment"
DEPLOY_ID=$(echo "${DEPLOY_RESPONSE}" | jq -r '.deploymentId // "unknown"')
log_info "✓ Deployment triggered: ${DEPLOY_ID}"
log_info "Monitor at: ${DOKPLOY_URL}/project/${PROJECT_ID}"
# Phase 6: Verification
log_step "Phase 6: Deployment Verification"
log_info "Waiting for deployment (60 seconds)..."
sleep 60
log_info "Testing health endpoint..."
if curl -sf "https://${DOMAIN}/health" > /dev/null 2>&1; then
log_info "✓ Application is healthy at https://${DOMAIN}"
# Get health status
HEALTH_RESPONSE=$(curl -s "https://${DOMAIN}/health")
echo "${HEALTH_RESPONSE}" | jq . || echo "${HEALTH_RESPONSE}"
else
log_warn "Health check failed - deployment may still be in progress"
log_info "Check status at: ${DOKPLOY_URL}/project/${PROJECT_ID}"
fi
# Save deployment record
log_step "Deployment Summary"
cat > "deployment-record-${TIMESTAMP}.txt" << EOF
Deployment Completed: $(date -Iseconds)
===========================================
Configuration:
- Project Name: ${PROJECT_NAME}
- Application Name: ${APP_NAME}
- Domain: https://${DOMAIN}
- Image: ${IMAGE_FOR_DOKPLOY}
Dokploy IDs:
- Project ID: ${PROJECT_ID}
- Application ID: ${APP_ID}
- Deployment ID: ${DEPLOY_ID}
Management:
- Dokploy Console: ${DOKPLOY_URL}/project/${PROJECT_ID}
- Application URL: https://${DOMAIN}
- Health Check: https://${DOMAIN}/health
Build Info:
- Timestamp: ${TIMESTAMP}
- Image ID: ${IMAGE_ID:-N/A}
- Image Size: ${IMAGE_SIZE:-N/A}
Status: SUCCESS
EOF
log_info "Deployment record saved: deployment-record-${TIMESTAMP}.txt"
log_info "Deployment log saved: ${LOG_FILE}"
echo ""
echo -e "${GREEN}========================================${NC}"
echo -e "${GREEN} Deployment Completed Successfully!${NC}"
echo -e "${GREEN}========================================${NC}"
echo ""
echo -e " 🌐 Application URL: ${GREEN}https://${DOMAIN}${NC}"
echo -e " ⚙️ Dokploy Console: ${DOKPLOY_URL}/project/${PROJECT_ID}"
echo -e " 📊 Health Check: https://${DOMAIN}/health"
echo ""
exit 0