#!/bin/bash # ThoughtFrame runtime entrypoint. # # First install: unpack the baked WAR into the mount. # Upgrade: overlay only the product code (WEB-INF/lib, WEB-INF/base, web.xml, # startup.xconf, oemounts.xml) so catalog data/elastic and site-local # files on the mounts survive. # Then: supervise Jetty, forwarding stop signals so `docker stop` is clean. set -euo pipefail APP_DIR="${APP_DIR:-/opt/jetty-base/webapps/ROOT}" BAKED_WAR="${BAKED_WAR:-/opt/thoughtframe/ROOT.war}" BOOTSTRAP_URL="${BOOTSTRAP_WAR_URL:-}" BOOTSTRAP_RESOLVE="${BOOTSTRAP_RESOLVE:-}" JAVA_OPTS="${JAVA_OPTS:-}" # JNA / Jetty use restricted native methods; enable access so JDK 25 doesn't warn (and # won't hard-fail once that becomes enforced). if [[ "$JAVA_OPTS" != *"--enable-native-access"* ]]; then JAVA_OPTS="$JAVA_OPTS --enable-native-access=ALL-UNNAMED" fi echo "Starting ThoughtFrame runtime container..." mkdir -p "$APP_DIR" resolve_war() { if [ -f "$BAKED_WAR" ]; then echo "$BAKED_WAR" return 0 fi if [ -n "$BOOTSTRAP_URL" ]; then echo "Fetching WAR from BOOTSTRAP_WAR_URL..." >&2 mkdir -p /tmp/bootstrap if [ -n "$BOOTSTRAP_RESOLVE" ]; then curl -fL --resolve "$BOOTSTRAP_RESOLVE" "$BOOTSTRAP_URL" -o /tmp/bootstrap/ROOT.war else curl -fL "$BOOTSTRAP_URL" -o /tmp/bootstrap/ROOT.war fi echo "/tmp/bootstrap/ROOT.war" return 0 fi return 1 } LIB_DIR="$APP_DIR/WEB-INF/lib" FIRST_INSTALL=0 if [ ! -d "$LIB_DIR" ] || [ -z "$(ls -A "$LIB_DIR" 2>/dev/null)" ]; then FIRST_INSTALL=1 fi WAR_FILE="$(resolve_war || true)" if [ -z "$WAR_FILE" ]; then echo "WARNING: no baked WAR at $BAKED_WAR and no BOOTSTRAP_WAR_URL — starting as-is." >&2 elif [ "$FIRST_INSTALL" -eq 1 ]; then echo "First install: unpacking $WAR_FILE into $APP_DIR" unzip -q -o "$WAR_FILE" -d "$APP_DIR" echo "Bootstrap complete." else # Overlay upgrade: product code only. Never wipe webapp/, data/ or elastic/ trees. echo "Existing site: overlaying product code from $WAR_FILE" rm -rf "$APP_DIR/WEB-INF/lib" "$APP_DIR/WEB-INF/base" # unzip exits 11 when a listed member is absent (no oemounts.xml in the WAR); the # matched members are still extracted, so only a real error (2+) stops the container. OVERLAY_STATUS=0 unzip -q -o "$WAR_FILE" \ "WEB-INF/lib/*" "WEB-INF/base/*" "WEB-INF/web.xml" "WEB-INF/startup.xconf" \ "WEB-INF/oemounts.xml" -d "$APP_DIR" || OVERLAY_STATUS=$? if [ "$OVERLAY_STATUS" -ne 0 ] && [ "$OVERLAY_STATUS" -ne 11 ]; then echo "ERROR: overlay of $WAR_FILE failed (status $OVERLAY_STATUS)" >&2 exit "$OVERLAY_STATUS" fi echo "Overlay complete." fi # WEB-INF/classes shadows WEB-INF/lib on the classpath and is never refreshed by the # overlay above; drop it if an older image left one behind. if [ -d "$APP_DIR/WEB-INF/classes" ]; then echo "Removing stale $APP_DIR/WEB-INF/classes (it would shadow WEB-INF/lib)" rm -rf "$APP_DIR/WEB-INF/classes" fi # --- supervisor: keep Jetty up, forward stop signals so `docker stop` is clean --- JETTY_PID="" shutdown() { echo "Stop signal received — shutting Jetty down." if [ -n "$JETTY_PID" ] && kill -0 "$JETTY_PID" 2>/dev/null; then kill -TERM "$JETTY_PID" 2>/dev/null || true wait "$JETTY_PID" 2>/dev/null || true fi exit 0 } trap shutdown TERM INT while true; do echo "Launching Jetty..." java $JAVA_OPTS -jar "$JETTY_HOME/start.jar" "jetty.base=$JETTY_BASE" & JETTY_PID=$! echo "Jetty PID: $JETTY_PID" EXIT_CODE=0 wait "$JETTY_PID" || EXIT_CODE=$? JETTY_PID="" echo "Jetty exited with code $EXIT_CODE" if [ "$EXIT_CODE" -eq 0 ]; then echo "Clean exit — stopping container." exit 0 fi sleep 2 echo "Restarting Jetty..." done