#!/bin/bash

set -u

PKG_VAR_DIR="${TRIM_PKGVAR:-}"
LOG_FILE="${PKG_VAR_DIR}/info.log"
BACKEND_PID_FILE="${PKG_VAR_DIR}/backend.pid"
GATEWAY_PID_FILE="${PKG_VAR_DIR}/gateway.pid"
READINESS_MARKER="${PKG_VAR_DIR}/runtime.ready"
GATEWAY_RUNTIME_LOG="${PKG_VAR_DIR}/gateway-runtime.log"
GATEWAY_RUNTIME_LOG_PREVIOUS="${PKG_VAR_DIR}/gateway-runtime.log.1"
ALTCHA_HMAC_KEY_FILE="${PKG_VAR_DIR}/altcha_hmac_key"
HMAC_SECRET_FILE="${PKG_VAR_DIR}/hmac_secret"
INTERNAL_RPC_TOKEN_FILE="${PKG_VAR_DIR}/internal_rpc_token"
ADMIN_PROXY_SECRET_FILE="${PKG_VAR_DIR}/admin_proxy_secret"
RUNTIME_PORT_FILE=""
GATEWAY_CONFIG_DIR="/usr/local/etc/fn-knock/"

APP_HOME="${TRIM_APPDEST:-}"
BACKEND_ENTRY="${APP_HOME}/server/server-admin-rs"
GATEWAY_BIN_DIR="${APP_HOME}/server"
GATEWAY_BIN="${GATEWAY_BIN_DIR}/go-reauth-proxy-linux-amd64"

ADMIN_STATIC_PATH="${APP_HOME}/ui/www"
AUTH_STATIC_PATH="${APP_HOME}/server-auth-view/dist"
ACME_BUNDLE_ZIP="${APP_HOME}/server/server-admin/resources/acmesh.zip"
PORT_PATTERN='^([1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$'
NOFILE_LIMIT="${FN_KNOCK_NOFILE_LIMIT:-1048576}"
START_TIMEOUT_SECONDS="${FN_KNOCK_START_TIMEOUT_SECONDS:-300}"
STOP_TIMEOUT_SECONDS="${FN_KNOCK_STOP_TIMEOUT_SECONDS:-75}"
FORCE_KILL_TIMEOUT_SECONDS="${FN_KNOCK_FORCE_KILL_TIMEOUT_SECONDS:-10}"
FIREWALL_WAIT_SECONDS="${FN_KNOCK_FIREWALL_WAIT_SECONDS:-1}"

BACKEND_PORT=""
ADMIN_VIEW_PORT=""
AUTH_PORT=""
GO_BACKEND_PORT=""
GO_REPROXY_PORT=""
ALTCHA_HMAC_KEY="${ALTCHA_HMAC_KEY:-}"
HMAC_SECRET="${HMAC_SECRET:-}"
FN_KNOCK_INTERNAL_RPC_TOKEN="${FN_KNOCK_INTERNAL_RPC_TOKEN:-}"
ADMIN_PROXY_SECRET="${ADMIN_PROXY_SECRET:-}"
ROOT_SHARE_DIR="${FN_KNOCK_ROOT_SHARE_DIR:-${FN_KNOCK_CERT_SHARE_DIR:-}}"
RUNTIME_LOG_DIR="${PKG_VAR_DIR}/runtime/logs"
SUPERVISOR_LOG="${RUNTIME_LOG_DIR}/supervisor.jsonl"
SUPERVISOR_EVENTS_DIR="${PKG_VAR_DIR}/runtime/supervisor-events"
PLANNED_STOP_OPERATION=""

log_msg() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "${LOG_FILE}"
}

