@@ -13,8 +13,11 @@ recreate_wait_seconds="${IB_GATEWAY_RECOVERY_RECREATE_WAIT_SECONDS:-600}"
1313# restart itself before the API socket listens. Do not interrupt that progress.
1414progress_wait_seconds=" ${IB_GATEWAY_RECOVERY_PROGRESS_WAIT_SECONDS:- 420} "
1515progress_extensions=" ${IB_GATEWAY_RECOVERY_PROGRESS_EXTENSIONS:- 2} "
16- progress_window_seconds=" ${IB_GATEWAY_RECOVERY_PROGRESS_WINDOW_SECONDS:- 420} "
17- progress_regex=" ${IB_GATEWAY_RECOVERY_PROGRESS_REGEX:- IBC: (Starting Gateway|Login attempt|Second Factor Authentication|Login has completed|Configuration tasks completed|Found Gateway main window|Getting config dialog|Getting main window)|Authentication window found|Auto-fill submitted|Dismissing post-login dialog|Passed token authentication|Authentication completed|Security code: } "
16+ log_probe_timeout_seconds=" ${IB_GATEWAY_RECOVERY_LOG_PROBE_TIMEOUT_SECONDS:- 10} "
17+ progress_regex=" ${IB_GATEWAY_RECOVERY_PROGRESS_REGEX:- IBC: (Starting Gateway|Login attempt|Second Factor Authentication|Login has completed|Configuration tasks completed|Found Gateway main window|Getting config dialog|Getting main window)|Authentication window found|Auto-fill submitted|Passed token authentication|Authentication completed|Security code: } "
18+ default_terminal_regex=' Connection reset by peer|Server disconnected|IBC: .*(Authentication|Login).*(timed out|timeout|failed)|IBC: .*(timed out|timeout).*(Authentication|Login)'
19+ terminal_regex=" ${IB_GATEWAY_RECOVERY_TERMINAL_REGEX:- $default_terminal_regex } "
20+ activity_classifier=" ${script_dir} /classify_ib_gateway_epoch_activity.awk"
1821lock_file=" ${IB_GATEWAY_RECOVERY_LOCK_FILE:-/ var/ lock/ ib_gateway_recovery.lock} "
1922lock_wait_seconds=" ${IB_GATEWAY_RECOVERY_LOCK_WAIT_SECONDS:- 900} "
2023
@@ -41,56 +44,77 @@ wait_for_ready() {
4144 bash " ${script_dir} /wait_for_ib_gateway_ready.sh" " ${gateway_mode} "
4245}
4346
44- gateway_recently_progressing_from_docker_logs () {
45- docker logs --since " ${progress_window_seconds} s" " ${container_name} " 2>&1 \
46- | grep -Eiq " ${progress_regex} "
47+ new_recovery_epoch () {
48+ date -u " +%Y-%m-%dT%H:%M:%S.%NZ"
4749}
4850
49- gateway_recently_progressing_from_file_logs () {
50- docker exec " ${container_name} " sh -s -- " ${progress_window_seconds} " " ${progress_regex} " << 'SH '
51- set -eu
51+ gateway_epoch_activity_from_docker_logs () {
52+ local attempt_start=" $1 "
53+ timeout " ${log_probe_timeout_seconds} " \
54+ docker logs --timestamps --since " ${attempt_start} " " ${container_name} " 2>&1 \
55+ | awk -v attempt_start=" ${attempt_start} " \
56+ -v progress_regex=" ${progress_regex} " \
57+ -v terminal_regex=" ${terminal_regex} " \
58+ -f " ${activity_classifier} "
59+ }
5260
53- progress_window_seconds="$1"
54- progress_regex="$2"
55- now="$(date +%s)"
56- cutoff_timestamp="$(date -u -d "@$((now - progress_window_seconds))" "+%Y-%m-%d %H:%M:%S")"
61+ gateway_epoch_activity_from_file_logs () {
62+ local attempt_start=" $1 "
63+ local log_path
5764
58- for log_path in /home/ibgateway/Jts/launcher.log /home/ibgateway/2fa.log; do
59- if [ ! -f "${log_path}" ]; then
60- continue
61- fi
65+ for log_path in /home/ibgateway/Jts/launcher.log /home/ibgateway/2fa.log; do
66+ { timeout " ${log_probe_timeout_seconds} " docker exec " ${container_name} " tail -n 400 " ${log_path} " 2> /dev/null || true ; } \
67+ | awk -v attempt_start=" ${attempt_start} " \
68+ -v progress_regex=" ${progress_regex} " \
69+ -v terminal_regex=" ${terminal_regex} " \
70+ -f " ${activity_classifier} "
71+ done
72+ }
6273
63- log_mtime="$(stat -c %Y "${log_path}" 2>/dev/null || echo 0)"
64- if [ $((now - log_mtime)) -le "${progress_window_seconds}" ]; then
65- tail -n 400 "${log_path}" 2>/dev/null \
66- | awk -v cutoff_timestamp="${cutoff_timestamp}" -v progress_regex="${progress_regex}" '
67- substr($0, 1, 19) >= cutoff_timestamp && $0 ~ progress_regex { found = 1 }
68- END { exit found ? 0 : 1 }
69- ' && exit 0
70- fi
71- done
74+ gateway_epoch_activity () {
75+ local attempt_start=" $1 "
76+ local progress_seen=false
77+ local state
7278
73- exit 1
74- SH
75- }
79+ while IFS= read -r state; do
80+ if [ " ${state} " = " terminal" ]; then
81+ echo terminal
82+ return 0
83+ fi
84+ if [ " ${state} " = " progress" ]; then
85+ progress_seen=true
86+ fi
87+ done < <(
88+ gateway_epoch_activity_from_docker_logs " ${attempt_start} " || true
89+ gateway_epoch_activity_from_file_logs " ${attempt_start} " || true
90+ )
7691
77- gateway_recently_progressing () {
78- gateway_recently_progressing_from_docker_logs || gateway_recently_progressing_from_file_logs
92+ if [ " ${progress_seen} " = " true" ]; then
93+ echo progress
94+ fi
7995}
8096
8197wait_for_ready_with_progress () {
8298 local timeout_seconds=" $1 "
8399 local stage=" $2 "
100+ local attempt_start=" $3 "
84101 local extension=0
102+ local activity
85103
86104 if wait_for_ready " ${timeout_seconds} " ; then
87105 return 0
88106 fi
89107
90108 while [ " ${extension} " -lt " ${progress_extensions} " ]; do
91- if ! gateway_recently_progressing; then
92- return 1
93- fi
109+ activity=" $( gateway_epoch_activity " ${attempt_start} " ) "
110+ case " ${activity} " in
111+ terminal)
112+ echo " Recent terminal IB gateway authentication failure detected in the current recovery epoch after ${stage} wait; skipping progress extension." >&2
113+ return 1
114+ ;;
115+ progress) ;;
116+ * ) return 1 ;;
117+ esac
94118
95119 extension=$(( extension + 1 ))
96120 echo " Recent IB gateway login/config progress detected after ${stage} wait; extending readiness wait (${extension} /${progress_extensions} ) by ${progress_wait_seconds} s before external recovery." >&2
@@ -107,27 +131,30 @@ ensure_2fa_bot_running() {
107131}
108132
109133echo " Ensuring ${container_name} is running before readiness check."
134+ attempt_start=" $( new_recovery_epoch) "
110135docker compose up -d --no-build " ${compose_service_name} "
111136ensure_2fa_bot_running
112137
113- if wait_for_ready_with_progress " ${initial_wait_seconds} " " initial" ; then
138+ if wait_for_ready_with_progress " ${initial_wait_seconds} " " initial" " ${attempt_start} " ; then
114139 exit 0
115140fi
116141
117142echo " IB gateway API was not ready; restarting ${container_name} and retrying." >&2
118143docker compose ps >&2 || true
144+ attempt_start=" $( new_recovery_epoch) "
119145docker compose restart " ${compose_service_name} "
120146ensure_2fa_bot_running
121147
122- if wait_for_ready_with_progress " ${restart_wait_seconds} " " restart" ; then
148+ if wait_for_ready_with_progress " ${restart_wait_seconds} " " restart" " ${attempt_start} " ; then
123149 exit 0
124150fi
125151
126152echo " IB gateway API is still not ready; recreating ${container_name} and retrying." >&2
153+ attempt_start=" $( new_recovery_epoch) "
127154docker compose up -d --force-recreate --no-build " ${compose_service_name} "
128155ensure_2fa_bot_running
129156
130- if wait_for_ready_with_progress " ${recreate_wait_seconds} " " recreate" ; then
157+ if wait_for_ready_with_progress " ${recreate_wait_seconds} " " recreate" " ${attempt_start} " ; then
131158 exit 0
132159fi
133160
0 commit comments