Files
TSYSDevStack/CloudronStack/output/package-functions.sh
ReachableCEO 18d5a57868 feat(cloudron): update CloudronStack core components
- Update CloudronStack/QWEN.md with latest development log information
- Update CloudronStack/collab/STATUS.md with current project status
- Update CloudronStack/output/master-control-script.sh with enhanced automation
- Update CloudronStack/output/package-functions.sh with improved packaging logic

These changes enhance the CloudronStack automation and packaging capabilities.
2025-10-30 09:00:38 -05:00

584 lines
19 KiB
Bash

#!/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
}