#!/usr/bin/env sh set -eu PROJECT_DIR="/app" CONFIG_TEMPLATE="${PROJECT_DIR}/config/config.example.yaml" CONFIG_PATH="${CONFIG_PATH:-${PROJECT_DIR}/config/local.yaml}" LOG_DIR="${PROJECT_DIR}/logs" CAMERA_ID="${CAMERA_ID:-store_cam_01}" RTSP_URL="${RTSP_URL:-}" EVENT_SINK_PATH="${EVENT_SINK_PATH:-logs/events.jsonl}" API_HOST="${API_HOST:-0.0.0.0}" API_PORT="${API_PORT:-18081}" mkdir -p "${LOG_DIR}" "$(dirname "${CONFIG_PATH}")" if [ ! -f "${CONFIG_PATH}" ]; then cp "${CONFIG_TEMPLATE}" "${CONFIG_PATH}" fi python - "$CONFIG_PATH" "$CAMERA_ID" "$RTSP_URL" "$EVENT_SINK_PATH" <<'PY' from pathlib import Path import sys import yaml config_path = Path(sys.argv[1]) camera_id = sys.argv[2] rtsp_url = sys.argv[3] event_sink_path = sys.argv[4] raw = yaml.safe_load(config_path.read_text(encoding="utf-8")) or {} raw["camera_id"] = camera_id stream = raw.setdefault("stream", {}) if rtsp_url: stream["rtsp_url"] = rtsp_url event_sink = raw.setdefault("event_sink", {}) event_sink["path"] = event_sink_path config_path.write_text( yaml.safe_dump(raw, allow_unicode=True, sort_keys=False), encoding="utf-8", ) PY exec python - "$CONFIG_PATH" "$API_HOST" "$API_PORT" <<'PY' import signal import subprocess import sys import time config_path, api_host, api_port = sys.argv[1:4] commands = [ [sys.executable, "-m", "app.main", "--config", config_path], [ sys.executable, "-m", "app.manage_api", "--config", config_path, "--host", api_host, "--port", api_port, ], ] processes = [subprocess.Popen(command) for command in commands] def terminate_all(signum, _frame): for process in processes: if process.poll() is None: process.terminate() deadline = time.time() + 10 for process in processes: if process.poll() is not None: continue timeout = max(0, deadline - time.time()) try: process.wait(timeout=timeout) except subprocess.TimeoutExpired: process.kill() raise SystemExit(128 + signum) for handled_signal in (signal.SIGINT, signal.SIGTERM): signal.signal(handled_signal, terminate_all) while True: for index, process in enumerate(processes): return_code = process.poll() if return_code is None: continue for other_index, other_process in enumerate(processes): if other_index == index or other_process.poll() is not None: continue other_process.terminate() deadline = time.time() + 10 for other_index, other_process in enumerate(processes): if other_index == index or other_process.poll() is not None: continue timeout = max(0, deadline - time.time()) try: other_process.wait(timeout=timeout) except subprocess.TimeoutExpired: other_process.kill() raise SystemExit(return_code) time.sleep(0.5) PY