#!/usr/bin/env bash # Unified script to stop all Maven services. # Usage: ./kill-maven.sh # - Docker deploy: `docker compose stop` (see why below). # - Bare-metal / dev run: graceful SIGTERM first, SIGKILL if anything lingers. # Exits non-zero if it cannot confirm everything is stopped. It must never say # "stopped" unless it checked. set -euo pipefail # The llama-server the phraser spawns has NO "maven" in its command line (its # args are `-m /path/to/.gguf --port ...`), so a `llama-server.*maven` # pattern matches nothing and leaks it — the exact bug that let orphans pile up # and OOM the box. # # We used to match the model name, defaulting to LFM2. The deploy now runs # Qwen3.5-0.8B, so that default matched nothing and the server survived every # kill. Match any llama-server serving a .gguf instead, so swapping the model in # deploy/mavend.json cannot break this script again. Set MODEL to narrow it if # some other llama-server on this box must be left alone. MODEL="${MODEL:-}" PAT='mavend|mavsttd|mavttsd|mavweb|mavpoll|mavenclient' if [ -n "$MODEL" ]; then LLM="llama-server.*${MODEL}" else LLM='llama-server.*\.gguf' fi COMPOSE_FILE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/docker-compose.yml" # --- containerised deploy ------------------------------------------------ # docker-compose.yml does not set `pid: host`, so each container has its own # PID namespace: pkill on the host sees nothing inside them. This script used # to print "all stopped" while every daemon was still happily running. Stop the # containers through compose instead — that actually reaches them. # # running_containers prints the ids of the project's running containers, or # nothing. Empty output plus a non-zero return means "could not ask docker", # which is different from "nothing is running" and is handled below. running_containers() { docker compose -f "$COMPOSE_FILE" ps -q --status running 2>/dev/null } DOCKER_OK=0 CONTAINERS="" if command -v docker >/dev/null 2>&1 && [ -f "$COMPOSE_FILE" ]; then if CONTAINERS="$(running_containers)"; then DOCKER_OK=1 fi fi if [ "$DOCKER_OK" = 1 ] && [ -n "$CONTAINERS" ]; then echo "--- Maven is running in containers: stopping via docker compose ---" if ! docker compose -f "$COMPOSE_FILE" stop; then echo "ERROR: 'docker compose stop' failed. Containers may still be running." >&2 exit 1 fi echo "--- Verifying containers are gone ---" LEFT="$(running_containers || true)" if [ -n "$LEFT" ]; then echo "ERROR: containers still running after stop:" >&2 docker compose -f "$COMPOSE_FILE" ps >&2 || true exit 1 fi echo "All containers stopped." exit 0 fi # --- bare-metal / dev run ----------------------------------------------- # pgrep -f matches whole command lines, so a shell that merely mentions # "mavend" (this script's own parent, for one) shows up. Drop ourselves and our # parent, otherwise the SIGKILL sweep can take out the terminal you ran this in. host_pids() { pgrep -f "$PAT|$LLM" | grep -v -e "^$$\$" -e "^$PPID\$" | paste -sd, - || true } HOST_PIDS=$(host_pids) if [ -z "$HOST_PIDS" ]; then # Nothing on the host. Whether that means "already down" depends on whether # we managed to ask docker, and the two must not read the same. if [ "$DOCKER_OK" = 1 ]; then # Docker answered and named no running containers, and there is nothing # on the host either. That is a real answer: Maven is already stopped. echo "Nothing to stop: no Maven processes and no running containers." exit 0 fi # We could not ask docker, so Maven may be alive in a container we cannot # see. Saying "stopped" here is the exact false success this script had. echo "ERROR: no Maven processes on this host, and docker could not be asked." >&2 echo " If this is the container deploy it may still be running:" >&2 echo " docker compose -f $COMPOSE_FILE stop" >&2 echo " Nothing was stopped. Check by hand before assuming Maven is down." >&2 exit 1 fi echo "--- Sending graceful SIGTERM to Maven services ---" pkill -TERM -f "$PAT" || true # mavend's Pdeathsig SIGKILLs its llama-server on exit, but sweep strays too # (an orphan whose mavend already died has no one left to reap it). pkill -TERM -f "$LLM" || true echo "--- Verifying processes are gone ---" sleep 1 PIDS=$(host_pids) if [ -n "$PIDS" ]; then echo "Warning: some processes still alive. PIDs: $PIDS" echo "--- Force killing with SIGKILL ---" echo "$PIDS" | tr ',' '\n' | xargs -r kill -9 sleep 1 LEFT=$(host_pids) if [ -n "$LEFT" ]; then echo "ERROR: still alive after SIGKILL. PIDs: $LEFT" >&2 exit 1 fi echo "Done (SIGKILL)." else echo "All services gracefully stopped." fi