Add a wrapper for generating external thread-dump files.

Change-Id: I2a2262a341ce6e981b713dc88b4aa4c1e0f1970e
diff --git a/tools/kustvakt-wrapper.sh b/tools/kustvakt-wrapper.sh
new file mode 100755
index 0000000..0f2c2c3
--- /dev/null
+++ b/tools/kustvakt-wrapper.sh
@@ -0,0 +1,164 @@
+# This script is automatocally generated by GH Copilot to wrap the command  
+# to run Kustvakt server and creates thread dumps on SIGQUIT/SIGTERM at 
+# data/threaddumps. Only fallback kill -3 since jstack and jcmd are only 
+# available in jdk. 
+#
+# The script is intended to be used with and be placed at the same location as 
+# a compose.yaml. 
+ 
+#!/bin/sh
+set -eu
+
+# POSIX-compatible wrapper to start the original command and capture thread
+# dumps into /kustvakt/data/threaddumps when the container receives SIGQUIT
+# or SIGTERM. This avoids bash-specific syntax so it works with /bin/sh.
+
+DUMP_DIR="/kustvakt/data/threaddumps"
+mkdir -p "$DUMP_DIR"
+
+# File where we collect the JVM's stderr stream so kill -3 output can be
+# extracted when jcmd/jstack are not available.
+STDERR_LOG="/kustvakt/data/logs/jvm-stderr.log"
+
+child_pid=0
+
+on_quit() {
+  TS=$(date +"%Y%m%d-%H%M%S")
+  FILE="$DUMP_DIR/threaddump-$TS.txt"
+  echo "[wrapper] Creating thread dump to $FILE" >&2
+
+  if command -v jcmd >/dev/null 2>&1; then
+    echo "[wrapper] Running: jcmd $child_pid Thread.print" >&2
+    if jcmd "$child_pid" Thread.print > "$FILE" 2>&1; then
+      echo "[wrapper] jcmd wrote $FILE" >&2
+    else
+      echo "[wrapper] jcmd failed" >&2
+    fi
+  elif command -v jstack >/dev/null 2>&1; then
+    echo "[wrapper] Running: jstack -l $child_pid" >&2
+    if jstack -l "$child_pid" > "$FILE" 2>&1; then
+      echo "[wrapper] jstack wrote $FILE" >&2
+    else
+      echo "[wrapper] jstack failed" >&2
+    fi
+  else
+    # If we don't have jcmd/jstack, trigger JVM to print a thread dump to
+    # stderr (kill -3) and capture the appended portion from the stderr log.
+    MARKER="===THREAD_DUMP_MARKER $TS $child_pid ==="
+    printf "%s\n" "$MARKER" >> "$STDERR_LOG" || true
+    kill -3 "$child_pid" 2>/dev/null || true
+    # give JVM a moment to write to stderr
+    sleep 1
+
+    # Extract the portion after the MARKER into the dump file.
+    # Find the last occurrence of the marker and tail from the next line.
+    if marker_line=$(grep -nF "$MARKER" "$STDERR_LOG" 2>/dev/null | tail -n 1 | cut -d: -f1); then
+      if [ -n "$marker_line" ]; then
+        # tail from marker_line+1
+        start=$((marker_line + 1))
+        if tail -n +$start "$STDERR_LOG" > "$FILE" 2>/dev/null; then
+          echo "[wrapper] Extracted JVM stderr to $FILE" >&2
+        else
+          echo "[wrapper] Failed to extract JVM stderr to $FILE" >&2
+        fi
+      else
+        # nothing found; create empty file so caller sees some output
+        : > "$FILE" || true
+      fi
+    else
+      : > "$FILE" || true
+    fi
+  fi
+
+  # If the dump file is empty or missing, fall back to writing last 2000 lines
+  if [ ! -s "$FILE" ]; then
+    echo "[wrapper] $FILE is empty; writing fallback from $STDERR_LOG" >&2
+    printf "[wrapper] Fallback thread dump (no jcmd/jstack output)\n" > "$FILE" || true
+    tail -n 2000 "$STDERR_LOG" >> "$FILE" 2>/dev/null || true
+    echo "[wrapper] Wrote fallback content to $FILE" >&2
+  fi
+
+  # Forward quit/term to child so it can shutdown
+  kill -s QUIT "$child_pid" 2>/dev/null || kill -s TERM "$child_pid" 2>/dev/null || true
+}
+
+trap 'on_quit' QUIT TERM
+
+if [ "$#" -eq 0 ]; then
+  echo "[wrapper] No command provided to start Kustvakt" >&2
+  exit 1
+fi
+
+first="$1"
+shift
+
+# Determine how to invoke the target process.
+if command -v "$first" >/dev/null 2>&1; then
+  # executable available in PATH
+  set -- "$first" "$@"
+else
+  case "$first" in
+    *.jar)
+      if [ -f "/kustvakt/$first" ]; then
+        set -- java -jar "/kustvakt/$first" "$@"
+      else
+        set -- java -jar "$first" "$@"
+      fi
+      ;;
+    /*)
+      if [ -f "$first" ]; then
+        case "$first" in
+          *.jar)
+            set -- java -jar "$first" "$@"
+            ;;
+          *)
+            set -- "$first" "$@"
+            ;;
+        esac
+      else
+        set -- "$first" "$@"
+      fi
+      ;;
+    *)
+      if [ -f "/kustvakt/$first" ]; then
+        case "/kustvakt/$first" in
+          *.jar)
+            set -- java -jar "/kustvakt/$first" "$@"
+            ;;
+          *)
+            set -- "/kustvakt/$first" "$@"
+            ;;
+        esac
+      else
+        set -- java -jar "$first" "$@"
+      fi
+      ;;
+  esac
+fi
+
+WRAPPER_LOG="/kustvakt/data/logs/kustvakt-wrapper.log"
+timestamp() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
+
+echo "[wrapper] Starting child: $*" | tee -a "$WRAPPER_LOG" >&2
+
+# Start the child in background and capture its PID.
+# Prefer starting the child in a separate session so it does not receive
+# the initial container signal (which docker sends to PID 1). We then
+# explicitly forward QUIT/TERM to the child after creating the dump.
+if command -v setsid >/dev/null 2>&1; then
+  echo "[$(timestamp)] wrapper: starting child with setsid" >> "$WRAPPER_LOG" || true
+  setsid "$@" >>"$STDERR_LOG" 2>&1 &
+else
+  echo "[$(timestamp)] wrapper: setsid not available; starting child normally" >> "$WRAPPER_LOG" || true
+  "$@" >>"$STDERR_LOG" 2>&1 &
+fi
+child_pid=$!
+
+# Record the child's PID to a file and wrapper log for easier debugging
+echo "$child_pid" > /kustvakt/data/kustvakt-child.pid 2>/dev/null || true
+echo "[$(timestamp)] wrapper: child_pid=$child_pid (pid file: /kustvakt/data/kustvakt-child.pid)" >> "$WRAPPER_LOG" || true
+
+# Wait for the child and propagate exit code
+wait "$child_pid"
+exit_code=$?
+exit $exit_code
\ No newline at end of file