#!/bin/sh # Update the test deployment, including the independently cloned modules. # Run this script as root when DEPLOY_OWNER requires chown privileges. set -eu SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) PROJECT_ROOT=${PROJECT_ROOT:-$(dirname -- "$SCRIPT_DIR")} DEPLOY_OWNER=${DEPLOY_OWNER:-www-data:www-data} LOCK_FILE=${LOCK_FILE:-/tmp/ktrix-test-update.lock} # Composer and npm can invoke Git themselves, bypassing git_in(). Pass the # deployment exceptions to every child Git process without changing global # Git configuration. GIT_CONFIG_COUNT=2 GIT_CONFIG_KEY_0=safe.directory GIT_CONFIG_VALUE_0=$PROJECT_ROOT GIT_CONFIG_KEY_1=safe.directory GIT_CONFIG_VALUE_1=$PROJECT_ROOT/modules/* export GIT_CONFIG_COUNT GIT_CONFIG_KEY_0 GIT_CONFIG_VALUE_0 export GIT_CONFIG_KEY_1 GIT_CONFIG_VALUE_1 log() { printf '%s %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" } require_command() { if ! command -v "$1" >/dev/null 2>&1; then log "ERROR: Required command not found: $1" exit 1 fi } # Cron runs with a bare PATH, so nvm-installed npm (added to PATH only by # .bashrc sourcing nvm.sh in an interactive shell) is invisible here even # though it works fine when this script is run by hand. Resolve nvm's # current npm via bash (nvm.sh is not POSIX sh compatible) and prepend it, # so a later `nvm use`/`nvm install` doesn't require updating this script # or the crontab. ensure_npm_on_path() { command -v npm >/dev/null 2>&1 && return 0 nvm_dir=${NVM_DIR:-${HOME:-/root}/.nvm} [ -s "$nvm_dir/nvm.sh" ] || return 0 command -v bash >/dev/null 2>&1 || return 0 npm_path=$(bash -c ". \"\$1/nvm.sh\" >/dev/null 2>&1 && command -v npm" _ "$nvm_dir" 2>/dev/null) || return 0 [ -n "$npm_path" ] || return 0 PATH=$(dirname -- "$npm_path"):$PATH export PATH log "Resolved npm via nvm: $npm_path" } ensure_npm_on_path git_in() { repository=$1 shift git -c "safe.directory=$repository" -C "$repository" "$@" } update_repository() { repository=$1 name=$2 if [ -n "$(git_in "$repository" status --porcelain)" ]; then log "ERROR: $name has local changes; refusing to overwrite them" exit 1 fi if ! git_in "$repository" rev-parse --abbrev-ref '@{upstream}' >/dev/null 2>&1; then log "ERROR: $name's current branch has no upstream" exit 1 fi log "Updating $name" git_in "$repository" pull --ff-only } install_dependencies() { directory=$1 name=$2 if [ -f "$directory/composer.json" ]; then log "Installing PHP dependencies for $name" composer install \ --working-dir="$directory" \ --no-interaction \ --prefer-dist \ --optimize-autoloader fi if [ -f "$directory/package-lock.json" ]; then log "Installing Node dependencies for $name" npm ci --prefix "$directory" fi } restore_ownership() { status=$? trap - EXIT HUP INT TERM log "Setting ownership to $DEPLOY_OWNER" if ! chown -R "$DEPLOY_OWNER" "$PROJECT_ROOT"; then log "ERROR: Could not restore project ownership" exit 1 fi if [ "$status" -ne 0 ]; then log "ERROR: Update failed with exit code $status" fi exit "$status" } for command in git composer npm flock chown; do require_command "$command" done if [ ! -d "$PROJECT_ROOT/.git" ]; then log "ERROR: PROJECT_ROOT is not a Git clone: $PROJECT_ROOT" exit 1 fi exec 9>"$LOCK_FILE" if ! flock -n 9; then log "Another update is already running; exiting" exit 0 fi trap restore_ownership EXIT HUP INT TERM log "Starting test-server update in $PROJECT_ROOT" update_repository "$PROJECT_ROOT" "server" for module in "$PROJECT_ROOT"/modules/*; do [ -d "$module/.git" ] || continue update_repository "$module" "module $(basename -- "$module")" done install_dependencies "$PROJECT_ROOT" "server" for module in "$PROJECT_ROOT"/modules/*; do [ -d "$module" ] || continue install_dependencies "$module" "module $(basename -- "$module")" done log "Building server and modules" npm run build:all --prefix "$PROJECT_ROOT" log "Test-server update completed successfully"