supervisor_log() {
  local level="$1" component="$2" event="$3" reason="$4" exit_code="${5:-null}"
  local signal="${6:-null}"
  if [[ "${exit_code}" =~ ^[0-9]+$ ]] && [ "${exit_code}" -gt 128 ]; then
    signal="$((exit_code - 128))"
  fi
    mkdir -p "${RUNTIME_LOG_DIR}"
    chmod 700 "${PKG_VAR_DIR}/runtime" "${RUNTIME_LOG_DIR}" 2>/dev/null || true
  if [ -f "${SUPERVISOR_LOG}" ] && [ "$(wc -c < "${SUPERVISOR_LOG}" 2>/dev/null || echo 0)" -ge 516096 ]; then
        mv -f "${SUPERVISOR_LOG}" "${SUPERVISOR_LOG}.1"
    fi
  printf '{"time":"%s","level":"%s","component":"%s","event":"%s","reason_code":"%s","fields":{"exit_code":%s,"signal":%s,"operation_id":"%s"}}\n' \
    "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" "${level}" "${component}" "${event}" "${reason}" "${exit_code}" "${signal}" "${PLANNED_STOP_OPERATION:-}" >> "${SUPERVISOR_LOG}"
    chmod 600 "${SUPERVISOR_LOG}" 2>/dev/null || true
    mkdir -p "${SUPERVISOR_EVENTS_DIR}"
    chmod 700 "${SUPERVISOR_EVENTS_DIR}" 2>/dev/null || true
    local hint_tmp="${SUPERVISOR_EVENTS_DIR}/.hint-$$-${RANDOM:-0}.tmp"
    local hint_path="${SUPERVISOR_EVENTS_DIR}/$(date -u '+%s')-$$-${RANDOM:-0}-${event}.json"
  printf '{"time":"%s","level":"%s","component":"%s","event":"%s","reason_code":"%s","fields":{"exit_code":%s,"signal":%s,"operation_id":"%s"}}\n' \
    "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" "${level}" "${component}" "${event}" "${reason}" "${exit_code}" "${signal}" "${PLANNED_STOP_OPERATION:-}" > "${hint_tmp}"
    chmod 600 "${hint_tmp}" 2>/dev/null || true
    mv -f "${hint_tmp}" "${hint_path}"
  find "${SUPERVISOR_EVENTS_DIR}" -type f -name '*.json' -mtime +7 -exec rm -f {} \; 2>/dev/null || true
    local hints=("${SUPERVISOR_EVENTS_DIR}"/*.json)
    while [ "${#hints[@]}" -gt 32 ]; do rm -f "${hints[0]}"; hints=("${SUPERVISOR_EVENTS_DIR}"/*.json); done
}

rotate_gateway_runtime_log() {
    local size=0
    if [ -f "${GATEWAY_RUNTIME_LOG}" ]; then
        size="$(wc -c < "${GATEWAY_RUNTIME_LOG}" 2>/dev/null || echo 0)"
    fi
    case "${size}" in
        ''|*[!0-9]*) size=0 ;;
    esac
    if [ "${size}" -ge 5242880 ]; then
        rm -f "${GATEWAY_RUNTIME_LOG_PREVIOUS}"
        mv "${GATEWAY_RUNTIME_LOG}" "${GATEWAY_RUNTIME_LOG_PREVIOUS}"
    fi
}

wait_runtime_ready() {
    local attempts=0
    local max_attempts=$((START_TIMEOUT_SECONDS * 4))
    local backend_pid=""
    local gateway_pid=""
    while [ "${attempts}" -lt "${max_attempts}" ]; do
        if [ -f "${READINESS_MARKER}" ]; then
            log_msg "Runtime readiness confirmed."
            return 0
        fi
        backend_pid="$(cat "${BACKEND_PID_FILE}" 2>/dev/null || true)"
        gateway_pid="$(cat "${GATEWAY_PID_FILE}" 2>/dev/null || true)"
        if ! check_process "${backend_pid}" || ! check_process "${gateway_pid}"; then
            break
        fi
        sleep 0.25
        attempts=$((attempts + 1))
    done
    log_msg "Runtime readiness failed or timed out after ${START_TIMEOUT_SECONDS} seconds."
    if [ -f "${GATEWAY_RUNTIME_LOG}" ]; then
        log_msg "Recent gateway runtime diagnostics:"
        tail -n 40 "${GATEWAY_RUNTIME_LOG}" >> "${LOG_FILE}" 2>/dev/null || true
    fi
    return 1
}

# Inspect only our chains: listing an entire iptables-nft table can fail when
# another application has installed native nft expressions in that table.
fn_connect_waf_chain_exists() {
    local firewall="$1" table="$2" chain="$3"
    local output="" family="ip" status=0
    if "${firewall}" --version 2>/dev/null | grep -q '(nf_tables)'; then
        [ "${firewall}" = ip6tables ] && family="ip6"
        output="$(nft list chain "${family}" "${table}" "${chain}" 2>&1)" || status=$?
    else
        output="$("${firewall}" -w "${FIREWALL_WAIT_SECONDS}" -t "${table}" -S "${chain}" 2>&1)" || status=$?
    fi
    [ "${status}" -eq 0 ] && return 0
    case "${output}" in
        *"No such file or directory"*|*"No chain/target/match by that name"*|*"does not exist"*) return 1 ;;
    esac
    log_msg "FN Connect WAF chain inspection failed: ${firewall} ${table} ${chain}: ${output}"
    return 2
}

cleanup_fn_connect_waf_rules_once() {
    local firewall=""
    local comment="fn-knock:fn-connect-waf"
    local attempts=0
    local max_jump_deletions=64
    local cleanup_failed=0
    local table=""
    local parent=""
    local parent_chain=""
    local chain=""

    for firewall in iptables ip6tables; do
        if ! command -v "${firewall}" >/dev/null 2>&1; then
            log_msg "FN Connect WAF cleanup skipped: ${firewall} is unavailable"
            continue
        fi

        for table_parent_chain in \
            "nat|PREROUTING|FNK_FNC_PRE" \
            "nat|OUTPUT|FNK_FNC_OUT" \
            "nat|OUTPUT|FNK_FNC_WAF" \
            "filter|INPUT|FNK_FNC_IN"; do
            table="${table_parent_chain%%|*}"
            parent_chain="${table_parent_chain#*|}"
            parent="${parent_chain%%|*}"
            chain="${parent_chain#*|}"
            attempts=0
            while [ "${attempts}" -lt "${max_jump_deletions}" ]; do
                if [ "${parent}" = "PREROUTING" ]; then
                    "${firewall}" -w "${FIREWALL_WAIT_SECONDS}" -t "${table}" -C "${parent}" \
                        -m addrtype --dst-type LOCAL \
                        -m comment --comment "${comment}" -j "${chain}" >/dev/null 2>&1 || break
                    if ! "${firewall}" -w "${FIREWALL_WAIT_SECONDS}" -t "${table}" -D "${parent}" \
                        -m addrtype --dst-type LOCAL \
                        -m comment --comment "${comment}" -j "${chain}" >/dev/null 2>&1; then
                        log_msg "FN Connect WAF cleanup failed: unable to delete ${firewall} ${table} ${parent} jump"
                        cleanup_failed=1
                        break
                    fi
                else
                    "${firewall}" -w "${FIREWALL_WAIT_SECONDS}" -t "${table}" -C "${parent}" \
                        -m comment --comment "${comment}" -j "${chain}" >/dev/null 2>&1 || break
                    if ! "${firewall}" -w "${FIREWALL_WAIT_SECONDS}" -t "${table}" -D "${parent}" \
                        -m comment --comment "${comment}" -j "${chain}" >/dev/null 2>&1; then
                        log_msg "FN Connect WAF cleanup failed: unable to delete ${firewall} ${table} ${parent} jump"
                        cleanup_failed=1
                        break
                    fi
                fi
                attempts=$((attempts + 1))
            done
            if [ "${attempts}" -eq "${max_jump_deletions}" ]; then
                if [ "${parent}" = "PREROUTING" ]; then
                    if "${firewall}" -w "${FIREWALL_WAIT_SECONDS}" -t "${table}" -C "${parent}" \
                        -m addrtype --dst-type LOCAL \
                        -m comment --comment "${comment}" -j "${chain}" >/dev/null 2>&1; then
                        log_msg "FN Connect WAF cleanup failed: too many ${firewall} ${table} ${parent} jumps to ${chain}"
                        cleanup_failed=1
                    fi
                elif "${firewall}" -w "${FIREWALL_WAIT_SECONDS}" -t "${table}" -C "${parent}" \
                    -m comment --comment "${comment}" -j "${chain}" >/dev/null 2>&1; then
                    log_msg "FN Connect WAF cleanup failed: too many ${firewall} ${table} ${parent} jumps to ${chain}"
                    cleanup_failed=1
                fi
            fi
        done

        for table_chain in \
            "nat|FNK_FNC_PRE" \
            "nat|FNK_FNC_OUT" \
            "nat|FNK_FNC_WAF" \
            "filter|FNK_FNC_IN"; do
            table="${table_chain%%|*}"
            chain="${table_chain#*|}"
            if ! fn_connect_waf_chain_exists "${firewall}" "${table}" "${chain}"; then
                continue
            fi
            if ! "${firewall}" -w "${FIREWALL_WAIT_SECONDS}" -t "${table}" -F "${chain}" >/dev/null 2>&1; then
                log_msg "FN Connect WAF cleanup failed: unable to flush ${firewall} ${table} chain ${chain}"
                cleanup_failed=1
            fi
            if ! "${firewall}" -w "${FIREWALL_WAIT_SECONDS}" -t "${table}" -X "${chain}" >/dev/null 2>&1; then
                log_msg "FN Connect WAF cleanup failed: unable to delete ${firewall} ${table} chain ${chain}"
                cleanup_failed=1
            fi
        done
    done
    return "${cleanup_failed}"
}

fn_connect_waf_rules_absent() {
    local firewall=""
    local table=""
    local rules=""
    local error=""

    for firewall in iptables ip6tables; do
        command -v "${firewall}" >/dev/null 2>&1 || continue

        if "${firewall}" --version 2>/dev/null | grep -q '(nf_tables)'; then
            local table_chain="" chain="" chain_status=0
            for table_chain in "nat|FNK_FNC_PRE" "nat|FNK_FNC_OUT" "nat|FNK_FNC_WAF" "filter|FNK_FNC_IN"; do
                table="${table_chain%%|*}"
                chain="${table_chain#*|}"
                chain_status=0
                fn_connect_waf_chain_exists "${firewall}" "${table}" "${chain}" || chain_status=$?
                case "${chain_status}" in
                    0) return 1 ;;
                    1) ;;
                    *) return 2 ;;
                esac
            done
            continue
        fi

        for table in nat filter; do
            error=""
            if ! rules="$("${firewall}" -w "${FIREWALL_WAIT_SECONDS}" -t "${table}" -S 2>&1)"; then
                error="${rules}"
                case "${error}" in
                    *"Table does not exist"*|*"table does not exist"*|\
                    *"Address family not supported"*|*"address family not supported"*|\
                    *"Protocol not supported"*|*"protocol not supported"*)
                        log_msg "FN Connect WAF verification skipped: ${firewall} ${table} is unavailable"
                        continue
                        ;;
                    *)
                        error="$(printf '%s' "${error}" | tr '\r\n' '  ' | cut -c 1-500)"
                        log_msg "FN Connect WAF verification failed: ${firewall} ${table}: ${error:-<no error output>}"
                        return 2
                        ;;
                esac
            fi
            if printf '%s\n' "${rules}" | grep -Eq \
                '^-N FNK_FNC_(PRE|OUT|WAF|IN)( |$)|-j FNK_FNC_(PRE|OUT|WAF|IN)( |$)'; then
                return 1
            fi
        done
    done

    return 0
}

cleanup_fn_connect_waf_rules() {
    local attempt=1
    local absence_status=0
    local max_attempts="${1:-3}"
    local pass_failed=0

    while [ "${attempt}" -le "${max_attempts}" ]; do
        absence_status=0
        fn_connect_waf_rules_absent || absence_status=$?
        if [ "${absence_status}" -eq 0 ]; then
            return 0
        fi

        pass_failed=0
        if [ "${absence_status}" -eq 1 ]; then
            cleanup_fn_connect_waf_rules_once || pass_failed=1
            absence_status=0
            fn_connect_waf_rules_absent || absence_status=$?
        fi
        if [ "${absence_status}" -eq 0 ]; then
            if [ "${pass_failed}" -ne 0 ]; then
                log_msg "FN Connect WAF cleanup converged after a concurrent rule change"
            fi
            return 0
        fi
        if [ "${attempt}" -lt "${max_attempts}" ]; then
            if [ "${absence_status}" -eq 1 ]; then
                log_msg "FN Connect WAF cleanup will retry because managed rules are still present"
            else
                log_msg "FN Connect WAF cleanup will retry because firewall state could not be verified"
            fi
            sleep 1
        fi
        attempt=$((attempt + 1))
    done

    if [ "${absence_status}" -eq 2 ]; then
        log_msg "FN Connect WAF cleanup failed: firewall state could not be verified after ${max_attempts} attempts"
        return 2
    else
        log_msg "FN Connect WAF cleanup failed: managed rules remain after ${max_attempts} attempts"
        return 1
    fi
}

cleanup_fn_connect_waf_for_stop() {
    local cleanup_status=0
    local max_attempts="${1:-1}"

    # App Center imposes its own stop deadline. Probe once with the short
    # xtables lock budget so a temporarily unavailable firewall cannot hold an
    # otherwise healthy upgrade open for minutes.
    cleanup_fn_connect_waf_rules "${max_attempts}" || cleanup_status=$?
    case "${cleanup_status}" in
        0)
            return 0
            ;;
        2)
            log_msg "FN Connect WAF firewall state is unavailable; continuing the bounded runtime stop"
            supervisor_log WARN management waf_cleanup_unverifiable fnos_stop null
            return 0
            ;;
        *)
            write_temp_log "fn-knock refused to stop because confirmed FN Connect WAF rules remain and could not be removed"
            return 1
            ;;
    esac
}

prepare_fn_connect_waf_for_start() {
    local cleanup_status=0

    cleanup_fn_connect_waf_rules || cleanup_status=$?
    case "${cleanup_status}" in
        0)
            return 0
            ;;
        2)
            log_msg "FN Connect WAF firewall state is unavailable; continuing startup without blocking the runtime"
            supervisor_log WARN management waf_cleanup_unverifiable fnos_start null
            return 0
            ;;
        *)
            write_temp_log "fn-knock refused to start because stale FN Connect WAF rules remain and could not be removed"
            return 1
            ;;
    esac
}

generate_random_hex() {
    local bytes="${1:-32}"

    if command -v openssl >/dev/null 2>&1; then
        openssl rand -hex "${bytes}" 2>/dev/null
        return $?
    fi

    od -An -v -N "${bytes}" -tx1 /dev/urandom 2>/dev/null | tr -d ' \n'
}

log_debug_cmd() {
    local title="$1"
    local cmd="$2"
    local output=""

    output="$(sh -c "${cmd}" 2>&1 || true)"
    if [ -z "${output}" ]; then
        log_msg "[debug] ${title}: <empty>"
        return 0
    fi

    while IFS= read -r line; do
        log_msg "[debug] ${title}: ${line}"
    done <<< "${output}"
}

raise_nofile_limit() {
    local limit="${1:-${NOFILE_LIMIT}}"
    local inherited_hard=""

    if ulimit -Hn "${limit}" >/dev/null 2>&1 && ulimit -Sn "${limit}" >/dev/null 2>&1; then
        log_msg "Raised nofile limit to soft=${limit}, hard=${limit}"
        return 0
    fi

    inherited_hard="$(ulimit -Hn 2>/dev/null || true)"
    if [ -n "${inherited_hard}" ] && [ "${inherited_hard}" != "unlimited" ]; then
        if ulimit -Sn "${inherited_hard}" >/dev/null 2>&1; then
            log_msg "Unable to raise nofile hard limit to ${limit}; raised soft limit to inherited hard=${inherited_hard}"
            return 0
        fi
    fi

    log_msg "Unable to raise nofile limit to soft=${limit}, hard=${limit}"
    return 1
}

log_backend_failure_debug() {
    local pid="$1"

    log_msg "[debug] backend-failure diagnostics begin"
    log_msg "[debug] pid_file=${BACKEND_PID_FILE}"
    log_msg "[debug] pid_value=${pid:-<empty>}"
    log_msg "[debug] app_home=${APP_HOME}"
    log_msg "[debug] data_dir=${PKG_VAR_DIR}"
    log_msg "[debug] root_share_dir=${ROOT_SHARE_DIR:-<empty>}"
    log_msg "[debug] ports admin_view=${ADMIN_VIEW_PORT} backend=${BACKEND_PORT} auth=${AUTH_PORT} go_admin=${GO_BACKEND_PORT} go_proxy=${GO_REPROXY_PORT}"

    if [ -f "${BACKEND_PID_FILE}" ]; then
        log_debug_cmd "pid_file_stat" "ls -l \"${BACKEND_PID_FILE}\""
    else
        log_msg "[debug] pid_file_stat: missing"
    fi

    log_debug_cmd "runtime_dir_stat" "ls -ld \"${PKG_VAR_DIR}\" \"${PKG_VAR_DIR}/frp\" \"${PKG_VAR_DIR}/cloudflared\" \"${PKG_VAR_DIR}/updates\""
    log_debug_cmd "path_stat" "ls -ld \"${APP_HOME}\" \"${ADMIN_STATIC_PATH}\" \"${AUTH_STATIC_PATH}\""
    log_debug_cmd "binary_stat" "ls -l \"${BACKEND_ENTRY}\" \"${GATEWAY_BIN}\""
    log_debug_cmd "data_dir_writable" "test -w \"${PKG_VAR_DIR}\" && echo writable || echo not-writable"
    log_debug_cmd "env_subset" "env | grep -E '^(TRIM_|FN_KNOCK_ROOT_SHARE_DIR|FN_KNOCK_CERT_SHARE_DIR|ADMIN_VIEW_PORT|BACKEND_PORT|AUTH_PORT|GO_BACKEND_PORT|GO_REPROXY_PORT|PATH)=' | sort"
    log_debug_cmd "ulimit" "ulimit -a"

    if [ -n "${pid}" ]; then
        log_debug_cmd "process_snapshot" "ps -p \"${pid}\" -o pid,ppid,user,stat,etime,args"
        if wait "${pid}" 2>/dev/null; then
            log_msg "[debug] wait_result: exited with code 0"
        else
            log_msg "[debug] wait_result: exited with code $?"
        fi
    fi

    if command -v ss >/dev/null 2>&1; then
        log_debug_cmd "port_occupy" "ss -lntp 2>/dev/null | grep -E \":(${ADMIN_VIEW_PORT}|${BACKEND_PORT}|${AUTH_PORT}|${GO_BACKEND_PORT}|${GO_REPROXY_PORT})\\b\" || true"
    elif command -v netstat >/dev/null 2>&1; then
        log_debug_cmd "port_occupy" "netstat -lntp 2>/dev/null | grep -E \":(${ADMIN_VIEW_PORT}|${BACKEND_PORT}|${AUTH_PORT}|${GO_BACKEND_PORT}|${GO_REPROXY_PORT})\\b\" || true"
    else
        log_msg "[debug] port_occupy: ss/netstat unavailable"
    fi

    log_debug_cmd "recent_log_tail" "tail -n 80 \"${LOG_FILE}\""
    log_msg "[debug] backend-failure diagnostics end"
}

fail_backend_start() {
    local reason="$1"
    local pid="${2:-}"
    log_msg "Backend failed to start: ${reason}"
    log_backend_failure_debug "${pid}"
    rm -f "${BACKEND_PID_FILE}"
    return 1
}

write_temp_log() {
    local message="$1"
    echo "${message}" > "${TRIM_TEMP_LOGFILE:-/dev/null}" 2>/dev/null || true
}

temp_log_has_message() {
    if [ -n "${TRIM_TEMP_LOGFILE:-}" ] && [ -s "${TRIM_TEMP_LOGFILE}" ]; then
        return 0
    fi

    return 1
}

ensure_runtime_layout() {
    if [ -z "${PKG_VAR_DIR}" ]; then
        echo "TRIM_PKGVAR is empty; cannot start fn-knock" > "${TRIM_TEMP_LOGFILE:-/dev/null}" 2>/dev/null || true
        return 1
    fi

    if [ -z "${APP_HOME}" ]; then
        echo "TRIM_APPDEST is empty; cannot start fn-knock" > "${TRIM_TEMP_LOGFILE:-/dev/null}" 2>/dev/null || true
        return 1
    fi

    if ! mkdir -p "${PKG_VAR_DIR}" "${PKG_VAR_DIR}/frp" "${PKG_VAR_DIR}/cloudflared" "${PKG_VAR_DIR}/updates" >/dev/null 2>&1; then
        echo "failed to create runtime directories under ${PKG_VAR_DIR}" > "${TRIM_TEMP_LOGFILE:-/dev/null}" 2>/dev/null || true
        return 1
    fi

    if [ ! -w "${PKG_VAR_DIR}" ]; then
        echo "runtime directory is not writable: ${PKG_VAR_DIR}" > "${TRIM_TEMP_LOGFILE:-/dev/null}" 2>/dev/null || true
        return 1
    fi

    RUNTIME_PORT_FILE="${PKG_VAR_DIR}/runtime-ports.env"

    return 0
}

ensure_gateway_config_dir() {
    if [ -d "${GATEWAY_CONFIG_DIR}" ]; then
        return 0
    fi

    if ! mkdir -p "${GATEWAY_CONFIG_DIR}" >/dev/null 2>&1; then
        local message="failed to create gateway config directory: ${GATEWAY_CONFIG_DIR}"
        log_msg "${message}"
        echo "${message}" > "${TRIM_TEMP_LOGFILE:-/dev/null}" 2>/dev/null || true
        return 1
    fi

    return 0
}

resolve_altcha_hmac_key() {
    local existing=""
    local generated=""

    if [ -n "${ALTCHA_HMAC_KEY:-}" ]; then
        return 0
    fi

    existing="$(cat "${ALTCHA_HMAC_KEY_FILE}" 2>/dev/null || true)"
    existing="$(printf '%s' "${existing}" | tr -d '\r\n')"
    if [ -n "${existing}" ]; then
        ALTCHA_HMAC_KEY="${existing}"
        return 0
    fi

    generated="$(generate_random_hex 32)"
    generated="$(printf '%s' "${generated}" | tr -d '\r\n')"
    if [ -z "${generated}" ]; then
        log_msg "Failed to generate ALTCHA_HMAC_KEY"
        echo "failed to generate ALTCHA_HMAC_KEY" > "${TRIM_TEMP_LOGFILE:-/dev/null}" 2>/dev/null || true
        return 1
    fi

    if ! printf '%s' "${generated}" > "${ALTCHA_HMAC_KEY_FILE}"; then
        log_msg "Failed to persist ALTCHA_HMAC_KEY to ${ALTCHA_HMAC_KEY_FILE}"
        echo "failed to persist ALTCHA_HMAC_KEY" > "${TRIM_TEMP_LOGFILE:-/dev/null}" 2>/dev/null || true
        return 1
    fi
    chmod 600 "${ALTCHA_HMAC_KEY_FILE}" 2>/dev/null || true

    ALTCHA_HMAC_KEY="${generated}"
    log_msg "Generated runtime ALTCHA_HMAC_KEY at ${ALTCHA_HMAC_KEY_FILE}"
    return 0
}

resolve_secret_value() {
    local var_name="$1"
    local file_path="$2"
    local label="$3"
    local existing=""
    local generated=""

    eval "existing=\"\${${var_name}:-}\""
    if [ -n "${existing}" ]; then
        return 0
    fi

    existing="$(cat "${file_path}" 2>/dev/null || true)"
    existing="$(printf '%s' "${existing}" | tr -d '\r\n')"
    if [ -z "${existing}" ]; then
        generated="$(generate_random_hex 32)"
        generated="$(printf '%s' "${generated}" | tr -d '\r\n')"
        if [ -z "${generated}" ]; then
            log_msg "Failed to generate ${label}"
            echo "failed to generate ${label}" > "${TRIM_TEMP_LOGFILE:-/dev/null}" 2>/dev/null || true
            return 1
        fi
        if ! printf '%s' "${generated}" > "${file_path}"; then
            log_msg "Failed to persist ${label} to ${file_path}"
            echo "failed to persist ${label}" > "${TRIM_TEMP_LOGFILE:-/dev/null}" 2>/dev/null || true
            return 1
        fi
        existing="${generated}"
        log_msg "Generated runtime ${label} at ${file_path}"
    fi
    chmod 600 "${file_path}" 2>/dev/null || true
    eval "${var_name}=\"\${existing}\""
    return 0
}

resolve_hmac_secret() {
    resolve_secret_value "HMAC_SECRET" "${HMAC_SECRET_FILE}" "HMAC_SECRET"
}

resolve_internal_rpc_token() {
    resolve_secret_value "FN_KNOCK_INTERNAL_RPC_TOKEN" "${INTERNAL_RPC_TOKEN_FILE}" "FN_KNOCK_INTERNAL_RPC_TOKEN"
}

resolve_admin_proxy_secret() {
    resolve_secret_value "ADMIN_PROXY_SECRET" "${ADMIN_PROXY_SECRET_FILE}" "ADMIN_PROXY_SECRET"
}

resolve_root_share_dir() {
    local share_paths=""
    local selected_path=""
    local candidate=""
    local old_ifs="${IFS}"

    if [ -n "${ROOT_SHARE_DIR:-}" ]; then
        return 0
    fi

    share_paths="${TRIM_DATA_SHARE_PATHS:-}"
    if [ -z "${share_paths}" ]; then
        return 0
    fi

    IFS=':'
    for candidate in ${share_paths}; do
        if [ -z "${candidate}" ]; then
            continue
        fi
        if [ -z "${selected_path}" ] || [ "${#candidate}" -lt "${#selected_path}" ]; then
            selected_path="${candidate}"
        fi
    done
    IFS="${old_ifs}"

    if [ -n "${selected_path}" ]; then
        ROOT_SHARE_DIR="${selected_path}"
    fi

    return 0
}

resolve_gateway_bin() {
    local raw_arch=""
    local normalized_arch=""

    raw_arch="$(uname -m 2>/dev/null || true)"
    case "${raw_arch}" in
        x86_64|amd64)
            normalized_arch="amd64"
            ;;
        aarch64|arm64)
            normalized_arch="arm64"
            ;;
        *)
            local message="Unsupported system architecture: ${raw_arch:-unknown}"
            log_msg "${message}"
            echo "${message}" > "${TRIM_TEMP_LOGFILE:-/dev/null}" 2>/dev/null || true
            return 1
            ;;
    esac

    GATEWAY_BIN="${GATEWAY_BIN_DIR}/go-reauth-proxy-linux-${normalized_arch}"
    log_msg "Detected system architecture ${raw_arch}, using gateway binary: ${GATEWAY_BIN}"
    return 0
}

resolve_port() {
    local explicit_key="$1"
    local wizard_key="$2"
    local fallback="$3"

    local value="${!explicit_key:-}"
    if [ -z "${value}" ]; then
        value="${!wizard_key:-}"
    fi
    if [ -z "${value}" ]; then
        value="${fallback}"
    fi

    echo "${value}"
}

load_runtime_ports() {
    local default_proxy_port="${TRIM_SERVICE_PORT:-7999}"
    ADMIN_VIEW_PORT="$(resolve_port "ADMIN_VIEW_PORT" "wizard_admin_view_port" "7991")"
    BACKEND_PORT="$(resolve_port "BACKEND_PORT" "wizard_backend_port" "7998")"
    AUTH_PORT="$(resolve_port "AUTH_PORT" "wizard_auth_port" "7997")"
    GO_BACKEND_PORT="$(resolve_port "GO_BACKEND_PORT" "wizard_go_backend_port" "7996")"
    GO_REPROXY_PORT="$(resolve_port "GO_REPROXY_PORT" "wizard_go_reproxy_port" "${default_proxy_port}")"
}

write_runtime_ports_file() {
    if [ -z "${RUNTIME_PORT_FILE}" ]; then
        log_msg "runtime port file path is empty"
        echo "runtime port file path is empty" > "${TRIM_TEMP_LOGFILE:-/dev/null}" 2>/dev/null || true
        return 1
    fi

    if ! (
        umask 077
        printf '%s\n' \
            "ADMIN_VIEW_PORT=${ADMIN_VIEW_PORT}" \
            "BACKEND_PORT=${BACKEND_PORT}" \
            "AUTH_PORT=${AUTH_PORT}" \
            "GO_BACKEND_PORT=${GO_BACKEND_PORT}" \
            "GO_REPROXY_PORT=${GO_REPROXY_PORT}" > "${RUNTIME_PORT_FILE}"
    ); then
        log_msg "failed to write runtime port file: ${RUNTIME_PORT_FILE}"
        echo "failed to write runtime port file" > "${TRIM_TEMP_LOGFILE:-/dev/null}" 2>/dev/null || true
        return 1
    fi

    return 0
}

validate_port() {
    local name="$1"
    local value="$2"
    if [[ "${value}" =~ ${PORT_PATTERN} ]]; then
        return 0
    fi

    local message="Invalid ${name}: ${value}. expected integer between 1 and 65535."
    log_msg "${message}"
    echo "${message}" > "${TRIM_TEMP_LOGFILE:-/dev/null}" 2>/dev/null || true
    return 1
}

validate_runtime_ports() {
    validate_port "ADMIN_VIEW_PORT" "${ADMIN_VIEW_PORT}" || return 1
    validate_port "BACKEND_PORT" "${BACKEND_PORT}" || return 1
    validate_port "AUTH_PORT" "${AUTH_PORT}" || return 1
    validate_port "GO_BACKEND_PORT" "${GO_BACKEND_PORT}" || return 1
    validate_port "GO_REPROXY_PORT" "${GO_REPROXY_PORT}" || return 1

    if [ "${ADMIN_VIEW_PORT}" = "${BACKEND_PORT}" ] ||
       [ "${ADMIN_VIEW_PORT}" = "${AUTH_PORT}" ] ||
       [ "${ADMIN_VIEW_PORT}" = "${GO_BACKEND_PORT}" ] ||
       [ "${ADMIN_VIEW_PORT}" = "${GO_REPROXY_PORT}" ] ||
       [ "${BACKEND_PORT}" = "${AUTH_PORT}" ] ||
       [ "${BACKEND_PORT}" = "${GO_BACKEND_PORT}" ] ||
       [ "${BACKEND_PORT}" = "${GO_REPROXY_PORT}" ] ||
       [ "${AUTH_PORT}" = "${GO_BACKEND_PORT}" ] ||
       [ "${AUTH_PORT}" = "${GO_REPROXY_PORT}" ] ||
       [ "${GO_BACKEND_PORT}" = "${GO_REPROXY_PORT}" ]; then
        local message="Invalid port configuration: ADMIN_VIEW_PORT, BACKEND_PORT, AUTH_PORT, GO_BACKEND_PORT and GO_REPROXY_PORT must be different."
        log_msg "${message}"
        echo "${message}" > "${TRIM_TEMP_LOGFILE:-/dev/null}" 2>/dev/null || true
        return 1
    fi

    return 0
}

check_process() {
    local pid="$1"
    if [ -z "${pid}" ]; then
        return 1
    fi
    if kill -0 "${pid}" 2>/dev/null; then
        return 0
    fi
    return 1
}

validate_lifecycle_timeouts() {
    local value=""
    for value in "${START_TIMEOUT_SECONDS}" "${STOP_TIMEOUT_SECONDS}" "${FORCE_KILL_TIMEOUT_SECONDS}" "${FIREWALL_WAIT_SECONDS}"; do
        case "${value}" in
            ''|*[!0-9]*|0) return 1 ;;
        esac
    done
    return 0
}

runtime_pid_file_is_running() {
    local pid_file="$1"
    local pid=""
    pid="$(cat "${pid_file}" 2>/dev/null || true)"
    check_process "${pid}"
}

# Instance-bound planned stop protocol, independent of the gateway RPC and SQLite.
planned_stop_write() {
    local action="$1" directory="${PKG_VAR_DIR}/runtime/planned-stop"
    [ -n "${PLANNED_STOP_OPERATION:-}" ] || return 0
    local tmp="${directory}/request.$$.tmp"
    ( umask 077
      printf '{"version":1,"operation_id":"%s","management_instance":"%s","gateway_instance":"%s","action":"%s","requested_at":%s,"timeout_seconds":%s}\n' \
        "${PLANNED_STOP_OPERATION}" "${PLANNED_STOP_MANAGEMENT}" "${PLANNED_STOP_GATEWAY}" \
        "${action}" "${PLANNED_STOP_REQUESTED}" "${PLANNED_STOP_TIMEOUT}" > "${tmp}" && \
      mv -f "${tmp}" "${directory}/request.json"
    )
}

planned_stop_process_matches() {
    local pid="$1" ticks="$2" raw=""
    case "${pid}:${ticks}" in *[!0-9:]*|:*|*:) return 1 ;; esac
    [ -r "/proc/${pid}/stat" ] || return 1
    raw="$(cat "/proc/${pid}/stat" 2>/dev/null)" || return 1
    raw="${raw##*) }"
    # Field 22 (starttime) is field 20 after stripping pid and comm.
    [ "$(printf '%s\n' "${raw}" | awk '{print $20}')" = "${ticks}" ]
}

planned_stop_manager_capable() {
    local pid="" ticks="" instance="" boot_id="" extra=""
    [ -r "${PKG_VAR_DIR}/runtime/planned-stop/manager" ] || return 1
    read -r pid ticks instance boot_id extra < "${PKG_VAR_DIR}/runtime/planned-stop/manager" || return 1
    [ "${boot_id}" = "$(cat /proc/sys/kernel/random/boot_id)" ] && [ -z "${extra}" ] && [ "${pid}" = "$(cat "${BACKEND_PID_FILE}" 2>/dev/null)" ] && planned_stop_process_matches "${pid}" "${ticks}"
}

prepare_planned_stop() {
    local directory="${PKG_VAR_DIR}/runtime/planned-stop"
    local protocol="" management_pid="" management_instance="" management_ticks=""
    local gateway_pid="" gateway_instance="" gateway_ticks="" boot_id="" extra=""
    PLANNED_STOP_OPERATION=""
    # Old binaries have no protocol descriptor. Preserve upgrade compatibility,
    # but do not pretend that their health monitor acknowledged the stop.
    if [ ! -r "${directory}/instance" ]; then
        planned_stop_manager_capable && return 1
        log_msg "Planned stop protocol unavailable; using legacy stop (health alerts may occur)."
        return 0
    fi
    read -r protocol management_pid management_instance management_ticks gateway_pid gateway_instance gateway_ticks boot_id extra < "${directory}/instance" || return 1
    if [ "${protocol}" != 1 ] || [ -n "${extra}" ] || \
        [[ ! "${management_instance}" =~ ^[a-zA-Z0-9-]{1,64}$ ]] || \
        [[ ! "${gateway_instance}" =~ ^[a-zA-Z0-9-]{1,64}$ ]]; then
        return 1
    fi
    if [ "${boot_id}" != "$(cat /proc/sys/kernel/random/boot_id)" ] || ! planned_stop_process_matches "${management_pid}" "${management_ticks}"; then
        planned_stop_manager_capable && return 1
        # A descriptor from a previous instance cannot mute this process.
        log_msg "Ignoring stale planned stop descriptor; using legacy stop."
        return 0
    fi
    if [ "${management_pid}" != "$(cat "${BACKEND_PID_FILE}" 2>/dev/null)" ]; then
        return 1
    fi
    if ! planned_stop_process_matches "${gateway_pid}" "${gateway_ticks}"; then
        log_msg "Gateway identity no longer matches; stop will not suppress health alerts."
        return 0
    fi
    [ "${gateway_pid}" = "$(cat "${GATEWAY_PID_FILE}" 2>/dev/null)" ] || return 1
    PLANNED_STOP_TIMEOUT=$((2 * (STOP_TIMEOUT_SECONDS + FORCE_KILL_TIMEOUT_SECONDS) + 10))
    [ "${PLANNED_STOP_TIMEOUT}" -le 3600 ] || return 1
    PLANNED_STOP_OPERATION="$(cat /proc/sys/kernel/random/uuid)" || return 1
    [[ "${PLANNED_STOP_OPERATION}" =~ ^[a-zA-Z0-9-]{1,64}$ ]] || return 1
    PLANNED_STOP_MANAGEMENT="${management_instance}"
    PLANNED_STOP_GATEWAY="${gateway_instance}"
    PLANNED_STOP_REQUESTED="$(date +%s)"
    planned_stop_write prepare || return 1
    local expected="${PLANNED_STOP_OPERATION} ${management_instance} ${gateway_instance}"
    local attempt
    for attempt in $(seq 1 30); do
        if [ "$(cat "${directory}/ack" 2>/dev/null)" = "${expected}" ]; then
            if planned_stop_process_matches "${management_pid}" "${management_ticks}" && \
                planned_stop_process_matches "${gateway_pid}" "${gateway_ticks}"; then
                log_msg "Planned stop acknowledged: ${PLANNED_STOP_OPERATION}"
                return 0
            fi
            break
        fi
        sleep 0.1
    done
    planned_stop_write cancel || true
    log_msg "Planned stop was not acknowledged; leaving processes running."
    return 1
}

cancel_planned_stop() {
    [ -n "${PLANNED_STOP_OPERATION:-}" ] && supervisor_log INFO gateway_process stop_canceled platform_stop null
    planned_stop_write cancel || log_msg "Could not cancel planned stop; bounded lease will expire."
}

stop_pid() {
    local pid="$1"
    local name="$2"

    if [ -z "${pid}" ]; then
        return 0
    fi
    if ! check_process "${pid}"; then
        return 0
    fi
    if [ "${pid}" = "$$" ]; then
        return 0
    fi

    log_msg "Stopping ${name} (PID: ${pid})..."
    if ! kill -TERM "${pid}" 2>>"${LOG_FILE}"; then
        log_msg "Failed to send SIGTERM to ${name} (PID: ${pid})."
        supervisor_log ERROR "$( [ "${name}" = Gateway ] && echo gateway_process || echo management )" stop_failed stop_signal_failed null
        return 1
    fi
    # The Rust service can legitimately spend about 30 seconds draining its
    # owned workers and checkpointing SQLite. Do not cut that sequence short.
    for _ in $(seq 1 $((STOP_TIMEOUT_SECONDS * 4))); do
        if ! check_process "${pid}"; then
            return 0
        fi
        sleep 0.25
    done
    if check_process "${pid}"; then
        log_msg "${name} did not stop gracefully; sending SIGKILL (PID: ${pid})."
        supervisor_log ERROR "$( [ "${name}" = Gateway ] && echo gateway_process || echo management )" stop_failed stop_timeout null
        if ! kill -KILL "${pid}" 2>>"${LOG_FILE}"; then
            log_msg "Failed to send SIGKILL to ${name} (PID: ${pid})."
            return 1
        fi
        supervisor_log WARN "$( [ "${name}" = Gateway ] && echo gateway_process || echo management )" signal_sent forced_termination null 9
        for _ in $(seq 1 $((FORCE_KILL_TIMEOUT_SECONDS * 4))); do
            if ! check_process "${pid}"; then
                return 0
            fi
            sleep 0.25
        done
    fi
    log_msg "${name} is still running after the stop deadline (PID: ${pid})."
    return 1
}

list_pids_by_keyword() {
    local keyword="$1"

    if command -v pgrep >/dev/null 2>&1; then
        pgrep -f "${keyword}" 2>/dev/null || true
        return 0
    fi

    ps -eo pid=,args= 2>/dev/null | awk -v kw="${keyword}" 'index($0, kw) > 0 { print $1 }'
}

stop_matching_processes() {
    local keyword="$1"
    local name="$2"
    local pids=""
    local matched=0
    local stop_failed=0

    pids="$(list_pids_by_keyword "${keyword}")"
    if [ -z "${pids}" ]; then
        return 0
    fi

    for pid in ${pids}; do
        if [ -z "${pid}" ]; then
            continue
        fi
        if [ "${pid}" = "$$" ]; then
            continue
        fi
        if ! stop_pid "${pid}" "${name}"; then
            stop_failed=1
        fi
        matched=1
    done

    if [ "${matched}" -eq 1 ]; then
        log_msg "${name} cleanup finished by keyword: ${keyword}"
    fi
    [ "${stop_failed}" -eq 0 ]
}

stop_service() {
    local pid_file="$1"
    local name="$2"

    if [ ! -f "${pid_file}" ]; then
        return 0
    fi

    local pid
    pid="$(cat "${pid_file}" 2>/dev/null || true)"
    if [ -z "${pid}" ]; then
        rm -f "${pid_file}"
        return 0
    fi

    if ! stop_pid "${pid}" "${name}"; then
        return 1
    fi

    rm -f "${pid_file}"
    log_msg "${name} stopped."
}

stop_external_resources() {
    local frpc_config="${PKG_VAR_DIR}/frp/frpc.toml"
    local cloudflared_bin="${PKG_VAR_DIR}/cloudflared/cloudflared"

    # FRP client managed by server-admin route (/api/admin/frpc/start)
    stop_matching_processes "${frpc_config}" "FRPC"
    if [ -d "${PKG_VAR_DIR}/frp/instances" ]; then
        for frpc_instance_config in "${PKG_VAR_DIR}"/frp/instances/*/frpc.toml; do
            [ -f "${frpc_instance_config}" ] || continue
            stop_matching_processes "${frpc_instance_config}" "FRPC"
        done
    fi
    # Cloudflared managed by server-admin route (/api/admin/cloudflared/start)
    stop_matching_processes "${cloudflared_bin}" "Cloudflared"
    # Go reauth proxy fallback cleanup (in case PID file is stale/missing)
    stop_matching_processes "${GATEWAY_BIN}" "Gateway"
}

start_gateway() {
    if [ -f "${GATEWAY_PID_FILE}" ]; then
        local pid
        pid="$(cat "${GATEWAY_PID_FILE}" 2>/dev/null || true)"
        if check_process "${pid}"; then
            log_msg "Gateway already running (PID: ${pid})"
            return 0
        fi
        rm -f "${GATEWAY_PID_FILE}"
    fi

    if [ ! -x "${GATEWAY_BIN}" ]; then
        chmod +x "${GATEWAY_BIN}" 2>/dev/null || true
    fi
    if [ ! -x "${GATEWAY_BIN}" ]; then
        log_msg "Gateway binary is missing or not executable: ${GATEWAY_BIN}"
        return 1
    fi
    if ! resolve_hmac_secret; then
        log_msg "HMAC_SECRET is unavailable"
        return 1
    fi
    if ! resolve_internal_rpc_token; then
        log_msg "FN_KNOCK_INTERNAL_RPC_TOKEN is unavailable"
        return 1
    fi
    local max_attempts=3
    local attempt=1
    local pid=""
    rotate_gateway_runtime_log

    while [ "${attempt}" -le "${max_attempts}" ]; do
        log_msg "Starting Go Reauth Proxy (admin-port=${GO_BACKEND_PORT}, proxy-port=${GO_REPROXY_PORT}, attempt=${attempt}/${max_attempts})..."
        BACKEND_PORT="${BACKEND_PORT}" \
        FN_KNOCK_DATA_DIR="${PKG_VAR_DIR}" \
        FN_KNOCK_INTERNAL_RPC_TOKEN="${FN_KNOCK_INTERNAL_RPC_TOKEN}" \
        HMAC_SECRET="${HMAC_SECRET}" \
        "${GATEWAY_BIN}" \
            -c "${GATEWAY_CONFIG_DIR}" \
            -admin-port "${GO_BACKEND_PORT}" \
            -proxy-port "${GO_REPROXY_PORT}" \
            >> "${GATEWAY_RUNTIME_LOG}" 2>&1 &

        echo "$!" > "${GATEWAY_PID_FILE}"
        sleep 1
        pid="$(cat "${GATEWAY_PID_FILE}" 2>/dev/null || true)"
        if check_process "${pid}"; then
            log_msg "Gateway started with PID: ${pid}"
            supervisor_log INFO gateway_process started fnos_start null
            return 0
        fi

        log_msg "Gateway failed to start (attempt ${attempt}/${max_attempts})."
        rm -f "${GATEWAY_PID_FILE}"
        if [ "${attempt}" -lt "${max_attempts}" ]; then
            sleep 1
        fi
        attempt=$((attempt + 1))
    done

    log_msg "Gateway failed to start after ${max_attempts} attempts."
    return 1
}

backend_glibc_tunables() {
    local tunables="${GLIBC_TUNABLES:-}"
    # The NAS backend has few allocator-heavy workers. Avoid retaining a
    # separate small-object cache on every Rust/SQLite thread. glibc reads this
    # before main(), so mallopt in the Rust binary cannot configure it.
    # Preserve explicit administrator settings and all unrelated tunables.
    case ":${tunables}:" in
        *:glibc.malloc.tcache_count=*) ;;
        *) tunables="${tunables:+${tunables}:}glibc.malloc.tcache_count=0" ;;
    esac
    printf '%s' "${tunables}"
}

