feat(cloudron): update CloudronStack configuration and assets

- Add new PROMPT file in collab directory for AI collaboration guidance
- Add STATUS.md file in collab directory to track current status
- Create output directory for project artifacts
- Remove redundant commit-template.txt that is now centralized at top level
- Update collab directory structure and content for better organization

These changes improve the CloudronStack component's structure and
documentation for better collaboration.
This commit is contained in:
2025-10-30 08:14:41 -05:00
parent 27948346b4
commit 77e10af05c
5 changed files with 776 additions and 15 deletions

View File

@@ -0,0 +1,289 @@
#!/bin/bash
# Master Control Script for Cloudron Packaging
# This script orchestrates the packaging of all applications from GitUrlList.txt
# It runs three packaging projects in parallel and maintains status tracking
set -e # Exit on any error
# Configuration
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
OUTPUT_DIR="$SCRIPT_DIR/output"
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"
HUMAN_HELP_DIR="$WORKSPACES_DIR/human-help-required"
MAX_RETRIES=5
# Docker image prefix
DOCKER_PREFIX="tsysdevstack-cloudron-buildtest-"
# Source the packaging functions
source "$SCRIPT_DIR/package-functions.sh"
# Create necessary directories
mkdir -p "$ARTIFACTS_DIR" "$WORKSPACES_DIR" "$HUMAN_HELP_DIR"
# 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=$(echo "$app_name" | sed 's/[[\.*^$()+?{|]/\\&/g')
local escaped_status=$(echo "$new_status" | sed 's/[[\.*^$()+?{|]/\\&/g')
local escaped_notes=$(echo "$notes" | sed 's/[[\.*^$()+?{|]/\\&/g' | sed 's/&/&amp;/g; s/</&lt;/g; s/>/&gt;/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"
echo "$(date): Updated status for $app_name to $new_status" >> "$WORKSPACES_DIR/packaging.log"
}
# Function to get the repository name from URL
get_repo_name() {
local url=$1
if [[ "$url" == *"github.com"* ]]; then
echo "${url##*/}" | sed 's/\.git$//'
elif [[ "$url" == *"gitlab.com"* ]]; then
echo "${url##*/}" | sed 's/\.git$//'
else
echo "${url##*/}" | sed 's/\.git$//'
fi
}
# Function to extract username/repo from URL for GitHub
get_username_repo() {
local url=$1
if [[ "$url" == *"github.com"* ]]; then
# Extract username/repo from GitHub URL
echo "${url#*github.com/}" | sed 's/\.git$//'
elif [[ "$url" == *"gitlab.com"* ]]; then
# Extract username/repo from GitLab URL
echo "${url#*gitlab.com/}" | sed 's/\.git$//'
else
# For other URLs, just return the repo name
echo "$(get_repo_name "$url")"
fi
}
# Function to run individual packaging script
run_packaging_script() {
local url=$1
local repo_name=$(get_repo_name "$url")
local username_repo=$(get_username_repo "$url")
local workspace_dir="$WORKSPACES_DIR/$repo_name"
local artifact_dir="$ARTIFACTS_DIR/$repo_name"
local packaging_script="$WORKSPACES_DIR/packaging-$repo_name.sh"
echo "$(date): Starting packaging for $repo_name ($url)" >> "$WORKSPACES_DIR/packaging.log"
# Update status to IN PROGRESS
update_status "$repo_name" "🔄 IN PROGRESS" "Packaging started"
# Initialize workspace
mkdir -p "$workspace_dir" "$artifact_dir"
# Clone repository
if [ ! -d "$workspace_dir/repo" ] || [ -z "$(ls -A "$workspace_dir/repo")" ]; then
echo "Cloning $url to $workspace_dir/repo"
git clone "$url" "$workspace_dir/repo"
else
# Update repository
echo "Updating $url in $workspace_dir/repo"
(cd "$workspace_dir/repo" && git fetch && git reset --hard origin/main 2>/dev/null || git reset --hard origin/master 2>/dev/null || git pull)
fi
# Attempt packaging with retries
local attempt=1
local success=0
while [ $attempt -le $MAX_RETRIES ] && [ $success -eq 0 ]; do
echo "$(date): Attempt $attempt/$MAX_RETRIES for $repo_name" >> "$WORKSPACES_DIR/packaging.log"
# Run the packaging process (this would call the specific packaging function)
if package_application "$repo_name" "$username_repo" "$workspace_dir" "$artifact_dir"; 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"
else
echo "$(date): Failed to package $repo_name on attempt $attempt" >> "$WORKSPACES_DIR/packaging.log"
((attempt++))
fi
done
if [ $success -eq 0 ]; then
# Mark as failed and create human help request
update_status "$repo_name" "🛑 FAILED" "Failed after $MAX_RETRIES attempts"
touch "$HUMAN_HELP_DIR/STATUS-HumanHelp-$repo_name"
echo "$(date): Marked $repo_name for human help after $MAX_RETRIES failed attempts" >> "$WORKSPACES_DIR/packaging.log"
fi
}
# Function to package a specific application
package_application() {
local repo_name=$1
local username_repo=$2
local workspace_dir=$3
local artifact_dir=$4
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"
}
# Function to create a Dockerfile based on the application type
create_dockerfile() {
local repo_name=$1
local repo_path=$2
# Detect application type and create appropriate Dockerfile
# This is a simplified approach - in reality, this would be much more complex
if [ -f "$repo_path/package.json" ]; then
# Node.js application
cat > "$repo_path/Dockerfile" << EOF
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npm", "start"]
EOF
elif [ -f "$repo_path/requirements.txt" ]; then
# Python application
cat > "$repo_path/Dockerfile" << EOF
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["python", "app.py"]
EOF
elif [ -f "$repo_path/composer.json" ]; then
# PHP application
cat > "$repo_path/Dockerfile" << EOF
FROM php:8.1-apache
RUN docker-php-ext-install mysqli && docker-php-ext-enable mysqli
COPY . /var/www/html/
EXPOSE 80
CMD ["apache2-foreground"]
EOF
elif [ -f "$repo_path/Gemfile" ]; then
# Ruby application
cat > "$repo_path/Dockerfile" << EOF
FROM ruby:3.0
WORKDIR /app
COPY Gemfile Gemfile.lock ./
RUN bundle install
COPY . .
EXPOSE 3000
CMD ["ruby", "app.rb"]
EOF
else
# Default to a basic server
cat > "$repo_path/Dockerfile" << EOF
FROM alpine:latest
WORKDIR /app
COPY . .
RUN apk add --no-cache bash
EXPOSE 8080
CMD ["sh", "-c", "while true; do sleep 30; done"]
EOF
fi
}
# Main function to process all applications
main() {
echo "$(date): Starting Cloudron packaging process" >> "$WORKSPACES_DIR/packaging.log"
# 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"
# Process applications in batches of 3 for parallel execution
local i=0
local total=${#urls[@]}
while [ $i -lt $total ]; do
# Process up to 3 applications in parallel
local end=$((i + 3))
[ $end -gt $total ] && end=$total
echo "$(date): Starting batch with applications $(printf '%s; ' "${urls[@]:i:3}")" >> "$WORKSPACES_DIR/packaging.log"
for ((j = i; j < end; j++)); do
echo "$(date): Starting packaging for ${urls[$j]}" >> "$WORKSPACES_DIR/packaging.log"
run_packaging_script "${urls[$j]}" &
done
# Wait for all background processes to complete
wait
# Update i for the next batch
i=$end
# Update progress summary in STATUS.md
local completed=$(grep -o "✅ COMPLETE" "$STATUS_FILE" | wc -l)
local failed=$(grep -o "🛑 FAILED" "$STATUS_FILE" | wc -l)
local in_progress=$(grep -o "🔄 IN PROGRESS" "$STATUS_FILE" | wc -l)
local pending=$((total - completed - failed - in_progress))
# Update summary section in STATUS.md
sed -i '/## Progress Summary/Q' "$STATUS_FILE"
cat >> "$STATUS_FILE" << EOF
## Progress Summary
- Total Applications: $total
- Completed: $completed ($(awk "BEGIN {printf \"%.0f\", $completed * 100 / $total}")%)
- In Progress: $in_progress ($(awk "BEGIN {printf \"%.0f\", $in_progress * 100 / $total}")%)
- Failed: $failed ($(awk "BEGIN {printf \"%.0f\", $failed * 100 / $total}")%)
- Pending: $pending ($(awk "BEGIN {printf \"%.0f\", $pending * 100 / $total}")%)
## Human Help Required
$(ls -1 "$HUMAN_HELP_DIR" 2>/dev/null || echo "None at the moment.")
## Last Updated
$(date)
EOF
done
echo "$(date): Completed Cloudron packaging process" >> "$WORKSPACES_DIR/packaging.log"
}
# Run the main function if script is executed directly
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main "$@"
fi

View File

@@ -0,0 +1,367 @@
#!/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
cd "$app_dir"
# Create Cloudron manifest
cat > app.manifest << EOF
{
"id": "com.$(echo "$app_name" | 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",
"admin": false,
"tags": ["nodejs", "auto-generated"],
"logo": "https://github.com/fluidicon.png",
"documentation": "https://github.com/$app_name",
"changelog": "Initial packaging"
}
EOF
# Create Dockerfile for Node.js
cat > Dockerfile << EOF
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install --only=production
COPY . .
EXPOSE 3000
CMD ["npm", "start"]
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
cd "$app_dir"
# Create Cloudron manifest
cat > app.manifest << EOF
{
"id": "com.$(echo "$app_name" | 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",
"admin": false,
"tags": ["python", "auto-generated"],
"logo": "https://github.com/fluidicon.png",
"documentation": "https://github.com/$app_name",
"changelog": "Initial packaging"
}
EOF
# Create Dockerfile for Python
cat > Dockerfile << EOF
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["python", "app.py"]
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
cd "$app_dir"
# Create Cloudron manifest
cat > app.manifest << EOF
{
"id": "com.$(echo "$app_name" | 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",
"admin": false,
"tags": ["php", "auto-generated"],
"logo": "https://github.com/fluidicon.png",
"documentation": "https://github.com/$app_name",
"changelog": "Initial packaging"
}
EOF
# Create Dockerfile for PHP
cat > Dockerfile << EOF
FROM php:8.1-apache
RUN docker-php-ext-install mysqli && docker-php-ext-enable mysqli
COPY . /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
cd "$app_dir"
# Create Cloudron manifest
cat > app.manifest << EOF
{
"id": "com.$(echo "$app_name" | 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",
"admin": false,
"tags": ["go", "auto-generated"],
"logo": "https://github.com/fluidicon.png",
"documentation": "https://github.com/$app_name",
"changelog": "Initial packaging"
}
EOF
# Create Dockerfile for Go
cat > Dockerfile << EOF
FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o myapp .
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/myapp .
EXPOSE 8080
CMD ["./myapp"]
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"
# 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
echo "Failed to start container for $app_name during smoke test"
docker rm "$container_name" > /dev/null 2>&1
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
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"
elif [ -f "requirements.txt" ] || [ -f "setup.py" ]; then
echo "Detected Python application"
package_python_app "$app_name" "$app_dir" "$artifact_dir"
elif [ -f "composer.json" ]; then
echo "Detected PHP application"
package_php_app "$app_name" "$app_dir" "$artifact_dir"
elif [ -f "go.mod" ] || [ -f "*.go" ]; then
echo "Detected Go application"
package_go_app "$app_name" "$app_dir" "$artifact_dir"
else
# Default generic approach
echo "Application type not detected, using generic approach"
package_generic_app "$app_name" "$app_dir" "$artifact_dir"
fi
}
# Generic packaging function for unknown application types
package_generic_app() {
local app_name=$1
local app_dir=$2
local artifact_dir=$3
cd "$app_dir"
# Create Cloudron manifest
cat > app.manifest << EOF
{
"id": "com.$(echo "$app_name" | 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",
"admin": false,
"tags": ["generic", "auto-generated"],
"logo": "https://github.com/fluidicon.png",
"documentation": "https://github.com/$app_name",
"changelog": "Initial packaging"
}
EOF
# Create a basic Dockerfile
cat > Dockerfile << EOF
FROM alpine:latest
WORKDIR /app
COPY . .
RUN apk add --no-cache bash curl tar
EXPOSE 8080
CMD ["sh", "-c", "while true; do sleep 30; done"]
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
}