#!/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" # 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": ["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 # Create Dockerfile for Node.js with appropriate start command and port cat > Dockerfile << EOF FROM node:18-alpine WORKDIR /app COPY package*.json ./ RUN npm install --only=production COPY . . EXPOSE $port CMD $start_cmd EOF # Build Docker image local docker_image="tsysdevstack-cloudron-buildtest-${app_name//[^a-zA-Z0-9]/-}: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]/-}.tar.gz" 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" # 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": ["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 local docker_image="tsysdevstack-cloudron-buildtest-${app_name//[^a-zA-Z0-9]/-}: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]/-}.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" # 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": ["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 local docker_image="tsysdevstack-cloudron-buildtest-${app_name//[^a-zA-Z0-9]/-}: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]/-}.tar.gz" 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" # 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": ["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 appropriate binary name cat > Dockerfile << EOF FROM golang:1.21-alpine AS builder WORKDIR /app COPY . . RUN CGO_ENABLED=0 GOOS=linux go build -o $binary_name . 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 local docker_image="tsysdevstack-cloudron-buildtest-${app_name//[^a-zA-Z0-9]/-}: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]/-}.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 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 # Run the container briefly to test if it starts correctly local container_name="smoke-test-${app_name//[^a-zA-Z0-9]/-}-$(date +%s)" # Run without specific health check initially, just see if container starts and stays running if ! docker run -d --name "$container_name" "$docker_image" >/dev/null 2>&1; then echo "Failed to start container for $app_name during smoke test" docker rm "$container_name" >/dev/null 2>&1 || true return 1 fi # Wait a few seconds to see if the container stays running sleep 15 # Check if the container is still running local container_status container_status=$(docker inspect -f '{{.State.Status}}' "$container_name" 2>/dev/null || echo "not_found") if [ "$container_status" = "running" ]; then echo "Smoke test passed for $app_name - container is running" # 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 logs for debugging echo "Container for $app_name did not stay running during smoke test (status: $container_status)" echo "Container logs:" docker logs "$container_name" 2>&1 | head -30 docker stop "$container_name" >/dev/null 2>&1 || true docker rm "$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" # 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 local docker_image="tsysdevstack-cloudron-buildtest-${app_name//[^a-zA-Z0-9]/-}: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]/-}.tar.gz" return 0 }