#!/bin/bash # Easypanel Deployment Skill v2.2 # Production-ready with error handling set -e CREDENTIALS_FILE="$HOME/.easypanel/credentials" STATE_FILE="$HOME/.easypanel/state.json" CONFIG_FILE="easypanel.config.json" # Load credentials if [ -f "$CREDENTIALS_FILE" ]; then export $(grep -v '^#' "$CREDENTIALS_FILE" | xargs) 2>/dev/null || true fi RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' log_info() { echo -e "${BLUE}ℹ️ $1${NC}"; } log_success() { echo -e "${GREEN}✅ $1${NC}"; } log_warning() { echo -e "${YELLOW}⚠️ $1${NC}"; } log_error() { echo -e "${RED}❌ $1${NC}"; } # Check token if [ -z "$EASYPANEL_API_TOKEN" ] || [ "$EASYPANEL_API_TOKEN" = "YOUR_API_TOKEN_HERE" ]; then log_error "API token not set!" echo "" echo "Edit ~/.easypanel/credentials and add your token" exit 1 fi # Load config load_config() { if [ -f "$CONFIG_FILE" ]; then APP_NAME=$(python3 -c "import json; print(json.load(open('$CONFIG_FILE'))['easypanel']['app']['name'])" 2>/dev/null || echo "") PORT=$(python3 -c "import json; print(json.load(open('$CONFIG_FILE'))['easypanel']['app']['port'])" 2>/dev/null || echo "4321") DOCKER_IMAGE=$(python3 -c "import json; print(json.load(open('$CONFIG_FILE'))['easypanel']['app']['image'])" 2>/dev/null || echo "") PROJECT_NAME=$(python3 -c "import json; print(json.load(open('$CONFIG_FILE'))['easypanel']['project'])" 2>/dev/null || echo "default") fi APP_NAME="${APP_NAME:-$(basename "$(pwd)")}" PORT="${PORT:-4321}" DOCKER_IMAGE="${DOCKER_IMAGE:-$APP_NAME:latest}" PROJECT_NAME="${PROJECT_NAME:-default}" } # Save state save_state() { python3 << PYEOF import json, os from datetime import datetime state_file = "$STATE_FILE" os.makedirs(os.path.dirname(state_file), exist_ok=True) if os.path.exists(state_file): with open(state_file, 'r') as f: state = json.load(f) else: state = {"version":"2.0","projects":{},"services":{},"deployments":[]} if "$1" == "service": state['services']['$2'] = {'id':'$3','project_id':'$PROJECT_ID','name':'$APP_NAME','port':$PORT,'updated':datetime.utcnow().isoformat()+"Z"} elif "$1" == "project": state['projects']['$2'] = {'id':'$3','name':'$2','updated':datetime.utcnow().isoformat()+"Z"} with open(state_file, 'w') as f: json.dump(state, f, indent=2) print(f"Saved: {$1}.$2 = $3") PYEOF } # Get state get_state() { python3 << PYEOF 2>/dev/null || echo "" import json try: with open('$STATE_FILE', 'r') as f: state = json.load(f) key, type = '$1', '$2' if type == 'service' and key in state.get('services', {}): print(state['services'][key].get('id', '')) elif type == 'project' and key in state.get('projects', {}): print(state['projects'][key].get('id', '')) except: pass PYEOF } # List projects list_projects() { log_info "Fetching projects from Easypanel..." local response=$(curl -s -w "\n%{http_code}" \ "http://110.164.146.46:3000/api/trpc/projects.listProjects" \ -H "Authorization: Bearer $EASYPANEL_API_TOKEN" \ --insecure --compressed) local http_code=$(echo "$response" | tail -1) local body=$(echo "$response" | sed '$d') if [ "$http_code" != "200" ]; then log_error "Failed to fetch projects (HTTP $http_code)" return 1 fi echo "$body" | python3 << 'PYEOF' import json, sys try: data = json.load(sys.stdin) result = data.get('result', {}) data_content = result.get('data', {}) if isinstance(result, dict) else {} items = data_content.get('items', []) if not items: print("No projects found") else: print(f"\n{'ID':<25} {'Name':<30}") print("-" * 60) for proj in items: pid = str(proj.get('id', ''))[:23] name = str(proj.get('name', ''))[:28] print(f"{pid:<25} {name:<30}") print("-" * 60) print(f"Total: {len(items)} project(s)\n") except Exception as e: print(f"Error parsing response: {e}") print("Raw response:", sys.stdin.read()[:200]) PYEOF } # Deploy command cmd_deploy() { load_config log_info "========================================" log_info "Deploying $APP_NAME" log_info "========================================" log_info "Project: $PROJECT_NAME" log_info "Image: $DOCKER_IMAGE" log_info "Port: $PORT" log_info "" # Build Docker log_info "Building Docker image..." if [ ! "$SKIP_BUILD" = "true" ]; then docker build -t "$DOCKER_IMAGE" . || { log_error "Docker build failed" exit 1 } log_success "Built: $DOCKER_IMAGE" fi # List projects and find/create log_info "Looking for project: $PROJECT_NAME" list_projects log_warning "" log_warning "⚠️ Manual Step Required" log_warning "" log_info "Easypanel API requires service creation via dashboard" log_info "" log_info "Steps:" log_info "1. Open: $EASYPANEL_URL" log_info "2. Select project: $PROJECT_NAME (or create it)" log_info "3. Click 'New Service' → 'Docker image'" log_info "4. Enter:" log_info " Name: $APP_NAME" log_info " Image: $DOCKER_IMAGE" log_info " Port: $PORT" log_info "5. Deploy" log_info "" log_info "After deployment, save the service ID:" log_info "./deploy.sh register SERVICE_ID" log_info "" # Save deployment attempt save_state "last_attempt" "$APP_NAME" "deployment" } # Register service cmd_register() { local service_id="$1" if [ -z "$service_id" ]; then log_error "Service ID required" log_info "Usage: ./deploy.sh register SERVICE_ID" exit 1 fi load_config save_state "service" "$APP_NAME" "$service_id" log_success "✅ Service registered!" log_info "Service ID: $service_id" log_info "Next update: ./deploy.sh update" } # Update service cmd_update() { load_config SERVICE_ID=$(get_state "$APP_NAME" "service") if [ -z "$SERVICE_ID" ]; then log_error "Service not registered" log_info "Run: ./deploy.sh deploy" exit 1 fi log_info "Updating: $APP_NAME ($SERVICE_ID)" # Rebuild if [ ! "$SKIP_BUILD" = "true" ]; then docker build -t "$DOCKER_IMAGE" . || exit 1 log_success "Rebuilt: $DOCKER_IMAGE" fi log_info "✅ Image ready" log_info "" log_info "To deploy update:" log_info "1. Go to $EASYPANEL_URL" log_info "2. Select service: $APP_NAME" log_info "3. Click 'Deploy' to pull new image" } # Status cmd_status() { load_config SERVICE_ID=$(get_state "$APP_NAME" "service") if [ -z "$SERVICE_ID" ]; then log_error "Not deployed" exit 1 fi log_info "Service: $APP_NAME" log_info "ID: $SERVICE_ID" log_info "Image: $DOCKER_IMAGE" log_info "Port: $PORT" } # Help cmd_help() { cat << 'EOF' Easypanel Deployment Skill v2.2 Usage: ./deploy.sh [command] Commands: deploy Build and prepare deployment register ID Register service with ID update Update existing service status Show status list List projects help This help Workflow: 1. ./deploy.sh deploy 2. Create service in Easypanel dashboard 3. ./deploy.sh register SERVICE_ID 4. ./deploy.sh update (for future updates) EOF } SKIP_BUILD="false" [ "$1" = "-b" ] || [ "$1" = "--skip-build" ] && SKIP_BUILD="true" case "${1:-deploy}" in deploy) cmd_deploy ;; register) cmd_register "$2" ;; update) cmd_update ;; status) cmd_status ;; list) list_projects ;; help|--help|-h) cmd_help ;; *) cmd_help; exit 1 ;; esac