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:
@@ -10,11 +10,11 @@ set -o pipefail # Exit on pipe failures
|
||||
|
||||
# Configuration
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
OUTPUT_DIR="$SCRIPT_DIR/output"
|
||||
OUTPUT_DIR="$SCRIPT_DIR"
|
||||
ARTIFACTS_DIR="$OUTPUT_DIR/CloudronPackages-Artifacts"
|
||||
WORKSPACES_DIR="$OUTPUT_DIR/CloudronPackages-Workspaces"
|
||||
STATUS_FILE="$SCRIPT_DIR/collab/STATUS.md"
|
||||
GIT_URL_LIST="$SCRIPT_DIR/collab/GitUrlList.txt"
|
||||
STATUS_FILE="$(dirname "$SCRIPT_DIR")/collab/STATUS.md"
|
||||
GIT_URL_LIST="$(dirname "$SCRIPT_DIR")/collab/GitUrlList.txt"
|
||||
HUMAN_HELP_DIR="$WORKSPACES_DIR/human-help-required"
|
||||
MAX_RETRIES=5
|
||||
LOG_FILE="$WORKSPACES_DIR/packaging.log"
|
||||
@@ -42,8 +42,8 @@ perform_audit() {
|
||||
|
||||
# Count total, completed, failed, and in-progress applications
|
||||
local total_count=$(grep -c "https://" "$GIT_URL_LIST" || echo 0)
|
||||
local completed_count=$(grep -c "✅ COMPLETE" "$STATUS_FILE" || echo 0)
|
||||
local failed_count=$(grep -c "🛑 FAILED" "$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" | grep -v "Human Help Required" || 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))
|
||||
|
||||
@@ -68,19 +68,89 @@ perform_audit() {
|
||||
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
|
||||
update_status() {
|
||||
local app_name=$1
|
||||
local new_status=$2
|
||||
local notes=${3:-""}
|
||||
|
||||
# Escape special characters for sed
|
||||
local escaped_app_name=$(printf '%s\n' "$app_name" | sed 's/[[\.*^$()+?{|]/\\&/g')
|
||||
local escaped_status=$(printf '%s\n' "$new_status" | sed 's/[[\.*^$()+?{|]/\\&/g')
|
||||
local escaped_notes=$(printf '%s\n' "$notes" | sed 's/[[\.*^$()+?{|]/\\&/g' | sed 's/&/&/g; s/</</g; s/>/>/g')
|
||||
# Validate inputs to prevent injection
|
||||
if [[ -z "$app_name" ]] || [[ -z "$new_status" ]]; then
|
||||
log_message "ERROR" "Empty app_name or new_status in update_status function"
|
||||
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
|
||||
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"
|
||||
}
|
||||
@@ -90,38 +160,82 @@ get_repo_name() {
|
||||
local url=$1
|
||||
if [[ -z "$url" ]]; then
|
||||
log_message "ERROR" "URL is empty in get_repo_name function"
|
||||
echo "unknown-repo"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local repo_part
|
||||
repo_part=$(basename "$url")
|
||||
repo_part=${repo_part%.git}
|
||||
# Extract the basename more securely by using parameter expansion
|
||||
# First remove any trailing slashes
|
||||
local clean_url="${url%/}"
|
||||
local repo_part="${clean_url##*/}"
|
||||
repo_part="${repo_part%.git}"
|
||||
|
||||
# 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() {
|
||||
local url=$1
|
||||
if [[ -z "$url" ]]; then
|
||||
log_message "ERROR" "URL is empty in get_username_repo function"
|
||||
echo "unknown-user/unknown-repo"
|
||||
return 1
|
||||
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
|
||||
local path=${url#*github.com/}
|
||||
local path=${clean_url#*github.com/}
|
||||
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"
|
||||
elif [[ "$url" == *"gitlab.com"* ]]; then
|
||||
elif [[ "$clean_url" == *"gitlab.com"* ]]; then
|
||||
# Extract username/repo from GitLab URL
|
||||
local path=${url#*gitlab.com/}
|
||||
local path=${clean_url#*gitlab.com/}
|
||||
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"
|
||||
else
|
||||
# For other URLs, just return the repo name
|
||||
get_repo_name "$url"
|
||||
# For other URLs, try to extract pattern user/repo
|
||||
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
|
||||
}
|
||||
|
||||
@@ -173,7 +287,7 @@ run_packaging_script() {
|
||||
echo "$(date): Attempt $attempt/$MAX_RETRIES for $repo_name" >> "$WORKSPACES_DIR/packaging.log"
|
||||
|
||||
# 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
|
||||
update_status "$repo_name" "✅ COMPLETE" "Packaged successfully on attempt $attempt"
|
||||
echo "$(date): Successfully packaged $repo_name on attempt $attempt" >> "$WORKSPACES_DIR/packaging.log"
|
||||
@@ -207,11 +321,12 @@ package_application() {
|
||||
local username_repo=$2
|
||||
local workspace_dir=$3
|
||||
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"
|
||||
|
||||
# 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
|
||||
@@ -301,19 +416,46 @@ EOF
|
||||
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() {
|
||||
log_message "INFO" "Starting Cloudron packaging process"
|
||||
|
||||
# Read URLs from GitUrlList.txt
|
||||
local urls=()
|
||||
while IFS= read -r line; do
|
||||
if [[ -n "$line" && ! "$line" =~ ^[[:space:]]*# ]]; then
|
||||
urls+=("$line")
|
||||
fi
|
||||
done < "$GIT_URL_LIST"
|
||||
# Validate that required files exist
|
||||
if [[ ! -f "$SCRIPT_DIR/package-functions.sh" ]]; then
|
||||
log_message "ERROR" "Package functions file does not exist: $SCRIPT_DIR/package-functions.sh"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
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"
|
||||
|
||||
# Process applications in batches of 3 for parallel execution
|
||||
@@ -324,11 +466,11 @@ main() {
|
||||
local end=$((i + 3))
|
||||
[ $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
|
||||
log_message "INFO" "Starting packaging for ${urls[$j]}"
|
||||
run_packaging_script "${urls[$j]}" &
|
||||
log_message "INFO" "Starting packaging for ${url_list[$j]}"
|
||||
run_packaging_script "${url_list[$j]}" &
|
||||
done
|
||||
|
||||
# Wait for all background processes to complete
|
||||
@@ -346,6 +488,9 @@ main() {
|
||||
local in_progress=$(grep -o "🔄 IN PROGRESS" "$STATUS_FILE" | wc -l)
|
||||
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
|
||||
sed -i '/## Progress Summary/Q' "$STATUS_FILE"
|
||||
cat >> "$STATUS_FILE" << EOF
|
||||
|
||||
@@ -10,28 +10,68 @@ 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 "$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",
|
||||
"version": "1.0.0",
|
||||
"build": "1",
|
||||
"description": "Cloudron package for $app_name",
|
||||
"author": "Auto-generated",
|
||||
"website": "https://github.com/$app_name",
|
||||
"website": "$app_url",
|
||||
"admin": false,
|
||||
"tags": ["nodejs", "auto-generated"],
|
||||
"logo": "https://github.com/fluidicon.png",
|
||||
"documentation": "https://github.com/$app_name",
|
||||
"documentation": "$app_url",
|
||||
"changelog": "Initial packaging"
|
||||
}
|
||||
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
|
||||
FROM node:18-alpine
|
||||
|
||||
@@ -42,9 +82,9 @@ RUN npm install --only=production
|
||||
|
||||
COPY . .
|
||||
|
||||
EXPOSE 3000
|
||||
EXPOSE $port
|
||||
|
||||
CMD ["npm", "start"]
|
||||
CMD $start_cmd
|
||||
EOF
|
||||
|
||||
# Build Docker image
|
||||
@@ -70,28 +110,68 @@ 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 "$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",
|
||||
"version": "1.0.0",
|
||||
"build": "1",
|
||||
"description": "Cloudron package for $app_name",
|
||||
"author": "Auto-generated",
|
||||
"website": "https://github.com/$app_name",
|
||||
"website": "$app_url",
|
||||
"admin": false,
|
||||
"tags": ["python", "auto-generated"],
|
||||
"logo": "https://github.com/fluidicon.png",
|
||||
"documentation": "https://github.com/$app_name",
|
||||
"documentation": "$app_url",
|
||||
"changelog": "Initial packaging"
|
||||
}
|
||||
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
|
||||
FROM python:3.11-slim
|
||||
|
||||
@@ -102,9 +182,9 @@ RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
COPY . .
|
||||
|
||||
EXPOSE 8000
|
||||
EXPOSE $port
|
||||
|
||||
CMD ["python", "app.py"]
|
||||
CMD $start_cmd
|
||||
EOF
|
||||
|
||||
# Build Docker image
|
||||
@@ -130,34 +210,57 @@ 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 "$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",
|
||||
"version": "1.0.0",
|
||||
"build": "1",
|
||||
"description": "Cloudron package for $app_name",
|
||||
"author": "Auto-generated",
|
||||
"website": "https://github.com/$app_name",
|
||||
"website": "$app_url",
|
||||
"admin": false,
|
||||
"tags": ["php", "auto-generated"],
|
||||
"logo": "https://github.com/fluidicon.png",
|
||||
"documentation": "https://github.com/$app_name",
|
||||
"documentation": "$app_url",
|
||||
"changelog": "Initial packaging"
|
||||
}
|
||||
EOF
|
||||
|
||||
# Create Dockerfile for PHP
|
||||
# Create Dockerfile for PHP with better configuration
|
||||
cat > Dockerfile << EOF
|
||||
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
|
||||
|
||||
@@ -187,41 +290,72 @@ 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 "$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",
|
||||
"version": "1.0.0",
|
||||
"build": "1",
|
||||
"description": "Cloudron package for $app_name",
|
||||
"author": "Auto-generated",
|
||||
"website": "https://github.com/$app_name",
|
||||
"website": "$app_url",
|
||||
"admin": false,
|
||||
"tags": ["go", "auto-generated"],
|
||||
"logo": "https://github.com/fluidicon.png",
|
||||
"documentation": "https://github.com/$app_name",
|
||||
"documentation": "$app_url",
|
||||
"changelog": "Initial packaging"
|
||||
}
|
||||
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
|
||||
FROM golang:1.21-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
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
|
||||
RUN apk --no-cache add ca-certificates
|
||||
WORKDIR /root/
|
||||
COPY --from=builder /app/myapp .
|
||||
COPY --from=builder /app/$binary_name .
|
||||
EXPOSE 8080
|
||||
CMD ["./myapp"]
|
||||
CMD ["./$binary_name"]
|
||||
EOF
|
||||
|
||||
# Build Docker image
|
||||
@@ -249,32 +383,42 @@ smoke_test_docker_image() {
|
||||
|
||||
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)"
|
||||
|
||||
# Try to run the container and check if it starts without immediate failure
|
||||
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
|
||||
# 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
|
||||
# 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
|
||||
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
|
||||
}
|
||||
@@ -284,26 +428,27 @@ 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"
|
||||
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"
|
||||
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"
|
||||
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"
|
||||
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"
|
||||
package_generic_app "$app_name" "$app_dir" "$artifact_dir" "$app_url"
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -312,41 +457,113 @@ 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 "$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",
|
||||
"version": "1.0.0",
|
||||
"build": "1",
|
||||
"description": "Cloudron package for $app_name",
|
||||
"author": "Auto-generated",
|
||||
"website": "https://github.com/$app_name",
|
||||
"website": "$app_url",
|
||||
"admin": false,
|
||||
"tags": ["generic", "auto-generated"],
|
||||
"logo": "https://github.com/fluidicon.png",
|
||||
"documentation": "https://github.com/$app_name",
|
||||
"documentation": "$app_url",
|
||||
"changelog": "Initial packaging"
|
||||
}
|
||||
EOF
|
||||
|
||||
# Create a basic Dockerfile
|
||||
cat > Dockerfile << EOF
|
||||
# Create a basic Dockerfile that tries to run common application types
|
||||
cat > Dockerfile << 'DOCKERFILE_EOF'
|
||||
FROM alpine:latest
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
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
|
||||
|
||||
CMD ["sh", "-c", "while true; do sleep 30; done"]
|
||||
EOF
|
||||
CMD ["/run-app.sh"]
|
||||
DOCKERFILE_EOF
|
||||
|
||||
# Build Docker image
|
||||
local docker_image="tsysdevstack-cloudron-buildtest-${app_name//[^a-zA-Z0-9]/-}:latest"
|
||||
|
||||
Reference in New Issue
Block a user