#!/bin/bash set -eu PACKAGE_ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) APP_ROOT="${TRIM_APPDEST:-$PACKAGE_ROOT/app}" DATA_ROOT="${TRIM_PKGVAR:-$PACKAGE_ROOT/var}" RUN_ROOT="$DATA_ROOT/run" LOG_ROOT="$DATA_ROOT/log" PID_FILE="$RUN_ROOT/imagefind.pid" LOG_FILE="$LOG_ROOT/imagefind.log" ACCESS_CONFIG="$DATA_ROOT/access.json" GATEWAY_SOCKET="$APP_ROOT/imagefind.sock" mkdir -p "$RUN_ROOT" "$LOG_ROOT" "$DATA_ROOT/data" "$DATA_ROOT/tmp" is_running() { [ -f "$PID_FILE" ] || return 1 PID=$(sed -n '1p' "$PID_FILE") [ -n "$PID" ] && kill -0 "$PID" 2>/dev/null } report_start_failure() { MESSAGE="ImageFind failed to start. Runtime log: $LOG_FILE" printf '%s\n' "$MESSAGE" >&2 if [ -n "${TRIM_TEMP_LOGFILE:-}" ]; then { printf '%s\n' "$MESSAGE" printf '%s\n' '--- imagefind.log (last 100 lines) ---' tail -n 100 "$LOG_FILE" 2>/dev/null || true } >>"$TRIM_TEMP_LOGFILE" 2>&1 || true fi } start_app() { if is_running; then return 0 fi rm -f "$PID_FILE" if ! TRIM_APPDEST="$APP_ROOT" TRIM_PKGVAR="$DATA_ROOT" "$PACKAGE_ROOT/cmd/runtime_bootstrap" \ >>"$LOG_FILE" 2>&1; then report_start_failure return 1 fi SERVER="$DATA_ROOT/runtime/current/bin/python" export IMAGEFIND_DATA_DIR="$DATA_ROOT/data" export IMAGEFIND_FRONTEND_DIR="$APP_ROOT/frontend" export IMAGEFIND_BUNDLED_AI_RUNTIME=false export IMAGEFIND_RUNTIME_REQUIREMENTS_DIR="$APP_ROOT/runtime/ai" export IMAGEFIND_BUNDLED_TOOLS_DIR="$APP_ROOT/bin" export IMAGEFIND_POSTGRES_CONF="$DATA_ROOT/postgres-client.conf" export IMAGEFIND_POSTGRES_ENROLLMENT_TOKEN_SEED="$DATA_ROOT/postgres-enrollment-token.seed" export PYTHONPATH="$DATA_ROOT/runtime/ai-current${PYTHONPATH:+:$PYTHONPATH}" export XDG_CACHE_HOME="$DATA_ROOT/cache" # Model conversion can temporarily exceed the small system /tmp volume. # Keep all large, recoverable conversion files on the application data volume. export TMPDIR="$DATA_ROOT/tmp" DIRECT_ENABLED=true DIRECT_PORT=8765 if [ -f "$ACCESS_CONFIG" ]; then CONFIG_ENABLED=$(sed -n 's/.*"enabled"[[:space:]]*:[[:space:]]*\(true\|false\).*/\1/p' "$ACCESS_CONFIG" | head -n 1) CONFIG_PORT=$(sed -n 's/.*"port"[[:space:]]*:[[:space:]]*\([0-9][0-9]*\).*/\1/p' "$ACCESS_CONFIG" | head -n 1) [ -z "$CONFIG_ENABLED" ] || DIRECT_ENABLED="$CONFIG_ENABLED" [ -z "$CONFIG_PORT" ] || DIRECT_PORT="$CONFIG_PORT" fi export IMAGEFIND_HOST="0.0.0.0" export IMAGEFIND_PORT="$DIRECT_PORT" export IMAGEFIND_DIRECT_ACCESS="$DIRECT_ENABLED" export IMAGEFIND_GATEWAY_PREFIX="/app/imagefind" export IMAGEFIND_GATEWAY_SOCKET="$GATEWAY_SOCKET" # Keep CPU fallback responsive on a NAS. Administrators can override any # value before launch, while the default avoids saturating every core. export IMAGEFIND_AI_CPU_THREADS="${IMAGEFIND_AI_CPU_THREADS:-1}" export OMP_NUM_THREADS="${OMP_NUM_THREADS:-$IMAGEFIND_AI_CPU_THREADS}" export OPENBLAS_NUM_THREADS="${OPENBLAS_NUM_THREADS:-$IMAGEFIND_AI_CPU_THREADS}" export MKL_NUM_THREADS="${MKL_NUM_THREADS:-$IMAGEFIND_AI_CPU_THREADS}" export LD_LIBRARY_PATH="$APP_ROOT/bin${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" if [ ! -f "$IMAGEFIND_POSTGRES_CONF" ] && [ -f "$IMAGEFIND_POSTGRES_ENROLLMENT_TOKEN_SEED" ]; then if ! "$SERVER" -m imagefind.main postgres-enroll >>"$LOG_FILE" 2>&1; then report_start_failure return 1 fi fi nohup "$SERVER" -m imagefind.main >>"$LOG_FILE" 2>&1 & printf '%s\n' "$!" > "$PID_FILE" sleep 1 if ! is_running; then report_start_failure return 1 fi } stop_pid() { FILE="$1" [ -f "$FILE" ] || return 0 PID=$(sed -n '1p' "$FILE") if [ -n "$PID" ] && kill -0 "$PID" 2>/dev/null; then kill "$PID" 2>/dev/null || true COUNT=0 while kill -0 "$PID" 2>/dev/null && [ "$COUNT" -lt 20 ]; do sleep 1 COUNT=$((COUNT + 1)) done kill -9 "$PID" 2>/dev/null || true fi rm -f "$FILE" } case "${1:-status}" in start) start_app ;; stop) stop_pid "$PID_FILE"; rm -f "$GATEWAY_SOCKET" ;; restart) "$0" stop; "$0" start ;; status) if is_running; then exit 0 fi # fnOS reserves 3 for a valid status check whose service is stopped. exit 3 ;; *) printf 'usage: %s {start|stop|restart|status}\n' "$0" >&2; exit 2 ;; esac