Format the bash script

This commit is contained in:
Jared Miller 2026-02-26 09:37:20 -05:00
parent 8ad02453a7
commit 087d6fbc55
Signed by: shmup
GPG key ID: 22B5C6D66A38B06C

View file

@ -29,7 +29,6 @@ NONCE_LOG_FILE="$DATA_DIR/run/nonces_$$"
AUTO_LISTEN_FLAG="$DATA_DIR/run/autolisten_$$" AUTO_LISTEN_FLAG="$DATA_DIR/run/autolisten_$$"
AUTO_LISTEN_PID="" AUTO_LISTEN_PID=""
# Defaults # Defaults
LISTEN_PORT=7777 LISTEN_PORT=7777
TOR_SOCKS_PORT=9050 TOR_SOCKS_PORT=9050
@ -136,7 +135,7 @@ kill_bg_processes() {
save_pid() { save_pid() {
local name="$1" pid="$2" local name="$1" pid="$2"
mkdir -p "$PID_DIR" mkdir -p "$PID_DIR"
echo "$pid" > "$PID_DIR/${name}.pid" echo "$pid" >"$PID_DIR/${name}.pid"
} }
log_info() { log_info() {
@ -174,7 +173,7 @@ load_config() {
echo "" echo ""
if [ -n "$_unlock_pass" ]; then if [ -n "$_unlock_pass" ]; then
SHARED_SECRET=$(openssl enc -d -aes-256-cbc -pbkdf2 -iter 100000 \ SHARED_SECRET=$(openssl enc -d -aes-256-cbc -pbkdf2 -iter 100000 \
-pass "fd:3" -in "$SECRET_FILE" 3<<< "${_unlock_pass}" 2>/dev/null) || true -pass "fd:3" -in "$SECRET_FILE" 3<<<"${_unlock_pass}" 2>/dev/null) || true
if [ -z "$SHARED_SECRET" ]; then if [ -z "$SHARED_SECRET" ]; then
log_warn "Failed to unlock secret (wrong passphrase?)" log_warn "Failed to unlock secret (wrong passphrase?)"
log_info "You can re-enter the secret with option 4" log_info "You can re-enter the secret with option 4"
@ -202,7 +201,7 @@ load_config() {
echo "" echo ""
if [ "$_new_pass" = "$_confirm_pass" ]; then if [ "$_new_pass" = "$_confirm_pass" ]; then
echo -n "$SHARED_SECRET" | openssl enc -aes-256-cbc -pbkdf2 -iter 100000 \ echo -n "$SHARED_SECRET" | openssl enc -aes-256-cbc -pbkdf2 -iter 100000 \
-pass "fd:3" -out "$SECRET_FILE" 3<<< "${_new_pass}" 2>/dev/null -pass "fd:3" -out "$SECRET_FILE" 3<<<"${_new_pass}" 2>/dev/null
chmod 600 "$SECRET_FILE" chmod 600 "$SECRET_FILE"
log_ok "Secret encrypted with passphrase" log_ok "Secret encrypted with passphrase"
else else
@ -221,7 +220,7 @@ load_config() {
save_config() { save_config() {
mkdir -p "$DATA_DIR" mkdir -p "$DATA_DIR"
cat > "$CONFIG_FILE" << EOF cat >"$CONFIG_FILE" <<EOF
LISTEN_PORT=$LISTEN_PORT LISTEN_PORT=$LISTEN_PORT
TOR_SOCKS_PORT=$TOR_SOCKS_PORT TOR_SOCKS_PORT=$TOR_SOCKS_PORT
OPUS_BITRATE=$OPUS_BITRATE OPUS_BITRATE=$OPUS_BITRATE
@ -355,7 +354,7 @@ setup_tor() {
mkdir -p "$TOR_DIR/hidden_service" mkdir -p "$TOR_DIR/hidden_service"
chmod 700 "$TOR_DIR/hidden_service" chmod 700 "$TOR_DIR/hidden_service"
cat > "$TOR_CONF" << EOF cat >"$TOR_CONF" <<EOF
SocksPort $TOR_SOCKS_PORT SocksPort $TOR_SOCKS_PORT
DataDirectory $TOR_DIR/data DataDirectory $TOR_DIR/data
HiddenServiceDir $TOR_DIR/hidden_service HiddenServiceDir $TOR_DIR/hidden_service
@ -371,13 +370,13 @@ EOF
[ -n "$geoip" ] && break [ -n "$geoip" ] && break
done done
if [ -n "$geoip" ]; then if [ -n "$geoip" ]; then
echo "GeoIPFile $geoip" >> "$TOR_CONF" echo "GeoIPFile $geoip" >>"$TOR_CONF"
[ -n "$geoip6" ] && echo "GeoIPv6File $geoip6" >> "$TOR_CONF" [ -n "$geoip6" ] && echo "GeoIPv6File $geoip6" >>"$TOR_CONF"
fi fi
# Append ControlPort config if circuit display is enabled # Append ControlPort config if circuit display is enabled
if [ "$SHOW_CIRCUIT" -eq 1 ]; then if [ "$SHOW_CIRCUIT" -eq 1 ]; then
cat >> "$TOR_CONF" << EOF cat >>"$TOR_CONF" <<EOF
ControlPort $TOR_CONTROL_PORT ControlPort $TOR_CONTROL_PORT
CookieAuthentication 1 CookieAuthentication 1
@ -387,7 +386,7 @@ EOF
# Append ExcludeNodes if configured # Append ExcludeNodes if configured
if [ -n "$EXCLUDE_NODES" ]; then if [ -n "$EXCLUDE_NODES" ]; then
cat >> "$TOR_CONF" << EOF cat >>"$TOR_CONF" <<EOF
ExcludeNodes $EXCLUDE_NODES ExcludeNodes $EXCLUDE_NODES
StrictNodes 1 StrictNodes 1
@ -399,7 +398,7 @@ EOF
if [ "$SNOWFLAKE_ENABLED" -eq 1 ] && check_dep snowflake-client; then if [ "$SNOWFLAKE_ENABLED" -eq 1 ] && check_dep snowflake-client; then
local sf_bin local sf_bin
sf_bin=$(command -v snowflake-client) sf_bin=$(command -v snowflake-client)
cat >> "$TOR_CONF" << EOF cat >>"$TOR_CONF" <<EOF
UseBridges 1 UseBridges 1
ClientTransportPlugin snowflake exec $sf_bin ClientTransportPlugin snowflake exec $sf_bin
@ -450,20 +449,17 @@ install_snowflake() {
fi fi
} }
start_tor() { start_tor() {
if [ -n "$TOR_PID" ] && kill -0 "$TOR_PID" 2>/dev/null; then if [ -n "$TOR_PID" ] && kill -0 "$TOR_PID" 2>/dev/null; then
log_info "Tor is already running (PID $TOR_PID)" log_info "Tor is already running (PID $TOR_PID)"
return 0 return 0
fi fi
setup_tor setup_tor
# Clear old log so we only see fresh output # Clear old log so we only see fresh output
local tor_log="$TOR_DIR/tor.log" local tor_log="$TOR_DIR/tor.log"
> "$tor_log" >"$tor_log"
log_info "Starting Tor..." log_info "Starting Tor..."
if [ "$SNOWFLAKE_ENABLED" -eq 1 ]; then if [ "$SNOWFLAKE_ENABLED" -eq 1 ]; then
@ -586,34 +582,34 @@ rotate_onion() {
# Map 2-letter country code to full name # Map 2-letter country code to full name
cc_to_country() { cc_to_country() {
case "${1,,}" in case "${1,,}" in
ad) echo "Andorra";; ae) echo "UAE";; al) echo "Albania";; am) echo "Armenia";; ad) echo "Andorra" ;; ae) echo "UAE" ;; al) echo "Albania" ;; am) echo "Armenia" ;;
at) echo "Austria";; au) echo "Australia";; az) echo "Azerbaijan";; at) echo "Austria" ;; au) echo "Australia" ;; az) echo "Azerbaijan" ;;
ba) echo "Bosnia";; be) echo "Belgium";; bg) echo "Bulgaria";; br) echo "Brazil";; ba) echo "Bosnia" ;; be) echo "Belgium" ;; bg) echo "Bulgaria" ;; br) echo "Brazil" ;;
by) echo "Belarus";; ca) echo "Canada";; ch) echo "Switzerland";; cl) echo "Chile";; by) echo "Belarus" ;; ca) echo "Canada" ;; ch) echo "Switzerland" ;; cl) echo "Chile" ;;
cn) echo "China";; co) echo "Colombia";; cr) echo "Costa Rica";; cn) echo "China" ;; co) echo "Colombia" ;; cr) echo "Costa Rica" ;;
cy) echo "Cyprus";; cz) echo "Czechia";; de) echo "Germany";; dk) echo "Denmark";; cy) echo "Cyprus" ;; cz) echo "Czechia" ;; de) echo "Germany" ;; dk) echo "Denmark" ;;
dz) echo "Algeria";; ec) echo "Ecuador";; ee) echo "Estonia";; eg) echo "Egypt";; dz) echo "Algeria" ;; ec) echo "Ecuador" ;; ee) echo "Estonia" ;; eg) echo "Egypt" ;;
es) echo "Spain";; fi) echo "Finland";; fr) echo "France";; es) echo "Spain" ;; fi) echo "Finland" ;; fr) echo "France" ;;
gb) echo "UK";; ge) echo "Georgia";; gr) echo "Greece";; gb) echo "UK" ;; ge) echo "Georgia" ;; gr) echo "Greece" ;;
hk) echo "Hong Kong";; hr) echo "Croatia";; hu) echo "Hungary";; hk) echo "Hong Kong" ;; hr) echo "Croatia" ;; hu) echo "Hungary" ;;
id) echo "Indonesia";; ie) echo "Ireland";; il) echo "Israel";; in) echo "India";; id) echo "Indonesia" ;; ie) echo "Ireland" ;; il) echo "Israel" ;; in) echo "India" ;;
iq) echo "Iraq";; ir) echo "Iran";; is) echo "Iceland";; it) echo "Italy";; iq) echo "Iraq" ;; ir) echo "Iran" ;; is) echo "Iceland" ;; it) echo "Italy" ;;
jp) echo "Japan";; ke) echo "Kenya";; kg) echo "Kyrgyzstan";; jp) echo "Japan" ;; ke) echo "Kenya" ;; kg) echo "Kyrgyzstan" ;;
kr) echo "South Korea";; kz) echo "Kazakhstan";; kr) echo "South Korea" ;; kz) echo "Kazakhstan" ;;
lb) echo "Lebanon";; li) echo "Liechtenstein";; lt) echo "Lithuania";; lb) echo "Lebanon" ;; li) echo "Liechtenstein" ;; lt) echo "Lithuania" ;;
lu) echo "Luxembourg";; lv) echo "Latvia";; lu) echo "Luxembourg" ;; lv) echo "Latvia" ;;
ma) echo "Morocco";; md) echo "Moldova";; me) echo "Montenegro";; mk) echo "N. Macedonia";; ma) echo "Morocco" ;; md) echo "Moldova" ;; me) echo "Montenegro" ;; mk) echo "N. Macedonia" ;;
mt) echo "Malta";; mx) echo "Mexico";; my) echo "Malaysia";; mt) echo "Malta" ;; mx) echo "Mexico" ;; my) echo "Malaysia" ;;
ng) echo "Nigeria";; nl) echo "Netherlands";; no) echo "Norway";; nz) echo "New Zealand";; ng) echo "Nigeria" ;; nl) echo "Netherlands" ;; no) echo "Norway" ;; nz) echo "New Zealand" ;;
pa) echo "Panama";; pe) echo "Peru";; ph) echo "Philippines";; pk) echo "Pakistan";; pa) echo "Panama" ;; pe) echo "Peru" ;; ph) echo "Philippines" ;; pk) echo "Pakistan" ;;
pl) echo "Poland";; pt) echo "Portugal";; pl) echo "Poland" ;; pt) echo "Portugal" ;;
ro) echo "Romania";; rs) echo "Serbia";; ru) echo "Russia";; ro) echo "Romania" ;; rs) echo "Serbia" ;; ru) echo "Russia" ;;
sa) echo "Saudi Arabia";; se) echo "Sweden";; sg) echo "Singapore";; si) echo "Slovenia";; sa) echo "Saudi Arabia" ;; se) echo "Sweden" ;; sg) echo "Singapore" ;; si) echo "Slovenia" ;;
sk) echo "Slovakia";; th) echo "Thailand";; tn) echo "Tunisia";; tr) echo "Turkey";; sk) echo "Slovakia" ;; th) echo "Thailand" ;; tn) echo "Tunisia" ;; tr) echo "Turkey" ;;
tw) echo "Taiwan";; ua) echo "Ukraine";; us) echo "USA";; tw) echo "Taiwan" ;; ua) echo "Ukraine" ;; us) echo "USA" ;;
uy) echo "Uruguay";; uz) echo "Uzbekistan";; ve) echo "Venezuela";; uy) echo "Uruguay" ;; uz) echo "Uzbekistan" ;; ve) echo "Venezuela" ;;
vn) echo "Vietnam";; za) echo "South Africa";; vn) echo "Vietnam" ;; za) echo "South Africa" ;;
*) echo "${1^^}";; *) echo "${1^^}" ;;
esac esac
} }
@ -642,8 +638,8 @@ get_circuit_hops() {
# Find best BUILT circuit — prefer HS circuits # Find best BUILT circuit — prefer HS circuits
local circuit_line local circuit_line
circuit_line=$(echo "$circ_resp" | grep " BUILT " \ circuit_line=$(echo "$circ_resp" | grep " BUILT " |
| grep -E "PURPOSE=HS_SERVICE_INTRO|PURPOSE=HS_CLIENT_REND" | head -1) || true grep -E "PURPOSE=HS_SERVICE_INTRO|PURPOSE=HS_CLIENT_REND" | head -1) || true
[ -z "$circuit_line" ] && circuit_line=$(echo "$circ_resp" | grep " BUILT " | head -1) [ -z "$circuit_line" ] && circuit_line=$(echo "$circ_resp" | grep " BUILT " | head -1)
[ -z "$circuit_line" ] && return 1 [ -z "$circuit_line" ] && return 1
@ -654,13 +650,15 @@ get_circuit_hops() {
# Parse relay names and fingerprints # Parse relay names and fingerprints
local names=() fps=() local names=() fps=()
IFS=',' read -ra relays <<< "$path" IFS=',' read -ra relays <<<"$path"
for r in "${relays[@]}"; do for r in "${relays[@]}"; do
local name fp local name fp
if [[ "$r" == *"~"* ]]; then if [[ "$r" == *"~"* ]]; then
name="${r#*~}"; fp="${r%%~*}" name="${r#*~}"
fp="${r%%~*}"
else else
name="${r:0:8}..."; fp="$r" name="${r:0:8}..."
fp="$r"
fi fi
names+=("$name") names+=("$name")
fps+=("${fp#\$}") fps+=("${fp#\$}")
@ -681,7 +679,7 @@ get_circuit_hops() {
if [ -n "$ns_resp" ]; then if [ -n "$ns_resp" ]; then
while IFS= read -r rline; do while IFS= read -r rline; do
ips+=("$(echo "$rline" | awk '{print $7}')") ips+=("$(echo "$rline" | awk '{print $7}')")
done <<< "$(echo "$ns_resp" | grep '^r ')" done <<<"$(echo "$ns_resp" | grep '^r ')"
fi fi
# Step 3: Resolve countries for all IPs (single session) # Step 3: Resolve countries for all IPs (single session)
@ -704,7 +702,7 @@ get_circuit_hops() {
local cc local cc
cc=$(echo "$ccline" | sed 's/.*=//') cc=$(echo "$ccline" | sed 's/.*=//')
countries+=("$(cc_to_country "$cc")") countries+=("$(cc_to_country "$cc")")
done <<< "$(echo "$cc_resp" | grep 'ip-to-country')" done <<<"$(echo "$cc_resp" | grep 'ip-to-country')"
fi fi
fi fi
@ -755,7 +753,7 @@ set_shared_secret() {
echo "" echo ""
if [ "$_pass" = "$_pass2" ]; then if [ "$_pass" = "$_pass2" ]; then
echo -n "$SHARED_SECRET" | openssl enc -aes-256-cbc -pbkdf2 -iter 100000 \ echo -n "$SHARED_SECRET" | openssl enc -aes-256-cbc -pbkdf2 -iter 100000 \
-pass "fd:3" -out "$SECRET_FILE" 3<<< "${_pass}" 2>/dev/null -pass "fd:3" -out "$SECRET_FILE" 3<<<"${_pass}" 2>/dev/null
chmod 600 "$SECRET_FILE" chmod 600 "$SECRET_FILE"
log_ok "Shared secret saved (encrypted with passphrase)" log_ok "Shared secret saved (encrypted with passphrase)"
return return
@ -769,19 +767,18 @@ set_shared_secret() {
fi fi
# Plaintext fallback # Plaintext fallback
echo -n "$SHARED_SECRET" > "$SECRET_FILE" echo -n "$SHARED_SECRET" >"$SECRET_FILE"
chmod 600 "$SECRET_FILE" chmod 600 "$SECRET_FILE"
log_ok "Shared secret saved" log_ok "Shared secret saved"
} }
# Encrypt a file # Encrypt a file
encrypt_file() { encrypt_file() {
local infile="$1" outfile="$2" local infile="$1" outfile="$2"
local c="$CIPHER" local c="$CIPHER"
[ -f "$CIPHER_RUNTIME_FILE" ] && c=$(cat "$CIPHER_RUNTIME_FILE") [ -f "$CIPHER_RUNTIME_FILE" ] && c=$(cat "$CIPHER_RUNTIME_FILE")
openssl enc -"${c}" -pbkdf2 -iter 10000 -pass "fd:3" \ openssl enc -"${c}" -pbkdf2 -iter 10000 -pass "fd:3" \
-in "$infile" -out "$outfile" 3<<< "${SHARED_SECRET}" 2>/dev/null -in "$infile" -out "$outfile" 3<<<"${SHARED_SECRET}" 2>/dev/null
} }
# Decrypt a file # Decrypt a file
@ -790,7 +787,7 @@ decrypt_file() {
local c="$CIPHER" local c="$CIPHER"
[ -f "$CIPHER_RUNTIME_FILE" ] && c=$(cat "$CIPHER_RUNTIME_FILE") [ -f "$CIPHER_RUNTIME_FILE" ] && c=$(cat "$CIPHER_RUNTIME_FILE")
openssl enc -d -"${c}" -pbkdf2 -iter 10000 -pass "fd:3" \ openssl enc -d -"${c}" -pbkdf2 -iter 10000 -pass "fd:3" \
-in "$infile" -out "$outfile" 3<<< "${SHARED_SECRET}" 2>/dev/null -in "$infile" -out "$outfile" 3<<<"${SHARED_SECRET}" 2>/dev/null
} }
#============================================================================= #=============================================================================
@ -839,7 +836,7 @@ proto_verify() {
if grep -qF "$nonce" "$NONCE_LOG_FILE" 2>/dev/null; then if grep -qF "$nonce" "$NONCE_LOG_FILE" 2>/dev/null; then
return 1 return 1
fi fi
echo "$nonce" >> "$NONCE_LOG_FILE" 2>/dev/null echo "$nonce" >>"$NONCE_LOG_FILE" 2>/dev/null
# Strip nonce prefix (nonce:message → message) # Strip nonce prefix (nonce:message → message)
echo "${signed_msg#*:}" echo "${signed_msg#*:}"
return 0 return 0
@ -984,9 +981,9 @@ stop_and_send() {
if [ -s "$enc_file" ]; then if [ -s "$enc_file" ]; then
local enc_size local enc_size
enc_size=$(stat -c%s "$enc_file" 2>/dev/null || echo 0) enc_size=$(stat -c%s "$enc_file" 2>/dev/null || echo 0)
local size_kb=$(( enc_size * 10 / 1024 )) local size_kb=$((enc_size * 10 / 1024))
local size_whole=$(( size_kb / 10 )) local size_whole=$((size_kb / 10))
local size_frac=$(( size_kb % 10 )) local size_frac=$((size_kb % 10))
local b64 local b64
b64=$(base64 -w 0 "$enc_file" 2>/dev/null) b64=$(base64 -w 0 "$enc_file" 2>/dev/null)
@ -1012,25 +1009,21 @@ audio_play() {
fi fi
} }
# Play an opus file # Play an opus file
play_chunk() { play_chunk() {
local opus_file="$1" local opus_file="$1"
if [ $IS_TERMUX -eq 1 ]; then if [ $IS_TERMUX -eq 1 ]; then
# Termux: pipe decode directly to sox play (avoids temp file + Android media framework) # Termux: pipe decode directly to sox play (avoids temp file + Android media framework)
opusdec --quiet --rate 48000 "$opus_file" - 2>/dev/null | \ opusdec --quiet --rate 48000 "$opus_file" - 2>/dev/null |
play -q -t raw -r 48000 -e signed -b 16 -c 1 - 2>/dev/null || true play -q -t raw -r 48000 -e signed -b 16 -c 1 - 2>/dev/null || true
else else
# Linux: pipe decode directly to aplay # Linux: pipe decode directly to aplay
opusdec --quiet --rate 48000 "$opus_file" - 2>/dev/null | \ opusdec --quiet --rate 48000 "$opus_file" - 2>/dev/null |
aplay -f S16_LE -r 48000 -c 1 -q 2>/dev/null || true aplay -f S16_LE -r 48000 -c 1 -q 2>/dev/null || true
fi fi
} }
#============================================================================= #=============================================================================
# CALL CLEANUP — RESET EVERYTHING TO FRESH STATE # CALL CLEANUP — RESET EVERYTHING TO FRESH STATE
#============================================================================= #=============================================================================
@ -1161,9 +1154,9 @@ start_vol_monitor() {
while [ -f "$CONNECTED_FLAG" ]; do while [ -f "$CONNECTED_FLAG" ]; do
local cur_vol local cur_vol
cur_vol=$(termux-volume 2>/dev/null \ cur_vol=$(termux-volume 2>/dev/null |
| jq -r '.[] | select(.stream=="music") | .volume' 2>/dev/null \ jq -r '.[] | select(.stream=="music") | .volume' 2>/dev/null ||
|| echo "") echo "")
# Remember the initial volume so we can restore after detection # Remember the initial volume so we can restore after detection
if [ -n "$cur_vol" ] && [ -z "$restore_vol" ]; then if [ -n "$cur_vol" ] && [ -z "$restore_vol" ]; then
@ -1171,7 +1164,7 @@ start_vol_monitor() {
fi fi
if [ -n "$cur_vol" ] && [ -n "$last_vol" ]; then if [ -n "$cur_vol" ] && [ -n "$last_vol" ]; then
local drop=$(( last_vol - cur_vol )) local drop=$((last_vol - cur_vol))
if [ "$drop" -ge 2 ] 2>/dev/null; then if [ "$drop" -ge 2 ] 2>/dev/null; then
# Rapid double-tap: both presses landed in one poll cycle # Rapid double-tap: both presses landed in one poll cycle
@ -1280,7 +1273,7 @@ listen_for_call() {
local user_input="" local user_input=""
if read -r -t 1 user_input 2>/dev/null; then if read -r -t 1 user_input 2>/dev/null; then
case "$user_input" in case "$user_input" in
q|Q) q | Q)
# Stop all listening (manual + auto), return to menu # Stop all listening (manual + auto), return to menu
kill "$socat_pid" 2>/dev/null || true kill "$socat_pid" 2>/dev/null || true
wait "$socat_pid" 2>/dev/null || true wait "$socat_pid" 2>/dev/null || true
@ -1291,7 +1284,7 @@ listen_for_call() {
log_info "Stopped listening." log_info "Stopped listening."
return 0 return 0
;; ;;
b|B) b | B)
# Move to background: kill manual socat, enable auto-listen # Move to background: kill manual socat, enable auto-listen
kill "$socat_pid" 2>/dev/null || true kill "$socat_pid" 2>/dev/null || true
wait "$socat_pid" 2>/dev/null || true wait "$socat_pid" 2>/dev/null || true
@ -1424,8 +1417,10 @@ draw_call_header() {
# Snowflake bridge info # Snowflake bridge info
if [ "$SNOWFLAKE_ENABLED" -eq 1 ]; then if [ "$SNOWFLAKE_ENABLED" -eq 1 ]; then
local tor_log="$TOR_DIR/tor.log" local tor_log="$TOR_DIR/tor.log"
echo "" >&2; _r=$((_r + 1)) echo "" >&2
echo -e " ${TOR_PURPLE}${NC} ${BOLD}Snowflake bridge${NC}" >&2; _r=$((_r + 1)) _r=$((_r + 1))
echo -e " ${TOR_PURPLE}${NC} ${BOLD}Snowflake bridge${NC}" >&2
_r=$((_r + 1))
if [ -f "$tor_log" ]; then if [ -f "$tor_log" ]; then
local bridge_line="" local bridge_line=""
bridge_line=$(grep "new bridge descriptor" "$tor_log" 2>/dev/null | tail -1 || true) bridge_line=$(grep "new bridge descriptor" "$tor_log" 2>/dev/null | tail -1 || true)
@ -1463,8 +1458,10 @@ draw_call_header() {
local _hop_data local _hop_data
_hop_data=$(get_circuit_hops 2>/dev/null) || true _hop_data=$(get_circuit_hops 2>/dev/null) || true
if [ -n "$_hop_data" ]; then if [ -n "$_hop_data" ]; then
echo "" >&2; _r=$((_r + 1)) echo "" >&2
echo -e " ${TOR_PURPLE}${NC} ${BOLD}Circuit${NC}" >&2; _r=$((_r + 1)) _r=$((_r + 1))
echo -e " ${TOR_PURPLE}${NC} ${BOLD}Circuit${NC}" >&2
_r=$((_r + 1))
CIRCUIT_START_ROW=$_r CIRCUIT_START_ROW=$_r
local _hop_i=0 _hop_total local _hop_i=0 _hop_total
_hop_total=$(echo "$_hop_data" | wc -l) _hop_total=$(echo "$_hop_data" | wc -l)
@ -1475,12 +1472,13 @@ draw_call_header() {
[ $_hop_i -eq $_hop_total ] && _hlabel="Rendezvous" [ $_hop_i -eq $_hop_total ] && _hlabel="Rendezvous"
printf ' \033[2m%-13s\033[0m \033[1;37m%s\033[0m \033[2m(%s)\033[0m\n' "${_hlabel}:" "$_hname" "$_hcountry" >&2 printf ' \033[2m%-13s\033[0m \033[1;37m%s\033[0m \033[2m(%s)\033[0m\n' "${_hlabel}:" "$_hname" "$_hcountry" >&2
_r=$((_r + 1)) _r=$((_r + 1))
done <<< "$_hop_data" done <<<"$_hop_data"
CIRCUIT_HOP_COUNT=$_hop_i CIRCUIT_HOP_COUNT=$_hop_i
fi fi
fi fi
echo "" >&2; _r=$((_r + 1)) echo "" >&2
_r=$((_r + 1))
# Static placeholders — updated in-place via ANSI positioning # Static placeholders — updated in-place via ANSI positioning
echo -e " ${DIM}Last sent: --${NC}" >&2 echo -e " ${DIM}Last sent: --${NC}" >&2
@ -1491,7 +1489,8 @@ draw_call_header() {
RECV_INFO_ROW=$_r RECV_INFO_ROW=$_r
_r=$((_r + 1)) _r=$((_r + 1))
echo "" >&2; _r=$((_r + 1)) echo "" >&2
_r=$((_r + 1))
# Static status bar # Static status bar
if [ $IS_TERMUX -eq 1 ]; then if [ $IS_TERMUX -eq 1 ]; then
@ -1528,7 +1527,7 @@ refresh_circuit_display() {
local _row=$((CIRCUIT_START_ROW + _hop_i - 1)) local _row=$((CIRCUIT_START_ROW + _hop_i - 1))
printf '\033[%d;1H\033[K' "$_row" >&2 printf '\033[%d;1H\033[K' "$_row" >&2
printf ' \033[2m%-13s\033[0m \033[1;37m%s\033[0m \033[2m(%s)\033[0m' "${_hlabel}:" "$_hname" "$_hcountry" >&2 printf ' \033[2m%-13s\033[0m \033[1;37m%s\033[0m \033[2m(%s)\033[0m' "${_hlabel}:" "$_hname" "$_hcountry" >&2
done <<< "$_hop_data" done <<<"$_hop_data"
printf '\033[u' >&2 # restore cursor printf '\033[u' >&2 # restore cursor
} }
@ -1563,13 +1562,13 @@ in_call_session() {
start_vol_monitor "$vol_trigger_file" start_vol_monitor "$vol_trigger_file"
# Write cipher to runtime file so subshells can track changes # Write cipher to runtime file so subshells can track changes
echo "$CIPHER" > "$CIPHER_RUNTIME_FILE" echo "$CIPHER" >"$CIPHER_RUNTIME_FILE"
echo "$HMAC_AUTH" > "$HMAC_RUNTIME_FILE" echo "$HMAC_AUTH" >"$HMAC_RUNTIME_FILE"
: > "$NONCE_LOG_FILE" : >"$NONCE_LOG_FILE"
# Open persistent file descriptors for the pipes # Open persistent file descriptors for the pipes
exec 3< "$recv_pipe" # fd 3 = read from remote exec 3<"$recv_pipe" # fd 3 = read from remote
exec 4> "$send_pipe" # fd 4 = write to remote exec 4>"$send_pipe" # fd 4 = write to remote
# Send our onion address and cipher for handshake # Send our onion address and cipher for handshake
local my_onion local my_onion
@ -1594,7 +1593,7 @@ in_call_session() {
first_line=$(proto_verify "$first_line") || first_line="" first_line=$(proto_verify "$first_line") || first_line=""
if [[ "$first_line" == ID:* ]]; then if [[ "$first_line" == ID:* ]]; then
remote_display="${first_line#ID:}" remote_display="${first_line#ID:}"
echo "$remote_display" > "$remote_id_file" echo "$remote_display" >"$remote_id_file"
elif [[ "$first_line" == CIPHER:* ]]; then elif [[ "$first_line" == CIPHER:* ]]; then
remote_cipher="${first_line#CIPHER:}" remote_cipher="${first_line#CIPHER:}"
fi fi
@ -1614,7 +1613,7 @@ in_call_session() {
# Save remote cipher for later redraws # Save remote cipher for later redraws
if [ -n "$remote_cipher" ]; then if [ -n "$remote_cipher" ]; then
echo "$remote_cipher" > "$remote_cipher_file" echo "$remote_cipher" >"$remote_cipher_file"
fi fi
# Kill connecting spinner and draw call header # Kill connecting spinner and draw call header
@ -1652,12 +1651,12 @@ in_call_session() {
ID:*) ID:*)
# Caller ID received (save but don't print — already in header) # Caller ID received (save but don't print — already in header)
local remote_addr="${line#ID:}" local remote_addr="${line#ID:}"
echo "$remote_addr" > "$remote_id_file" echo "$remote_addr" >"$remote_id_file"
;; ;;
CIPHER:*) CIPHER:*)
# Remote side sent/changed their cipher — save and update display # Remote side sent/changed their cipher — save and update display
local rc="${line#CIPHER:}" local rc="${line#CIPHER:}"
echo "$rc" > "$remote_cipher_file" 2>/dev/null || true echo "$rc" >"$remote_cipher_file" 2>/dev/null || true
# Read current local cipher from runtime file # Read current local cipher from runtime file
local _cur_cipher="$CIPHER" local _cur_cipher="$CIPHER"
[ -f "$CIPHER_RUNTIME_FILE" ] && _cur_cipher=$(cat "$CIPHER_RUNTIME_FILE") [ -f "$CIPHER_RUNTIME_FILE" ] && _cur_cipher=$(cat "$CIPHER_RUNTIME_FILE")
@ -1683,7 +1682,7 @@ in_call_session() {
local _mid=$(uid) local _mid=$(uid)
local msg_enc="$AUDIO_DIR/msg_enc_${_mid}.tmp" local msg_enc="$AUDIO_DIR/msg_enc_${_mid}.tmp"
local msg_dec="$AUDIO_DIR/msg_dec_${_mid}.tmp" local msg_dec="$AUDIO_DIR/msg_dec_${_mid}.tmp"
echo "$msg_b64" | base64 -d > "$msg_enc" 2>/dev/null || true echo "$msg_b64" | base64 -d >"$msg_enc" 2>/dev/null || true
if [ -s "$msg_enc" ]; then if [ -s "$msg_enc" ]; then
if decrypt_file "$msg_enc" "$msg_dec" 2>/dev/null; then if decrypt_file "$msg_enc" "$msg_dec" 2>/dev/null; then
local msg_text local msg_text
@ -1700,15 +1699,15 @@ in_call_session() {
local enc_file="$AUDIO_DIR/recv_enc_${_rid}.tmp" local enc_file="$AUDIO_DIR/recv_enc_${_rid}.tmp"
local dec_file="$AUDIO_DIR/recv_dec_${_rid}.tmp" local dec_file="$AUDIO_DIR/recv_dec_${_rid}.tmp"
echo "$b64_data" | base64 -d > "$enc_file" 2>/dev/null || true echo "$b64_data" | base64 -d >"$enc_file" 2>/dev/null || true
if [ -s "$enc_file" ]; then if [ -s "$enc_file" ]; then
if decrypt_file "$enc_file" "$dec_file" 2>/dev/null; then if decrypt_file "$enc_file" "$dec_file" 2>/dev/null; then
# Calculate recv size # Calculate recv size
local _enc_sz=0 local _enc_sz=0
_enc_sz=$(stat -c%s "$enc_file" 2>/dev/null || echo 0) _enc_sz=$(stat -c%s "$enc_file" 2>/dev/null || echo 0)
local _sz_kb=$(( _enc_sz * 10 / 1024 )) local _sz_kb=$((_enc_sz * 10 / 1024))
local _sz_w=$(( _sz_kb / 10 )) local _sz_w=$((_sz_kb / 10))
local _sz_f=$(( _sz_kb % 10 )) local _sz_f=$((_sz_kb % 10))
local _recv_info="${_sz_w}.${_sz_f}KB" local _recv_info="${_sz_w}.${_sz_f}KB"
# Update static "Last recv" row via ANSI positioning # Update static "Last recv" row via ANSI positioning
printf '\033[s' >&2 printf '\033[s' >&2
@ -1765,7 +1764,8 @@ in_call_session() {
# TERMUX: Toggle mode # TERMUX: Toggle mode
if [ $ptt_active -eq 0 ]; then if [ $ptt_active -eq 0 ]; then
ptt_active=1 ptt_active=1
printf '\033[s' >&2; printf '\033[%d;1H\033[K' "$STATUS_ROW" >&2 printf '\033[s' >&2
printf '\033[%d;1H\033[K' "$STATUS_ROW" >&2
printf ' \033[41;1;37m \u25cf RECORDING \033[0m \033[2m[SPACE]=Send\033[0m ' >&2 printf ' \033[41;1;37m \u25cf RECORDING \033[0m \033[2m[SPACE]=Send\033[0m ' >&2
printf '\033[u' >&2 printf '\033[u' >&2
start_recording start_recording
@ -1785,7 +1785,8 @@ in_call_session() {
# LINUX: Hold-to-talk # LINUX: Hold-to-talk
if [ $ptt_active -eq 0 ]; then if [ $ptt_active -eq 0 ]; then
ptt_active=1 ptt_active=1
printf '\033[s' >&2; printf '\033[%d;1H\033[K' "$STATUS_ROW" >&2 printf '\033[s' >&2
printf '\033[%d;1H\033[K' "$STATUS_ROW" >&2
printf ' \033[41;1;37;5m \u25cf RECORDING \033[0m ' >&2 printf ' \033[41;1;37;5m \u25cf RECORDING \033[0m ' >&2
printf '\033[u' >&2 printf '\033[u' >&2
stty time 5 # longer timeout to span keyboard repeat delay stty time 5 # longer timeout to span keyboard repeat delay
@ -1839,7 +1840,7 @@ in_call_session() {
local _cid=$(uid) local _cid=$(uid)
local chat_plain="$AUDIO_DIR/chat_${_cid}.tmp" local chat_plain="$AUDIO_DIR/chat_${_cid}.tmp"
local chat_enc="$AUDIO_DIR/chat_enc_${_cid}.tmp" local chat_enc="$AUDIO_DIR/chat_enc_${_cid}.tmp"
echo -n "$chat_msg" > "$chat_plain" echo -n "$chat_msg" >"$chat_plain"
encrypt_file "$chat_plain" "$chat_enc" 2>/dev/null encrypt_file "$chat_plain" "$chat_enc" 2>/dev/null
if [ -s "$chat_enc" ]; then if [ -s "$chat_enc" ]; then
local chat_b64 local chat_b64
@ -2170,7 +2171,7 @@ settings_menu() {
;; ;;
6) settings_tor ;; 6) settings_tor ;;
7) settings_security ;; 7) settings_security ;;
0|q|Q) return ;; 0 | q | Q) return ;;
*) *)
echo -e "\n ${RED}Invalid choice${NC}" echo -e "\n ${RED}Invalid choice${NC}"
sleep 1 sleep 1
@ -2248,11 +2249,10 @@ settings_cipher() {
read -r cinput read -r cinput
case "$cinput" in case "$cinput" in
0|q|Q) 0 | q | Q)
return return
;; ;;
'') '') ;;
;;
*) *)
if [[ "$cinput" =~ ^[0-9]+$ ]] && [ "$cinput" -ge 1 ] && [ "$cinput" -le "$total" ]; then if [[ "$cinput" =~ ^[0-9]+$ ]] && [ "$cinput" -ge 1 ] && [ "$cinput" -le "$total" ]; then
local selected="${ciphers[$((cinput - 1))]}" local selected="${ciphers[$((cinput - 1))]}"
@ -2261,7 +2261,7 @@ settings_cipher() {
CIPHER="$selected" CIPHER="$selected"
save_config save_config
# Update runtime file for live mid-call sync # Update runtime file for live mid-call sync
[ -f "$CIPHER_RUNTIME_FILE" ] && echo "$CIPHER" > "$CIPHER_RUNTIME_FILE" [ -f "$CIPHER_RUNTIME_FILE" ] && echo "$CIPHER" >"$CIPHER_RUNTIME_FILE"
# Notify remote side if in a call # Notify remote side if in a call
if [ "$CALL_ACTIVE" -eq 1 ]; then if [ "$CALL_ACTIVE" -eq 1 ]; then
proto_send "CIPHER:${CIPHER}" proto_send "CIPHER:${CIPHER}"
@ -2330,7 +2330,7 @@ settings_opus() {
echo -e "\n ${RED}Invalid bitrate. Must be 6510.${NC}" echo -e "\n ${RED}Invalid bitrate. Must be 6510.${NC}"
fi fi
;; ;;
0|q|Q) 0 | q | Q)
return return
;; ;;
*) *)
@ -2405,7 +2405,7 @@ settings_snowflake() {
echo -e " ${DIM}Restart Tor for changes to take effect.${NC}" echo -e " ${DIM}Restart Tor for changes to take effect.${NC}"
fi fi
;; ;;
0|q|Q) return ;; 0 | q | Q) return ;;
*) *)
echo -e "\n ${RED}Invalid choice${NC}" echo -e "\n ${RED}Invalid choice${NC}"
;; ;;
@ -2431,7 +2431,7 @@ settings_voice() {
local i local i
for i in "${!effects[@]}"; do for i in "${!effects[@]}"; do
local num=$(( i + 1 )) local num=$((i + 1))
local marker=" " local marker=" "
if [ "${effects[$i]}" = "$VOICE_EFFECT" ]; then if [ "${effects[$i]}" = "$VOICE_EFFECT" ]; then
marker="${GREEN}> ${NC}" marker="${GREEN}> ${NC}"
@ -2451,10 +2451,11 @@ settings_voice() {
4) VOICE_EFFECT="robot" ;; 4) VOICE_EFFECT="robot" ;;
5) VOICE_EFFECT="echo" ;; 5) VOICE_EFFECT="echo" ;;
6) VOICE_EFFECT="whisper" ;; 6) VOICE_EFFECT="whisper" ;;
7) VOICE_EFFECT="custom" 7)
VOICE_EFFECT="custom"
settings_voice_custom settings_voice_custom
;; ;;
0|q|Q) return ;; 0 | q | Q) return ;;
*) *)
echo -e "\n ${RED}Invalid choice${NC}" echo -e "\n ${RED}Invalid choice${NC}"
sleep 1 sleep 1
@ -2533,7 +2534,7 @@ settings_voice_custom() {
read -r val read -r val
[[ "$val" =~ ^[0-9]+$ ]] && VOICE_TREMOLO=$val [[ "$val" =~ ^[0-9]+$ ]] && VOICE_TREMOLO=$val
;; ;;
0|q|Q) 0 | q | Q)
save_config save_config
return return
;; ;;
@ -2591,7 +2592,7 @@ settings_tor() {
;; ;;
2) settings_exclude_nodes ;; 2) settings_exclude_nodes ;;
3) settings_snowflake ;; 3) settings_snowflake ;;
0|q|Q) return ;; 0 | q | Q) return ;;
*) *)
echo -e "\n ${RED}Invalid choice${NC}" echo -e "\n ${RED}Invalid choice${NC}"
sleep 1 sleep 1
@ -2699,7 +2700,7 @@ settings_exclude_nodes() {
log_ok "All countries allowed" log_ok "All countries allowed"
sleep 1 sleep 1
;; ;;
0|q|Q) return ;; 0 | q | Q) return ;;
*) *)
echo -e "\n ${RED}Invalid choice${NC}" echo -e "\n ${RED}Invalid choice${NC}"
sleep 1 sleep 1
@ -2732,7 +2733,7 @@ settings_security() {
case "$_sec_choice" in case "$_sec_choice" in
1) settings_cipher ;; 1) settings_cipher ;;
2) settings_hmac ;; 2) settings_hmac ;;
0|q|Q) return ;; 0 | q | Q) return ;;
*) *)
echo -e "\n ${RED}Invalid choice${NC}" echo -e "\n ${RED}Invalid choice${NC}"
sleep 1 sleep 1
@ -2806,7 +2807,7 @@ settings_hmac() {
fi fi
sleep 1 sleep 1
;; ;;
0|q|Q) return ;; 0 | q | Q) return ;;
*) *)
echo -e "\n ${RED}Invalid choice${NC}" echo -e "\n ${RED}Invalid choice${NC}"
sleep 1 sleep 1
@ -2960,15 +2961,18 @@ main_menu() {
read -r read -r
;; ;;
4) set_shared_secret ;; 4) set_shared_secret ;;
5) test_audio 5)
test_audio
echo -ne " ${DIM}Press Enter to continue...${NC}" echo -ne " ${DIM}Press Enter to continue...${NC}"
read -r read -r
;; ;;
6) show_status 6)
show_status
echo -ne " ${DIM}Press Enter to continue...${NC}" echo -ne " ${DIM}Press Enter to continue...${NC}"
read -r read -r
;; ;;
7) install_deps 7)
install_deps
echo -ne "\n ${DIM}Press Enter to continue...${NC}" echo -ne "\n ${DIM}Press Enter to continue...${NC}"
read -r read -r
;; ;;
@ -2998,7 +3002,7 @@ main_menu() {
12) 12)
settings_menu settings_menu
;; ;;
0|q|Q) 0 | q | Q)
echo -e "\n${GREEN}Goodbye!${NC}" echo -e "\n${GREEN}Goodbye!${NC}"
stop_tor stop_tor
cleanup cleanup
@ -3029,20 +3033,20 @@ load_config
# Handle command-line arguments # Handle command-line arguments
case "${1:-}" in case "${1:-}" in
install) install)
install_deps install_deps
;; ;;
test) test)
test_audio test_audio
;; ;;
status) status)
show_status show_status
;; ;;
listen) listen)
load_config load_config
listen_for_call listen_for_call
;; ;;
call) call)
load_config load_config
if [ -n "${2:-}" ]; then if [ -n "${2:-}" ]; then
remote_onion="$2" remote_onion="$2"
@ -3055,7 +3059,7 @@ case "${1:-}" in
echo "Usage: $0 call <onion-address>" echo "Usage: $0 call <onion-address>"
fi fi
;; ;;
help|-h|--help) help | -h | --help)
echo -e "${BOLD}${APP_NAME} v${VERSION}${NC}" echo -e "${BOLD}${APP_NAME} v${VERSION}${NC}"
echo "" echo ""
echo "Usage: $0 [command]" echo "Usage: $0 [command]"
@ -3069,7 +3073,7 @@ case "${1:-}" in
echo " call ADDR Call an onion address" echo " call ADDR Call an onion address"
echo " help Show this help" echo " help Show this help"
;; ;;
*) *)
main_menu main_menu
;; ;;
esac esac