ovtools 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (198) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +121 -2
  3. package/assets/common/bond-inspect.sh +368 -0
  4. package/assets/common/config.sh +263 -0
  5. package/assets/common/extract-data.sh +197 -0
  6. package/assets/common/harden-host.sh +37 -0
  7. package/assets/common/install-deps.sh +93 -0
  8. package/assets/common/node-enroll.sh +65 -0
  9. package/assets/common/node-setup.sh +150 -0
  10. package/assets/common/node-start.sh +90 -0
  11. package/assets/common/node-stop.sh +18 -0
  12. package/assets/common/node-upgrade.sh +45 -0
  13. package/assets/common/pre-run.sh +497 -0
  14. package/assets/common/promote-validator.sh +104 -0
  15. package/assets/deploy/10-prepare-host.sh +98 -0
  16. package/assets/deploy/20-install-toolchain.sh +93 -0
  17. package/assets/deploy/30-setup-node.sh +82 -0
  18. package/assets/deploy/40-start-node.sh +75 -0
  19. package/assets/deploy/50-verify.sh +259 -0
  20. package/assets/deploy/60-enroll.sh +304 -0
  21. package/assets/deploy/70-promote.sh +152 -0
  22. package/assets/deploy/lib.sh +398 -0
  23. package/assets/manage/10-bond-increase.sh +149 -0
  24. package/assets/manage/10-diagnose-node.sh +169 -0
  25. package/assets/manage/20-exit-validator.sh +259 -0
  26. package/assets/manage/25-recover-node.sh +291 -0
  27. package/assets/manage/30-status.sh +64 -0
  28. package/assets/manage/40-exit-withdraw.sh +133 -0
  29. package/assets/manage/bond_inc.sh +318 -0
  30. package/assets/manage/dashboard_monitor/app.js +1544 -0
  31. package/assets/manage/dashboard_monitor/assets/v2.2/css/base.css +113 -0
  32. package/assets/manage/dashboard_monitor/assets/v2.2/css/components/content.css +133 -0
  33. package/assets/manage/dashboard_monitor/assets/v2.2/css/components/controls.css +229 -0
  34. package/assets/manage/dashboard_monitor/assets/v2.2/css/components/data.css +374 -0
  35. package/assets/manage/dashboard_monitor/assets/v2.2/css/components/icons.css +35 -0
  36. package/assets/manage/dashboard_monitor/assets/v2.2/css/components/motion.css +57 -0
  37. package/assets/manage/dashboard_monitor/assets/v2.2/css/components/shell.css +51 -0
  38. package/assets/manage/dashboard_monitor/assets/v2.2/css/components/sidebar.css +242 -0
  39. package/assets/manage/dashboard_monitor/assets/v2.2/css/components/topbar.css +138 -0
  40. package/assets/manage/dashboard_monitor/assets/v2.2/css/fonts.css +30 -0
  41. package/assets/manage/dashboard_monitor/assets/v2.2/css/synteq-ui-v2.css +25 -0
  42. package/assets/manage/dashboard_monitor/assets/v2.2/css/theme-dark.css +38 -0
  43. package/assets/manage/dashboard_monitor/assets/v2.2/css/theme-light.css +36 -0
  44. package/assets/manage/dashboard_monitor/assets/v2.2/css/tokens.css +61 -0
  45. package/assets/manage/dashboard_monitor/assets/v2.2/fonts/GoogleSansCode-Medium.ttf +0 -0
  46. package/assets/manage/dashboard_monitor/assets/v2.2/fonts/GoogleSansCode-Regular.ttf +0 -0
  47. package/assets/manage/dashboard_monitor/assets/v2.2/fonts/GoogleSansCode-SemiBold.ttf +0 -0
  48. package/assets/manage/dashboard_monitor/assets/v2.2/js/synteq-ui-v2.js +869 -0
  49. package/assets/manage/dashboard_monitor/deploy-dashboard.sh +230 -0
  50. package/assets/manage/dashboard_monitor/favicon.svg +13 -0
  51. package/assets/manage/dashboard_monitor/index.html +1197 -0
  52. package/assets/manage/dashboard_monitor/nginx-octra.conf +76 -0
  53. package/assets/manage/dashboard_monitor/octra-dashboard.service +29 -0
  54. package/assets/manage/dashboard_monitor/octra-node-prune.cron +12 -0
  55. package/assets/manage/dashboard_monitor/octra-node-prune.service +16 -0
  56. package/assets/manage/dashboard_monitor/octra-node-prune.timer +12 -0
  57. package/assets/manage/dashboard_monitor/prune-node-junk.sh +329 -0
  58. package/assets/manage/dashboard_monitor/server.py +1887 -0
  59. package/assets/manage/dashboard_monitor/ssl-certbot.sh +90 -0
  60. package/assets/manage/dashboard_monitor/styles.css +327 -0
  61. package/assets/manage/lib.sh +347 -0
  62. package/assets/manifest.json +143 -0
  63. package/assets/migrate/00-backup-identity.sh +67 -0
  64. package/assets/migrate/01-prepare-target.sh +145 -0
  65. package/assets/migrate/02-authorize-pull.sh +43 -0
  66. package/assets/migrate/03-pull-state.sh +102 -0
  67. package/assets/migrate/04-stop-source.sh +85 -0
  68. package/assets/migrate/05-finalize-target.sh +140 -0
  69. package/assets/migrate/06-start-target.sh +57 -0
  70. package/assets/migrate/07-verify.sh +237 -0
  71. package/assets/migrate/08-rollback.sh +54 -0
  72. package/assets/migrate/lib.sh +149 -0
  73. package/assets/upgrade/10-preflight.sh +120 -0
  74. package/assets/upgrade/20-upgrade.sh +142 -0
  75. package/assets/upgrade/30-manual-upgrade.sh +129 -0
  76. package/assets/upgrade/40-storage-prune.sh +65 -0
  77. package/assets/upgrade/50-status.sh +58 -0
  78. package/assets/upgrade/lib.sh +232 -0
  79. package/bin/ovtools.js +32 -0
  80. package/dist/api/checks.d.ts +6 -0
  81. package/dist/api/checks.d.ts.map +1 -0
  82. package/dist/api/checks.js +173 -0
  83. package/dist/api/checks.js.map +1 -0
  84. package/dist/api/hostkeys.d.ts +10 -0
  85. package/dist/api/hostkeys.d.ts.map +1 -0
  86. package/dist/api/hostkeys.js +58 -0
  87. package/dist/api/hostkeys.js.map +1 -0
  88. package/dist/api/jobs.d.ts +33 -0
  89. package/dist/api/jobs.d.ts.map +1 -0
  90. package/dist/api/jobs.js +483 -0
  91. package/dist/api/jobs.js.map +1 -0
  92. package/dist/api/nodes.d.ts +16 -0
  93. package/dist/api/nodes.d.ts.map +1 -0
  94. package/dist/api/nodes.js +126 -0
  95. package/dist/api/nodes.js.map +1 -0
  96. package/dist/api/server.d.ts +3 -0
  97. package/dist/api/server.d.ts.map +1 -0
  98. package/dist/api/server.js +443 -0
  99. package/dist/api/server.js.map +1 -0
  100. package/dist/cli.d.ts +3 -0
  101. package/dist/cli.d.ts.map +1 -0
  102. package/dist/cli.js +301 -0
  103. package/dist/cli.js.map +1 -0
  104. package/dist/constants.d.ts +76 -0
  105. package/dist/constants.d.ts.map +1 -0
  106. package/dist/constants.js +150 -0
  107. package/dist/constants.js.map +1 -0
  108. package/dist/index.d.ts +18 -0
  109. package/dist/index.d.ts.map +1 -0
  110. package/dist/index.js +60 -0
  111. package/dist/index.js.map +1 -0
  112. package/dist/orchestrator/base.d.ts +52 -0
  113. package/dist/orchestrator/base.d.ts.map +1 -0
  114. package/dist/orchestrator/base.js +219 -0
  115. package/dist/orchestrator/base.js.map +1 -0
  116. package/dist/orchestrator/deploy.d.ts +33 -0
  117. package/dist/orchestrator/deploy.d.ts.map +1 -0
  118. package/dist/orchestrator/deploy.js +313 -0
  119. package/dist/orchestrator/deploy.js.map +1 -0
  120. package/dist/orchestrator/manage.d.ts +28 -0
  121. package/dist/orchestrator/manage.d.ts.map +1 -0
  122. package/dist/orchestrator/manage.js +267 -0
  123. package/dist/orchestrator/manage.js.map +1 -0
  124. package/dist/orchestrator/migrate.d.ts +31 -0
  125. package/dist/orchestrator/migrate.d.ts.map +1 -0
  126. package/dist/orchestrator/migrate.js +442 -0
  127. package/dist/orchestrator/migrate.js.map +1 -0
  128. package/dist/ssh/client.d.ts +39 -0
  129. package/dist/ssh/client.d.ts.map +1 -0
  130. package/dist/ssh/client.js +418 -0
  131. package/dist/ssh/client.js.map +1 -0
  132. package/dist/test/core.test.d.ts +2 -0
  133. package/dist/test/core.test.d.ts.map +1 -0
  134. package/dist/test/core.test.js +188 -0
  135. package/dist/test/core.test.js.map +1 -0
  136. package/dist/types.d.ts +267 -0
  137. package/dist/types.d.ts.map +1 -0
  138. package/dist/types.js +3 -0
  139. package/dist/types.js.map +1 -0
  140. package/dist/utils/assets.d.ts +28 -0
  141. package/dist/utils/assets.d.ts.map +1 -0
  142. package/dist/utils/assets.js +583 -0
  143. package/dist/utils/assets.js.map +1 -0
  144. package/dist/utils/browser.d.ts +2 -0
  145. package/dist/utils/browser.d.ts.map +1 -0
  146. package/dist/utils/browser.js +23 -0
  147. package/dist/utils/browser.js.map +1 -0
  148. package/dist/utils/dashboard.d.ts +16 -0
  149. package/dist/utils/dashboard.d.ts.map +1 -0
  150. package/dist/utils/dashboard.js +177 -0
  151. package/dist/utils/dashboard.js.map +1 -0
  152. package/dist/utils/helpers.d.ts +29 -0
  153. package/dist/utils/helpers.d.ts.map +1 -0
  154. package/dist/utils/helpers.js +204 -0
  155. package/dist/utils/helpers.js.map +1 -0
  156. package/dist/utils/scripts.d.ts +60 -0
  157. package/dist/utils/scripts.d.ts.map +1 -0
  158. package/dist/utils/scripts.js +174 -0
  159. package/dist/utils/scripts.js.map +1 -0
  160. package/dist/utils/state.d.ts +12 -0
  161. package/dist/utils/state.d.ts.map +1 -0
  162. package/dist/utils/state.js +44 -0
  163. package/dist/utils/state.js.map +1 -0
  164. package/dist/utils/tar.d.ts +11 -0
  165. package/dist/utils/tar.d.ts.map +1 -0
  166. package/dist/utils/tar.js +93 -0
  167. package/dist/utils/tar.js.map +1 -0
  168. package/dist/utils/update.d.ts +9 -0
  169. package/dist/utils/update.d.ts.map +1 -0
  170. package/dist/utils/update.js +134 -0
  171. package/dist/utils/update.js.map +1 -0
  172. package/dist/utils/websocket.d.ts +10 -0
  173. package/dist/utils/websocket.d.ts.map +1 -0
  174. package/dist/utils/websocket.js +203 -0
  175. package/dist/utils/websocket.js.map +1 -0
  176. package/package.json +55 -4
  177. package/public/app.js +2712 -0
  178. package/public/assets/v2.2/css/base.css +113 -0
  179. package/public/assets/v2.2/css/components/content.css +133 -0
  180. package/public/assets/v2.2/css/components/controls.css +229 -0
  181. package/public/assets/v2.2/css/components/data.css +373 -0
  182. package/public/assets/v2.2/css/components/icons.css +35 -0
  183. package/public/assets/v2.2/css/components/motion.css +57 -0
  184. package/public/assets/v2.2/css/components/shell.css +51 -0
  185. package/public/assets/v2.2/css/components/sidebar.css +242 -0
  186. package/public/assets/v2.2/css/components/topbar.css +138 -0
  187. package/public/assets/v2.2/css/fonts.css +30 -0
  188. package/public/assets/v2.2/css/synteq-ui-v2.css +25 -0
  189. package/public/assets/v2.2/css/theme-dark.css +38 -0
  190. package/public/assets/v2.2/css/theme-light.css +36 -0
  191. package/public/assets/v2.2/css/tokens.css +61 -0
  192. package/public/assets/v2.2/fonts/GoogleSansCode-Medium.ttf +0 -0
  193. package/public/assets/v2.2/fonts/GoogleSansCode-Regular.ttf +0 -0
  194. package/public/assets/v2.2/fonts/GoogleSansCode-SemiBold.ttf +0 -0
  195. package/public/assets/v2.2/js/synteq-ui-v2.js +869 -0
  196. package/public/favicon.svg +7 -0
  197. package/public/index.html +1080 -0
  198. package/public/styles.css +552 -0
