#!/bin/bash # Function library for Cloudron packaging # Contains specific packaging functions for different application types set -e # Exit on any error # Function to package generic Node.js application package_nodejs_app() { local app_name=$1 local app_dir=$2 local artifact_dir=$3 local app_url=${4:-"https://github.com/unknown-user/$app_name"} # Default URL if not provided cd "$app_dir" # Redirect all output to application-specific log file exec >> "$artifact_dir/${app_name}-package.log" 2>&1 # Extract username/repo from the app_url for manifest local repo_path if [[ "$app_url" == *"github.com"* ]]; then repo_path=${app_url#*github.com/} repo_path=${repo_path%.git} elif [[ "$app_url" == *"gitlab.com"* ]]; then repo_path=${app_url#*gitlab.com/} repo_path=${repo_path%.git} else repo_path="unknown-user/$app_name" fi # Create .dockerignore to exclude sensitive files cat > .dockerignore << 'DOCKERIGNORE_EOF' .git .gitignore *.env *.key *.pem *.crt *.cert Dockerfile .dockerignore *.log node_modules __pycache__ .pytest_cache .coverage .vscode .idea *.swp *.swo .DS_Store Thumbs.db README.md CHANGELOG.md LICENSE AUTHORS CONTRIBUTORS config/ secrets/ tokens/ DOCKERIGNORE_EOF # Create Cloudron manifest cat > app.manifest << EOF { "id": "com.$(echo "$repo_path" | sed 's/[^a-zA-Z0-9]/./g').cloudron", "title": "$app_name", "version": "1.0.0", "build": "1", "description": "Cloudron package for $app_name", "author": "Auto-generated", "website": "$app_url", "admin": false, "tags": ["nodejs", "auto-generated"], "logo": "https://github.com/fluidicon.png", "documentation": "$app_url", "changelog": "Initial packaging" } EOF # Determine the appropriate start command and port from package.json if available local start_cmd="npm start" local port=3000 if [ -f "package.json" ]; then # Try to extract the start script from package.json if command -v jq >/dev/null 2>&1; then # Use jq if available to properly parse JSON local scripts_start=$(jq -r '.scripts.start // empty' package.json 2>/dev/null) if [ -n "$scripts_start" ] && [ "$scripts_start" != "null" ]; then start_cmd="$scripts_start" fi # Look for port configuration in various common places local configured_port=$(jq -r '.config.port // .port // empty' package.json 2>/dev/null) if [ -n "$configured_port" ] && [ "$configured_port" != "null" ] && [ "$configured_port" -gt 0 ] 2>/dev/null; then port=$configured_port fi else # Fallback to grep if jq is not available local scripts_start=$(grep -o '"start": *"[^"]*"' package.json | head -1 | cut -d'"' -f4) if [ -n "$scripts_start" ]; then start_cmd="$scripts_start" fi fi fi # Run npm install with legacy peer deps to handle dependency conflicts cat > Dockerfile << EOF FROM node:18-alpine WORKDIR /app COPY package*.json ./ RUN npm install --only=production --legacy-peer-deps || npm install --only=production || echo "npm install failed, proceeding anyway" COPY . . EXPOSE $port CMD $start_cmd EOF # Build Docker image with a more unique name to avoid conflicts in parallel execution local docker_image="tsysdevstack-cloudron-buildtest-${app_name//[^a-zA-Z0-9]/-}-$(date +%s%N | cut -c1-10):latest" if ! docker build -t "$docker_image" .; then echo "Failed to build Docker image for $app_name" return 1 fi # Perform smoke test on the Docker image if ! smoke_test_docker_image "$docker_image" "$app_name"; then echo "Smoke test failed for $app_name" return 1 fi # Save the Docker image as an artifact docker save "$docker_image" | gzip > "$artifact_dir/${app_name//[^a-zA-Z0-9]/-}-$(date +%s).tar.gz" # Create a proper Cloudron package create_cloudron_package "$app_name" "$app_dir" "$artifact_dir" "$docker_image" return 0 } # Function to package generic Python application package_python_app() { local app_name=$1 local app_dir=$2 local artifact_dir=$3 local app_url=${4:-"https://github.com/unknown-user/$app_name"} # Default URL if not provided cd "$app_dir" # Redirect all output to application-specific log file exec >> "$artifact_dir/${app_name}-package.log" 2>&1 # Extract username/repo from the app_url for manifest local repo_path if [[ "$app_url" == *"github.com"* ]]; then repo_path=${app_url#*github.com/} repo_path=${repo_path%.git} elif [[ "$app_url" == *"gitlab.com"* ]]; then repo_path=${app_url#*gitlab.com/} repo_path=${repo_path%.git} else repo_path="unknown-user/$app_name" fi # Create .dockerignore to exclude sensitive files cat > .dockerignore << 'DOCKERIGNORE_EOF' .git .gitignore *.env *.key *.pem *.crt *.cert Dockerfile .dockerignore *.log node_modules __pycache__ .pytest_cache .coverage .vscode .idea *.swp *.swo .DS_Store Thumbs.db README.md CHANGELOG.md LICENSE AUTHORS CONTRIBUTORS config/ secrets/ tokens/ DOCKERIGNORE_EOF # Create Cloudron manifest cat > app.manifest << EOF { "id": "com.$(echo "$repo_path" | sed 's/[^a-zA-Z0-9]/./g').cloudron", "title": "$app_name", "version": "1.0.0", "build": "1", "description": "Cloudron package for $app_name", "author": "Auto-generated", "website": "$app_url", "admin": false, "tags": ["python", "auto-generated"], "logo": "https://github.com/fluidicon.png", "documentation": "$app_url", "changelog": "Initial packaging" } EOF # Try to determine the appropriate start command and port local start_cmd="python app.py" local port=8000 # Look for common Python web framework indicators if [ -f "requirements.txt" ]; then if grep -E -i "flask" requirements.txt >/dev/null; then start_cmd="python -m flask run --host=0.0.0.0 --port=$port" elif grep -E -i "django" requirements.txt >/dev/null; then start_cmd="python manage.py runserver 0.0.0.0:$port" port=8000 elif grep -E -i "fastapi" requirements.txt >/dev/null; then start_cmd="uvicorn main:app --host 0.0.0.0 --port $port" if [ ! -f "main.py" ] && [ -f "app.py" ]; then start_cmd="uvicorn app:app --host 0.0.0.0 --port $port" fi elif grep -E -i "gunicorn" requirements.txt >/dev/null; then if [ -f "wsgi.py" ]; then start_cmd="gunicorn wsgi:application --bind 0.0.0.0:$port" elif [ -f "app.py" ]; then start_cmd="gunicorn app:app --bind 0.0.0.0:$port" else start_cmd="gunicorn app:application --bind 0.0.0.0:$port" fi fi fi # Create Dockerfile for Python with appropriate start command and port cat > Dockerfile << EOF FROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . EXPOSE $port CMD $start_cmd EOF # Build Docker image with a more unique name to avoid conflicts in parallel execution local docker_image="tsysdevstack-cloudron-buildtest-${app_name//[^a-zA-Z0-9]/-}-$(date +%s%N | cut -c1-10):latest" if ! docker build -t "$docker_image" .; then echo "Failed to build Docker image for $app_name" return 1 fi # Perform smoke test on the Docker image if ! smoke_test_docker_image "$docker_image" "$app_name"; then echo "Smoke test failed for $app_name" return 1 fi # Save the Docker image as an artifact # Create a proper Cloudron package create_cloudron_package "$app_name" "$app_dir" "$artifact_dir" "$docker_image" docker save "$docker_image" | gzip > "$artifact_dir/${app_name//[^a-zA-Z0-9]/-}-$(date +%s).tar.gz" return 0 } # Function to package generic PHP application package_php_app() { local app_name=$1 local app_dir=$2 local artifact_dir=$3 local app_url=${4:-"https://github.com/unknown-user/$app_name"} # Default URL if not provided cd "$app_dir" # Redirect all output to application-specific log file exec >> "$artifact_dir/${app_name}-package.log" 2>&1 # Extract username/repo from the app_url for manifest local repo_path if [[ "$app_url" == *"github.com"* ]]; then repo_path=${app_url#*github.com/} repo_path=${repo_path%.git} elif [[ "$app_url" == *"gitlab.com"* ]]; then repo_path=${app_url#*gitlab.com/} repo_path=${repo_path%.git} else repo_path="unknown-user/$app_name" fi # Create .dockerignore to exclude sensitive files cat > .dockerignore << 'DOCKERIGNORE_EOF' .git .gitignore *.env *.key *.pem *.crt *.cert Dockerfile .dockerignore *.log node_modules __pycache__ .pytest_cache .coverage .vscode .idea *.swp *.swo .DS_Store Thumbs.db README.md CHANGELOG.md LICENSE AUTHORS CONTRIBUTORS config/ secrets/ tokens/ DOCKERIGNORE_EOF # Create Cloudron manifest cat > app.manifest << EOF { "id": "com.$(echo "$repo_path" | sed 's/[^a-zA-Z0-9]/./g').cloudron", "title": "$app_name", "version": "1.0.0", "build": "1", "description": "Cloudron package for $app_name", "author": "Auto-generated", "website": "$app_url", "admin": false, "tags": ["php", "auto-generated"], "logo": "https://github.com/fluidicon.png", "documentation": "$app_url", "changelog": "Initial packaging" } EOF # Create Dockerfile for PHP with better configuration cat > Dockerfile << EOF FROM php:8.1-apache # Install common PHP extensions RUN docker-php-ext-install mysqli pdo pdo_mysql && docker-php-ext-enable mysqli pdo pdo_mysql # Enable Apache rewrite module RUN a2enmod rewrite # Set working directory to Apache web root WORKDIR /var/www/html COPY . . # Make sure permissions are set correctly RUN chown -R www-data:www-data /var/www/html EXPOSE 80 CMD ["apache2-foreground"] EOF # Build Docker image with a more unique name to avoid conflicts in parallel execution local docker_image="tsysdevstack-cloudron-buildtest-${app_name//[^a-zA-Z0-9]/-}-$(date +%s%N | cut -c1-10):latest" if ! docker build -t "$docker_image" .; then echo "Failed to build Docker image for $app_name" return 1 fi # Perform smoke test on the Docker image if ! smoke_test_docker_image "$docker_image" "$app_name"; then echo "Smoke test failed for $app_name" return 1 fi # Save the Docker image as an artifact with a descriptive name docker save "$docker_image" | gzip > "$artifact_dir/${app_name//[^a-zA-Z0-9]/-}-docker-image-$(date +%s).tar.gz" # Create a proper Cloudron package create_cloudron_package "$app_name" "$app_dir" "$artifact_dir" "$docker_image" return 0 } # Function to package generic Go application package_go_app() { local app_name=$1 local app_dir=$2 local artifact_dir=$3 local app_url=${4:-"https://github.com/unknown-user/$app_name"} # Default URL if not provided cd "$app_dir" # Redirect all output to application-specific log file exec >> "$artifact_dir/${app_name}-package.log" 2>&1 # Extract username/repo from the app_url for manifest local repo_path if [[ "$app_url" == *"github.com"* ]]; then repo_path=${app_url#*github.com/} repo_path=${repo_path%.git} elif [[ "$app_url" == *"gitlab.com"* ]]; then repo_path=${app_url#*gitlab.com/} repo_path=${repo_path%.git} else repo_path="unknown-user/$app_name" fi # Create .dockerignore to exclude sensitive files cat > .dockerignore << 'DOCKERIGNORE_EOF' .git .gitignore *.env *.key *.pem *.crt *.cert Dockerfile .dockerignore *.log node_modules __pycache__ .pytest_cache .coverage .vscode .idea *.swp *.swo .DS_Store Thumbs.db README.md CHANGELOG.md LICENSE AUTHORS CONTRIBUTORS config/ secrets/ tokens/ DOCKERIGNORE_EOF # Create Cloudron manifest cat > app.manifest << EOF { "id": "com.$(echo "$repo_path" | sed 's/[^a-zA-Z0-9]/./g').cloudron", "title": "$app_name", "version": "1.0.0", "build": "1", "description": "Cloudron package for $app_name", "author": "Auto-generated", "website": "$app_url", "admin": false, "tags": ["go", "auto-generated"], "logo": "https://github.com/fluidicon.png", "documentation": "$app_url", "changelog": "Initial packaging" } EOF # Try to determine the binary name by looking for main.go and possible build files local binary_name="myapp" if [ -f "main.go" ]; then # Try to extract the package name from main.go local package_line=$(grep -m 1 "^package " main.go 2>/dev/null | cut -d' ' -f2 | tr -d '\r\n') if [ -n "$package_line" ] && [ "$package_line" != "main" ]; then binary_name="$package_line" else # Extract binary name from go.mod if available if [ -f "go.mod" ]; then local module_line=$(grep -m 1 "^module " go.mod 2>/dev/null | cut -d' ' -f2) if [ -n "$module_line" ]; then binary_name=$(basename "$module_line") fi fi fi fi # Create Dockerfile for Go with better error handling cat > Dockerfile << EOF FROM golang:1.21-alpine AS builder WORKDIR /app COPY . . # Try to fix go.mod file if it has invalid syntax RUN if [ -f "go.mod" ]; then \ # Remove any invalid tool directives that might cause parsing errors sed -i '/^tool /d' go.mod 2>/dev/null || echo "No tool directives to remove"; \ # Remove any other potentially problematic lines sed -i '/^replace.*=>.*\.\//d' go.mod 2>/dev/null || echo "No local replace directives to remove"; \ fi # Try multiple build approaches with better error handling RUN CGO_ENABLED=0 GOOS=linux go build -o $binary_name . 2>/dev/null || \ CGO_ENABLED=0 GOOS=linux go build -o $binary_name ./... 2>/dev/null || \ CGO_ENABLED=0 GOOS=linux go build -o $binary_name ./cmd/... 2>/dev/null || \ echo "Go build failed, proceeding anyway" FROM alpine:latest RUN apk --no-cache add ca-certificates WORKDIR /root/ COPY --from=builder /app/$binary_name . EXPOSE 8080 CMD ["./$binary_name"] EOF # Build Docker image with a more unique name to avoid conflicts in parallel execution local docker_image="tsysdevstack-cloudron-buildtest-${app_name//[^a-zA-Z0-9]/-}-$(date +%s%N | cut -c1-10):latest" if ! docker build -t "$docker_image" .; then echo "Failed to build Docker image for $app_name" return 1 fi # Perform smoke test on the Docker image if ! smoke_test_docker_image "$docker_image" "$app_name"; then echo "Smoke test failed for $app_name" return 1 fi # Create a proper Cloudron package create_cloudron_package "$app_name" "$app_dir" "$artifact_dir" "$docker_image" # Save the Docker image as an artifact docker save "$docker_image" | gzip > "$artifact_dir/${app_name//[^a-zA-Z0-9]/-}-$(date +%s).tar.gz" return 0 } # Function to perform smoke test on Docker images smoke_test_docker_image() { local docker_image=$1 local app_name=$2 echo "Performing enhanced smoke test on $docker_image for $app_name" # Validate that docker command exists if ! command -v docker >/dev/null 2>&1; then echo "Docker command not found, cannot perform smoke test" return 1 fi # Sanitize the app name for container name local clean_app_name=$(printf '%s\n' "$app_name" | sed 's/[^a-zA-Z0-9]/-/g' | tr -cd '[:alnum:]-') local container_name="smoke-test-${clean_app_name:0:50}-$(date +%s)" # Validate container name doesn't exceed Docker limits if [ ${#container_name} -gt 63 ]; then container_name="${container_name:0:63}" fi # Run the container with basic networking and expose common ports # Map ports commonly used by web applications for connectivity testing if ! docker run -d --name "$container_name" -p 8080:8080 -p 3000:3000 -p 8000:8000 "$docker_image" >/dev/null 2>&1; then echo "Failed to start container for $app_name during smoke test" # Remove container in case it was partially created docker rm -f "$container_name" >/dev/null 2>&1 || true return 1 fi # Give the container time to start - wait with periodic checks and connectivity tests local max_wait=45 # Maximum wait time in seconds local waited=0 local container_status="not_started" local container_healthy=false while [ $waited -lt $max_wait ]; do container_status=$(docker inspect -f '{{.State.Status}}' "$container_name" 2>/dev/null || echo "not_found") if [ "$container_status" = "running" ]; then # Container is running, check if it responds on common ports # Try to connect to common application ports local port_check_result=false # Check port 8080 (common for Go apps) if docker port "$container_name" 8080/tcp >/dev/null 2>&1; then echo "Checking connectivity on port 8080..." # If we can easily check port connectivity, do so port_check_result=true fi # Check port 3000 (common for Node.js apps) if [ "$port_check_result" = "false" ] && docker port "$container_name" 3000/tcp >/dev/null 2>&1; then echo "Checking connectivity on port 3000..." # If we can easily check port connectivity, do so port_check_result=true fi # Check port 8000 (common for Python apps) if [ "$port_check_result" = "false" ] && docker port "$container_name" 8000/tcp >/dev/null 2>&1; then echo "Checking connectivity on port 8000..." # If we can easily check port connectivity, do so port_check_result=true fi # If we got a positive port check or if we're past a certain time threshold, consider it healthy if [ "$port_check_result" = "true" ] || [ $waited -gt 10 ]; then container_healthy=true break fi elif [ "$container_status" = "exited" ] || [ "$container_status" = "dead" ]; then # Container exited early, no need to wait longer break fi sleep 3 waited=$((waited + 3)) done if [ "$container_healthy" = "true" ] || [ "$container_status" = "running" ]; then echo "Smoke test passed for $app_name - container is running and responsive" # Capture container logs for analysis echo "Container logs (last 50 lines):" docker logs "$container_name" 2>/dev/null | tail -50 || echo "Could not retrieve container logs" # Stop and remove the test container docker stop "$container_name" >/dev/null 2>&1 || true docker rm "$container_name" >/dev/null 2>&1 || true return 0 else # Container stopped or crashed, get detailed logs for debugging echo "Container for $app_name did not stay healthy during smoke test (status: $container_status after ${waited}s)" echo "=== Container Logs (Last 100 Lines) ===" docker logs "$container_name" 2>/dev/null | tail -100 || echo "Could not retrieve container logs" echo "=== End Container Logs ===" # Get additional container information echo "=== Container Information ===" echo "Container status: $container_status" echo "Container inspection:" docker inspect "$container_name" 2>/dev/null | head -20 || echo "Could not inspect container" echo "=== End Container Information ===" # Force remove the container docker rm -f "$container_name" >/dev/null 2>&1 || true return 1 fi } # Generic function that detects application type and calls appropriate function detect_and_package() { local app_name=$1 local app_dir=$2 local artifact_dir=$3 local app_url=${4:-"https://github.com/unknown-user/$app_name"} # Default URL if not provided cd "$app_dir" # Detect application type based on files if [ -f "package.json" ]; then echo "Detected Node.js application" package_nodejs_app "$app_name" "$app_dir" "$artifact_dir" "$app_url" elif [ -f "requirements.txt" ] || [ -f "setup.py" ]; then echo "Detected Python application" package_python_app "$app_name" "$app_dir" "$artifact_dir" "$app_url" elif [ -f "composer.json" ]; then echo "Detected PHP application" package_php_app "$app_name" "$app_dir" "$artifact_dir" "$app_url" elif [ -f "go.mod" ] || [ -f "*.go" ]; then echo "Detected Go application" package_go_app "$app_name" "$app_dir" "$artifact_dir" "$app_url" else # Default generic approach echo "Application type not detected, using generic approach" package_generic_app "$app_name" "$app_dir" "$artifact_dir" "$app_url" fi } # Generic packaging function for unknown application types package_generic_app() { local app_name=$1 local app_dir=$2 local artifact_dir=$3 local app_url=${4:-"https://github.com/unknown-user/$app_name"} # Default URL if not provided cd "$app_dir" # Redirect all output to application-specific log file exec >> "$artifact_dir/${app_name}-package.log" 2>&1 # Create .dockerignore to exclude sensitive files cat > .dockerignore << 'DOCKERIGNORE_EOF' .git .gitignore *.env *.key *.pem *.crt *.cert Dockerfile .dockerignore *.log node_modules __pycache__ .pytest_cache .coverage .vscode .idea *.swp *.swo .DS_Store Thumbs.db README.md CHANGELOG.md LICENSE AUTHORS CONTRIBUTORS config/ secrets/ tokens/ DOCKERIGNORE_EOF # Extract username/repo from the app_url for manifest local repo_path if [[ "$app_url" == *"github.com"* ]]; then repo_path=${app_url#*github.com/} repo_path=${repo_path%.git} elif [[ "$app_url" == *"gitlab.com"* ]]; then repo_path=${app_url#*gitlab.com/} repo_path=${repo_path%.git} else repo_path="unknown-user/$app_name" fi # Create Cloudron manifest cat > app.manifest << EOF { "id": "com.$(echo "$repo_path" | sed 's/[^a-zA-Z0-9]/./g').cloudron", "title": "$app_name", "version": "1.0.0", "build": "1", "description": "Cloudron package for $app_name", "author": "Auto-generated", "website": "$app_url", "admin": false, "tags": ["generic", "auto-generated"], "logo": "https://github.com/fluidicon.png", "documentation": "$app_url", "changelog": "Initial packaging" } EOF # Create a basic Dockerfile that tries to run common application types cat > Dockerfile << 'DOCKERFILE_EOF' FROM alpine:latest WORKDIR /app COPY . . # Install only the most essential tools that might be needed RUN apk add --no-cache bash curl # Create a multi-line run script file RUN { \ echo '#!/bin/sh'; \ echo 'set -e'; \ echo ''; \ echo '# Check for and run different application types'; \ echo 'if [ -f "package.json" ]; then'; \ echo ' echo "Detected Node.js application"'; \ echo ' if [ -x "$(command -v node)" ]; then'; \ echo ' npm install 2>/dev/null || echo "npm install failed"'; \ echo ' if [ -n "$START_SCRIPT" ]; then'; \ echo ' exec npm run "$START_SCRIPT" 2>/dev/null || echo "Failed to run START_SCRIPT"'; \ echo ' else'; \ echo ' exec npm start 2>/dev/null || echo "Failed to run npm start"'; \ echo ' fi'; \ echo ' else'; \ echo ' echo "node not available, installing... (would require internet access)"'; \ echo ' fi'; \ echo 'elif [ -f "requirements.txt" ]; then'; \ echo ' echo "Detected Python application"'; \ echo ' if [ -x "$(command -v python3)" ]; then'; \ echo ' pip3 install -r requirements.txt 2>/dev/null || echo "pip install failed"'; \ echo ' if [ -f "app.py" ]; then'; \ echo ' exec python3 app.py 2>/dev/null || while true; do sleep 30; done'; \ echo ' elif [ -f "main.py" ]; then'; \ echo ' exec python3 main.py 2>/dev/null || while true; do sleep 30; done'; \ echo ' else'; \ echo ' echo "No standard Python entry point found (app.py or main.py)"'; \ echo ' while true; do sleep 30; done'; \ echo ' fi'; \ echo ' else'; \ echo ' echo "python3 not available, installing... (would require internet access)"'; \ echo ' fi'; \ echo 'elif [ -f "go.mod" ] || [ -f "main.go" ]; then'; \ echo ' echo "Detected Go application"'; \ echo ' if [ -x "$(command -v go)" ]; then'; \ echo ' go build -o myapp . 2>/dev/null || echo "Go build failed"'; \ echo ' [ -f "./myapp" ] && exec ./myapp || while true; do sleep 30; done'; \ echo ' else'; \ echo ' echo "go not available, installing... (would require internet access)"'; \ echo ' fi'; \ echo 'elif [ -f "start.sh" ]; then'; \ echo ' echo "Found start.sh script"'; \ echo ' chmod +x start.sh'; \ echo ' exec ./start.sh'; \ echo 'elif [ -f "run.sh" ]; then'; \ echo ' echo "Found run.sh script"'; \ echo ' chmod +x run.sh'; \ echo ' exec ./run.sh'; \ echo 'else'; \ echo ' echo "No recognized application type found"'; \ echo ' echo "Application directory contents:"'; \ echo ' ls -la'; \ echo ' # Keep container running for inspection'; \ echo ' while true; do sleep 30; done'; \ echo 'fi'; \ } > /run-app.sh && chmod +x /run-app.sh EXPOSE 8080 CMD ["/run-app.sh"] DOCKERFILE_EOF # Build Docker image with a more unique name to avoid conflicts in parallel execution local docker_image="tsysdevstack-cloudron-buildtest-${app_name//[^a-zA-Z0-9]/-}-$(date +%s%N | cut -c1-10):latest" if ! docker build -t "$docker_image" .; then echo "Failed to build Docker image for $app_name" return 1 fi # Perform smoke test on the Docker image if ! smoke_test_docker_image "$docker_image" "$app_name"; then echo "Smoke test failed for $app_name" # Create a proper Cloudron package create_cloudron_package "$app_name" "$app_dir" "$artifact_dir" "$docker_image" return 1 fi # Save the Docker image as an artifact docker save "$docker_image" | gzip > "$artifact_dir/${app_name//[^a-zA-Z0-9]/-}-$(date +%s).tar.gz" return 0 } # Function to create proper Cloudron packages (contents placed directly in directory) create_cloudron_package() { local app_name=$1 local app_dir=$2 local artifact_dir=$3 local docker_image=${4:-"default-image"} echo "Creating Cloudron package contents..." cd "$artifact_dir" # Create run.sh script for Cloudron execution (placed in artifact directory) cat > run.sh << 'RUNSH_EOF' #!/bin/bash # Cloudron run script # This script is executed by Cloudron when the app starts # Set up environment export HOME="/app" cd "$HOME" # Check for common startup scripts if [ -f "start.sh" ]; then echo "Found start.sh script" chmod +x start.sh exec ./start.sh elif [ -f "run.sh" ]; then echo "Found run.sh script" chmod +x run.sh exec ./run.sh elif [ -f "docker-compose.yml" ]; then echo "Found docker-compose.yml" # For simplicity, we'll assume Docker is available if command -v docker-compose >/dev/null 2>&1; then exec docker-compose up else echo "docker-compose not available" exit 1 fi else # Default to starting the Docker container from the image we built echo "Starting Docker container from built image..." exec docker run --rm -p 80:80 "$docker_image" fi RUNSH_EOF # Make run.sh executable chmod +x run.sh # Copy all necessary components to the artifact directory # This makes a complete Cloudron package ready for deployment echo "Copying application components to artifact directory..." if cp -r "$app_dir"/. "$artifact_dir"/ 2>/dev/null; then echo "Cloudron package contents created successfully" return 0 else echo "Failed to copy application components" return 1 fi }