start_backend() {
    if [ -f "${BACKEND_PID_FILE}" ]; then
        local pid
        pid="$(cat "${BACKEND_PID_FILE}" 2>/dev/null || true)"
        if check_process "${pid}"; then
            log_msg "Backend already running (PID: ${pid})"
            return 0
        fi
        rm -f "${BACKEND_PID_FILE}"
    fi

    if [ ! -x "${BACKEND_ENTRY}" ]; then
        chmod +x "${BACKEND_ENTRY}" 2>/dev/null || true
    fi
    if [ ! -x "${BACKEND_ENTRY}" ]; then
        fail_backend_start "Rust backend is missing or not executable: ${BACKEND_ENTRY}" ""
        return 1
    fi
    if [ ! -d "${ADMIN_STATIC_PATH}" ]; then
        fail_backend_start "Admin static path missing: ${ADMIN_STATIC_PATH}" ""
        return 1
    fi
    if [ ! -d "${AUTH_STATIC_PATH}" ]; then
        fail_backend_start "Auth static path missing: ${AUTH_STATIC_PATH}" ""
        return 1
    fi
    if ! resolve_altcha_hmac_key; then
        fail_backend_start "ALTCHA_HMAC_KEY is unavailable" ""
        return 1
    fi
    if ! resolve_hmac_secret; then
        fail_backend_start "HMAC_SECRET is unavailable" ""
        return 1
    fi
    if ! resolve_internal_rpc_token; then
        fail_backend_start "FN_KNOCK_INTERNAL_RPC_TOKEN is unavailable" ""
        return 1
    fi
    if ! resolve_admin_proxy_secret; then
        fail_backend_start "ADMIN_PROXY_SECRET is unavailable" ""
        return 1
    fi
    resolve_root_share_dir
    rm -f "${READINESS_MARKER}"

    local expected_gateway_pid="" expected_gateway_stat="" expected_gateway_start=""
    expected_gateway_pid="$(cat "${GATEWAY_PID_FILE}" 2>/dev/null || true)"
    if [[ "${expected_gateway_pid}" =~ ^[0-9]+$ ]] && [ -r "/proc/${expected_gateway_pid}/stat" ]; then
        expected_gateway_stat="$(cat "/proc/${expected_gateway_pid}/stat")"
        expected_gateway_stat="${expected_gateway_stat##*) }"
        expected_gateway_start="${expected_gateway_pid}:$(printf '%s\n' "${expected_gateway_stat}" | awk '{print $20}')"
    fi
    log_msg "Starting Rust backend (admin-view-port=${ADMIN_VIEW_PORT}, backend-port=${BACKEND_PORT}, auth-port=${AUTH_PORT})..."
    local backend_allocator_tunables
    backend_allocator_tunables="$(backend_glibc_tunables)"
    (
        cd "${APP_HOME}" && \
        GLIBC_TUNABLES="${backend_allocator_tunables}" \
        ADMIN_STATIC_PATH="${ADMIN_STATIC_PATH}" \
        AUTH_STATIC_PATH="${AUTH_STATIC_PATH}" \
        FN_KNOCK_RUNTIME_TARGET="fpk" \
        FN_KNOCK_EXPECTED_GATEWAY_START="${expected_gateway_start}" \
        FN_KNOCK_BACKEND_IMPL="rust" \
        FN_KNOCK_DATA_DIR="${PKG_VAR_DIR}" \
        FN_KNOCK_GATEWAY_CONFIG_DIR="${GATEWAY_CONFIG_DIR}" \
        FN_KNOCK_ROOT_SHARE_DIR="${ROOT_SHARE_DIR}" \
        ACME_BUNDLE_ZIP="${ACME_BUNDLE_ZIP}" \
        ADMIN_VIEW_PORT="${ADMIN_VIEW_PORT}" \
        BACKEND_PORT="${BACKEND_PORT}" \
        AUTH_PORT="${AUTH_PORT}" \
        GO_BACKEND_PORT="${GO_BACKEND_PORT}" \
        GO_REPROXY_PORT="${GO_REPROXY_PORT}" \
        GO_BACKEND_GRPC_ADDR="127.0.0.1:${GO_BACKEND_PORT}" \
        FN_KNOCK_INTERNAL_RPC_TOKEN="${FN_KNOCK_INTERNAL_RPC_TOKEN}" \
        ALTCHA_HMAC_KEY="${ALTCHA_HMAC_KEY}" \
        HMAC_SECRET="${HMAC_SECRET}" \
        ADMIN_PROXY_SECRET="${ADMIN_PROXY_SECRET}" \
        FN_KNOCK_START_TIMEOUT_SECONDS="${START_TIMEOUT_SECONDS}" \
        FN_KNOCK_READY_FILE="${READINESS_MARKER}" \
        "${BACKEND_ENTRY}"
    ) >> "${LOG_FILE}" 2>&1 &

    echo "$!" > "${BACKEND_PID_FILE}"
    sleep 1
    if [ ! -s "${BACKEND_PID_FILE}" ]; then
        fail_backend_start "Backend pid file missing or empty after start: ${BACKEND_PID_FILE}" ""
        return 1
    fi
    local pid
    pid="$(cat "${BACKEND_PID_FILE}" 2>/dev/null || true)"
    if ! check_process "${pid}"; then
        fail_backend_start "process exited early (pid=${pid:-<empty>})" "${pid}"
        return 1
    fi
    log_msg "Backend started with PID: ${pid}"
    supervisor_log INFO management started fnos_start null
    return 0
}

