From ecdf36aad88b628e3773f36ff25ca22be0726524 Mon Sep 17 00:00:00 2001 From: EdyTheCow Date: Tue, 7 Oct 2025 23:53:13 +0200 Subject: [PATCH] Split up functions and other improvements - Split all functions into smaller script files - whmcs dir is now properly set to be owned by www-data user - Added automation for moving crons folder and adjusting configs. Currently disabled due to whmcs having to generate config first. Will keep it disabled until I find a good work around --- whmcs-nginx/Dockerfile | 12 +- whmcs-nginx/config/05-whmcs-fetch.sh | 102 --------------- .../docker-entrypoint.d/10-download-whmcs.sh | 11 ++ .../20-init-whmcs-storage.sh | 10 ++ .../30-move-crons-dir.sh.disabled | 5 + whmcs-nginx/config/lib/whmcs-lib.sh | 119 ++++++++++++++++++ 6 files changed, 153 insertions(+), 106 deletions(-) delete mode 100644 whmcs-nginx/config/05-whmcs-fetch.sh create mode 100644 whmcs-nginx/config/docker-entrypoint.d/10-download-whmcs.sh create mode 100644 whmcs-nginx/config/docker-entrypoint.d/20-init-whmcs-storage.sh create mode 100644 whmcs-nginx/config/docker-entrypoint.d/30-move-crons-dir.sh.disabled create mode 100644 whmcs-nginx/config/lib/whmcs-lib.sh diff --git a/whmcs-nginx/Dockerfile b/whmcs-nginx/Dockerfile index d0f58c9..e4abdfe 100644 --- a/whmcs-nginx/Dockerfile +++ b/whmcs-nginx/Dockerfile @@ -1,11 +1,15 @@ FROM nginx:alpine - # --- Runtime tools used by the init script --- + +# --- Runtime tools used by the init script --- # curl: fetch WHMCS; jq: parse JSON; unzip: extract; su-exec: drop root when needed RUN apk add --no-cache curl jq unzip su-exec # --- Template with custom variables --- COPY config/default.conf.template /etc/nginx/templates/default.conf.template -# --- Init script: runs automatically on container start (before nginx) --- -COPY config/05-whmcs-fetch.sh /docker-entrypoint.d/05-whmcs-fetch.sh -RUN chmod +x /docker-entrypoint.d/05-whmcs-fetch.sh \ No newline at end of file +# --- Library with shared helpers --- +COPY config/lib/whmcs-lib.sh /usr/local/lib/whmcs-lib.sh + +# --- Modular init scripts (executed in sorted order by the official entrypoint) --- +COPY config/docker-entrypoint.d/*.sh /docker-entrypoint.d/ +RUN chmod +x /docker-entrypoint.d/*.sh \ No newline at end of file diff --git a/whmcs-nginx/config/05-whmcs-fetch.sh b/whmcs-nginx/config/05-whmcs-fetch.sh deleted file mode 100644 index 756b10b..0000000 --- a/whmcs-nginx/config/05-whmcs-fetch.sh +++ /dev/null @@ -1,102 +0,0 @@ -#!/bin/sh -set -eu - -# -------------------- defaults (override via Docker env) -------------------- -: "${WHMCS_WEB_ROOT:=/var/www/html}" -: "${WHMCS_STORAGE_DIR:=/var/www/whmcs_storage}" -: "${WHMCS_CHANNEL:=stable}" # or beta|rc|preprod|any -: "${WHMCS_URL:=}" # if set, overrides channel -: "${WHMCS_SHA256:=}" # optional explicit checksum -: "${WHMCS_WRITE_UID:=33}" # Debian www-data uid -: "${WHMCS_WRITE_GID:=33}" # Debian www-data gid - -log() { echo "[whmcs-init] $*"; } -warn() { echo "[whmcs-init][WARN] $*" >&2; } -die() { echo "[whmcs-init][ERROR] $*" >&2; exit 1; } - -# Treat directory as empty if nothing (except '.gitignore') -is_empty_dir() { - [ -z "$(find "$1" -mindepth 1 -maxdepth 1 -not -name '.gitignore' -print -quit 2>/dev/null)" ] -} - -# Create a tree of subpaths only when the root is empty -# usage: ensure_tree_if_empty ... -ensure_tree_if_empty() { - root="$1"; shift - mkdir -p "$root" - if is_empty_dir "$root"; then - # Build absolute paths; create with exact perms; then chown the whole root - set -- $(printf "%s " "$@") # normalize - abs="" - for rel in "$@"; do abs="$abs $root/$rel"; done - install -d -m 0755 $abs - chown -R "$WHMCS_WRITE_UID:$WHMCS_WRITE_GID" "$root" - log "Initialized storage tree at $root" - else - log "$root has content, skipping storage init." - fi -} - -download_and_unpack_whmcs() { - tmp="$(mktemp -d)"; dir="$tmp/unzip"; mkdir -p "$dir" - - if [ -z "$WHMCS_URL" ]; then - log "Querying WHMCS Distributions API (type=${WHMCS_CHANNEL})..." - # Returns JSON with keys including: url, sha256Checksum, releaseNotesUrl, changelogUrl - # https://docs.whmcs.com/about-whmcs/whmcs-distributions/ - json="$(curl -fsSL "https://api1.whmcs.com/download/latest?type=${WHMCS_CHANNEL}")" || die "Failed to query Distributions API" - url="$(echo "$json" | jq -r '.url')" || die "Failed to parse URL from API" - sha="$(echo "$json" | jq -r '.sha256Checksum')" || sha="" - else - url="$WHMCS_URL" - sha="$WHMCS_SHA256" - fi - - [ -n "$url" ] && [ "$url" != "null" ] || die "No WHMCS download URL available" - - log "Downloading WHMCS package..." - curl -fSL "$url" -o "$tmp/whmcs.zip" || die "Download failed" - - if [ -n "${sha:-}" ] && [ "$sha" != "null" ]; then - echo "${sha} $tmp/whmcs.zip" | sha256sum -c - || die "Checksum verification failed" - else - warn "No sha256Checksum provided, skipping verification." - fi - - unzip -q "$tmp/whmcs.zip" -d "$dir" || die "Unzip failed" - [ -d "$dir/whmcs" ] && src="$dir/whmcs" || src="$dir" - - mkdir -p "$WHMCS_WEB_ROOT" - # Copy into the (empty) web root volume - cp -a "$src"/. "$WHMCS_WEB_ROOT"/ - - rm -rf "$tmp" - log "WHMCS files installed to $WHMCS_WEB_ROOT" -} - -main() { - # Ensure roots exist - mkdir -p "$WHMCS_WEB_ROOT" "$WHMCS_STORAGE_DIR" - - # 1) Seed WHMCS app into web root ONLY if empty - if is_empty_dir "$WHMCS_WEB_ROOT"; then - log "Empty $WHMCS_WEB_ROOT detected, fetching WHMCS..." - download_and_unpack_whmcs - else - log "$WHMCS_WEB_ROOT has content, skipping WHMCS download." - fi - - # 2) Create/chown storage tree ONLY if storage dir is empty - # Include both 'attachments' and 'attachments/projects' so the parent gets created too. - ensure_tree_if_empty "$WHMCS_STORAGE_DIR" \ - attachments \ - attachments/projects \ - downloads \ - templates_c \ - whmcs_updater_tmp_dir - - # Done. Let the official nginx entrypoint continue (template render + nginx start) - exit 0 -} - -main "$@" \ No newline at end of file diff --git a/whmcs-nginx/config/docker-entrypoint.d/10-download-whmcs.sh b/whmcs-nginx/config/docker-entrypoint.d/10-download-whmcs.sh new file mode 100644 index 0000000..70a74e9 --- /dev/null +++ b/whmcs-nginx/config/docker-entrypoint.d/10-download-whmcs.sh @@ -0,0 +1,11 @@ +#!/bin/sh +set -eu; (set -o pipefail 2>/dev/null) && set -o pipefail +. /usr/local/lib/whmcs-lib.sh + +mkdir -p "$WHMCS_WEB_ROOT" +if is_empty_dir "$WHMCS_WEB_ROOT"; then + log "Empty $WHMCS_WEB_ROOT; fetching WHMCS…" + fetch_whmcs_into "$WHMCS_WEB_ROOT" +else + log "$WHMCS_WEB_ROOT has content; skipping download." +fi \ No newline at end of file diff --git a/whmcs-nginx/config/docker-entrypoint.d/20-init-whmcs-storage.sh b/whmcs-nginx/config/docker-entrypoint.d/20-init-whmcs-storage.sh new file mode 100644 index 0000000..e866c79 --- /dev/null +++ b/whmcs-nginx/config/docker-entrypoint.d/20-init-whmcs-storage.sh @@ -0,0 +1,10 @@ +#!/bin/sh +set -eu; (set -o pipefail 2>/dev/null) && set -o pipefail +. /usr/local/lib/whmcs-lib.sh + +ensure_tree_if_empty "$WHMCS_STORAGE_DIR" \ + attachments \ + attachments/projects \ + downloads \ + templates_c \ + whmcs_updater_tmp_dir \ No newline at end of file diff --git a/whmcs-nginx/config/docker-entrypoint.d/30-move-crons-dir.sh.disabled b/whmcs-nginx/config/docker-entrypoint.d/30-move-crons-dir.sh.disabled new file mode 100644 index 0000000..c9dc867 --- /dev/null +++ b/whmcs-nginx/config/docker-entrypoint.d/30-move-crons-dir.sh.disabled @@ -0,0 +1,5 @@ +#!/bin/sh +set -eu; (set -o pipefail 2>/dev/null) && set -o pipefail +. /usr/local/lib/whmcs-lib.sh + +move_crons_to_storage \ No newline at end of file diff --git a/whmcs-nginx/config/lib/whmcs-lib.sh b/whmcs-nginx/config/lib/whmcs-lib.sh new file mode 100644 index 0000000..5c282aa --- /dev/null +++ b/whmcs-nginx/config/lib/whmcs-lib.sh @@ -0,0 +1,119 @@ +set -eu +# enable pipefail where supported (BusyBox ash supports it) +(set -o pipefail 2>/dev/null) && set -o pipefail + +# Defaults (override via env) +: "${WHMCS_WEB_ROOT:=/var/www/html}" +: "${WHMCS_STORAGE_DIR:=/var/www/whmcs_storage}" +: "${WHMCS_CHANNEL:=stable}" +: "${WHMCS_URL:=}" +: "${WHMCS_SHA256:=}" +: "${WHMCS_WRITE_UID:=33}" +: "${WHMCS_WRITE_GID:=33}" + +log() { echo "[whmcs-init] $*"; } +warn() { echo "[whmcs-init][WARN] $*" >&2; } +die() { echo "[whmcs-init][ERROR] $*" >&2; exit 1; } + +is_empty_dir() { + # empty if no entries except possible .gitignore + [ -z "$(find "$1" -mindepth 1 -maxdepth 1 -not -name '.gitignore' -print -quit 2>/dev/null)" ] +} + +ensure_tree_if_empty() { + root="$1"; shift + mkdir -p "$root" + if is_empty_dir "$root"; then + # build abs paths and create with exact perms + abs="" + for rel in "$@"; do abs="$abs $root/$rel"; done + # shellcheck disable=SC2086 + install -d -m 0755 $abs + chown -R "$WHMCS_WRITE_UID:$WHMCS_WRITE_GID" "$root" + log "Initialized storage tree at $root" + else + log "$root has content; skipping storage init." + fi +} + +fetch_whmcs_into() { + dest="$1" + tmp="$(mktemp -d)"; dir="$tmp/unzip"; mkdir -p "$dir" + + # Resolve download URL + checksum + if [ -z "$WHMCS_URL" ]; then + log "Querying WHMCS Distributions API (type=${WHMCS_CHANNEL})…" + json="$(curl -fsSL "https://api1.whmcs.com/download/latest?type=${WHMCS_CHANNEL}")" || die "API request failed" + url="$(echo "$json" | jq -r '.url')" || die "parse url failed" + sha="$(echo "$json" | jq -r '.sha256Checksum' || true)" + else + url="$WHMCS_URL"; sha="$WHMCS_SHA256" + fi + [ -n "$url" ] && [ "$url" != "null" ] || die "No WHMCS download URL" + + # Download + verify + log "Downloading WHMCS…" + curl -fSL "$url" -o "$tmp/whmcs.zip" || die "Download failed" + if [ -n "${sha:-}" ] && [ "$sha" != "null" ]; then + echo "${sha} $tmp/whmcs.zip" | sha256sum -c - || die "Checksum verification failed" + else + warn "No sha256Checksum provided; skipping verification." + fi + + # Unzip to temp; some zips nest under top-level 'whmcs/' + unzip -q "$tmp/whmcs.zip" -d "$dir" || die "Unzip failed" + [ -d "$dir/whmcs" ] && src="$dir/whmcs" || src="$dir" + + # Ensure dest exists & owned by target uid/gid + install -d -o "$WHMCS_WRITE_UID" -g "$WHMCS_WRITE_GID" -m 0755 "$dest" + + # Extract AS the target uid/gid (no chown -R needed) + command -v su-exec >/dev/null 2>&1 || die "su-exec not found in PATH" + (cd "$src" && tar -cf - .) | su-exec "$WHMCS_WRITE_UID:$WHMCS_WRITE_GID" tar -C "$dest" -xf - || die "Copy failed" + + rm -rf "$tmp" + log "WHMCS installed to $dest (owned by $WHMCS_WRITE_UID:$WHMCS_WRITE_GID)" +} + +move_crons_to_storage() { + src="$WHMCS_WEB_ROOT/crons" + dst="$WHMCS_STORAGE_DIR/crons" + [ -d "$src" ] || { log "No crons/ in web root; skipping move."; return 0; } + mkdir -p "$dst" + if ! is_empty_dir "$dst"; then + log "$dst already has content; leaving crons/ as-is." + return 0 + fi + log "Moving crons/ to $dst …" + cp -a "$src"/. "$dst"/ && rm -rf "$src" + chown -R "$WHMCS_WRITE_UID:$WHMCS_WRITE_GID" "$dst" + + cron_cfg_new="$dst/config.php.new"; cron_cfg="$dst/config.php" + [ -f "$cron_cfg_new" ] && [ ! -f "$cron_cfg" ] && mv "$cron_cfg_new" "$cron_cfg" + + if [ -f "$cron_cfg" ]; then + root_path="${WHMCS_WEB_ROOT%/}/" + if grep -q '^[[:space:]]*//[[:space:]]*\$whmcspath' "$cron_cfg"; then + sed -i "s#^[[:space:]]*//[[:space:]]*\\\$whmcspath.*#\$whmcspath = '${root_path//\//\\/}';#" "$cron_cfg" + elif grep -q '^[[:space:]]*\\$whmcspath' "$cron_cfg"; then + sed -i "s#^[[:space:]]*\\\$whmcspath.*#\$whmcspath = '${root_path//\//\\/}';#" "$cron_cfg" + else + printf "\n\$whmcspath = '%s';\n" "$root_path" >> "$cron_cfg" + fi + else + warn "$cron_cfg not found; add \$whmcspath later." + fi + + main_cfg="$WHMCS_WEB_ROOT/configuration.sample.php" + crons_path="${dst%/}/" + if [ -f "$main_cfg" ]; then + if grep -q '^[[:space:]]*\\$crons_dir' "$main_cfg"; then + sed -i "s#^[[:space:]]*\\\$crons_dir.*#\$crons_dir = '${crons_path//\//\\/}';#" "$main_cfg" + else + printf "\n\$crons_dir = '%s';\n" "$crons_path" >> "$main_cfg" + fi + else + warn "$main_cfg not found; after install add: \$crons_dir = '$crons_path'." + fi + log "crons/ moved and configs updated (per WHMCS guidance)." +} \ No newline at end of file