#!/usr/bin/env bash require_root() { if [[ ${EUID:-$(id -u)} -ne 0 ]]; then echo "need root" exit 1 fi } ensure_runtime_user() { local user_name="$1" id -u "$user_name" >/dev/null 2>&1 || useradd --system --no-create-home --shell /usr/sbin/nologin "$user_name" } ensure_log_file() { local log_file="$1" local owner="$2" mkdir -p "$(dirname "$log_file")" touch "$log_file" chown "$owner":"$owner" "$log_file" } pid_matches_marker() { local pid="$1" local marker="$2" [[ "$pid" =~ ^[0-9]+$ ]] || return 1 [[ -r "/proc/$pid/cmdline" ]] || return 1 local cmdline cmdline="$(tr '\0' ' ' < "/proc/$pid/cmdline" 2>/dev/null || true)" [[ "$cmdline" == *"$marker"* ]] } find_pids_by_marker() { local marker="$1" local proc_path for proc_path in /proc/[0-9]*; do local pid="${proc_path##*/}" if pid_matches_marker "$pid" "$marker"; then printf '%s\n' "$pid" fi done } stop_pid() { local pid="$1" kill "$pid" 2>/dev/null || true for _ in {1..30}; do if ! kill -0 "$pid" 2>/dev/null; then return 0 fi sleep 0.1 done if kill -0 "$pid" 2>/dev/null; then kill -9 "$pid" 2>/dev/null || true fi } stop_pid_file() { local pid_file="$1" local marker="$2" local pid="" if [[ -f "$pid_file" ]]; then pid="$(cat "$pid_file" 2>/dev/null || true)" fi if pid_matches_marker "$pid" "$marker"; then stop_pid "$pid" else while IFS= read -r matched_pid; do [[ -n "$matched_pid" ]] || continue stop_pid "$matched_pid" done < <(find_pids_by_marker "$marker") fi rm -f "$pid_file" } wait_for_tcp_listen() { local host="$1" local port="$2" local timeout_sec="${3:-15}" local deadline=$((SECONDS + timeout_sec)) local escaped_host="${host//./\\.}" local pattern="${escaped_host}:${port}( |$)" while (( SECONDS < deadline )); do if ss -H -lnt 2>/dev/null | grep -qE "$pattern"; then return 0 fi sleep 0.2 done return 1 } start_python_service() { local runtime_user="$1" local workdir="$2" local pythonpath="$3" local log_file="$4" local pid_file="$5" local marker="$6" local argv_json="$7" local launcher=' import json import os import subprocess import sys runtime_user, workdir, pythonpath, log_file, pid_file, marker, argv_json = sys.argv[1:] argv = json.loads(argv_json) env = os.environ.copy() env["PYTHONUNBUFFERED"] = "1" env["PYTHONPATH"] = pythonpath with open(log_file, "ab", buffering=0) as handle: proc = subprocess.Popen( argv, cwd=workdir, env=env, stdin=subprocess.DEVNULL, stdout=handle, stderr=subprocess.STDOUT, start_new_session=True, ) print(proc.pid) ' local pid pid="$( runuser -u "$runtime_user" -- python3 -c "$launcher" \ "$runtime_user" "$workdir" "$pythonpath" "$log_file" "$pid_file" "$marker" "$argv_json" )" printf '%s\n' "$pid" > "$pid_file" printf '%s\n' "$pid" } ensure_nat_jump_absent() { local cmd="$1" local table="$2" local chain="$3" shift 3 while "$cmd" -t "$table" -C "$chain" "$@" >/dev/null 2>&1; do "$cmd" -t "$table" -D "$chain" "$@" >/dev/null 2>&1 || break done } ensure_nat_chain_absent() { local cmd="$1" local chain="$2" if ! command -v "$cmd" >/dev/null 2>&1; then return 0 fi if ! "$cmd" -t nat -S >/dev/null 2>&1; then return 0 fi "$cmd" -t nat -F "$chain" 2>/dev/null || true "$cmd" -t nat -X "$chain" 2>/dev/null || true }