start() {
    if ! validate_lifecycle_timeouts; then
        write_temp_log "invalid fn-knock lifecycle timeout configuration"
        return 1
    fi
    if ! ensure_runtime_layout; then
        return 1
    fi
    if ! resolve_gateway_bin; then
        return 1
    fi
    if ! ensure_gateway_config_dir; then
        return 1
    fi
    load_runtime_ports
    if ! validate_runtime_ports; then
        return 1
    fi
    if ! write_runtime_ports_file; then
        return 1
    fi
    log_msg "=== fn-knock start ==="
    log_msg "Port config: admin-view=${ADMIN_VIEW_PORT}, backend=${BACKEND_PORT}, auth=${AUTH_PORT}, go-admin=${GO_BACKEND_PORT}, go-proxy=${GO_REPROXY_PORT}"
    local backend_running=1
    local gateway_running=1
    runtime_pid_file_is_running "${BACKEND_PID_FILE}" && backend_running=0
    runtime_pid_file_is_running "${GATEWAY_PID_FILE}" && gateway_running=0
    if [ "${backend_running}" -eq 0 ] && [ "${gateway_running}" -eq 0 ] && \
        [ -f "${READINESS_MARKER}" ]; then
        log_msg "Runtime is already fully ready."
        return 0
    fi
    if [ "${backend_running}" -eq 0 ] || [ "${gateway_running}" -eq 0 ]; then
        log_msg "Incomplete runtime detected; stopping it before a clean start."
        stop || return 1
    fi
    rm -f "${READINESS_MARKER}"
    raise_nofile_limit "${NOFILE_LIMIT}" || true
    if ! prepare_fn_connect_waf_for_start; then
        return 1
    fi

    if ! start_gateway; then
        write_temp_log "fn-knock gateway start failed"
        return 1
    fi
    if ! start_backend; then
        stop_service "${GATEWAY_PID_FILE}" "Gateway"
        if ! temp_log_has_message; then
            write_temp_log "fn-knock backend start failed"
        fi
        return 1
    fi
    if ! wait_runtime_ready; then
        local cleanup_failed=0
        local gateway_stop_failed=0
        if cleanup_fn_connect_waf_rules; then
            stop_service "${GATEWAY_PID_FILE}" "Gateway" || gateway_stop_failed=1
            stop_matching_processes "${GATEWAY_BIN}" "Gateway" || gateway_stop_failed=1
            if [ "${gateway_stop_failed}" -eq 0 ]; then
                stop_service "${BACKEND_PID_FILE}" "Backend" || cleanup_failed=1
                stop_matching_processes "${BACKEND_ENTRY}" "Backend" || cleanup_failed=1
            else
                cleanup_failed=1
                log_msg "Backend kept running because the gateway could not be fully stopped"
            fi
        else
            cleanup_failed=1
            log_msg "Runtime kept running because FN Connect WAF rules could not be safely removed"
        fi
        if [ "${cleanup_failed}" -eq 0 ]; then
            rm -f "${READINESS_MARKER}"
        fi
        write_temp_log "fn-knock runtime readiness failed; inspect ${LOG_FILE} and ${GATEWAY_RUNTIME_LOG}"
        return 1
    fi
    return 0
}

