| | #!/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")))' |
| |
|