| #!/usr/bin/env bash |
| set -euo pipefail |
|
|
| log() { |
| printf '[asa-api] %s\n' "$*" >&2 |
| } |
|
|
| proxy_url="${ASA_PROXY:-socks5h://127.0.0.1:9050}" |
| proxy_without_scheme="${proxy_url#*://}" |
| proxy_host_port="${proxy_without_scheme%%/*}" |
| proxy_host="${proxy_host_port%:*}" |
| proxy_port="${proxy_host_port##*:}" |
|
|
| if [[ -z "${proxy_host}" || "${proxy_host}" == "${proxy_host_port}" ]]; then |
| proxy_host="127.0.0.1" |
| fi |
|
|
| if [[ "${proxy_host}" != "127.0.0.1" && "${proxy_host}" != "localhost" ]]; then |
| log "ASA_PROXY must target a local Tor SOCKS listener. Got: ${proxy_url}" |
| exit 1 |
| fi |
|
|
| if ! [[ "${proxy_port}" =~ ^[0-9]+$ ]]; then |
| log "ASA_PROXY must include a numeric port. Got: ${proxy_url}" |
| exit 1 |
| fi |
|
|
| control_port="${TOR_CONTROL_PORT:-9051}" |
| if ! [[ "${control_port}" =~ ^[0-9]+$ ]]; then |
| log "TOR_CONTROL_PORT must be numeric. Got: ${control_port}" |
| exit 1 |
| fi |
|
|
| tor_root="/tmp/tor" |
| tor_data_dir="${tor_root}/data" |
| tor_cookie_path="${ASA_TOR_CONTROL_COOKIE:-${tor_root}/control.authcookie}" |
| tor_pid_path="${tor_root}/tor.pid" |
| torrc_path="${tor_root}/torrc" |
| tor_probe_url="${ASA_TOR_PROBE_URL:-https://check.torproject.org/api/ip}" |
| tor_startup_timeout="${ASA_TOR_STARTUP_TIMEOUT_SEC:-90}" |
| if ! [[ "${tor_startup_timeout}" =~ ^[0-9]+$ ]]; then |
| log "ASA_TOR_STARTUP_TIMEOUT_SEC must be numeric. Got: ${tor_startup_timeout}" |
| exit 1 |
| fi |
|
|
| install -d -m 700 "${tor_root}" "${tor_data_dir}" "$(dirname "${tor_cookie_path}")" |
| rm -f "${tor_pid_path}" "${tor_cookie_path}" |
|
|
| cat > "${torrc_path}" <<EOF |
| ClientOnly 1 |
| DataDirectory ${tor_data_dir} |
| PidFile ${tor_pid_path} |
| SocksPort 127.0.0.1:${proxy_port} |
| ControlPort 127.0.0.1:${control_port} |
| CookieAuthentication 1 |
| CookieAuthFile ${tor_cookie_path} |
| AvoidDiskWrites 1 |
| Log notice stderr |
| EOF |
|
|
| log "Starting Tor on ${proxy_url} with control port ${control_port}" |
| tor -f "${torrc_path}" & |
| tor_pid=$! |
|
|
| tor_probe_file="$(mktemp)" |
| tor_probe_error_file="$(mktemp)" |
| cleanup_probe_files() { |
| rm -f "${tor_probe_file}" "${tor_probe_error_file}" |
| } |
| trap cleanup_probe_files EXIT |
|
|
| port_open() { |
| local host="$1" |
| local port="$2" |
| (exec 3<>"/dev/tcp/${host}/${port}") >/dev/null 2>&1 |
| } |
|
|
| wait_for_tor() { |
| local started_at now |
| started_at="$(date +%s)" |
|
|
| while true; do |
| if ! kill -0 "${tor_pid}" 2>/dev/null; then |
| wait "${tor_pid}" || true |
| log "Tor exited before startup completed" |
| exit 1 |
| fi |
|
|
| if curl --silent --show-error --fail --max-time 15 \ |
| --proxy "${proxy_url}" \ |
| "${tor_probe_url}" > "${tor_probe_file}" 2> "${tor_probe_error_file}"; then |
| if grep -Eq '"IsTor"[[:space:]]*:[[:space:]]*true' "${tor_probe_file}"; then |
| if [[ -r "${tor_cookie_path}" ]] && port_open "127.0.0.1" "${control_port}"; then |
| return 0 |
| fi |
| fi |
| fi |
|
|
| now="$(date +%s)" |
| if (( now - started_at >= tor_startup_timeout )); then |
| log "Timed out waiting for Tor readiness" |
| if [[ -s "${tor_probe_error_file}" ]]; then |
| log "Last probe error: $(tail -n 1 "${tor_probe_error_file}")" |
| fi |
| if [[ -s "${tor_probe_file}" ]]; then |
| log "Last probe body: $(tr -d '\n' < "${tor_probe_file}")" |
| fi |
| exit 1 |
| fi |
|
|
| sleep 1 |
| done |
| } |
|
|
| wait_for_tor |
|
|
| export ASA_PROXY="${proxy_url}" |
| export TOR_CONTROL_PORT="${control_port}" |
| export ASA_TOR_CONTROL_COOKIE="${tor_cookie_path}" |
|
|
| log "Tor ready; starting API on port ${PORT:-7860}" |
| exec Rscript -e 'pr <- plumber::plumb("R/plumber.R"); pr$run(host = "0.0.0.0", port = as.integer(Sys.getenv("PORT", "7860")))' |
|
|