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.
This commit is contained in:
		| @@ -64,3 +64,10 @@ Current tasks and progress: | |||||||
| - Established identity as CloudronStack QWEN agent | - Established identity as CloudronStack QWEN agent | ||||||
| - Confirmed scope limited to CloudronStack directory | - Confirmed scope limited to CloudronStack directory | ||||||
| - Updated QWEN.md with agent identity information | - Updated QWEN.md with agent identity information | ||||||
|  |  | ||||||
|  | ### Session 3 (2025-10-30) | ||||||
|  | - Reviewed and enhanced directory organization with application-specific subdirectories | ||||||
|  | - Added functionality to handle additional Git URLs dynamically | ||||||
|  | - Created add_git_url and add_git_urls_from_file functions | ||||||
|  | - Improved URL validation and duplicate checking | ||||||
|  | - Enhanced system to support adding more applications later | ||||||
|   | |||||||
| @@ -22,7 +22,7 @@ This file tracks the status of Cloudron packaging for all upstream applications. | |||||||
| | database-gateway | https://github.com/kazhuravlev/database-gateway | ⏳ PENDING | | | | database-gateway | https://github.com/kazhuravlev/database-gateway | ⏳ PENDING | | | ||||||
| | webhook | https://github.com/adnanh/webhook | ⏳ PENDING | | | | webhook | https://github.com/adnanh/webhook | ⏳ PENDING | | | ||||||
| | fx | https://github.com/metrue/fx | ⏳ PENDING | | | | fx | https://github.com/metrue/fx | ⏳ PENDING | | | ||||||
| | fonoster | https://github.com/metrue/fx | ⏳ PENDING | | | | fonoster | https://github.com/fonoster/fonoster | ⏳ PENDING | | | ||||||
| | oat-sa | https://github.com/oat-sa | ⏳ PENDING | | | | oat-sa | https://github.com/oat-sa | ⏳ PENDING | | | ||||||
| | rundeck | https://github.com/rundeck/rundeck | ⏳ PENDING | | | | rundeck | https://github.com/rundeck/rundeck | ⏳ PENDING | | | ||||||
| | hyperswitch | https://github.com/juspay/hyperswitch | ⏳ PENDING | | | | hyperswitch | https://github.com/juspay/hyperswitch | ⏳ PENDING | | | ||||||
|   | |||||||
| @@ -10,11 +10,11 @@ set -o pipefail  # Exit on pipe failures | |||||||
|  |  | ||||||
| # Configuration | # Configuration | ||||||
| SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | ||||||
| OUTPUT_DIR="$SCRIPT_DIR/output" | OUTPUT_DIR="$SCRIPT_DIR" | ||||||
| ARTIFACTS_DIR="$OUTPUT_DIR/CloudronPackages-Artifacts" | ARTIFACTS_DIR="$OUTPUT_DIR/CloudronPackages-Artifacts" | ||||||
| WORKSPACES_DIR="$OUTPUT_DIR/CloudronPackages-Workspaces" | WORKSPACES_DIR="$OUTPUT_DIR/CloudronPackages-Workspaces" | ||||||
| STATUS_FILE="$SCRIPT_DIR/collab/STATUS.md" | STATUS_FILE="$(dirname "$SCRIPT_DIR")/collab/STATUS.md" | ||||||
| GIT_URL_LIST="$SCRIPT_DIR/collab/GitUrlList.txt" | GIT_URL_LIST="$(dirname "$SCRIPT_DIR")/collab/GitUrlList.txt" | ||||||
| HUMAN_HELP_DIR="$WORKSPACES_DIR/human-help-required" | HUMAN_HELP_DIR="$WORKSPACES_DIR/human-help-required" | ||||||
| MAX_RETRIES=5 | MAX_RETRIES=5 | ||||||
| LOG_FILE="$WORKSPACES_DIR/packaging.log" | LOG_FILE="$WORKSPACES_DIR/packaging.log" | ||||||
| @@ -42,8 +42,8 @@ perform_audit() { | |||||||
|      |      | ||||||
|     # Count total, completed, failed, and in-progress applications |     # Count total, completed, failed, and in-progress applications | ||||||
|     local total_count=$(grep -c "https://" "$GIT_URL_LIST" || echo 0) |     local total_count=$(grep -c "https://" "$GIT_URL_LIST" || echo 0) | ||||||
|     local completed_count=$(grep -c "✅ COMPLETE" "$STATUS_FILE" || echo 0) |     local completed_count=$(grep -c "✅ COMPLETE" "$STATUS_FILE" | grep -v "Progress Summary" || echo 0) | ||||||
|     local failed_count=$(grep -c "🛑 FAILED" "$STATUS_FILE" || echo 0) |     local failed_count=$(grep -c "🛑 FAILED" "$STATUS_FILE" | grep -v "Human Help Required" || echo 0) | ||||||
|     local in_progress_count=$(grep -c "🔄 IN PROGRESS" "$STATUS_FILE" || echo 0) |     local in_progress_count=$(grep -c "🔄 IN PROGRESS" "$STATUS_FILE" || echo 0) | ||||||
|     local pending_count=$((total_count - completed_count - failed_count - in_progress_count)) |     local pending_count=$((total_count - completed_count - failed_count - in_progress_count)) | ||||||
|      |      | ||||||
| @@ -68,19 +68,89 @@ perform_audit() { | |||||||
|     log_message "INFO" "Audit process completed" |     log_message "INFO" "Audit process completed" | ||||||
| } | } | ||||||
|  |  | ||||||
|  | # Function to add a new Git URL to the list | ||||||
|  | add_git_url() { | ||||||
|  |     local new_url=$1 | ||||||
|  |     local git_list_file=${2:-"$GIT_URL_LIST"} | ||||||
|  |      | ||||||
|  |     if [[ -z "$new_url" ]]; then | ||||||
|  |         log_message "ERROR" "No URL provided to add_git_url function" | ||||||
|  |         return 1 | ||||||
|  |     fi | ||||||
|  |      | ||||||
|  |     # Validate URL format | ||||||
|  |     if [[ ! "$new_url" =~ ^https?:// ]]; then | ||||||
|  |         log_message "ERROR" "Invalid URL format: $new_url" | ||||||
|  |         return 1 | ||||||
|  |     fi | ||||||
|  |      | ||||||
|  |     # Check if URL already exists in the file | ||||||
|  |     if grep -Fxq "$new_url" "$git_list_file"; then | ||||||
|  |         log_message "INFO" "URL already exists in $git_list_file: $new_url" | ||||||
|  |         return 0 | ||||||
|  |     fi | ||||||
|  |      | ||||||
|  |     # Add the URL to the file | ||||||
|  |     echo "$new_url" >> "$git_list_file" | ||||||
|  |     log_message "INFO" "Added new URL to $git_list_file: $new_url" | ||||||
|  |      | ||||||
|  |     # Also update STATUS.md to include the new application | ||||||
|  |     local repo_name=$(get_repo_name "$new_url") | ||||||
|  |     local username_repo=$(get_username_repo "$new_url") | ||||||
|  |      | ||||||
|  |     # Check if the application is already in STATUS.md | ||||||
|  |     if ! grep -q "| $repo_name |" "$STATUS_FILE"; then | ||||||
|  |         # Append the new application to the table in STATUS.md | ||||||
|  |         sed -i "/## Applications Status/,/|-----|-----|-----|-----|/ {/|-----|-----|-----|-----|/a\| $repo_name | $new_url | ⏳ PENDING | |" "$STATUS_FILE" | ||||||
|  |         log_message "INFO" "Added $repo_name to STATUS.md" | ||||||
|  |     else | ||||||
|  |         log_message "INFO" "Application $repo_name already exists in STATUS.md" | ||||||
|  |     fi | ||||||
|  |      | ||||||
|  |     return 0 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | # Function to add multiple Git URLs from a file | ||||||
|  | add_git_urls_from_file() { | ||||||
|  |     local input_file=$1 | ||||||
|  |     local git_list_file=${2:-"$GIT_URL_LIST"} | ||||||
|  |      | ||||||
|  |     if [[ ! -f "$input_file" ]]; then | ||||||
|  |         log_message "ERROR" "Input file does not exist: $input_file" | ||||||
|  |         return 1 | ||||||
|  |     fi | ||||||
|  |      | ||||||
|  |     while IFS= read -r url; do | ||||||
|  |         # Skip empty lines and comments | ||||||
|  |         if [[ -n "$url" && ! "$url" =~ ^[[:space:]]*# ]]; then | ||||||
|  |             add_git_url "$url" "$git_list_file" | ||||||
|  |         fi | ||||||
|  |     done < "$input_file" | ||||||
|  |      | ||||||
|  |     log_message "INFO" "Finished processing URLs from $input_file" | ||||||
|  | } | ||||||
|  |  | ||||||
| # Function to update status in STATUS.md | # Function to update status in STATUS.md | ||||||
| update_status() { | update_status() { | ||||||
|     local app_name=$1 |     local app_name=$1 | ||||||
|     local new_status=$2 |     local new_status=$2 | ||||||
|     local notes=${3:-""} |     local notes=${3:-""} | ||||||
|      |      | ||||||
|     # Escape special characters for sed |     # Validate inputs to prevent injection | ||||||
|     local escaped_app_name=$(printf '%s\n' "$app_name" | sed 's/[[\.*^$()+?{|]/\\&/g') |     if [[ -z "$app_name" ]] || [[ -z "$new_status" ]]; then | ||||||
|     local escaped_status=$(printf '%s\n' "$new_status" | sed 's/[[\.*^$()+?{|]/\\&/g') |         log_message "ERROR" "Empty app_name or new_status in update_status function" | ||||||
|     local escaped_notes=$(printf '%s\n' "$notes" | sed 's/[[\.*^$()+?{|]/\\&/g' | sed 's/&/&/g; s/</</g; s/>/>/g') |         return 1 | ||||||
|  |     fi | ||||||
|  |      | ||||||
|  |     # Sanitize inputs to prevent injection | ||||||
|  |     # Remove any pipe characters which would interfere with table format | ||||||
|  |     local clean_app_name=$(printf '%s\n' "$app_name" | sed 's/|//g; s/[[\.*^$()+?{|]/\\&/g') | ||||||
|  |     local clean_status=$(printf '%s\n' "$new_status" | sed 's/|//g; s/[[\.*^$()+?{|]/\\&/g') | ||||||
|  |     local clean_notes=$(printf '%s\n' "$notes" | sed 's/|//g; s/[[\.*^$()+?{|]/\\&/g' | sed 's/&/&/g; s/</</g; s/>/>/g') | ||||||
|      |      | ||||||
|     # Update status in the file - find the line with the app name and update its status |     # Update status in the file - find the line with the app name and update its status | ||||||
|     sed -i "s/^| $escaped_app_name |.*|.*|.*$/| $app_name |.*| $new_status | $escaped_notes |/" "$STATUS_FILE" |     # Use a more targeted sed pattern to reduce chance of unintended matches | ||||||
|  |     sed -i "s/^| $clean_app_name | \([^|]*\) | \([^|]*\) | \([^|]*\) |$/| $clean_app_name | \1 | $clean_status | $clean_notes |/" "$STATUS_FILE" | ||||||
|      |      | ||||||
|     log_message "INFO" "Updated status for $app_name to $new_status" |     log_message "INFO" "Updated status for $app_name to $new_status" | ||||||
| } | } | ||||||
| @@ -90,38 +160,82 @@ get_repo_name() { | |||||||
|     local url=$1 |     local url=$1 | ||||||
|     if [[ -z "$url" ]]; then |     if [[ -z "$url" ]]; then | ||||||
|         log_message "ERROR" "URL is empty in get_repo_name function" |         log_message "ERROR" "URL is empty in get_repo_name function" | ||||||
|  |         echo "unknown-repo" | ||||||
|         return 1 |         return 1 | ||||||
|     fi |     fi | ||||||
|      |      | ||||||
|     local repo_part |     # Extract the basename more securely by using parameter expansion | ||||||
|     repo_part=$(basename "$url") |     # First remove any trailing slashes | ||||||
|     repo_part=${repo_part%.git} |     local clean_url="${url%/}" | ||||||
|  |     local repo_part="${clean_url##*/}" | ||||||
|  |     repo_part="${repo_part%.git}" | ||||||
|      |      | ||||||
|     # Sanitize the repo name to contain only valid characters |     # Sanitize the repo name to contain only valid characters | ||||||
|     echo "$repo_part" | sed 's/[^a-zA-Z0-9._-]/-/g' |     local sanitized=$(printf '%s\n' "$repo_part" | sed 's/[^a-zA-Z0-9._-]/-/g') | ||||||
|  |      | ||||||
|  |     # Double-check to prevent path traversal | ||||||
|  |     sanitized=$(printf '%s\n' "$sanitized" | sed 's/\.\.//g; s/\/\///g') | ||||||
|  |      | ||||||
|  |     # Ensure the result is not empty | ||||||
|  |     if [[ -z "$sanitized" ]] || [[ "$sanitized" == "." ]] || [[ "$sanitized" == ".." ]]; then | ||||||
|  |         sanitized="unknown-repo-$(date +%s)" | ||||||
|  |     fi | ||||||
|  |      | ||||||
|  |     echo "$sanitized" | ||||||
| } | } | ||||||
|  |  | ||||||
| # Function to extract username/repo from URL for GitHub | # Function to extract username/repo from URL for GitHub/GitLab/other | ||||||
| get_username_repo() { | get_username_repo() { | ||||||
|     local url=$1 |     local url=$1 | ||||||
|     if [[ -z "$url" ]]; then |     if [[ -z "$url" ]]; then | ||||||
|         log_message "ERROR" "URL is empty in get_username_repo function" |         log_message "ERROR" "URL is empty in get_username_repo function" | ||||||
|  |         echo "unknown-user/unknown-repo" | ||||||
|         return 1 |         return 1 | ||||||
|     fi |     fi | ||||||
|      |      | ||||||
|     if [[ "$url" == *"github.com"* ]]; then |     # Clean the URL to prevent path traversal | ||||||
|  |     local clean_url="${url#*://}"   # Remove protocol | ||||||
|  |     clean_url="${clean_url#*[email]*/}"  # Remove potential user@host | ||||||
|  |      | ||||||
|  |     if [[ "$clean_url" == *"github.com"* ]]; then | ||||||
|         # Extract username/repo from GitHub URL |         # Extract username/repo from GitHub URL | ||||||
|         local path=${url#*github.com/} |         local path=${clean_url#*github.com/} | ||||||
|         path=${path%.git} |         path=${path%.git} | ||||||
|  |         # Ensure we have a valid path | ||||||
|  |         if [[ "$path" != *"/"* ]] || [[ "$path" == "/" ]]; then | ||||||
|  |             # If there's no slash, it might be malformed, use repo name | ||||||
|  |             path="unknown-user/$(get_repo_name "$url")" | ||||||
|  |         else | ||||||
|  |             # Sanitize the path to prevent directory traversal | ||||||
|  |             path=$(printf '%s\n' "$path" | sed 's/\.\.//g; s/\/\///g') | ||||||
|  |         fi | ||||||
|         echo "$path" |         echo "$path" | ||||||
|     elif [[ "$url" == *"gitlab.com"* ]]; then |     elif [[ "$clean_url" == *"gitlab.com"* ]]; then | ||||||
|         # Extract username/repo from GitLab URL |         # Extract username/repo from GitLab URL | ||||||
|         local path=${url#*gitlab.com/} |         local path=${clean_url#*gitlab.com/} | ||||||
|         path=${path%.git} |         path=${path%.git} | ||||||
|  |         # Ensure we have a valid path | ||||||
|  |         if [[ "$path" != *"/"* ]] || [[ "$path" == "/" ]]; then | ||||||
|  |             # If there's no slash, it might be malformed, use repo name | ||||||
|  |             path="unknown-user/$(get_repo_name "$url")" | ||||||
|  |         else | ||||||
|  |             # Sanitize the path to prevent directory traversal | ||||||
|  |             path=$(printf '%s\n' "$path" | sed 's/\.\.//g; s/\/\///g') | ||||||
|  |         fi | ||||||
|         echo "$path" |         echo "$path" | ||||||
|     else |     else | ||||||
|         # For other URLs, just return the repo name |         # For other URLs, try to extract pattern user/repo | ||||||
|         get_repo_name "$url" |         local path=${clean_url#*/}  # Remove host part | ||||||
|  |         if [[ "$path" == *"/"* ]]; then | ||||||
|  |             path=${path%.git} | ||||||
|  |             # Sanitize the path to prevent directory traversal | ||||||
|  |             path=$(printf '%s\n' "$path" | sed 's/\.\.//g; s/\/\///g') | ||||||
|  |         else | ||||||
|  |             # If no slash, use a generic format | ||||||
|  |             local repo=$(get_repo_name "$url") | ||||||
|  |             path="unknown-user/$repo" | ||||||
|  |         fi | ||||||
|  |         echo "$path" | ||||||
|     fi |     fi | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -173,7 +287,7 @@ run_packaging_script() { | |||||||
|         echo "$(date): Attempt $attempt/$MAX_RETRIES for $repo_name" >> "$WORKSPACES_DIR/packaging.log" |         echo "$(date): Attempt $attempt/$MAX_RETRIES for $repo_name" >> "$WORKSPACES_DIR/packaging.log" | ||||||
|          |          | ||||||
|         # Capture the output and error of the packaging function |         # Capture the output and error of the packaging function | ||||||
|         if package_application "$repo_name" "$username_repo" "$workspace_dir" "$artifact_dir" 2>"$workspace_dir/error.log"; then |         if package_application "$repo_name" "$username_repo" "$workspace_dir" "$artifact_dir" "$url" 2>"$workspace_dir/error.log"; then | ||||||
|             success=1 |             success=1 | ||||||
|             update_status "$repo_name" "✅ COMPLETE" "Packaged successfully on attempt $attempt" |             update_status "$repo_name" "✅ COMPLETE" "Packaged successfully on attempt $attempt" | ||||||
|             echo "$(date): Successfully packaged $repo_name on attempt $attempt" >> "$WORKSPACES_DIR/packaging.log" |             echo "$(date): Successfully packaged $repo_name on attempt $attempt" >> "$WORKSPACES_DIR/packaging.log" | ||||||
| @@ -207,11 +321,12 @@ package_application() { | |||||||
|     local username_repo=$2 |     local username_repo=$2 | ||||||
|     local workspace_dir=$3 |     local workspace_dir=$3 | ||||||
|     local artifact_dir=$4 |     local artifact_dir=$4 | ||||||
|  |     local url=${5:-"https://github.com/unknown-user/$repo_name"}  # Default URL if not provided | ||||||
|      |      | ||||||
|     local repo_path="$workspace_dir/repo" |     local repo_path="$workspace_dir/repo" | ||||||
|      |      | ||||||
|     # Use the function library to detect and package the application |     # Use the function library to detect and package the application | ||||||
|     detect_and_package "$repo_name" "$repo_path" "$artifact_dir" |     detect_and_package "$repo_name" "$repo_path" "$artifact_dir" "$url" | ||||||
| } | } | ||||||
|  |  | ||||||
| # Function to create a Dockerfile based on the application type | # Function to create a Dockerfile based on the application type | ||||||
| @@ -301,19 +416,46 @@ EOF | |||||||
|     fi |     fi | ||||||
| } | } | ||||||
|  |  | ||||||
|  | # Function to load URLs from Git URL list file | ||||||
|  | load_git_urls() { | ||||||
|  |     local git_list_file=${1:-"$GIT_URL_LIST"} | ||||||
|  |     local urls=() | ||||||
|  |      | ||||||
|  |     if [[ ! -f "$git_list_file" ]]; then | ||||||
|  |         log_message "ERROR" "Git URL list file does not exist: $git_list_file" | ||||||
|  |         return 1 | ||||||
|  |     fi | ||||||
|  |      | ||||||
|  |     while IFS= read -r line; do | ||||||
|  |         # Skip empty lines and comments | ||||||
|  |         if [[ -n "$line" && ! "$line" =~ ^[[:space:]]*# ]]; then | ||||||
|  |             # Validate that the line looks like a URL | ||||||
|  |             if [[ "$line" =~ ^https?:// ]]; then | ||||||
|  |                 urls+=("$line") | ||||||
|  |             else | ||||||
|  |                 log_message "WARN" "Invalid URL format skipped: $line" | ||||||
|  |             fi | ||||||
|  |         fi | ||||||
|  |     done < "$git_list_file" | ||||||
|  |      | ||||||
|  |     # Print the urls array to stdout so the caller can capture it | ||||||
|  |     printf '%s\n' "${urls[@]}" | ||||||
|  | } | ||||||
|  |  | ||||||
| # Main function to process all applications | # Main function to process all applications | ||||||
| main() { | main() { | ||||||
|     log_message "INFO" "Starting Cloudron packaging process" |     log_message "INFO" "Starting Cloudron packaging process" | ||||||
|      |      | ||||||
|     # Read URLs from GitUrlList.txt |     # Validate that required files exist | ||||||
|     local urls=() |     if [[ ! -f "$SCRIPT_DIR/package-functions.sh" ]]; then | ||||||
|     while IFS= read -r line; do |         log_message "ERROR" "Package functions file does not exist: $SCRIPT_DIR/package-functions.sh" | ||||||
|         if [[ -n "$line" && ! "$line" =~ ^[[:space:]]*# ]]; then |         exit 1 | ||||||
|             urls+=("$line") |     fi | ||||||
|         fi |  | ||||||
|     done < "$GIT_URL_LIST" |  | ||||||
|      |      | ||||||
|     local total=${#urls[@]} |     # Load URLs from the git list file | ||||||
|  |     local url_list | ||||||
|  |     mapfile -t url_list < <(load_git_urls) | ||||||
|  |     local total=${#url_list[@]} | ||||||
|     log_message "INFO" "Found $total URLs to process" |     log_message "INFO" "Found $total URLs to process" | ||||||
|      |      | ||||||
|     # Process applications in batches of 3 for parallel execution |     # Process applications in batches of 3 for parallel execution | ||||||
| @@ -324,11 +466,11 @@ main() { | |||||||
|         local end=$((i + 3)) |         local end=$((i + 3)) | ||||||
|         [ $end -gt $total ] && end=$total |         [ $end -gt $total ] && end=$total | ||||||
|          |          | ||||||
|         log_message "INFO" "Starting batch with applications $(printf '%s; ' "${urls[@]:i:3}")" |         log_message "INFO" "Starting batch with applications $(printf '%s; ' "${url_list[@]:i:3}")" | ||||||
|          |          | ||||||
|         for ((j = i; j < end; j++)); do |         for ((j = i; j < end; j++)); do | ||||||
|             log_message "INFO" "Starting packaging for ${urls[$j]}" |             log_message "INFO" "Starting packaging for ${url_list[$j]}" | ||||||
|             run_packaging_script "${urls[$j]}" & |             run_packaging_script "${url_list[$j]}" & | ||||||
|         done |         done | ||||||
|          |          | ||||||
|         # Wait for all background processes to complete |         # Wait for all background processes to complete | ||||||
| @@ -346,6 +488,9 @@ main() { | |||||||
|         local in_progress=$(grep -o "🔄 IN PROGRESS" "$STATUS_FILE" | wc -l) |         local in_progress=$(grep -o "🔄 IN PROGRESS" "$STATUS_FILE" | wc -l) | ||||||
|         local pending=$((total - completed - failed - in_progress)) |         local pending=$((total - completed - failed - in_progress)) | ||||||
|          |          | ||||||
|  |         # Ensure we don't have negative pending due to counting issues | ||||||
|  |         [ $pending -lt 0 ] && pending=0 | ||||||
|  |          | ||||||
|         # Update summary section in STATUS.md |         # Update summary section in STATUS.md | ||||||
|         sed -i '/## Progress Summary/Q' "$STATUS_FILE" |         sed -i '/## Progress Summary/Q' "$STATUS_FILE" | ||||||
|         cat >> "$STATUS_FILE" << EOF |         cat >> "$STATUS_FILE" << EOF | ||||||
|   | |||||||
| @@ -10,28 +10,68 @@ package_nodejs_app() { | |||||||
|     local app_name=$1 |     local app_name=$1 | ||||||
|     local app_dir=$2 |     local app_dir=$2 | ||||||
|     local artifact_dir=$3 |     local artifact_dir=$3 | ||||||
|  |     local app_url=${4:-"https://github.com/unknown-user/$app_name"}  # Default URL if not provided | ||||||
|      |      | ||||||
|     cd "$app_dir" |     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 |     # Create Cloudron manifest | ||||||
|     cat > app.manifest << EOF |     cat > app.manifest << EOF | ||||||
| { | { | ||||||
|     "id": "com.$(echo "$app_name" | sed 's/[^a-zA-Z0-9]/./g').cloudron", |     "id": "com.$(echo "$repo_path" | sed 's/[^a-zA-Z0-9]/./g').cloudron", | ||||||
|     "title": "$app_name", |     "title": "$app_name", | ||||||
|     "version": "1.0.0", |     "version": "1.0.0", | ||||||
|     "build": "1", |     "build": "1", | ||||||
|     "description": "Cloudron package for $app_name", |     "description": "Cloudron package for $app_name", | ||||||
|     "author": "Auto-generated", |     "author": "Auto-generated", | ||||||
|     "website": "https://github.com/$app_name", |     "website": "$app_url", | ||||||
|     "admin": false, |     "admin": false, | ||||||
|     "tags": ["nodejs", "auto-generated"], |     "tags": ["nodejs", "auto-generated"], | ||||||
|     "logo": "https://github.com/fluidicon.png", |     "logo": "https://github.com/fluidicon.png", | ||||||
|     "documentation": "https://github.com/$app_name", |     "documentation": "$app_url", | ||||||
|     "changelog": "Initial packaging" |     "changelog": "Initial packaging" | ||||||
| } | } | ||||||
| EOF | EOF | ||||||
|      |      | ||||||
|     # Create Dockerfile for Node.js |     # 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 |     cat > Dockerfile << EOF | ||||||
| FROM node:18-alpine | FROM node:18-alpine | ||||||
|  |  | ||||||
| @@ -42,9 +82,9 @@ RUN npm install --only=production | |||||||
|  |  | ||||||
| COPY . . | COPY . . | ||||||
|  |  | ||||||
| EXPOSE 3000 | EXPOSE $port | ||||||
|  |  | ||||||
| CMD ["npm", "start"] | CMD $start_cmd | ||||||
| EOF | EOF | ||||||
|      |      | ||||||
|     # Build Docker image |     # Build Docker image | ||||||
| @@ -70,28 +110,68 @@ package_python_app() { | |||||||
|     local app_name=$1 |     local app_name=$1 | ||||||
|     local app_dir=$2 |     local app_dir=$2 | ||||||
|     local artifact_dir=$3 |     local artifact_dir=$3 | ||||||
|  |     local app_url=${4:-"https://github.com/unknown-user/$app_name"}  # Default URL if not provided | ||||||
|      |      | ||||||
|     cd "$app_dir" |     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 |     # Create Cloudron manifest | ||||||
|     cat > app.manifest << EOF |     cat > app.manifest << EOF | ||||||
| { | { | ||||||
|     "id": "com.$(echo "$app_name" | sed 's/[^a-zA-Z0-9]/./g').cloudron", |     "id": "com.$(echo "$repo_path" | sed 's/[^a-zA-Z0-9]/./g').cloudron", | ||||||
|     "title": "$app_name", |     "title": "$app_name", | ||||||
|     "version": "1.0.0", |     "version": "1.0.0", | ||||||
|     "build": "1", |     "build": "1", | ||||||
|     "description": "Cloudron package for $app_name", |     "description": "Cloudron package for $app_name", | ||||||
|     "author": "Auto-generated", |     "author": "Auto-generated", | ||||||
|     "website": "https://github.com/$app_name", |     "website": "$app_url", | ||||||
|     "admin": false, |     "admin": false, | ||||||
|     "tags": ["python", "auto-generated"], |     "tags": ["python", "auto-generated"], | ||||||
|     "logo": "https://github.com/fluidicon.png", |     "logo": "https://github.com/fluidicon.png", | ||||||
|     "documentation": "https://github.com/$app_name", |     "documentation": "$app_url", | ||||||
|     "changelog": "Initial packaging" |     "changelog": "Initial packaging" | ||||||
| } | } | ||||||
| EOF | EOF | ||||||
|      |      | ||||||
|     # Create Dockerfile for Python |     # 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 |     cat > Dockerfile << EOF | ||||||
| FROM python:3.11-slim | FROM python:3.11-slim | ||||||
|  |  | ||||||
| @@ -102,9 +182,9 @@ RUN pip install --no-cache-dir -r requirements.txt | |||||||
|  |  | ||||||
| COPY . . | COPY . . | ||||||
|  |  | ||||||
| EXPOSE 8000 | EXPOSE $port | ||||||
|  |  | ||||||
| CMD ["python", "app.py"] | CMD $start_cmd | ||||||
| EOF | EOF | ||||||
|      |      | ||||||
|     # Build Docker image |     # Build Docker image | ||||||
| @@ -130,34 +210,57 @@ package_php_app() { | |||||||
|     local app_name=$1 |     local app_name=$1 | ||||||
|     local app_dir=$2 |     local app_dir=$2 | ||||||
|     local artifact_dir=$3 |     local artifact_dir=$3 | ||||||
|  |     local app_url=${4:-"https://github.com/unknown-user/$app_name"}  # Default URL if not provided | ||||||
|      |      | ||||||
|     cd "$app_dir" |     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 |     # Create Cloudron manifest | ||||||
|     cat > app.manifest << EOF |     cat > app.manifest << EOF | ||||||
| { | { | ||||||
|     "id": "com.$(echo "$app_name" | sed 's/[^a-zA-Z0-9]/./g').cloudron", |     "id": "com.$(echo "$repo_path" | sed 's/[^a-zA-Z0-9]/./g').cloudron", | ||||||
|     "title": "$app_name", |     "title": "$app_name", | ||||||
|     "version": "1.0.0", |     "version": "1.0.0", | ||||||
|     "build": "1", |     "build": "1", | ||||||
|     "description": "Cloudron package for $app_name", |     "description": "Cloudron package for $app_name", | ||||||
|     "author": "Auto-generated", |     "author": "Auto-generated", | ||||||
|     "website": "https://github.com/$app_name", |     "website": "$app_url", | ||||||
|     "admin": false, |     "admin": false, | ||||||
|     "tags": ["php", "auto-generated"], |     "tags": ["php", "auto-generated"], | ||||||
|     "logo": "https://github.com/fluidicon.png", |     "logo": "https://github.com/fluidicon.png", | ||||||
|     "documentation": "https://github.com/$app_name", |     "documentation": "$app_url", | ||||||
|     "changelog": "Initial packaging" |     "changelog": "Initial packaging" | ||||||
| } | } | ||||||
| EOF | EOF | ||||||
|      |      | ||||||
|     # Create Dockerfile for PHP |     # Create Dockerfile for PHP with better configuration | ||||||
|     cat > Dockerfile << EOF |     cat > Dockerfile << EOF | ||||||
| FROM php:8.1-apache | FROM php:8.1-apache | ||||||
|  |  | ||||||
| RUN docker-php-ext-install mysqli && docker-php-ext-enable mysqli | # Install common PHP extensions | ||||||
|  | RUN docker-php-ext-install mysqli pdo pdo_mysql && docker-php-ext-enable mysqli pdo pdo_mysql | ||||||
|  |  | ||||||
| COPY . /var/www/html/ | # 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 | EXPOSE 80 | ||||||
|  |  | ||||||
| @@ -187,41 +290,72 @@ package_go_app() { | |||||||
|     local app_name=$1 |     local app_name=$1 | ||||||
|     local app_dir=$2 |     local app_dir=$2 | ||||||
|     local artifact_dir=$3 |     local artifact_dir=$3 | ||||||
|  |     local app_url=${4:-"https://github.com/unknown-user/$app_name"}  # Default URL if not provided | ||||||
|      |      | ||||||
|     cd "$app_dir" |     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 |     # Create Cloudron manifest | ||||||
|     cat > app.manifest << EOF |     cat > app.manifest << EOF | ||||||
| { | { | ||||||
|     "id": "com.$(echo "$app_name" | sed 's/[^a-zA-Z0-9]/./g').cloudron", |     "id": "com.$(echo "$repo_path" | sed 's/[^a-zA-Z0-9]/./g').cloudron", | ||||||
|     "title": "$app_name", |     "title": "$app_name", | ||||||
|     "version": "1.0.0", |     "version": "1.0.0", | ||||||
|     "build": "1", |     "build": "1", | ||||||
|     "description": "Cloudron package for $app_name", |     "description": "Cloudron package for $app_name", | ||||||
|     "author": "Auto-generated", |     "author": "Auto-generated", | ||||||
|     "website": "https://github.com/$app_name", |     "website": "$app_url", | ||||||
|     "admin": false, |     "admin": false, | ||||||
|     "tags": ["go", "auto-generated"], |     "tags": ["go", "auto-generated"], | ||||||
|     "logo": "https://github.com/fluidicon.png", |     "logo": "https://github.com/fluidicon.png", | ||||||
|     "documentation": "https://github.com/$app_name", |     "documentation": "$app_url", | ||||||
|     "changelog": "Initial packaging" |     "changelog": "Initial packaging" | ||||||
| } | } | ||||||
| EOF | EOF | ||||||
|      |      | ||||||
|     # Create Dockerfile for Go |     # 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 |     cat > Dockerfile << EOF | ||||||
| FROM golang:1.21-alpine AS builder | FROM golang:1.21-alpine AS builder | ||||||
|  |  | ||||||
| WORKDIR /app | WORKDIR /app | ||||||
| COPY . . | COPY . . | ||||||
| RUN CGO_ENABLED=0 GOOS=linux go build -o myapp . | RUN CGO_ENABLED=0 GOOS=linux go build -o $binary_name . | ||||||
|  |  | ||||||
| FROM alpine:latest | FROM alpine:latest | ||||||
| RUN apk --no-cache add ca-certificates | RUN apk --no-cache add ca-certificates | ||||||
| WORKDIR /root/ | WORKDIR /root/ | ||||||
| COPY --from=builder /app/myapp . | COPY --from=builder /app/$binary_name . | ||||||
| EXPOSE 8080 | EXPOSE 8080 | ||||||
| CMD ["./myapp"] | CMD ["./$binary_name"] | ||||||
| EOF | EOF | ||||||
|      |      | ||||||
|     # Build Docker image |     # Build Docker image | ||||||
| @@ -249,32 +383,42 @@ smoke_test_docker_image() { | |||||||
|      |      | ||||||
|     echo "Performing smoke test on $docker_image for $app_name" |     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 |     # Run the container briefly to test if it starts correctly | ||||||
|     local container_name="smoke-test-${app_name//[^a-zA-Z0-9]/-}-$(date +%s)" |     local container_name="smoke-test-${app_name//[^a-zA-Z0-9]/-}-$(date +%s)" | ||||||
|      |      | ||||||
|     # Try to run the container and check if it starts without immediate failure |     # Run without specific health check initially, just see if container starts and stays running | ||||||
|     if docker run -d --name "$container_name" --health-cmd="curl -f http://localhost/ || exit 1" --health-interval=5s --health-timeout=3s --health-retries=3 "$docker_image"; then |     if ! docker run -d --name "$container_name" "$docker_image" >/dev/null 2>&1; then | ||||||
|         # Wait a few seconds to see if the container stays running |  | ||||||
|         sleep 10 |  | ||||||
|          |  | ||||||
|         # Check if the container is still running and healthy |  | ||||||
|         if [ "$(docker inspect -f '{{.State.Status}}' "$container_name" 2>/dev/null)" = "running" ]; then |  | ||||||
|             echo "Smoke test passed for $app_name" |  | ||||||
|             # Stop and remove the test container |  | ||||||
|             docker stop "$container_name" > /dev/null 2>&1 |  | ||||||
|             docker rm "$container_name" > /dev/null 2>&1 |  | ||||||
|             return 0 |  | ||||||
|         else |  | ||||||
|             echo "Container for $app_name did not stay running during smoke test" |  | ||||||
|             # Get logs for debugging |  | ||||||
|             docker logs "$container_name" 2>&1 | head -20 |  | ||||||
|             docker stop "$container_name" > /dev/null 2>&1 |  | ||||||
|             docker rm "$container_name" > /dev/null 2>&1 |  | ||||||
|             return 1 |  | ||||||
|         fi |  | ||||||
|     else |  | ||||||
|         echo "Failed to start container for $app_name during smoke test" |         echo "Failed to start container for $app_name during smoke test" | ||||||
|         docker rm "$container_name" > /dev/null 2>&1 |         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 |         return 1 | ||||||
|     fi |     fi | ||||||
| } | } | ||||||
| @@ -284,26 +428,27 @@ detect_and_package() { | |||||||
|     local app_name=$1 |     local app_name=$1 | ||||||
|     local app_dir=$2 |     local app_dir=$2 | ||||||
|     local artifact_dir=$3 |     local artifact_dir=$3 | ||||||
|  |     local app_url=${4:-"https://github.com/unknown-user/$app_name"}  # Default URL if not provided | ||||||
|      |      | ||||||
|     cd "$app_dir" |     cd "$app_dir" | ||||||
|      |      | ||||||
|     # Detect application type based on files |     # Detect application type based on files | ||||||
|     if [ -f "package.json" ]; then |     if [ -f "package.json" ]; then | ||||||
|         echo "Detected Node.js application" |         echo "Detected Node.js application" | ||||||
|         package_nodejs_app "$app_name" "$app_dir" "$artifact_dir" |         package_nodejs_app "$app_name" "$app_dir" "$artifact_dir" "$app_url" | ||||||
|     elif [ -f "requirements.txt" ] || [ -f "setup.py" ]; then |     elif [ -f "requirements.txt" ] || [ -f "setup.py" ]; then | ||||||
|         echo "Detected Python application" |         echo "Detected Python application" | ||||||
|         package_python_app "$app_name" "$app_dir" "$artifact_dir" |         package_python_app "$app_name" "$app_dir" "$artifact_dir" "$app_url" | ||||||
|     elif [ -f "composer.json" ]; then |     elif [ -f "composer.json" ]; then | ||||||
|         echo "Detected PHP application" |         echo "Detected PHP application" | ||||||
|         package_php_app "$app_name" "$app_dir" "$artifact_dir" |         package_php_app "$app_name" "$app_dir" "$artifact_dir" "$app_url" | ||||||
|     elif [ -f "go.mod" ] || [ -f "*.go" ]; then |     elif [ -f "go.mod" ] || [ -f "*.go" ]; then | ||||||
|         echo "Detected Go application" |         echo "Detected Go application" | ||||||
|         package_go_app "$app_name" "$app_dir" "$artifact_dir" |         package_go_app "$app_name" "$app_dir" "$artifact_dir" "$app_url" | ||||||
|     else |     else | ||||||
|         # Default generic approach |         # Default generic approach | ||||||
|         echo "Application type not detected, using generic approach" |         echo "Application type not detected, using generic approach" | ||||||
|         package_generic_app "$app_name" "$app_dir" "$artifact_dir" |         package_generic_app "$app_name" "$app_dir" "$artifact_dir" "$app_url" | ||||||
|     fi |     fi | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -312,41 +457,113 @@ package_generic_app() { | |||||||
|     local app_name=$1 |     local app_name=$1 | ||||||
|     local app_dir=$2 |     local app_dir=$2 | ||||||
|     local artifact_dir=$3 |     local artifact_dir=$3 | ||||||
|  |     local app_url=${4:-"https://github.com/unknown-user/$app_name"}  # Default URL if not provided | ||||||
|      |      | ||||||
|     cd "$app_dir" |     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 |     # Create Cloudron manifest | ||||||
|     cat > app.manifest << EOF |     cat > app.manifest << EOF | ||||||
| { | { | ||||||
|     "id": "com.$(echo "$app_name" | sed 's/[^a-zA-Z0-9]/./g').cloudron", |     "id": "com.$(echo "$repo_path" | sed 's/[^a-zA-Z0-9]/./g').cloudron", | ||||||
|     "title": "$app_name", |     "title": "$app_name", | ||||||
|     "version": "1.0.0", |     "version": "1.0.0", | ||||||
|     "build": "1", |     "build": "1", | ||||||
|     "description": "Cloudron package for $app_name", |     "description": "Cloudron package for $app_name", | ||||||
|     "author": "Auto-generated", |     "author": "Auto-generated", | ||||||
|     "website": "https://github.com/$app_name", |     "website": "$app_url", | ||||||
|     "admin": false, |     "admin": false, | ||||||
|     "tags": ["generic", "auto-generated"], |     "tags": ["generic", "auto-generated"], | ||||||
|     "logo": "https://github.com/fluidicon.png", |     "logo": "https://github.com/fluidicon.png", | ||||||
|     "documentation": "https://github.com/$app_name", |     "documentation": "$app_url", | ||||||
|     "changelog": "Initial packaging" |     "changelog": "Initial packaging" | ||||||
| } | } | ||||||
| EOF | EOF | ||||||
|      |      | ||||||
|     # Create a basic Dockerfile |     # Create a basic Dockerfile that tries to run common application types | ||||||
|     cat > Dockerfile << EOF |     cat > Dockerfile << 'DOCKERFILE_EOF' | ||||||
| FROM alpine:latest | FROM alpine:latest | ||||||
|  |  | ||||||
| WORKDIR /app | WORKDIR /app | ||||||
|  |  | ||||||
| COPY . . | COPY . . | ||||||
|  |  | ||||||
| RUN apk add --no-cache bash curl tar | # 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 | EXPOSE 8080 | ||||||
|  |  | ||||||
| CMD ["sh", "-c", "while true; do sleep 30; done"] | CMD ["/run-app.sh"] | ||||||
| EOF | DOCKERFILE_EOF | ||||||
|      |      | ||||||
|     # Build Docker image |     # Build Docker image | ||||||
|     local docker_image="tsysdevstack-cloudron-buildtest-${app_name//[^a-zA-Z0-9]/-}:latest" |     local docker_image="tsysdevstack-cloudron-buildtest-${app_name//[^a-zA-Z0-9]/-}:latest" | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user