From 8bb6d00b0fb6cd77e907cc5f560f68936a889793 Mon Sep 17 00:00:00 2001 From: ReachableCEO Date: Fri, 12 Sep 2025 14:18:41 -0500 Subject: [PATCH] chore: add Cloudron PackageTemplate, helper script; add .gitattributes/.editorconfig; refine .gitignore; improve workspace scripts --- .editorconfig | 25 +++++++++ .gitattributes | 16 ++++++ .gitignore | 3 ++ .../PackageTemplate/.dockerignore | 11 ++++ .../PackageTemplate/CloudronManifest.json | 19 +++++++ CloudronPackages/PackageTemplate/Dockerfile | 39 ++++++++++++++ CloudronPackages/PackageTemplate/README.md | 24 +++++++++ CloudronPackages/PackageTemplate/config.yaml | 11 ++++ CloudronPackages/PackageTemplate/nginx.conf | 26 +++++++++ CloudronPackages/PackageTemplate/start.sh | 39 ++++++++++++++ .../PackageTemplate/supervisord.conf | 12 +++++ .../UpstreamVendor-Clone.sh | 25 +++++++-- .../UpstreamVendor-Update.sh | 29 +++++----- scripts/new-package.sh | 53 +++++++++++++++++++ 14 files changed, 314 insertions(+), 18 deletions(-) create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 CloudronPackages/PackageTemplate/.dockerignore create mode 100644 CloudronPackages/PackageTemplate/CloudronManifest.json create mode 100644 CloudronPackages/PackageTemplate/Dockerfile create mode 100644 CloudronPackages/PackageTemplate/README.md create mode 100644 CloudronPackages/PackageTemplate/config.yaml create mode 100644 CloudronPackages/PackageTemplate/nginx.conf create mode 100644 CloudronPackages/PackageTemplate/start.sh create mode 100644 CloudronPackages/PackageTemplate/supervisord.conf create mode 100755 scripts/new-package.sh diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..7e43f5d --- /dev/null +++ b/.editorconfig @@ -0,0 +1,25 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space +indent_size = 2 + +[Dockerfile] +indent_size = 2 + +[*.sh] +indent_size = 2 + +[*.yml] +indent_size = 2 + +[*.yaml] +indent_size = 2 + +[*.json] +indent_size = 2 + diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..9541fc2 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,16 @@ +* text=auto eol=lf + +# Enforce LF line endings for key file types +*.sh text eol=lf +Dockerfile text eol=lf +*.yml text eol=lf +*.yaml text eol=lf +*.json text eol=lf +*.md text eol=lf + +# Binary assets +*.png binary +*.jpg binary +*.jpeg binary +*.ico binary + diff --git a/.gitignore b/.gitignore index a78a58f..43a7b5c 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,9 @@ packaging_temp/ *.env *.local *.log +.envrc +.python-version +.tool-versions .vscode/ .idea/ *.swp diff --git a/CloudronPackages/PackageTemplate/.dockerignore b/CloudronPackages/PackageTemplate/.dockerignore new file mode 100644 index 0000000..090ba8b --- /dev/null +++ b/CloudronPackages/PackageTemplate/.dockerignore @@ -0,0 +1,11 @@ +# Ignore typical build context clutter +.git +.gitignore +node_modules +npm-debug.log +*.log +dist +build +Dockerfile.* +.DS_Store + diff --git a/CloudronPackages/PackageTemplate/CloudronManifest.json b/CloudronPackages/PackageTemplate/CloudronManifest.json new file mode 100644 index 0000000..7ee1a01 --- /dev/null +++ b/CloudronPackages/PackageTemplate/CloudronManifest.json @@ -0,0 +1,19 @@ +{ + "manifestVersion": 2, + "id": "__APP_ID__", + "title": "__APP_TITLE__", + "author": "KNEL", + "description": "Cloudron packaging template for __APP_TITLE__", + "website": "https://example.com", + "contactEmail": "admin@example.com", + "version": "0.1.0", + "changelog": "Initial package template", + "healthCheckPath": "/", + "httpPort": __HTTP_PORT__, + "addons": { + "localstorage": {} + }, + "tags": ["template", "example"], + "icon": "logo.png" +} + diff --git a/CloudronPackages/PackageTemplate/Dockerfile b/CloudronPackages/PackageTemplate/Dockerfile new file mode 100644 index 0000000..9fbeebb --- /dev/null +++ b/CloudronPackages/PackageTemplate/Dockerfile @@ -0,0 +1,39 @@ +FROM cloudron/base:4.2.0 + +# Metadata labels (edit as needed) +LABEL org.opencontainers.image.title="__APP_TITLE__" +LABEL org.opencontainers.image.description="Cloudron package for __APP_TITLE__" +LABEL org.opencontainers.image.source="https://example.com" + +# Install OS dependencies here as needed +# RUN apt-get update && apt-get install -y --no-install-recommends \ +# curl ca-certificates tini \ +# && rm -rf /var/lib/apt/lists/* + +# App code lives in /app/code (read-only at runtime) +WORKDIR /app/code + +# Copy application code (adjust as needed) +# COPY . /app/code + +# Create persistent directory for application data +RUN mkdir -p /app/data && chown -R cloudron:cloudron /app/data + +# Copy startup script +COPY start.sh /app/pkg/start.sh +RUN chmod +x /app/pkg/start.sh && chown cloudron:cloudron /app/pkg/start.sh + +USER cloudron + +# Expose the app port specified in manifest +EXPOSE __HTTP_PORT__ + +# Default environment (customize per app) +ENV NODE_ENV=production \ + APP_PORT=__HTTP_PORT__ + +HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \ + CMD curl -fsS http://127.0.0.1:${APP_PORT}/ || exit 1 + +CMD ["/app/pkg/start.sh"] + diff --git a/CloudronPackages/PackageTemplate/README.md b/CloudronPackages/PackageTemplate/README.md new file mode 100644 index 0000000..6c8921d --- /dev/null +++ b/CloudronPackages/PackageTemplate/README.md @@ -0,0 +1,24 @@ +# Package Template for Cloudron Apps + +This is a minimal template to package an application for Cloudron. + +Replace placeholders in files with your app specifics: +- `__APP_ID__` (e.g., com.example.myapp) +- `__APP_TITLE__` (human name) +- `__HTTP_PORT__` (default internal app port) + +Files +- `CloudronManifest.json` – base manifest +- `Dockerfile` – uses cloudron/base, non-root user, healthcheck +- `start.sh` – startup script with addon detection examples +- `nginx.conf` (optional) – example reverse proxy +- `supervisord.conf` (optional) – process manager example +- `config.yaml` (optional) – sample app config +- `logo.png` – add your 512x512 PNG icon here (not provided in template) + +Usage +1. Create a new package from this template using `scripts/new-package.sh`: + `scripts/new-package.sh MyApp --id com.example.myapp --title "My App" --port 3000` +2. Adjust Dockerfile and start.sh to run your app. +3. Build and test locally; then commit and push. + diff --git a/CloudronPackages/PackageTemplate/config.yaml b/CloudronPackages/PackageTemplate/config.yaml new file mode 100644 index 0000000..445d0da --- /dev/null +++ b/CloudronPackages/PackageTemplate/config.yaml @@ -0,0 +1,11 @@ +# Example configuration template for __APP_TITLE__ +server: + port: __HTTP_PORT__ + +data: + dir: /app/data + +database: + # url: ${CLOUDRON_POSTGRESQL_URL} + # redis: ${CLOUDRON_REDIS_URL} + diff --git a/CloudronPackages/PackageTemplate/nginx.conf b/CloudronPackages/PackageTemplate/nginx.conf new file mode 100644 index 0000000..25ebb29 --- /dev/null +++ b/CloudronPackages/PackageTemplate/nginx.conf @@ -0,0 +1,26 @@ +user cloudron; +worker_processes auto; + +error_log /var/log/nginx/error.log warn; +pid /run/nginx.pid; + +events { worker_connections 1024; } + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + access_log /var/log/nginx/access.log main; + sendfile on; + + server { + listen __HTTP_PORT__; + server_name _; + + location / { + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_pass http://127.0.0.1:__HTTP_PORT__; + } + } +} + diff --git a/CloudronPackages/PackageTemplate/start.sh b/CloudronPackages/PackageTemplate/start.sh new file mode 100644 index 0000000..9f2e423 --- /dev/null +++ b/CloudronPackages/PackageTemplate/start.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +set -euo pipefail + +log() { echo "[start] $(date -Is) $*"; } +abort() { echo "[start] ERROR: $*" >&2; exit 1; } + +# Defaults +: "${APP_PORT:=__HTTP_PORT__}" + +log "Starting __APP_TITLE__ on port ${APP_PORT}" + +# Example: ensure /app/data exists and is writable +mkdir -p /app/data +chown -R cloudron:cloudron /app/data || true + +# Example addon integration (uncomment and adapt as needed) +# if [[ -n "${CLOUDRON_POSTGRESQL_URL:-}" ]]; then +# log "Detected PostgreSQL addon" +# # Use $CLOUDRON_POSTGRESQL_* env vars +# fi + +# if [[ -n "${CLOUDRON_REDIS_URL:-}" ]]; then +# log "Detected Redis addon" +# fi + +# If your app needs config generation, do it here +# cat > /app/data/config.yaml <<'YAML' +# key: value +# YAML + +# Example: start a simple HTTP server (placeholder) +# Replace with your actual app start command +if command -v python3 >/dev/null 2>&1; then + log "Launching placeholder server: python3 -m http.server ${APP_PORT}" + exec python3 -m http.server "${APP_PORT}" --bind 0.0.0.0 +else + abort "No application command configured. Replace placeholder with your app's start command." +fi + diff --git a/CloudronPackages/PackageTemplate/supervisord.conf b/CloudronPackages/PackageTemplate/supervisord.conf new file mode 100644 index 0000000..e73a12a --- /dev/null +++ b/CloudronPackages/PackageTemplate/supervisord.conf @@ -0,0 +1,12 @@ +[supervisord] +logfile=/var/log/supervisor/supervisord.log +pidfile=/run/supervisord.pid +nodaemon=true + +[program:app] +command=/app/pkg/start.sh +autorestart=true +stdout_logfile=/var/log/supervisor/app.stdout.log +stderr_logfile=/var/log/supervisor/app.stderr.log +user=cloudron + diff --git a/PackagingForCloudronWorkspace/UpstreamVendor-Clone.sh b/PackagingForCloudronWorkspace/UpstreamVendor-Clone.sh index 78381f2..c01e79e 100755 --- a/PackagingForCloudronWorkspace/UpstreamVendor-Clone.sh +++ b/PackagingForCloudronWorkspace/UpstreamVendor-Clone.sh @@ -205,10 +205,25 @@ https://github.com/funmusicplace/mirlo.git ) -cd Docker +WORKDIR="$(cd "$(dirname "$0")" && pwd)" +TARGET_DIR="${WORKDIR}/Docker" +mkdir -p "$TARGET_DIR" -IFS=$'\n\t' +# If REPOS.txt exists, read additional repos (lines; ignore # and blanks) +EXTRA_REPOS_FILE="${WORKDIR}/REPOS.txt" +if [[ -f "$EXTRA_REPOS_FILE" ]]; then + mapfile -t EXTRA_REPOS < <(sed -e 's/#.*$//' -e '/^\s*$/d' "$EXTRA_REPOS_FILE") +else + EXTRA_REPOS=() +fi -for GIT_REPO in ${GIT_REPO_LIST[@]};do - git clone --depth 1 $GIT_REPO || true -done \ No newline at end of file +ALL_REPOS=("${GIT_REPO_LIST[@]}" "${EXTRA_REPOS[@]}") + +echo "Cloning to: $TARGET_DIR" +printf ' - %s\n' "${ALL_REPOS[@]}" + +cd "$TARGET_DIR" + +# Parallel clones (default 4 jobs). Avoid failing the whole script on single failures. +JOBS="${JOBS:-4}" +printf '%s\n' "${ALL_REPOS[@]}" | xargs -n1 -P "$JOBS" -I{} bash -lc 'repo="{}"; name=$(basename -s .git "$repo"); if [[ -d "$name/.git" ]]; then echo "exists: $name"; else git clone --depth 1 "$repo" "$name" || echo "failed: $repo"; fi' diff --git a/PackagingForCloudronWorkspace/UpstreamVendor-Update.sh b/PackagingForCloudronWorkspace/UpstreamVendor-Update.sh index 359af16..66649b5 100755 --- a/PackagingForCloudronWorkspace/UpstreamVendor-Update.sh +++ b/PackagingForCloudronWorkspace/UpstreamVendor-Update.sh @@ -27,18 +27,21 @@ set -o nounset set -o pipefail set -o functrace +WORKDIR="$(cd "$(dirname "$0")" && pwd)" +TARGET_DIR="${WORKDIR}/Docker" -cd Docker +cd "$TARGET_DIR" -GIT_REPO_LIST="$(ls -d */)" - -IFS=$'\n\t' - -for GIT_REPO in ${GIT_REPO_LIST[@]}; -do - CURRENT_DIR=$(realpath $PWD) - echo "Updating from $GIT_REPO..." - cd $GIT_REPO - git pull - cd $CURRENT_DIR -done \ No newline at end of file +# Iterate only over directories that are git repos +while IFS= read -r -d '' repo_dir; do + echo "Updating: ${repo_dir}" + pushd "$repo_dir" >/dev/null + if [[ -d .git ]]; then + git -c advice.detachedHead=false fetch --all --prune || true + # Fast-forward only to avoid unintended merges + git -c advice.detachedHead=false pull --ff-only || true + else + echo "Skipping (not a git repo): ${repo_dir}" + fi + popd >/dev/null +done < <(find . -mindepth 1 -maxdepth 1 -type d -print0) diff --git a/scripts/new-package.sh b/scripts/new-package.sh new file mode 100755 index 0000000..60aace2 --- /dev/null +++ b/scripts/new-package.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +set -euo pipefail + +usage() { + cat <<'EOF' +Usage: scripts/new-package.sh [--id ] [--title ] [--port <port>] + +Creates CloudronPackages/<AppName> from PackageTemplate and replaces placeholders: + __APP_ID__, __APP_TITLE__, __HTTP_PORT__ + +Examples: + scripts/new-package.sh MyApp --id com.example.myapp --title "My App" --port 3000 +EOF +} + +if [[ $# -lt 1 ]]; then + usage; exit 1 +fi + +APP_NAME="$1"; shift +APP_ID="com.example.${APP_NAME,,}" +APP_TITLE="$APP_NAME" +HTTP_PORT="3000" + +while [[ $# -gt 0 ]]; do + case "$1" in + --id) APP_ID="$2"; shift 2;; + --title) APP_TITLE="$2"; shift 2;; + --port) HTTP_PORT="$2"; shift 2;; + -h|--help) usage; exit 0;; + *) echo "Unknown argument: $1"; usage; exit 1;; + esac +done + +SRC_DIR="CloudronPackages/PackageTemplate" +DEST_DIR="CloudronPackages/${APP_NAME}" + +[[ -d "$SRC_DIR" ]] || { echo "Template not found: $SRC_DIR"; exit 1; } +[[ -e "$DEST_DIR" ]] && { echo "Destination already exists: $DEST_DIR"; exit 1; } + +mkdir -p "$DEST_DIR" +cp -a "$SRC_DIR"/. "$DEST_DIR"/ + +# Replace placeholders in text files +find "$DEST_DIR" -type f \( -name "*" ! -name "*.png" \) -print0 | while IFS= read -r -d '' f; do + sed -i "s#__APP_ID__#${APP_ID}#g" "$f" + sed -i "s#__APP_TITLE__#${APP_TITLE}#g" "$f" + sed -i "s#__HTTP_PORT__#${HTTP_PORT}#g" "$f" +done + +echo "Created package at: $DEST_DIR" +echo "Next steps: edit Dockerfile and start.sh to run your app. Add logo.png if desired." +