ovtools 0.0.0-stage → 1.0.1

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 +486 -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 +2736 -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 +13 -0
  197. package/public/index.html +1083 -0
  198. package/public/styles.css +580 -0
@@ -0,0 +1,237 @@
1
+ #!/usr/bin/env bash
2
+ # ===================================================================
3
+ # 07-verify.sh - prove the migrated node is a live voting validator
4
+ #
5
+ # Checks (all must pass):
6
+ # process = online, rpc = ready
7
+ # role = validator, voting = enabled
8
+ # validator_active = true, validator_enrollment = ready
9
+ # epoch advances between samples (node follows the network head)
10
+ # p2p_connected > 0
11
+ #
12
+ # Usage (on the NEW vps):
13
+ # EXPECTED_ADDRESS=oct... bash 07-verify.sh # 3 samples / 30s
14
+ # SAMPLES=10 INTERVAL=30 bash 07-verify.sh # longer watch
15
+ # ===================================================================
16
+
17
+ set -euo pipefail
18
+ source "$(dirname "$0")/lib.sh"
19
+
20
+ require_root
21
+ SAMPLES="${SAMPLES:-3}"
22
+ INTERVAL="${INTERVAL:-30}"
23
+ EXPECTED_ADDRESS="${EXPECTED_ADDRESS:-}"
24
+ if [[ -z "${EXPECTED_ADDRESS}" && -s "${MIG_DIR}/source-address.txt" ]]; then
25
+ EXPECTED_ADDRESS=$(cat "${MIG_DIR}/source-address.txt")
26
+ fi
27
+ if [[ -z "${EXPECTED_ADDRESS}" && -f "${MIG_DIR}/source-final-stat.txt" ]]; then
28
+ EXPECTED_ADDRESS=$(grep -m1 '^address = ' "${MIG_DIR}/source-final-stat.txt" | awk -F' = ' '{print $2}' || true)
29
+ fi
30
+
31
+ set_status RUNNING "verify"
32
+ log "=== verifying validator on $(hostname) - ${SAMPLES} samples every ${INTERVAL}s ==="
33
+
34
+ # --- wait for the node to finish opening its store -------------------
35
+ # A freshly started node must open a ~38 GB irmin-pack store, replay the
36
+ # epochlog/rule graph and rejoin consensus before its RPC answers. On 2
37
+ # vCPU that takes minutes, so poll instead of failing straight away.
38
+ WAIT_SEC="${WAIT_SEC:-900}"
39
+ DEADLINE=$(( $(date +%s) + WAIT_SEC ))
40
+ WAIT_START=$(date +%s)
41
+ LAST_LOG=$WAIT_START
42
+ log "waiting for rpc = ready (up to ${WAIT_SEC}s)..."
43
+ while (( $(date +%s) < DEADLINE )); do
44
+ SNAP=$(node_stat)
45
+ RPC_NOW=$(printf '%s\n' "${SNAP}" | stat_field rpc)
46
+ PROC_NOW=$(printf '%s\n' "${SNAP}" | stat_field process)
47
+ HEAD_NOW=$(printf '%s\n' "${SNAP}" | stat_field head_epoch)
48
+ if [[ "${RPC_NOW}" == "ready" ]]; then
49
+ log "rpc ready after $(( $(date +%s) - WAIT_START ))s (head_epoch=${HEAD_NOW})"
50
+ break
51
+ fi
52
+ # One line a minute instead of a carriage-return animation: every \r update
53
+ # arrives as its own log row in the console, which buried the real steps.
54
+ if (( $(date +%s) - LAST_LOG >= 60 )); then
55
+ log " waiting for rpc: $(( $(date +%s) - WAIT_START ))s of ${WAIT_SEC}s (rpc=${RPC_NOW:-?} process=${PROC_NOW:-?} head=${HEAD_NOW:-?})"
56
+ LAST_LOG=$(date +%s)
57
+ fi
58
+ sleep 10
59
+ done
60
+ RPC_NOW=$(node_stat | stat_field rpc)
61
+ if [[ "${RPC_NOW}" != "ready" ]]; then
62
+ warn "rpc still unavailable after ${WAIT_SEC}s - running the checks anyway"
63
+ fi
64
+ echo
65
+
66
+ # --- wait for the set fold to re-admit the validator -----------------
67
+ # After any downtime the network drops a validator from the active set
68
+ # (`set_fold_refused ... reason = marks_short`). It is re-admitted by the
69
+ # fold once its duty pulses span `rejoin_span = 64` epochs - measured at
70
+ # ~27 minutes on this devnet. Nothing has to be re-enrolled: the bond and
71
+ # the committed `ready` state stay valid, the node pulses automatically.
72
+ MEMBER_WAIT_SEC="${MEMBER_WAIT_SEC:-2700}"
73
+ ACTIVE_NOW=$(node_stat | stat_field validator_active)
74
+ if [[ "${ACTIVE_NOW}" != "true" ]]; then
75
+ log "validator is online but not a member yet - waiting up to ${MEMBER_WAIT_SEC}s for the set fold"
76
+ MEMBER_DEADLINE=$(( $(date +%s) + MEMBER_WAIT_SEC ))
77
+ MEMBER_START=$(date +%s)
78
+ LAST_LOG=$MEMBER_START
79
+ LAST_SCHED=""
80
+ FIRST_EPOCH=""
81
+ while (( $(date +%s) < MEMBER_DEADLINE )); do
82
+ SNAP=$(node_stat)
83
+ ACTIVE_NOW=$(printf '%s\n' "${SNAP}" | stat_field validator_active)
84
+ SCHED_NOW=$(printf '%s\n' "${SNAP}" | stat_field validator_scheduled)
85
+ EPOCH_NOW=$(printf '%s\n' "${SNAP}" | stat_field epoch)
86
+ if [[ "${ACTIVE_NOW}" == "true" ]]; then
87
+ log "validator re-admitted to the active set at epoch ${EPOCH_NOW} (after $(( $(date +%s) - MEMBER_START ))s)"
88
+ break
89
+ fi
90
+ if [[ -z "${FIRST_EPOCH}" && -n "${EPOCH_NOW}" ]]; then
91
+ FIRST_EPOCH="${EPOCH_NOW}"
92
+ fi
93
+ NOW=$(date +%s)
94
+ # A state change is news, so it is logged immediately; otherwise one status
95
+ # line every two minutes. (A \r progress line becomes its own log row in the
96
+ # console, which produced ~50 identical "epoch=... active=false" rows.)
97
+ if [[ -n "${SCHED_NOW}" && "${SCHED_NOW}" != "${LAST_SCHED}" ]]; then
98
+ log " validator_scheduled=${SCHED_NOW} at epoch ${EPOCH_NOW} - the fold re-admits after ~64 duty epochs"
99
+ LAST_SCHED="${SCHED_NOW}"
100
+ LAST_LOG=$NOW
101
+ elif (( NOW - LAST_LOG >= 120 )); then
102
+ log " still waiting: $(( (NOW - MEMBER_START) / 60 ))m of $(( MEMBER_WAIT_SEC / 60 ))m, epoch +$(( EPOCH_NOW - FIRST_EPOCH )) since the wait began (active=${ACTIVE_NOW:-?} scheduled=${SCHED_NOW:-false})"
103
+ LAST_LOG=$NOW
104
+ fi
105
+ sleep 20
106
+ done
107
+ [[ "${ACTIVE_NOW}" == "true" ]] || \
108
+ warn "still not active after ${MEMBER_WAIT_SEC}s - the fold re-admits on its own, keep monitoring"
109
+ fi
110
+
111
+ # The enrollment projection can lag behind the membership view right after the
112
+ # RPC opens (observed on a live migration: active=true and voting=enabled for
113
+ # three 30 s samples while validator_enrollment still read 'unavailable', then
114
+ # 'ready'). Wait for the projection before the strict samples instead of turning
115
+ # that race into a false "migration failed".
116
+ ENROLL_WAIT_SEC="${ENROLL_WAIT_SEC:-900}"
117
+ ACTIVE_NOW=$(node_stat | stat_field validator_active)
118
+ ENROLL_NOW=$(node_stat | stat_field validator_enrollment)
119
+ if [[ "${ACTIVE_NOW}" == "true" && "${ENROLL_NOW}" != "ready" && "${ENROLL_NOW}" != "bonded" ]]; then
120
+ log "validator is active but enrollment reads '${ENROLL_NOW:-unavailable}' - waiting up to ${ENROLL_WAIT_SEC}s for the projection"
121
+ ENROLL_DEADLINE=$(( $(date +%s) + ENROLL_WAIT_SEC ))
122
+ ENROLL_START=$(date +%s)
123
+ LAST_LOG=$ENROLL_START
124
+ while (( $(date +%s) < ENROLL_DEADLINE )); do
125
+ SNAP=$(node_stat)
126
+ ACTIVE_NOW=$(printf '%s\n' "${SNAP}" | stat_field validator_active)
127
+ ENROLL_NOW=$(printf '%s\n' "${SNAP}" | stat_field validator_enrollment)
128
+ if [[ "${ENROLL_NOW}" == "ready" || "${ENROLL_NOW}" == "bonded" ]]; then
129
+ log "enrollment projection ready after $(( $(date +%s) - ENROLL_START ))s (${ENROLL_NOW})"
130
+ break
131
+ fi
132
+ if (( $(date +%s) - LAST_LOG >= 60 )); then
133
+ log " waiting for enrollment: $(( $(date +%s) - ENROLL_START ))s of ${ENROLL_WAIT_SEC}s (active=${ACTIVE_NOW:-?} enrollment=${ENROLL_NOW:-?})"
134
+ LAST_LOG=$(date +%s)
135
+ fi
136
+ sleep 10
137
+ done
138
+ if [[ "${ENROLL_NOW}" != "ready" && "${ENROLL_NOW}" != "bonded" ]]; then
139
+ warn "enrollment still reads '${ENROLL_NOW:-unavailable}' after ${ENROLL_WAIT_SEC}s - active membership is authoritative, the samples below decide"
140
+ fi
141
+ fi
142
+
143
+ # A migrated STUCK validator is in sync and healthy but the chain has not admitted
144
+ # it yet: that is a schedule, not a failure (2026-11-06, .99 -> .231). Ending the
145
+ # phase with FAILED there made the operator think the migration broke, while the
146
+ # bond and the readiness were fine and only the fold was missing.
147
+ if [[ "${ACTIVE_NOW}" != "true" ]]; then
148
+ MEM_STAT=$(node_stat)
149
+ MEM_ENROLL=$(printf "%s\n" "${MEM_STAT}" | stat_field validator_enrollment)
150
+ MEM_PROC=$(printf "%s\n" "${MEM_STAT}" | stat_field process)
151
+ MEM_RPC=$(printf "%s\n" "${MEM_STAT}" | stat_field rpc)
152
+ MEM_EPOCH=$(printf "%s\n" "${MEM_STAT}" | stat_field epoch)
153
+ if [[ "${MEM_ENROLL}" =~ ^(ready|bonded)$ && "${MEM_PROC}" == "online" && "${MEM_RPC}" == "ready" ]]; then
154
+ log "=== RESULT: WAITING-FOLD - the node runs, is in sync and holds the bond (enrollment=${MEM_ENROLL}) ==="
155
+ log "the set has not admitted it yet: the fold does that on its own (~64 duty epochs), nothing was re-enrolled"
156
+ log "nothing to fix: the console keeps watching and opens the activation popup when it happens"
157
+ set_status WAITING-FOLD "migrated and in sync; waiting for the set fold (enrollment=${MEM_ENROLL} epoch=${MEM_EPOCH})"
158
+ echo "MIGRATE_VERIFY state=waiting_fold enrollment=${MEM_ENROLL} epoch=${MEM_EPOCH}"
159
+ exit 2
160
+ fi
161
+ warn "the node is not active AND not ready (process=${MEM_PROC:-?} rpc=${MEM_RPC:-?} enrollment=${MEM_ENROLL:-?}) - the samples below show why"
162
+ fi
163
+
164
+ FAIL=0
165
+ PREV_EPOCH=""
166
+
167
+ for ((i = 1; i <= SAMPLES; i++)); do
168
+ STAT=$(node_stat)
169
+ printf '%s\n' "${STAT}" > "${MIG_DIR}/verify-sample-${i}.txt"
170
+
171
+ ROLE=$(printf '%s\n' "${STAT}" | stat_field role)
172
+ ADDR=$(printf '%s\n' "${STAT}" | stat_field address)
173
+ PROC=$(printf '%s\n' "${STAT}" | stat_field process)
174
+ RPC=$(printf '%s\n' "${STAT}" | stat_field rpc)
175
+ EPOCH=$(printf '%s\n' "${STAT}" | stat_field epoch)
176
+ HEAD=$(printf '%s\n' "${STAT}" | stat_field head_epoch)
177
+ VOTING=$(printf '%s\n' "${STAT}" | stat_field voting)
178
+ ACTIVE=$(printf '%s\n' "${STAT}" | stat_field validator_active)
179
+ ENROLL=$(printf '%s\n' "${STAT}" | stat_field validator_enrollment)
180
+ P2P=$(printf '%s\n' "${STAT}" | stat_field p2p_connected)
181
+ AGREED=$(printf '%s\n' "${STAT}"| stat_field round_agreed)
182
+ STEP=$(printf '%s\n' "${STAT}" | stat_field round_step)
183
+ PEERS=$(printf '%s\n' "${STAT}" | stat_field round_peers)
184
+ UPTIME=$(printf '%s\n' "${STAT}"| stat_field uptime)
185
+ RESTARTS=$(printf '%s\n' "${STAT}" | stat_field restarts)
186
+
187
+ log "sample ${i}/${SAMPLES}: epoch=${EPOCH} head=${HEAD} role=${ROLE} voting=${VOTING} active=${ACTIVE} p2p=${P2P} round_peers=${PEERS} agreed=${AGREED} step=${STEP} uptime=${UPTIME} restarts=${RESTARTS}"
188
+
189
+ [[ "${PROC}" == "online" ]] || { err "process is not online: ${PROC}"; FAIL=1; }
190
+ [[ "${RPC}" == "ready" ]] || { err "rpc is not ready: ${RPC}"; FAIL=1; }
191
+ [[ "${ROLE}" == "validator" ]] || { err "role is not validator: ${ROLE}"; FAIL=1; }
192
+ [[ "${VOTING}" == "enabled" ]] || { err "voting is not enabled: ${VOTING}"; FAIL=1; }
193
+ [[ "${ACTIVE}" == "true" ]] || { err "validator_active is not true: ${ACTIVE}"; FAIL=1; }
194
+ if [[ "${ENROLL}" != "ready" ]]; then
195
+ if [[ "${ACTIVE}" == "true" && "${VOTING}" == "enabled" ]]; then
196
+ # active + voting is stronger evidence than the enrollment projection; the
197
+ # projection can answer 'unavailable' while the RPC warms up.
198
+ warn "enrollment projection reads '${ENROLL:-unavailable}' while the validator is active and voting - accepting active membership"
199
+ else
200
+ err "validator_enrollment is not ready: ${ENROLL}"
201
+ FAIL=1
202
+ fi
203
+ fi
204
+ [[ -z "${EXPECTED_ADDRESS}" || "${ADDR}" == "${EXPECTED_ADDRESS}" ]] || { err "address mismatch: ${ADDR} != ${EXPECTED_ADDRESS}"; FAIL=1; }
205
+ [[ "${P2P:-0}" -gt 0 ]] || { err "no p2p peers connected"; FAIL=1; }
206
+
207
+ if [[ -n "${PREV_EPOCH}" ]]; then
208
+ if (( EPOCH > PREV_EPOCH )); then
209
+ log " epoch advanced ${PREV_EPOCH} -> ${EPOCH} (+$((EPOCH - PREV_EPOCH)))"
210
+ else
211
+ warn " epoch did not advance (${PREV_EPOCH} -> ${EPOCH}) - node may still be catching up"
212
+ fi
213
+ fi
214
+ PREV_EPOCH="${EPOCH}"
215
+
216
+ if (( i < SAMPLES )); then
217
+ log " sleeping ${INTERVAL}s before the next sample"
218
+ sleep "${INTERVAL}"
219
+ fi
220
+ done
221
+
222
+ echo
223
+ if (( FAIL == 0 )); then
224
+ log "=== RESULT: PASS - validator is online, voting and following the chain ==="
225
+ if [[ -s "${MIG_DIR}/downtime-start.txt" ]]; then
226
+ START_TS=$(date -u -d "$(cat "${MIG_DIR}/downtime-start.txt")" +%s 2>/dev/null || true)
227
+ if [[ -n "${START_TS}" ]]; then
228
+ DOWN=$(( $(date +%s) - START_TS ))
229
+ log "total time from source stop to verified voting: $((DOWN / 60))m$((DOWN % 60))s"
230
+ fi
231
+ fi
232
+ set_status DONE "verify-pass" "epoch=${PREV_EPOCH} address=${ADDR}"
233
+ else
234
+ err "=== RESULT: FAIL - see the errors above ==="
235
+ set_status FAILED "verify-failed"
236
+ exit 1
237
+ fi
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env bash
2
+ # ===================================================================
3
+ # 08-rollback.sh — abort the migration and hand the validator back
4
+ #
5
+ # Only valid while the NEW node is fenced: the target node is stopped
6
+ # first, and only then is the source node started again. Both nodes
7
+ # must never run at the same time (same wallet = equivocation).
8
+ #
9
+ # Usage (on the NEW vps):
10
+ # OLD_HOST=203.0.113.27 bash 08-rollback.sh
11
+ # ===================================================================
12
+
13
+ set -euo pipefail
14
+ source "$(dirname "$0")/lib.sh"
15
+
16
+ require_root
17
+ require_old_host
18
+ set_status RUNNING "rollback"
19
+
20
+ log "=== ROLLBACK — stopping the new node first ==="
21
+ if [[ -f "${NODE_ENV}" ]] && [[ -n "$(node_processes)" ]]; then
22
+ as_octra "cd ${NODE_SRC_DIR} && sh controls/stop.sh" 2>&1 | tee -a "${MIG_LOG}" || true
23
+ fi
24
+
25
+ LEFT=$(node_processes)
26
+ for i in {1..20}; do
27
+ [[ -z "${LEFT}" ]] && break
28
+ sleep 1
29
+ LEFT=$(node_processes)
30
+ done
31
+ if [[ -n "${LEFT}" ]]; then
32
+ warn "target processes still alive — SIGTERM"
33
+ pkill -TERM -f "${NODE_PROC_RE}" || true
34
+ sleep 5
35
+ LEFT=$(node_processes)
36
+ fi
37
+ [[ -z "${LEFT}" ]] || die "cannot stop the target node — aborting rollback (source stays down)"
38
+ log "Target node stopped."
39
+
40
+ log "=== restarting the node on the old host ${OLD_HOST} ==="
41
+ if on_old "systemctl is-enabled --quiet pm2-${NODE_USER} 2>/dev/null"; then
42
+ log "pm2 boot unit already enabled on the source"
43
+ else
44
+ on_old "systemctl enable pm2-${NODE_USER} >/dev/null 2>&1 || true"
45
+ log "pm2 boot unit re-enabled on the source"
46
+ fi
47
+ on_old "su - ${NODE_USER} -c 'cd ${NODE_SRC_DIR} && sh controls/run.sh'" 2>&1 | tee -a "${MIG_LOG}"
48
+
49
+ sleep 10
50
+ log "=== source status after rollback ==="
51
+ on_old "su - ${NODE_USER} -c 'cd ${NODE_SRC_DIR} && sh controls/stat.sh'" 2>&1 | \
52
+ grep -E '^(role|process|epoch|voting|validator_active) =' || true
53
+
54
+ set_status DONE "rollback"
@@ -0,0 +1,149 @@
1
+ #!/usr/bin/env bash
2
+ # ===================================================================
3
+ # lib.sh — shared helpers for the Octra node migration toolkit
4
+ #
5
+ # Sourced by every script in scripts/migrate/.
6
+ # ===================================================================
7
+
8
+ set -euo pipefail
9
+
10
+ # --- Paths ---------------------------------------------------------
11
+ MIG_DIR="${MIG_DIR:-/root/migration}"
12
+ MIG_LOG="${MIG_LOG:-${MIG_DIR}/migrate.log}"
13
+ MIG_STATUS="${MIG_STATUS:-${MIG_DIR}/status}"
14
+
15
+ # --- Node paths (must match scripts/config.sh) ----------------------
16
+ NODE_SRC_DIR="${NODE_SRC_DIR:-/opt/octra/libv_litecore}"
17
+ NODE_DATA_ROOT="${NODE_DATA_ROOT:-/var/lib/octra}"
18
+ NODE_DATA_DIR="${NODE_DATA_DIR:-${NODE_DATA_ROOT}/devnet}"
19
+ NODE_SYNC_STAGE="${NODE_SYNC_STAGE:-${NODE_DATA_ROOT}/devnet.state_sync}"
20
+ NODE_ENV="${NODE_ENV:-${NODE_SRC_DIR}/.keys/validator/node.env}"
21
+ NODE_USER="${NODE_USER:-octra}"
22
+ CONSENSUS_PORT="${CONSENSUS_PORT:-19000}"
23
+
24
+ mkdir -p "${MIG_DIR}"
25
+
26
+ # --- Tool policy: check -> install -> continue ----------------------
27
+ # The shared implementation lives in scripts/config.sh, which the push puts at
28
+ # /root/config.sh on BOTH hosts. A host that still carries an old copy gets a
29
+ # loud warning instead of a bare "command not found" half way through a pull.
30
+ # Sourced BEFORE the logging helpers below, so migrate/log/err keep tee-ing into
31
+ # /root/migration/migrate.log.
32
+ if [[ -f /root/config.sh ]]; then
33
+ # shellcheck disable=SC1091
34
+ source /root/config.sh
35
+ fi
36
+
37
+ # --- Logging -------------------------------------------------------
38
+ log() { echo -e "\033[1;32m[migrate]\033[0m $*" | tee -a "${MIG_LOG}"; }
39
+ warn() { echo -e "\033[1;33m[warn]\033[0m $*" | tee -a "${MIG_LOG}" >&2; }
40
+ err() { echo -e "\033[1;31m[err]\033[0m $*" | tee -a "${MIG_LOG}" >&2; }
41
+
42
+ if ! declare -F ensure_tool >/dev/null 2>&1; then
43
+ warn "/root/config.sh has no ensure_tool (old copy on the host) - pushing the scripts again fixes it"
44
+ ensure_tool() {
45
+ command -v "$1" >/dev/null 2>&1 || die "tool $1 is missing and /root/config.sh cannot install it (push the scripts again)"
46
+ }
47
+ fi
48
+
49
+ die() { err "$*"; set_status FAILED "$*"; exit 1; }
50
+
51
+ # --- Status markers (read by the Windows orchestrator) -------------
52
+ set_status() { # set_status <STATE> [detail...]
53
+ # all arguments after the state form the detail (a 3rd argument used to be
54
+ # dropped, which is why the console showed the step label instead of the result)
55
+ local state="$1" detail
56
+ shift || true
57
+ detail="$*"
58
+ printf '%s %s %s\n' "${state}" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "${detail}" > "${MIG_STATUS}"
59
+ log "status = ${state} ${detail}"
60
+ }
61
+
62
+ require_root() {
63
+ [[ $EUID -eq 0 ]] || die "run as root"
64
+ }
65
+
66
+ # --- a step must never die leaving status = RUNNING -------------------
67
+ # The migration orchestrator follows /root/migration/status (Wait-RemoteState).
68
+ # A script that died from an unexpected error - a helper that no longer exists, an
69
+ # unset variable with `set -u`, a full disk - used to leave that file at RUNNING,
70
+ # so the step looked hung until its timeout. This trap writes FAILED immediately.
71
+ _migrate_on_err() {
72
+ local rc="$1" line="$2" cmd="$3" state
73
+ state=$(awk 'NR==1 {print $1}' "${MIG_STATUS}" 2>/dev/null || true)
74
+ case "${state}" in
75
+ DONE|FAILED|WAITING-*) return 0 ;; # the script already decided
76
+ esac
77
+ err "unexpected failure (exit ${rc}) at line ${line}: ${cmd}"
78
+ set_status FAILED "line ${line}: ${cmd} (exit ${rc})"
79
+ }
80
+ trap '_migrate_on_err "$?" "$LINENO" "$BASH_COMMAND"' ERR
81
+
82
+ # Create every directory the node and the migration write into. A fresh host
83
+ # has none of them, and "no such file or directory" half-way through a pull is
84
+ # exactly the kind of failure this toolkit must not have.
85
+ ensure_dirs() {
86
+ local dir
87
+ for dir in "${MIG_DIR}" "${MIG_DIR}/backups" "${NODE_SRC_DIR}" \
88
+ "${NODE_DATA_ROOT}" "${NODE_DATA_DIR}" "${NODE_SYNC_STAGE}" \
89
+ "${NODE_DATA_ROOT}/validator-control"; do
90
+ mkdir -p "${dir}"
91
+ done
92
+ if id "${NODE_USER}" >/dev/null 2>&1; then
93
+ chown "${NODE_USER}:${NODE_USER}" "${NODE_DATA_DIR}" "${NODE_SYNC_STAGE}" 2>/dev/null || true
94
+ fi
95
+ }
96
+
97
+ require_cmd() {
98
+ command -v "$1" >/dev/null 2>&1 || die "missing command: $1"
99
+ }
100
+
101
+ # --- Node helpers --------------------------------------------------
102
+ as_octra() { su - "${NODE_USER}" -c "$*"; }
103
+
104
+ node_stat() {
105
+ as_octra "cd ${NODE_SRC_DIR} && sh controls/stat.sh 2>/dev/null" || true
106
+ }
107
+
108
+ stat_field() { # stat_field <name> (reads node_stat from stdin when piped)
109
+ # the node prints Python's `None` for a field it cannot read: that is an EMPTY
110
+ # value, not a value (see scripts/deploy/lib.sh for the incident)
111
+ awk -F' = ' -v k="$1" '$1==k { v=$2; if (v=="None" || v=="null") v=""; print v; exit }'
112
+ }
113
+
114
+ countdown() { # countdown <seconds> <message>
115
+ local n="$1" msg="${2:-waiting}"
116
+ while (( n > 0 )); do
117
+ printf '\r%s %ds ' "${msg}" "${n}"
118
+ sleep 1
119
+ n=$((n - 1))
120
+ done
121
+ printf '\r%s done\n' "${msg}"
122
+ }
123
+
124
+ # --- Remote (source VPS) helpers ----------------------------------
125
+ OLD_HOST="${OLD_HOST:-}"
126
+ OLD_USER="${OLD_USER:-root}"
127
+ SSH_OPTS=(-o BatchMode=yes -o StrictHostKeyChecking=accept-new -o ConnectTimeout=10)
128
+
129
+ require_old_host() {
130
+ [[ -n "${OLD_HOST}" ]] || die "OLD_HOST is not set (export OLD_HOST=<old-vps-ip>)"
131
+ ssh "${SSH_OPTS[@]}" "${OLD_USER}@${OLD_HOST}" "echo ok" >/dev/null 2>&1 \
132
+ || die "cannot ssh ${OLD_USER}@${OLD_HOST} as root (key not installed?)"
133
+ }
134
+
135
+ on_old() { ssh "${SSH_OPTS[@]}" "${OLD_USER}@${OLD_HOST}" "$*"; }
136
+
137
+ # --- Node process detection ----------------------------------------
138
+ # IMPORTANT: pgrep/pkill -f patterns would also match the shell that runs
139
+ # the check itself (its command line contains the pattern), which makes a
140
+ # dead node look alive. The bracket trick ([o]ctra_node) avoids that.
141
+ NODE_PROC_RE='[o]ctra_node\.exe|[o]ctra_pvac_worker\.exe'
142
+
143
+ node_processes() { # processes of the node on THIS host
144
+ ps -eo pid,args --no-headers 2>/dev/null | grep -E "${NODE_PROC_RE}" || true
145
+ }
146
+
147
+ on_old_node_processes() { # processes of the node on the SOURCE host
148
+ on_old "ps -eo pid,args --no-headers 2>/dev/null | grep -E '${NODE_PROC_RE}' || true"
149
+ }
@@ -0,0 +1,120 @@
1
+ #!/usr/bin/env bash
2
+ # ===================================================================
3
+ # 10-preflight.sh - is this host a node we may update, and what is on it?
4
+ #
5
+ # Read-only. Nothing is pulled, built or restarted here: the step reports the
6
+ # checkout (commit/branch/dirty), the running binary, the chain role and the disk
7
+ # head-room, and refuses clearly when this is not an Octra node at all.
8
+ #
9
+ # Upstream rules this step reminds the operator of (README, "How to update"):
10
+ # never run `--sync` again, never change config/network.env, never re-enroll.
11
+ #
12
+ # Usage: sudo bash 10-preflight.sh
13
+ # Env: MIN_FREE_GB (20) - warn below that much free space on the data volume
14
+ # STATE_TIMEOUT (60)
15
+ # ===================================================================
16
+
17
+ set -euo pipefail
18
+ source "$(dirname "$0")/lib.sh"
19
+
20
+ require_root
21
+ set_status RUNNING "preflight"
22
+
23
+ MIN_FREE_GB="${MIN_FREE_GB:-20}"
24
+ WARNINGS=0
25
+
26
+ [[ -d "${NODE_SRC_DIR}" ]] || die "no node at ${NODE_SRC_DIR} - this host does not run an Octra node"
27
+ [[ -d "${NODE_SRC_DIR}/.git" ]] || die "${NODE_SRC_DIR} is not a git checkout - the node cannot be updated from the source tree"
28
+ [[ -f "${NODE_WALLET}" ]] || die "wallet.json is missing (${NODE_WALLET}) - this is not the validator host"
29
+ [[ -f "${NODE_ENV}" ]] || die "node.env is missing (${NODE_ENV}) - the node has no configuration to update"
30
+ [[ -d "${NODE_DATA_DIR}" ]] || die "the data directory is missing (${NODE_DATA_DIR}) - nothing to keep in sync"
31
+
32
+ for tool in git make opam; do
33
+ if command -v "${tool}" >/dev/null 2>&1; then
34
+ log "tool ${tool}: present ($(command -v "${tool}"))"
35
+ else
36
+ warn "tool ${tool}: MISSING - the update needs it (the Apply step installs it via apt)"
37
+ WARNINGS=$(( WARNINGS + 1 ))
38
+ fi
39
+ done
40
+
41
+ # --- what the checkout says -------------------------------------------
42
+ COMMIT=$(source_commit)
43
+ HEAD=$(git_head)
44
+ BRANCH=$(git_branch)
45
+ DIRTY=$(git_dirty)
46
+ REMOTE=$(git_remote)
47
+ log "repository: ${REMOTE}"
48
+ log "checkout : SOURCE_COMMIT=${COMMIT:-unknown} git HEAD=${HEAD:-unknown}${BRANCH:+ on ${BRANCH}}"
49
+ if [[ -n "${DIRTY}" ]]; then
50
+ warn "the working tree has local changes - the official tool refuses to update a dirty checkout"
51
+ WARNINGS=$(( WARNINGS + 1 ))
52
+ fi
53
+
54
+ # --- the control scripts the update needs -----------------------------
55
+ [[ -f "${CHECK_SH}" ]] || warn "controls/check.sh is missing (the package cannot be verified)"
56
+ if [[ -f "${UPGRADE_SH}" ]]; then
57
+ log "official tool: controls/upgrade.sh present"
58
+ else
59
+ warn "controls/upgrade.sh is missing - use the manual update (fallback) on this node version"
60
+ WARNINGS=$(( WARNINGS + 1 ))
61
+ fi
62
+ if [[ -f "${STORAGE_SH}" ]]; then
63
+ log "storage tool : controls/storage.sh present"
64
+ else
65
+ warn "controls/storage.sh is missing - 'Remove preserved states' is not available on this node"
66
+ WARNINGS=$(( WARNINGS + 1 ))
67
+ fi
68
+ [[ -f "${STOP_SH}" && -f "${RUN_SH}" ]] || warn "controls/stop.sh or controls/run.sh is missing - the restart may fail"
69
+
70
+ # --- the running node --------------------------------------------------
71
+ read_state
72
+ log "node : role=${ROLE:-?} process=${PROCESS:-?} rpc=${RPC:-?} voting=${VOTING:-?} epoch=${EPOCH:-?} uptime=${PROCESS_UP:-?}"
73
+ log "membership: active=${ACTIVE:-?} scheduled=${SCHED:-?} enrollment=${ENROLL:-?}"
74
+ log "binary : ${BINARY:-unknown}${PVAC:+ pvac ${PVAC}}"
75
+ if [[ "${PROCESS}" != "online" ]]; then
76
+ warn "the node process is ${PROCESS:-unknown} - the update restarts it (controls/run.sh)"
77
+ WARNINGS=$(( WARNINGS + 1 ))
78
+ fi
79
+ if [[ "${RPC}" != "ready" ]]; then
80
+ warn "the node rpc is ${RPC:-unknown} - the upgrade tool waits for it, so the update may take longer"
81
+ WARNINGS=$(( WARNINGS + 1 ))
82
+ fi
83
+
84
+ # a join is running: the upgrade restarts the node and could interrupt it
85
+ if systemctl is-active --quiet octra-enroll-join 2>/dev/null; then
86
+ warn "an enrollment join is running (unit octra-enroll-join) - finish the enrollment before updating"
87
+ WARNINGS=$(( WARNINGS + 1 ))
88
+ fi
89
+ if [[ -f "${NODE_DATA_DIR}/validator-control/bond-increase.json" ]]; then
90
+ warn "a signed bond increase is stored on this host - make sure it is confirmed before restarting the node"
91
+ fi
92
+
93
+ # --- disk head-room (a build + a state sync need room) -----------------
94
+ FREE_GB=""
95
+ if command -v df >/dev/null 2>&1; then
96
+ FREE_GB=$(df -B1 --output=avail "${NODE_DATA_ROOT}" 2>/dev/null | tail -1 | tr -d ' ' || true)
97
+ if [[ "${FREE_GB}" =~ ^[0-9]+$ ]]; then
98
+ FREE_H=$(awk -v b="${FREE_GB}" 'BEGIN { printf "%.1f GiB", b / 1073741824 }')
99
+ log "data volume: ${FREE_H} free on ${NODE_DATA_ROOT}"
100
+ if (( FREE_GB < MIN_FREE_GB * 1073741824 )); then
101
+ warn "less than ${MIN_FREE_GB} GiB free - free space before updating (controls/storage.sh --prune-prior --yes)"
102
+ WARNINGS=$(( WARNINGS + 1 ))
103
+ fi
104
+ fi
105
+ fi
106
+ if command -v du >/dev/null 2>&1; then
107
+ USED=$(du -sh "${NODE_DATA_DIR}" 2>/dev/null | cut -f1 || true)
108
+ [[ -n "${USED}" ]] && log "data store : ${USED} in ${NODE_DATA_DIR}"
109
+ fi
110
+
111
+ if [[ -f "${STORAGE_SH}" ]]; then
112
+ SUMMARY=$(storage_summary)
113
+ [[ -n "${SUMMARY}" ]] && log "storage : ${SUMMARY}"
114
+ fi
115
+
116
+ log "upstream rules: do NOT run --sync again, do NOT change config/network.env, do NOT re-enroll"
117
+ if (( WARNINGS > 0 )); then
118
+ log "${WARNINGS} warning(s) above - the update can still run"
119
+ fi
120
+ set_status DONE "commit=${HEAD:-unknown}${BRANCH:+ on ${BRANCH}} running=${ROLE:-?} active=${ACTIVE:-?} warnings=${WARNINGS}"