@@ -0,0 +1,291 @@
1
+ #!/usr/bin/env bash
2
+ # ===================================================================
3
+ # 25-recover-node.sh - the solved path for a node stuck "ready / outside set"
4
+ #
5
+ # Every action is one the node's own tooling recommends; nothing is forced:
6
+ #
7
+ # 1. diagnose (the same evidence 10-diagnose-node.sh prints),
8
+ # 2. clean restart when the node is frozen or /status times out
9
+ # (`pm2 save` + `pm2 restart`) - a fresh session re-handshakes peers and asks
10
+ # the state-sync source for a new range, which is what unblocks catch-up,
11
+ # 3. `pm2 save` + `pm2 kill` + `pm2 resurrect` when the node's own tool refuses
12
+ # with "data directory is used outside PM2" (the pm2 entry and the running
13
+ # process disagree about the owner, so rejoin.sh cannot run at all),
14
+ # 4. wait (bounded) for head_epoch to move and the lag to shrink. If the
15
+ # state-sync source keeps answering `503 state_sync_range_busy` the step
16
+ # reports WAITING-SYNC with the quoted reason - it never "fixes" it by hand,
17
+ # 5. once in sync: `rejoin.sh --check`, then `--meet-stage` only when the tool
18
+ # itself says the round is behind (`round_lagging`/`not_ready_to_vote`),
19
+ # 6. re-publish readiness (`enroll.sh join`, idempotent: the bond already exists)
20
+ # when enrollment is ready/bonded but the set has not picked it up,
21
+ # 7. verdict: DONE active / DONE scheduled / WAITING-FOLD (with the epoch) /
22
+ # WAITING-SYNC / FAILED (the tool's own words).
23
+ #
24
+ # Nothing here signs a new bond, exits, or replaces the store: `recover.sh
25
+ # --replace-state` is only ever *reported* as the next step when the tool asks.
26
+ #
27
+ # Usage: sudo bash 25-recover-node.sh
28
+ # Env: SYNC_WAIT=<sec> how long catch-up may take before WAITING-SYNC (900)
29
+ # SYNC_MIN=<epochs> head must advance at least this much (3)
30
+ # SYNC_LAG_MAX=<epochs> lag that counts as "in sync" (5)
31
+ # FOLD_WAIT=<sec> wait for scheduled/active after the rejoin (300)
32
+ # PROGRESS_SEC=<sec> progress line interval (60)
33
+ # ALLOW_RESTART=0 report what would be done, restart nothing
34
+ # ===================================================================
35
+
36
+ set -euo pipefail
37
+ source "$(dirname "$0")/lib.sh"
38
+
39
+ require_root
40
+ set_status RUNNING "recover-node"
41
+
42
+ ensure_tool jq jq || die "jq is missing and could not be installed"
43
+ ensure_tool curl curl || die "curl is missing and could not be installed"
44
+ [[ -f "${NODE_ENV}" ]] || die "node.env is missing (${NODE_ENV}) - is this the validator host?"
45
+ [[ -f "${NODE_WALLET}" ]] || die "wallet.json is missing (${NODE_WALLET})"
46
+
47
+ SYNC_WAIT="${SYNC_WAIT:-900}"
48
+ SYNC_MIN="${SYNC_MIN:-3}"
49
+ LAG_MAX="${SYNC_LAG_MAX:-5}"
50
+ FOLD_WAIT="${FOLD_WAIT:-300}"
51
+ ALLOW_RESTART="${ALLOW_RESTART:-1}"
52
+ POLL="${STATUS_POLL:-20}"
53
+
54
+ WALLET=$(wallet_address || echo unknown)
55
+ log "=== recover ${WALLET} on $(hostname) ==="
56
+
57
+ # --- helpers ------------------------------------------------------------
58
+ rpc_up() { [[ "$(head_epoch)" =~ ^[0-9]+$ ]]; }
59
+
60
+ wait_rpc() { # wait_rpc <seconds>
61
+ local limit="$1" start now
62
+ start=$(date +%s)
63
+ while :; do
64
+ rpc_up && return 0
65
+ now=$(date +%s)
66
+ (( now - start >= limit )) && return 1
67
+ sleep 5
68
+ done
69
+ }
70
+
71
+ pm2_as() { su - "${NODE_USER}" -c "pm2 $*" 2>&1 | sed 's/^/ /'; }
72
+
73
+ busy_join() { pgrep -af "enroll.sh[ ]join" >/dev/null 2>&1; }
74
+
75
+ # --- 0. never fight another enrollment ---------------------------------
76
+ if busy_join; then
77
+ warn "a join is running right now - not touching the node"
78
+ set_status WAITING-BUSY "a join/enroll is already running; re-run when it finishes"
79
+ exit 2
80
+ fi
81
+
82
+ # --- 1. diagnose -------------------------------------------------------
83
+ ENROLL=$(rpc_field octra_validatorEnrollment '[]' state)
84
+ HEAD=$(head_epoch)
85
+ CODE=$(status_code)
86
+ log "before : enrollment=${ENROLL:-?} head=${HEAD:-?} /status=HTTP ${CODE:-000} pm2=$(pm2_state)"
87
+
88
+ REJOIN=$(node_script 180 "sh controls/rejoin.sh --check" || true)
89
+ REJOIN_LINE=$(printf '%s\n' "${REJOIN}" | grep -aE '^status = ' | tail -1 || true)
90
+ log "rejoin : ${REJOIN_LINE:-<no status line>}"
91
+ if printf '%s' "${REJOIN_LINE}" | grep -qi "recovery_required"; then
92
+ die "the local store failed its checkpoint check - run: sh controls/recover.sh --replace-state (nothing was changed here)"
93
+ fi
94
+
95
+ MISMATCH=0
96
+ printf '%s' "${REJOIN_LINE}" | grep -qi "used outside PM2" && MISMATCH=1
97
+ # The tool could not even produce a verdict (the RPC is down): re-creating the pm2
98
+ # session is the safest single action then - it re-applies the env from the dump and
99
+ # gives the node a fresh start, which is also what the "outside PM2" case needs.
100
+ if [[ -z "${REJOIN_LINE}" ]] && ! rpc_up; then
101
+ MISMATCH=1
102
+ warn "rejoin.sh could not reach a verdict (the node does not answer its RPC) - re-creating the pm2 session"
103
+ fi
104
+
105
+ # already fine?
106
+ if printf '%s' "${REJOIN_LINE}" | grep -q "validator_active"; then
107
+ log "this node is already the ACTIVE validator - nothing to recover"
108
+ set_status DONE "already active (nothing to do)"
109
+ echo "RECOVER state=active head=${HEAD:-none}"
110
+ exit 0
111
+
112
+ # --- 2/3. the two things that actually need a restart -------------------
113
+ RESTARTED=0
114
+ if (( MISMATCH )); then
115
+ if (( ALLOW_RESTART )); then
116
+ warn "the data directory is 'used outside PM2' (or the RPC is gone): the pm2 session has to be re-created"
117
+ # `pm2 kill` is NOT used here: it kills the daemon that systemd supervises, so
118
+ # pm2-octra.service ends up 'failed' while the (hung) node keeps running - that
119
+ # is exactly what happened on .99 (2026-11-06). systemd does stop/start, and a
120
+ # node that ignores SIGTERM is reaped by exact process name.
121
+ log "re-creating the pm2 session through systemd (stop -> reap -> start); the node restarts from its own dump"
122
+ systemctl stop pm2-octra 2>/dev/null || true
123
+ systemctl reset-failed pm2-octra 2>/dev/null || true
124
+ sleep 3
125
+ for p in $(pgrep -x octra_node.exe 2>/dev/null || true); do
126
+ warn "the previous node (pid ${p}) survived the stop - sending SIGKILL"
127
+ kill -9 "${p}" 2>/dev/null || true
128
+ done
129
+ sleep 3
130
+ systemctl start pm2-octra 2>/dev/null || warn "systemctl start pm2-octra failed - check: systemctl status pm2-octra"
131
+ sleep 5
132
+ log "pm2-octra : $(systemctl is-active pm2-octra 2>/dev/null || echo unknown)"
133
+ RESTARTED=1
134
+ else
135
+ warn "would re-create the pm2 session (ALLOW_RESTART=0) - the node's own tools cannot run until it is done"
136
+ fi
137
+ elif [[ "${CODE}" != "200" || -z "${HEAD:-}" ]]; then
138
+ if (( ALLOW_RESTART )); then
139
+ warn "the node is not answering its own RPC properly (/status HTTP ${CODE:-000}) - one clean restart"
140
+ pm2_as save >/dev/null 2>&1 || true
141
+ pm2_as restart "$(pm2_name)" || true
142
+ RESTARTED=1
143
+ fi
144
+ fi
145
+
146
+ if (( RESTARTED )); then
147
+ # opening a 38 GB store takes minutes: 180 s was too optimistic on .99
148
+ if wait_rpc "${RPC_WAIT:-600}"; then
149
+ log "the node answers again: head=$(head_epoch) (pid $(pm2_state | awk '{print $3}'))"
150
+ else
151
+ warn "the RPC did not come back within ${RPC_WAIT:-600}s after the restart"
152
+ MEM_TOTAL=$(awk '/MemTotal/ {printf "%d", $2/1024}' /proc/meminfo 2>/dev/null || echo 0)
153
+ NODE_PID=$(pgrep -x octra_node.exe 2>/dev/null | head -1 || true)
154
+ NODE_RSS=""
155
+ [[ -n "${NODE_PID}" ]] && NODE_RSS=$(awk '/VmRSS/ {printf "%d", $2/1024}' "/proc/${NODE_PID}/status" 2>/dev/null || true)
156
+ warn "the process is up (pid ${NODE_PID:-none}, ${NODE_RSS:-?} MB RSS) on a ${MEM_TOTAL:-?} MB VM"
157
+ if (( ${MEM_TOTAL:-0} > 0 && MEM_TOTAL < 7600 )); then
158
+ warn "the store needs ~8 GB of RAM (16 comfortable): on this VM the node cannot finish opening it -"
159
+ warn "raise the RAM and start it again, or move the node with the Migrate node program"
160
+ set_status WAITING-STARTUP "the node cannot finish starting on a ${MEM_TOTAL} MB VM (needs ~8 GB) - raise the RAM or migrate"
161
+ else
162
+ warn "opening the store can take longer on a slow disk - re-run this step in a few minutes"
163
+ set_status WAITING-RPC "restarted, the local RPC is still silent - it may still be opening the store"
164
+ fi
165
+ echo "RECOVER state=restarted_no_rpc head=none"
166
+ exit 2
167
+ fi
168
+ fi
169
+
170
+ # --- 4. catch-up: the real gate ----------------------------------------
171
+ START=$(date +%s)
172
+ LAST=${START}
173
+ FIRST=$(head_epoch)
174
+ FIRST="${FIRST:-0}"
175
+ BEST_LAG=""
176
+ VERDICT=""
177
+ while :; do
178
+ NOW_HEAD=$(head_epoch)
179
+ NET=$(network_epoch)
180
+ LAG=""
181
+ if [[ "${NOW_HEAD}" =~ ^[0-9]+$ && "${NET}" =~ ^[0-9]+$ ]]; then
182
+ LAG=$(( NET - NOW_HEAD ))
183
+ (( LAG < 0 )) && LAG=0
184
+ BEST_LAG="${LAG}"
185
+ fi
186
+ MOVED=$(( ${NOW_HEAD:-0} - FIRST ))
187
+ # in sync = head moved at least SYNC_MIN epochs and the lag is within LAG_MAX
188
+ if (( MOVED >= SYNC_MIN )) && [[ -n "${LAG}" ]] && (( LAG <= LAG_MAX )); then
189
+ VERDICT="synced"
190
+ break
191
+ fi
192
+ ELAPSED=$(( $(date +%s) - START ))
193
+ if (( ELAPSED >= SYNC_WAIT )); then
194
+ VERDICT="sync-timeout"
195
+ break
196
+ fi
197
+ if (( $(date +%s) - LAST >= PROGRESS_SEC )); then
198
+ REASON=$(catchup_reason)
199
+ log " catch-up: head ${NOW_HEAD:-?} -> network ${NET:-?} (lag ${LAG:-?}, moved ${MOVED}/${SYNC_MIN})$([[ -n "${REASON}" ]] && echo " - ${REASON}")"
200
+ LAST=$(date +%s)
201
+ fi
202
+ sleep "${POLL}"
203
+ done
204
+
205
+ if [[ "${VERDICT}" == "sync-timeout" ]]; then
206
+ REASON=$(catchup_reason)
207
+ warn "the node is still behind after ${SYNC_WAIT}s (head ${NOW_HEAD:-?}, network ${NET:-?}, lag ${LAG:-?})"
208
+ [[ -n "${REASON}" ]] && warn "the last catch-up attempt said: ${REASON}"
209
+ warn "the node retries by itself - nothing was forced; re-run this step later"
210
+ set_status WAITING-SYNC "catch-up did not finish in ${SYNC_WAIT}s (lag ${LAG:-?}${REASON:+; ${REASON}})"
211
+ echo "RECOVER state=syncing head=${NOW_HEAD:-none} behind=${LAG:-none}"
212
+ exit 2
213
+ fi
214
+ log "in sync: head ${NOW_HEAD:-?} (lag ${BEST_LAG:-?}) - the set logic can run now"
215
+
216
+
217
+ # --- 5. the node's own rejoin path -------------------------------------
218
+ REJOIN=$(node_script 300 "sh controls/rejoin.sh --check" || true)
219
+ REJOIN_LINE=$(printf '%s\n' "${REJOIN}" | grep -aE '^status = ' | tail -1 || true)
220
+ log "rejoin : ${REJOIN_LINE:-<no status line>}"
221
+ REJOIN_STATE=$(printf '%s' "${REJOIN_LINE}" | sed -n 's/^status = \([a-z_]*\).*/\1/p')
222
+
223
+ if [[ "${REJOIN_STATE}" == "validator_active" ]]; then
224
+ set_status DONE "back in the set: validator_active"
225
+ echo "RECOVER state=active head=${NOW_HEAD:-none}"
226
+ exit 0
227
+ fi
228
+ if [[ "${REJOIN_STATE}" == "validator_scheduled" ]]; then
229
+ set_status WAITING-FOLD "admitted to the next set - waiting for the fold ($(post_hint))"
230
+ echo "RECOVER state=scheduled head=${NOW_HEAD:-none}"
231
+ exit 2
232
+ fi
233
+ if printf '%s' "${REJOIN_LINE}" | grep -qi "used outside PM2"; then
234
+ die "the node still reports 'data directory is used outside PM2' - pm2 was not re-created (ALLOW_RESTART=${ALLOW_RESTART})"
235
+ fi
236
+
237
+ # the round is behind / readiness is stale: --meet-stage is the tool's own fix and
238
+ # it does not stop the node (only --meet-start would, and that needs a stop)
239
+ if printf '%s' "${REJOIN_LINE}" | grep -qE "round_lagging|round_unobserved|round_unconfirmed|not_ready_to_vote"; then
240
+ warn "the tool says the round/readiness is stale (${REJOIN_STATE}) - staging a rejoin (rejoin.sh --meet-stage)"
241
+ OUT=$(node_script 600 "sh controls/rejoin.sh --meet-stage" || true)
242
+ printf '%s\n' "${OUT}" | grep -aE '^(status|event|round) = ' | sed 's/^/ /' || true
243
+ STAGE_STATE=$(printf '%s\n' "${OUT}" | grep -aE '^status = ' | tail -1 | sed -n 's/^status = \([a-z_]*\).*/\1/p' || true)
244
+ log "meet-stage : ${STAGE_STATE:-<no status line>}"
245
+ case "${STAGE_STATE}" in
246
+ "meet_staged")
247
+ log "the rejoin is staged - the node asks for a round when it is ready (no restart, no exit)" ;;
248
+ "refused")
249
+ warn "rejoin refused: $(printf '%s\n' "${OUT}" | grep -aE '^status = ' | tail -1)" ;;
250
+ *) : ;;
251
+ esac
252
+ fi
253
+
254
+ # --- 6. re-publish readiness (idempotent: the bond is already on chain) --
255
+ ENROLL=$(rpc_field octra_validatorEnrollment '[]' state)
256
+ if [[ "${ENROLL}" == "bonded" ]]; then
257
+ log "enrollment is 'bonded' without readiness - publishing it again (enroll.sh join)"
258
+ OUT=$(node_script 900 "sh controls/enroll.sh join" || true)
259
+ printf '%s\n' "${OUT}" | grep -aE '^(event|status) = ' | tail -6 | sed 's/^/ /' || true
260
+ elif [[ "${ENROLL}" == "ready" ]]; then
261
+ log "enrollment is 'ready' - the bond and the readiness are on chain; only the set fold is missing"
262
+ fi
263
+
264
+ # --- 7. wait for the fold ----------------------------------------------
265
+ START=$(date +%s)
266
+ LAST=${START}
267
+ while :; do
268
+ OUT=$(node_script 180 "sh controls/rejoin.sh --check" || true)
269
+ LINE=$(printf '%s\n' "${OUT}" | grep -aE '^status = ' | tail -1 || true)
270
+ STATE=$(printf '%s' "${LINE}" | sed -n 's/^status = \([a-z_]*\).*/\1/p')
271
+ if [[ "${STATE}" == "validator_active" ]]; then
272
+ log "back in the set: $(rpc_field octra_validatorEnrollment '[]' head_epoch)"
273
+ set_status DONE "back in the set: validator_active (rejoin=${STATE})"
274
+ echo "RECOVER state=active head=$(head_epoch)"
275
+ exit 0
276
+ fi
277
+ if (( $(date +%s) - START >= FOLD_WAIT )); then
278
+ warn "the node is in sync but the set has not admitted it yet (${LINE:-no status})"
279
+ warn "this is a chain event now - the console keeps watching and reports the activation"
280
+ set_status WAITING-FOLD "in sync, waiting for the set fold (${STATE:-unknown})"
281
+ echo "RECOVER state=waiting_fold head=$(head_epoch)"
282
+ exit 2
283
+ fi
284
+ if (( $(date +%s) - LAST >= PROGRESS_SEC )); then
285
+ log " waiting for the fold: ${LINE:-no status} ($(post_hint))"
286
+ LAST=$(date +%s)
287
+ fi
288
+ sleep "${POLL}"
289
+ done
290
+
291
+ fi
@@ -0,0 +1,64 @@
1
+ #!/usr/bin/env bash
2
+ # ===================================================================
3
+ # 30-status.sh - read-only overview of the ACTIVE node
4
+ #
5
+ # Prints what the console shows for the manage program: identity, membership,
6
+ # bond, wallet balance, the saved bond-increase record and the validator
7
+ # pointer. It signs nothing, restarts nothing.
8
+ #
9
+ # Usage: sudo bash 30-status.sh
10
+ # ===================================================================
11
+
12
+ set -euo pipefail
13
+ source "$(dirname "$0")/lib.sh"
14
+
15
+ require_root
16
+ set_status RUNNING "status"
17
+
18
+ ensure_tool jq jq || die "jq is missing and could not be installed"
19
+ ensure_tool curl curl || die "curl is missing and could not be installed"
20
+
21
+ read_state
22
+ ADDR=$(wallet_address || echo unknown)
23
+ PORT=$(node_env_value OCTRA_API_PORT)
24
+ PORT="${PORT:-8080}"
25
+
26
+ rpc() { # rpc <method> <params-json>
27
+ timeout 20 curl -s -X POST -H 'Content-Type: application/json' \
28
+ -d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"$1\",\"params\":$2}" \
29
+ "http://127.0.0.1:${PORT}/rpc" 2>/dev/null || true
30
+ }
31
+
32
+ BALANCE_RAW=$(rpc octra_balance "[\"${ADDR}\"]" | jq -r '.result.balance // empty' 2>/dev/null || true)
33
+ [[ "${BALANCE_RAW}" =~ ^[0-9]+$ ]] || BALANCE_RAW=0
34
+
35
+ log "=== active node: $(hostname) ==="
36
+ log "wallet : ${ADDR}"
37
+ log "role : ${ROLE:-?} process: ${PROCESS:-?} rpc: ${RPC:-?} voting: ${VOTING:-?}"
38
+ log "epoch : ${EPOCH:-?}"
39
+ log "membership : active=${ACTIVE:-?} scheduled=${SCHED:-?} enrollment=${ENROLL:-?}"
40
+ log "bond : $(raw_to_oct "${BOND:-0}") OCT"
41
+ log "wallet balance: $(raw_to_oct "${BALANCE_RAW}") OCT"
42
+
43
+ PATH_SAVED=$(saved_increase_path)
44
+ if [[ -n "${PATH_SAVED}" ]]; then
45
+ log "saved increase:"
46
+ jq -r '" amount = \(.amount) raw, stage = \(.stage), tx = \(.tx_hash)"' "${PATH_SAVED}" 2>/dev/null | sed 's/^/ /' || true
47
+ log " a further increase needs --new (the console adds it automatically)"
48
+ else
49
+ log "saved increase: none (the next increase is the first one, no --new)"
50
+ fi
51
+
52
+ log "--- enroll.sh status ---"
53
+ node_script 120 "sh controls/enroll.sh status" | grep -E '^(event = (membership|committed_enrollment|enrollment_tx|validator_pointer))' | sed 's/^/ /' || true
54
+
55
+ # an exit leaves the withdrawal to the host timer: say where it stands, so the
56
+ # operator never has to re-run a phase (or read a systemd unit) to find out
57
+ EXIT_WATCH=$(exit_watch_status || true)
58
+ if [[ -n "${EXIT_WATCH}" ]]; then
59
+ log "--- withdrawal ---"
60
+ log " ${EXIT_WATCH}"
61
+ log " retried every 15 minutes by ${EXIT_WATCH_UNIT}.timer (also after a reboot)"
62
+ fi
63
+
64
+ set_status DONE "role=${ROLE:-?} active=${ACTIVE:-?} bond=$(raw_to_oct "${BOND:-0}") OCT balance=$(raw_to_oct "${BALANCE_RAW}") OCT"
@@ -0,0 +1,133 @@
1
+ #!/usr/bin/env bash
2
+ # ===================================================================
3
+ # 40-exit-withdraw.sh - finish an exit: withdraw the stake when the chain allows it
4
+ #
5
+ # Installed by 20-exit-validator.sh as `octra-exit-withdraw.timer` (every 15
6
+ # minutes, also after a reboot). The chain only signs the withdrawal once the
7
+ # unbonding period is over AND the validator is out of the set - which can be
8
+ # weeks after the exit itself, far too long for a console step, and nobody
9
+ # should have to re-run anything by hand. So this job runs on the host:
10
+ #
11
+ # * not yet allowed -> one line in exit-watch.log, the next run retries
12
+ # * the chain allows it -> `controls/enroll.sh withdraw`, wait for
13
+ # validator_bond = 0, then disarm its own timer
14
+ #
15
+ # It never writes /root/manage/status: that file is the verdict of a step the
16
+ # console follows, and a timer must not overwrite it.
17
+ #
18
+ # Usage: sudo bash 40-exit-withdraw.sh (the timer runs this for you)
19
+ # Env: WITHDRAW_TIMEOUT=<sec> bound for enroll.sh withdraw (default 900)
20
+ # BOND_WAIT=<sec> wait for validator_bond = 0 (default 600)
21
+ # ===================================================================
22
+ set -uo pipefail
23
+ source "$(dirname "$0")/lib.sh"
24
+
25
+ # a timer job must never touch a step's verdict, and it must survive a command it
26
+ # is *expecting* to fail (the chain refuses a withdrawal that is too early):
27
+ # lib.sh turns `set -e` on, so it is turned off here again.
28
+ trap - ERR
29
+ set +e
30
+ WATCH_STATUS="${EXIT_WATCH_STATUS:-${MANAGE_DIR}/exit-watch.status}"
31
+ WATCH_LOG="${EXIT_WATCH_LOG:-${MANAGE_DIR}/exit-watch.log}"
32
+ WITHDRAW_TIMEOUT="${WITHDRAW_TIMEOUT:-900}"
33
+ BOND_WAIT="${BOND_WAIT:-600}"
34
+
35
+ mkdir -p "${MANAGE_DIR}" 2>/dev/null || true
36
+ log() { echo -e "[exit-watch] $(date -u +%Y-%m-%dT%H:%M:%SZ) $*" | tee -a "${WATCH_LOG}"; }
37
+
38
+ record() { # record <RESULT> <detail...> - read by the console (30-status.sh)
39
+ local result="$1"
40
+ shift
41
+ printf '%s %s %s\n' "${result}" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$*" > "${WATCH_STATUS}"
42
+ }
43
+
44
+ summary_value() { printf '%s' "$1" | sed -n "s/.*$2=\([^ ]*\).*/\1/p"; }
45
+
46
+ enrollment_summary() {
47
+ local events state exit_ep withdraw_ep head remaining
48
+ events=$(node_script 120 "sh controls/enroll.sh status" || true)
49
+ state=$(printf '%s\n' "${events}" | grep -oE 'committed_enrollment state = [a-z]+' | awk '{print $4}' | tail -1 || true)
50
+ exit_ep=$(printf '%s\n' "${events}" | grep -oE 'exit_epoch = [0-9]+' | awk '{print $3}' | tail -1 || true)
51
+ withdraw_ep=$(printf '%s\n' "${events}" | grep -oE 'withdraw_epoch = [0-9]+' | awk '{print $3}' | tail -1 || true)
52
+ head=$(printf '%s\n' "${events}" | grep -oE 'head_epoch = [0-9]+' | awk '{print $3}' | tail -1 || true)
53
+ remaining=$(printf '%s\n' "${events}" | grep -oE 'remaining_epochs = [0-9]+' | awk '{print $3}' | tail -1 || true)
54
+ printf 'state=%s exit_epoch=%s withdraw_epoch=%s head=%s remaining=%s\n' \
55
+ "${state:-unknown}" "${exit_ep:-none}" "${withdraw_ep:-none}" "${head:-0}" "${remaining:-none}"
56
+ }
57
+
58
+ read_state
59
+ ADDR=$(wallet_address || echo unknown)
60
+
61
+ # --- already finished? -------------------------------------------------
62
+ if [[ "${BOND:-0}" == "0" && "${ENROLL:-}" != "bonded" && "${ENROLL:-}" != "ready" && "${ENROLL:-}" != "exiting" ]]; then
63
+ log "${ADDR}: no bond on chain any more (enrollment=${ENROLL:-absent}) - the exit is complete"
64
+ record DONE "bond 0 (enrollment=${ENROLL:-absent}) - nothing left to withdraw"
65
+ exit_watch_disarm
66
+ exit 0
67
+ fi
68
+
69
+
70
+ SUMMARY=$(enrollment_summary)
71
+ STATE=$(summary_value "${SUMMARY}" state)
72
+ WITHDRAW_EP=$(summary_value "${SUMMARY}" withdraw_epoch)
73
+ HEAD=$(summary_value "${SUMMARY}" head)
74
+ REMAINING=$(summary_value "${SUMMARY}" remaining)
75
+
76
+ if [[ "${STATE}" != "exiting" ]]; then
77
+ log "${ADDR}: the exit is not committed yet (enrollment=${ENROLL:-?}, state=${STATE:-unknown}) - the console submits it"
78
+ record WAITING "exit not committed yet (state=${STATE:-unknown}, enrollment=${ENROLL:-?})"
79
+ exit 0
80
+ fi
81
+
82
+ if [[ "${REMAINING}" =~ ^[0-9]+$ ]] && (( REMAINING > 0 )); then
83
+ RATE=$(epoch_seconds || true)
84
+ ETA=""
85
+ if [[ -n "${RATE}" ]]; then
86
+ SECS=$(epoch_eta "${REMAINING}" "${RATE}" || true)
87
+ [[ -n "${SECS}" ]] && ETA=$(fmt_duration "${SECS}")
88
+ fi
89
+ log "${ADDR}: unbonding - head ${HEAD} of unlock epoch ${WITHDRAW_EP}, ${REMAINING} epoch(s) left${ETA:+ (about ${ETA})}"
90
+ record WAITING "unbonding: head ${HEAD} of ${WITHDRAW_EP} (${REMAINING} epoch(s) left${ETA:+, about ${ETA}})"
91
+ exit 0
92
+ fi
93
+
94
+ # --- the chain allows the withdrawal now -------------------------------
95
+ if [[ "${ACTIVE:-false}" == "true" || "${SCHED:-false}" == "true" ]]; then
96
+ log "${ADDR}: unbonding over, but the validator is still in the set (active=${ACTIVE:-?} scheduled=${SCHED:-?}) - retrying"
97
+ record WAITING "unbonding over, still in the set (active=${ACTIVE:-?} scheduled=${SCHED:-?})"
98
+ exit 0
99
+ fi
100
+
101
+ log "${ADDR}: unbonding over (head ${HEAD}, unlock epoch ${WITHDRAW_EP}) - signing the withdrawal"
102
+ # no `|| true` here: the exit status of the signing attempt decides whether this
103
+ # run is a WAITING (the chain refuses) or an ERROR (something is wrong)
104
+ OUT=$(node_script "${WITHDRAW_TIMEOUT}" "sh controls/enroll.sh withdraw")
105
+ RC=$?
106
+ printf '%s\n' "${OUT}" | sed 's/^/ /' >> "${WATCH_LOG}"
107
+ if (( RC != 0 )) && ! printf '%s' "${OUT}" | grep -qE "already|previously_confirmed"; then
108
+ if printf '%s' "${OUT}" | grep -qE "unbonding period incomplete|waits for removal"; then
109
+ log "the chain still refuses the withdrawal ($(printf '%s' "${OUT}" | grep -oE 'unbonding period incomplete.*|withdrawal waits.*' | tail -1)) - retrying"
110
+ record WAITING "chain refused the withdrawal - retrying every 15 min"
111
+ exit 0
112
+ fi
113
+ log "the withdrawal failed (exit ${RC}) - see the lines above; the next run retries"
114
+ record ERROR "withdraw failed (exit ${RC})"
115
+ exit 1
116
+ fi
117
+
118
+ START=$(date +%s)
119
+ while :; do
120
+ read_state
121
+ if [[ "${BOND:-0}" == "0" ]]; then
122
+ log "${ADDR}: stake returned - validator_bond = 0, the exit is complete"
123
+ record DONE "stake returned (bond 0, exit epoch $(summary_value "${SUMMARY}" exit_epoch))"
124
+ exit_watch_disarm
125
+ exit 0
126
+ fi
127
+ if (( $(date +%s) - START >= BOND_WAIT )); then
128
+ log "${ADDR}: withdrawal submitted, the registry still shows $(raw_to_oct "${BOND:-0}") OCT - retrying"
129
+ record WAITING "withdraw submitted, bond still $(raw_to_oct "${BOND:-0}") OCT"
130
+ exit 0
131
+ fi
132
+ sleep 20
133
+ done