stop() {
    if ! validate_lifecycle_timeouts; then
        return 1
    fi
    log_msg "=== fn-knock stop ==="
    supervisor_log INFO supervisor stop_requested fnos_stop null
    resolve_gateway_bin >/dev/null 2>&1 || true
    if ! cleanup_fn_connect_waf_for_stop 1; then
        log_msg "Runtime kept running because FN Connect WAF rules could not be safely removed"
        return 1
    fi
    if ! prepare_planned_stop; then
        supervisor_log ERROR gateway_process stop_failed stop_prepare_failed null
        cancel_planned_stop
        return 1
    fi
    if ! stop_service "${GATEWAY_PID_FILE}" "Gateway"; then
        cancel_planned_stop
        log_msg "Gateway stop failed; backend kept running to preserve the auth upstream."
        return 1
    fi
    if ! stop_matching_processes "${GATEWAY_BIN}" "Gateway"; then
        cancel_planned_stop
        log_msg "Orphaned gateway stop failed; backend kept running to preserve the auth upstream."
        return 1
    fi
    planned_stop_write gateway_stopped || log_msg "Could not record gateway stop completion."
    if ! stop_service "${BACKEND_PID_FILE}" "Backend"; then
        cancel_planned_stop
        log_msg "Backend stop failed; refusing to finalize shutdown."
        return 1
    fi
    if ! stop_matching_processes "${BACKEND_ENTRY}" "Backend"; then
        cancel_planned_stop
        log_msg "Orphaned backend stop failed; refusing to finalize shutdown."
        return 1
    fi
    rm -f "${READINESS_MARKER}"
    stop_external_resources
    if ! cleanup_fn_connect_waf_for_stop 3; then
        log_msg "Runtime stopped, but residual FN Connect WAF rules could not be safely removed"
        return 1
    fi
    supervisor_log INFO supervisor stop_completed fnos_stop null
    return 0
}

status() {
    local backend_running=1
    local gateway_running=1

    if [ -f "${BACKEND_PID_FILE}" ]; then
        if check_process "$(cat "${BACKEND_PID_FILE}" 2>/dev/null || true)"; then
            backend_running=0
        else
            rm -f "${BACKEND_PID_FILE}"
        fi
    fi

    if [ -f "${GATEWAY_PID_FILE}" ]; then
        if check_process "$(cat "${GATEWAY_PID_FILE}" 2>/dev/null || true)"; then
            gateway_running=0
        else
            rm -f "${GATEWAY_PID_FILE}"
        fi
    fi

    if [ "${backend_running}" -eq 0 ] && [ "${gateway_running}" -eq 0 ]; then
        return 0
    fi
    return 1
}

# Serialize app-center invocations; -o keeps child daemons from retaining the lock.
if [[ "${1:-}" == start || "${1:-}" == stop ]] && [ "${FN_KNOCK_LIFECYCLE_LOCKED:-}" != 1 ]; then
    mkdir -p "${PKG_VAR_DIR}/runtime" || exit 1
    command -v flock >/dev/null 2>&1 || { log_msg "flock is required for safe lifecycle operations"; exit 1; }
    exec flock -o -w 5 "${PKG_VAR_DIR}/runtime/lifecycle.lock" env FN_KNOCK_LIFECYCLE_LOCKED=1 bash "$0" "$@"
fi
unset FN_KNOCK_LIFECYCLE_LOCKED

case "${1:-}" in
start)
    if start; then
        exit 0
    fi
    exit 1
    ;;
stop)
    if stop; then
        exit 0
    fi
    exit 1
    ;;
status)
    if status; then
        exit 0
    else
        exit 3
    fi
    ;;
*)
    exit 1
    ;;
esac
