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,347 @@
1
+ #!/usr/bin/env bash
2
+ # ===================================================================
3
+ # scripts/manage/lib.sh - shared helpers for the MANAGE-ACTIVE-NODE program
4
+ #
5
+ # The third program of the console: it does not create or move a node, it
6
+ # operates the validator that is already running (increase the bond, exit it and
7
+ # take the stake back). Everything here runs on the node host as root and calls
8
+ # the node's own controls as the `octra` user, exactly like the other two
9
+ # programs - same status markers, same ERR trap, same tool policy.
10
+ #
11
+ # /root/manage/ this program (/root/deploy, /root/migration = the others)
12
+ # ===================================================================
13
+ set -euo pipefail
14
+
15
+ # Tool policy (check -> install -> continue) lives in scripts/config.sh, which is
16
+ # pushed to /root/config.sh next to these scripts.
17
+ CONFIG_FILE="${CONFIG_FILE:-/root/config.sh}"
18
+ if [[ -f "${CONFIG_FILE}" ]]; then
19
+ # shellcheck disable=SC1090
20
+ source "${CONFIG_FILE}"
21
+ fi
22
+ if ! declare -F ensure_tool >/dev/null 2>&1; then
23
+ ensure_tool() { command -v "$1" >/dev/null 2>&1 || die "tool $1 is missing and ${CONFIG_FILE} cannot install it (push the scripts again)"; }
24
+ fi
25
+
26
+ MANAGE_DIR="${MANAGE_DIR:-/root/manage}"
27
+ MANAGE_LOG="${MANAGE_LOG:-${MANAGE_DIR}/manage.log}"
28
+ MANAGE_STATUS="${MANAGE_STATUS:-${MANAGE_DIR}/status}"
29
+
30
+ NODE_SRC_DIR="${NODE_SRC_DIR:-/opt/octra/libv_litecore}"
31
+ NODE_DATA_ROOT="${NODE_DATA_ROOT:-/var/lib/octra}"
32
+ NODE_DATA_DIR="${NODE_DATA_DIR:-${NODE_DATA_ROOT}/devnet}"
33
+ NODE_WALLET="${NODE_WALLET:-${NODE_SRC_DIR}/.keys/validator/wallet.json}"
34
+ NODE_ENV="${NODE_ENV:-${NODE_SRC_DIR}/.keys/validator/node.env}"
35
+ NODE_USER="${NODE_USER:-octra}"
36
+ NODE_LOG="${NODE_LOG:-${NODE_SRC_DIR}/data/operator_logs/node.log}"
37
+ TOKEN_DECIMALS=6
38
+ TOKEN_UNIT=1000000
39
+
40
+ mkdir -p "${MANAGE_DIR}"
41
+
42
+ log() { echo -e "[manage] $*" | tee -a "${MANAGE_LOG}"; }
43
+ warn() { echo -e "[warn] $*" | tee -a "${MANAGE_LOG}" >&2; }
44
+ err() { echo -e "[err] $*" | tee -a "${MANAGE_LOG}" >&2; }
45
+
46
+ set_status() { # set_status <STATE> [detail...] - read by the Windows console
47
+ local state="$1" detail
48
+ shift || true
49
+ detail="$*"
50
+ printf '%s %s %s\n' "${state}" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "${detail}" > "${MANAGE_STATUS}"
51
+ log "status = ${state} ${detail}"
52
+ }
53
+
54
+ die() { err "$*"; set_status FAILED "$*"; exit 1; }
55
+
56
+ require_root() { [[ $EUID -eq 0 ]] || die "run as root"; }
57
+
58
+ # A step must never die leaving status = RUNNING: the console follows this file.
59
+ _manage_on_err() {
60
+ local rc="$1" line="$2" cmd="$3" state
61
+ state=$(awk 'NR==1 {print $1}' "${MANAGE_STATUS}" 2>/dev/null || true)
62
+ case "${state}" in
63
+ DONE|FAILED|WAITING-*) return 0 ;;
64
+ esac
65
+ err "unexpected failure (exit ${rc}) at line ${line}: ${cmd}"
66
+ set_status FAILED "line ${line}: ${cmd} (exit ${rc})"
67
+ }
68
+ trap '_manage_on_err "$?" "$LINENO" "$BASH_COMMAND"' ERR
69
+
70
+ # --- the node's own controls (always as the octra user) ---------------
71
+ as_octra() { su - "${NODE_USER}" -c "$*"; }
72
+
73
+ # node_script <timeout> <command...>: run one of the node's control scripts from
74
+ # the node directory as the node user, bounded (a control script that waits on
75
+ # the chain must never freeze a step).
76
+ node_script() {
77
+ local limit="$1"
78
+ shift
79
+ timeout "${limit}" su - "${NODE_USER}" -c "cd ${NODE_SRC_DIR} && $*" 2>&1
80
+ }
81
+
82
+ stat_raw() { node_script "${STATE_TIMEOUT:-60}" "sh controls/stat.sh" || true; }
83
+ stat_field() { # an unreadable field is EMPTY, not the string "None" (see deploy/lib.sh)
84
+ awk -F' = ' -v k="$1" '$1==k { v=$2; if (v=="None" || v=="null") v=""; print v; exit }'
85
+ }
86
+
87
+ # fill SNAP/ROLE/RPC/ACTIVE/SCHED/ENROLL/BOND/EPOCH/VOTING/PROCESS from the node
88
+ read_state() {
89
+ SNAP=$(stat_raw)
90
+ ROLE=$(printf '%s\n' "${SNAP}" | stat_field role)
91
+ RPC=$(printf '%s\n' "${SNAP}" | stat_field rpc)
92
+ ACTIVE=$(printf '%s\n' "${SNAP}" | stat_field validator_active)
93
+ SCHED=$(printf '%s\n' "${SNAP}" | stat_field validator_scheduled)
94
+ ENROLL=$(printf '%s\n' "${SNAP}" | stat_field validator_enrollment)
95
+ BOND=$(printf '%s\n' "${SNAP}" | stat_field validator_bond)
96
+ EPOCH=$(printf '%s\n' "${SNAP}" | stat_field epoch)
97
+ VOTING=$(printf '%s\n' "${SNAP}" | stat_field voting)
98
+ PROCESS=$(printf '%s\n' "${SNAP}" | stat_field process)
99
+ }
100
+
101
+ # how often a waiting step re-prints its progress line
102
+ EXIT_PROGRESS_SEC="${EXIT_PROGRESS_SEC:-120}"
103
+
104
+ # --- "when will the stake be back?" -----------------------------------
105
+ # An exit is a chain event, not a command: the set releases the validator at a
106
+ # fold, then the bond unlocks at `withdraw_epoch` (weeks away on this network).
107
+ # The console and the operator need two numbers for that - the epoch and a
108
+ # duration - so the cadence is measured from the chain itself (two head_epoch
109
+ # samples), never hard-coded: a testnet that speeds up changes the answer.
110
+ EPOCH_SAMPLE_SEC="${EPOCH_SAMPLE_SEC:-60}"
111
+
112
+ # fmt_duration <seconds> -> "1 day 18 h" | "3 h 05 min" | "42 min" | "45 s"
113
+ fmt_duration() {
114
+ local secs="${1:-}"
115
+ [[ "${secs}" =~ ^[0-9]+$ ]] || { printf 'unknown'; return 0; }
116
+ if (( secs >= 86400 )); then
117
+ local days=$(( secs / 86400 )) hours=$(( (secs % 86400) / 3600 ))
118
+ printf '%d day%s %d h' "${days}" "$([[ ${days} -eq 1 ]] && printf '' || printf 's')" "${hours}"
119
+ elif (( secs >= 3600 )); then
120
+ printf '%d h %02d min' $(( secs / 3600 )) $(( (secs % 3600) / 60 ))
121
+ elif (( secs >= 60 )); then
122
+ printf '%d min' $(( secs / 60 ))
123
+ else
124
+ printf '%d s' "${secs}"
125
+ fi
126
+ return 0
127
+ }
128
+
129
+ # epoch_seconds: how long one epoch lasts. EPOCH_SECONDS wins (the console sends
130
+ # the cadence it measured, a test pins it); otherwise two stat.sh samples give it.
131
+ epoch_seconds() {
132
+ local e1 e2 t1 t2 de dt
133
+ if [[ "${EPOCH_SECONDS:-}" =~ ^[0-9]+([.][0-9]+)?$ ]]; then
134
+ printf '%s\n' "${EPOCH_SECONDS}"
135
+ return 0
136
+ fi
137
+ [[ "${EPOCH_SAMPLE_SEC}" =~ ^[0-9]+$ ]] && (( EPOCH_SAMPLE_SEC > 0 )) || return 0
138
+ e1=$(stat_raw | stat_field epoch)
139
+ [[ "${e1}" =~ ^[0-9]+$ ]] || return 0
140
+ t1=$(date +%s)
141
+ sleep "${EPOCH_SAMPLE_SEC}"
142
+ e2=$(stat_raw | stat_field epoch)
143
+ [[ "${e2}" =~ ^[0-9]+$ ]] || return 0
144
+ t2=$(date +%s)
145
+ de=$(( e2 - e1 ))
146
+ dt=$(( t2 - t1 ))
147
+ (( de > 0 && dt > 0 )) || return 0
148
+ awk -v dt="${dt}" -v de="${de}" 'BEGIN { printf "%.2f\n", dt / de }'
149
+ return 0
150
+ }
151
+
152
+ # epoch_eta <epochs-left> <seconds-per-epoch> -> "1 day 18 h" (empty when unknown)
153
+ epoch_eta() {
154
+ local left="${1:-}" rate="${2:-}"
155
+ [[ "${left}" =~ ^[0-9]+$ ]] || return 0
156
+ [[ "${rate}" =~ ^[0-9]+([.][0-9]+)?$ ]] || return 0
157
+ awk -v l="${left}" -v r="${rate}" 'BEGIN { printf "%d\n", (l * r) + 0.5 }'
158
+ return 0
159
+ }
160
+
161
+ # --- the withdrawal keeps itself alive on the host ---------------------
162
+ # `controls/enroll.sh withdraw` only signs once the chain allows it (unbonding
163
+ # over AND the validator out of the set). Waiting for that inside a step would
164
+ # block the console for weeks, and only re-running the step by hand would ever
165
+ # withdraw the stake - so the step installs a small systemd timer instead. It
166
+ # survives reboots and console restarts, and disarms itself when bond = 0.
167
+ EXIT_WATCH_UNIT="${EXIT_WATCH_UNIT:-octra-exit-withdraw}"
168
+ EXIT_WATCH_SCRIPT="${EXIT_WATCH_SCRIPT:-/root/manage/40-exit-withdraw.sh}"
169
+ EXIT_WATCH_STATUS="${EXIT_WATCH_STATUS:-${MANAGE_DIR}/exit-watch.status}"
170
+ EXIT_WATCH_LOG="${EXIT_WATCH_LOG:-${MANAGE_DIR}/exit-watch.log}"
171
+ SYSTEMD_DIR="${OCTRA_SYSTEMD_DIR:-/etc/systemd/system}"
172
+ SYSTEMCTL="${OCTRA_SYSTEMCTL:-systemctl}"
173
+
174
+ exit_watch_status() {
175
+ [[ -f "${EXIT_WATCH_STATUS}" ]] && head -1 "${EXIT_WATCH_STATUS}"
176
+ return 0
177
+ }
178
+
179
+ exit_watch_install() {
180
+ local service="${SYSTEMD_DIR}/${EXIT_WATCH_UNIT}.service"
181
+ local timer="${SYSTEMD_DIR}/${EXIT_WATCH_UNIT}.timer"
182
+ if [[ ! -f "${EXIT_WATCH_SCRIPT}" ]]; then
183
+ warn "cannot arm the withdrawal timer: ${EXIT_WATCH_SCRIPT} is missing (push the manage scripts again)"
184
+ return 0
185
+ fi
186
+ mkdir -p "${SYSTEMD_DIR}" 2>/dev/null || true
187
+ cat > "${service}" <<UNIT || warn "could not write ${service}"
188
+ [Unit]
189
+ Description=Octra validator stake withdrawal (retries until the unbonding period is over)
190
+
191
+ [Service]
192
+ Type=oneshot
193
+ ExecStart=/bin/bash ${EXIT_WATCH_SCRIPT}
194
+ UNIT
195
+ cat > "${timer}" <<UNIT || warn "could not write ${timer}"
196
+ [Unit]
197
+ Description=Octra validator stake withdrawal retry (every 15 minutes)
198
+
199
+ [Timer]
200
+ OnBootSec=5min
201
+ OnUnitActiveSec=15min
202
+ AccuracySec=1min
203
+ Unit=${EXIT_WATCH_UNIT}.service
204
+
205
+ [Install]
206
+ WantedBy=timers.target
207
+ UNIT
208
+ if "${SYSTEMCTL}" daemon-reload >/dev/null 2>&1 \
209
+ && "${SYSTEMCTL}" enable --now "${EXIT_WATCH_UNIT}.timer" >/dev/null 2>&1; then
210
+ log "the withdrawal retry is armed: ${EXIT_WATCH_UNIT}.timer (every 15 min, also after a reboot)"
211
+ else
212
+ warn "could not enable ${EXIT_WATCH_UNIT}.timer - the console still follows the unbonding"
213
+ fi
214
+ return 0
215
+ }
216
+
217
+ exit_watch_disarm() {
218
+ if "${SYSTEMCTL}" disable --now "${EXIT_WATCH_UNIT}.timer" >/dev/null 2>&1; then
219
+ log "the withdrawal retry is off (${EXIT_WATCH_UNIT}.timer)"
220
+ fi
221
+ return 0
222
+ }
223
+
224
+ # The node's own operator log explains a slow set fold / refused readiness post.
225
+ #
226
+ # 2026-11-04: `line=$(... | grep ...)` returned 1 when the log had no
227
+ # `set_post_failed` line (a HEALTHY node) and `set -e` + pipefail made the caller
228
+ # die as `unexpected failure ... HINT=$(post_hint)`. Optional text must never
229
+ # fail - see the same note in scripts/deploy/lib.sh.
230
+ post_hint() {
231
+ local line=""
232
+ [[ -f "${NODE_LOG}" ]] || return 0
233
+ line=$(tail -n 400 "${NODE_LOG}" 2>/dev/null | grep -a "set_post_failed" | tail -1 || true)
234
+ if [[ -n "${line}" ]]; then
235
+ printf '%s' "${line##*reason = }"
236
+ fi
237
+ return 0
238
+ }
239
+
240
+ # --- the node's own RPC (fast path when /status is too slow) ------------
241
+ # `stat.sh` reads /status, which builds a big snapshot; on a node that is stuck
242
+ # in catch-up that call can take longer than the tool timeout and everything
243
+ # reads as "rpc unavailable" even though the RPC answers /rpc in milliseconds.
244
+ # These helpers ask the small endpoints directly.
245
+ api_port() { local p; p=$(node_env_value OCTRA_API_PORT); printf '%s' "${p:-8080}"; }
246
+
247
+ rpc() { # rpc <method> [params-json]
248
+ timeout 20 curl -s -X POST -H 'Content-Type: application/json' \
249
+ -d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"$1\",\"params\":${2:-[]}}" \
250
+ "http://127.0.0.1:$(api_port)/rpc" 2>/dev/null || true
251
+ }
252
+
253
+ rpc_field() { # rpc_field <method> <params-json> <field>
254
+ rpc "$1" "${2:-[]}" | jq -r --arg k "$3" '.result[$k] // empty' 2>/dev/null || true
255
+ }
256
+
257
+ head_epoch() { rpc_field octra_validatorEnrollment '[]' head_epoch; }
258
+
259
+ status_code() { # status_code -> the HTTP code of /status (000 = timed out)
260
+ timeout 8 curl -s -o /dev/null -w '%{http_code}' "http://127.0.0.1:$(api_port)/status" 2>/dev/null || true
261
+ }
262
+
263
+ pm2_name() { local n; n=$(node_env_value OCTRA_OPERATOR_PM2_NAME); printf '%s' "${n:-octra-validator-01}"; }
264
+
265
+ pm2_state() { # pm2_state -> "<name> <status> <pid>" (empty when pm2 has no such app)
266
+ su - "${NODE_USER}" -c "pm2 jlist" 2>/dev/null \
267
+ | jq -r --arg n "$(pm2_name)" '.[] | select(.name==$n) | "\(.name) \(.pm2_env.status) \(.pid)"' 2>/dev/null \
268
+ | head -1 || true
269
+ }
270
+
271
+ # how far behind the network this node is (epochs), and the network's own idea of
272
+ # the current epoch, both from the operator log - the RPC only knows its own head
273
+ network_epoch() {
274
+ [[ -f "${NODE_LOG}" ]] || return 0
275
+ tail -n 400 "${NODE_LOG}" 2>/dev/null | grep -aoE 'epoch = [0-9]+' | tail -1 | awk '{print $3}' || true
276
+ }
277
+
278
+ catchup_reason() { # the last reason catch-up could not fetch a range
279
+ [[ -f "${NODE_LOG}" ]] || return 0
280
+ tail -n 400 "${NODE_LOG}" 2>/dev/null \
281
+ | grep -a 'range_http_unavailable' | tail -1 \
282
+ | sed -n 's/.*error = //p' || true
283
+ }
284
+
285
+ # 1 OCT = 1000000 raw; the console sends OCT as a string (at most six decimals).
286
+ oct_to_raw() {
287
+ local value="$1" whole fraction pad
288
+ [[ "${value}" =~ ^[0-9]+(\.[0-9]{1,6})?$ ]] || die "amount must be OCT with at most six decimals (got '${value}')"
289
+ whole="${value%%.*}"
290
+ fraction="000000"
291
+ [[ "${value}" == *.* ]] && fraction="${value#*.}"
292
+ pad="${fraction}000000"
293
+ pad="${pad:0:6}"
294
+ printf '%s\n' "$(( 10#${whole} * TOKEN_UNIT + 10#${pad} ))"
295
+ }
296
+
297
+ raw_to_oct() {
298
+ awk -v raw="${1:-0}" -v unit="${TOKEN_UNIT}" 'BEGIN { printf "%.6f\n", raw / unit }' | sed -e 's/0*$//' -e 's/\.$//'
299
+ }
300
+
301
+ wallet_address() {
302
+ if [[ -f "${NODE_WALLET}" ]]; then
303
+ jq -r '.address // empty' "${NODE_WALLET}" 2>/dev/null || true
304
+ fi
305
+ }
306
+
307
+ node_env_value() { # node_env_value <KEY>
308
+ [[ -f "${NODE_ENV}" ]] || return 0
309
+ grep -m1 "^$1=" "${NODE_ENV}" 2>/dev/null | cut -d= -f2- || true
310
+ }
311
+
312
+ # --- the bond increase tool (pushed by the console) -------------------
313
+ BOND_INC_LOCAL="${BOND_INC_LOCAL:-${MANAGE_DIR}/bond_inc.sh}"
314
+ BOND_INC_REMOTE="${BOND_INC_REMOTE:-${NODE_SRC_DIR}/bond_inc.sh}"
315
+
316
+ install_bond_inc() {
317
+ [[ -f "${BOND_INC_LOCAL}" ]] || die "bond_inc.sh was not pushed to ${BOND_INC_LOCAL} - start the increase again from the console"
318
+ if [[ -f "${BOND_INC_REMOTE}" ]] && cmp -s "${BOND_INC_LOCAL}" "${BOND_INC_REMOTE}"; then
319
+ log "bond_inc.sh in the node directory is up to date"
320
+ else
321
+ log "installing bond_inc.sh into ${NODE_SRC_DIR} (owned by ${NODE_USER})"
322
+ install -m 0700 "${BOND_INC_LOCAL}" "${BOND_INC_REMOTE}" \
323
+ || die "could not write ${BOND_INC_REMOTE}"
324
+ # the tool must be owned by the node user (it reads that wallet as that user)
325
+ chown "${NODE_USER}:${NODE_USER}" "${BOND_INC_REMOTE}" 2>/dev/null \
326
+ || warn "could not chown ${BOND_INC_REMOTE} to ${NODE_USER} - check the owner"
327
+ fi
328
+ }
329
+
330
+ # bond_inc.sh keeps ONE signed increase in validator-control/bond-increase.json
331
+ # and compares it with the arguments + the registry on every run:
332
+ # * no record -> the first increase, run without --new
333
+ # * a record that is confirmed -> a further increase needs --new
334
+ # * a record pending/staging -> nothing new may be signed (wait)
335
+ saved_increase_path() {
336
+ local path="${NODE_DATA_DIR}/validator-control/bond-increase.json"
337
+ [[ -f "${path}" ]] && printf '%s\n' "${path}"
338
+ return 0
339
+ }
340
+
341
+ show_saved_increase() {
342
+ local path
343
+ path=$(saved_increase_path)
344
+ [[ -n "${path}" ]] || return 0
345
+ log "a signed bond increase is already stored on this host:"
346
+ jq -r '" amount = \(.amount) raw, stage = \(.stage), tx = \(.tx_hash)"' "${path}" 2>/dev/null | sed 's/^/ /' || true
347
+ }
@@ -0,0 +1,143 @@
1
+ {
2
+ "schema": 1,
3
+ "description": "Octra validator task assets. The console may clone this repository on the VPS and copy only the task it runs.",
4
+ "entrypoints": {
5
+ "deploy": "assets/deploy/40-start-node.sh",
6
+ "migrate": "assets/migrate/03-pull-state.sh",
7
+ "manage": "assets/manage/30-status.sh",
8
+ "upgrade": "assets/upgrade/20-upgrade.sh",
9
+ "dashboard_monitor": "assets/manage/dashboard_monitor/deploy-dashboard.sh"
10
+ },
11
+ "tasks": {
12
+ "common": {
13
+ "source": "assets/common",
14
+ "target": "/root",
15
+ "dependsOn": [],
16
+ "files": [
17
+ "bond-inspect.sh",
18
+ "config.sh",
19
+ "extract-data.sh",
20
+ "harden-host.sh",
21
+ "install-deps.sh",
22
+ "node-enroll.sh",
23
+ "node-setup.sh",
24
+ "node-start.sh",
25
+ "node-stop.sh",
26
+ "node-upgrade.sh",
27
+ "pre-run.sh",
28
+ "promote-validator.sh"
29
+ ]
30
+ },
31
+ "deploy": {
32
+ "source": "assets/deploy",
33
+ "target": "/root/deploy",
34
+ "dependsOn": [
35
+ "common"
36
+ ],
37
+ "files": [
38
+ "10-prepare-host.sh",
39
+ "20-install-toolchain.sh",
40
+ "30-setup-node.sh",
41
+ "40-start-node.sh",
42
+ "50-verify.sh",
43
+ "60-enroll.sh",
44
+ "70-promote.sh",
45
+ "lib.sh"
46
+ ],
47
+ "entrypoint": "assets/deploy/40-start-node.sh"
48
+ },
49
+ "migrate": {
50
+ "source": "assets/migrate",
51
+ "target": "/root/migrate",
52
+ "dependsOn": [
53
+ "common"
54
+ ],
55
+ "files": [
56
+ "00-backup-identity.sh",
57
+ "01-prepare-target.sh",
58
+ "02-authorize-pull.sh",
59
+ "03-pull-state.sh",
60
+ "04-stop-source.sh",
61
+ "05-finalize-target.sh",
62
+ "06-start-target.sh",
63
+ "07-verify.sh",
64
+ "08-rollback.sh",
65
+ "lib.sh"
66
+ ],
67
+ "entrypoint": "assets/migrate/03-pull-state.sh"
68
+ },
69
+ "manage": {
70
+ "source": "assets/manage",
71
+ "target": "/root/manage",
72
+ "dependsOn": [
73
+ "common"
74
+ ],
75
+ "files": [
76
+ "10-bond-increase.sh",
77
+ "10-diagnose-node.sh",
78
+ "20-exit-validator.sh",
79
+ "25-recover-node.sh",
80
+ "30-status.sh",
81
+ "40-exit-withdraw.sh",
82
+ "bond_inc.sh",
83
+ "lib.sh"
84
+ ],
85
+ "entrypoint": "assets/manage/30-status.sh"
86
+ },
87
+ "upgrade": {
88
+ "source": "assets/upgrade",
89
+ "target": "/root/upgrade",
90
+ "dependsOn": [
91
+ "common"
92
+ ],
93
+ "files": [
94
+ "10-preflight.sh",
95
+ "20-upgrade.sh",
96
+ "30-manual-upgrade.sh",
97
+ "40-storage-prune.sh",
98
+ "50-status.sh",
99
+ "lib.sh"
100
+ ],
101
+ "entrypoint": "assets/upgrade/20-upgrade.sh"
102
+ },
103
+ "dashboard_monitor": {
104
+ "source": "assets/manage/dashboard_monitor",
105
+ "target": "/root/dashboard",
106
+ "dependsOn": [],
107
+ "files": [
108
+ "app.js",
109
+ "assets/v2.2/css/base.css",
110
+ "assets/v2.2/css/components/content.css",
111
+ "assets/v2.2/css/components/controls.css",
112
+ "assets/v2.2/css/components/data.css",
113
+ "assets/v2.2/css/components/icons.css",
114
+ "assets/v2.2/css/components/motion.css",
115
+ "assets/v2.2/css/components/shell.css",
116
+ "assets/v2.2/css/components/sidebar.css",
117
+ "assets/v2.2/css/components/topbar.css",
118
+ "assets/v2.2/css/fonts.css",
119
+ "assets/v2.2/css/synteq-ui-v2.css",
120
+ "assets/v2.2/css/theme-dark.css",
121
+ "assets/v2.2/css/theme-light.css",
122
+ "assets/v2.2/css/tokens.css",
123
+ "assets/v2.2/fonts/GoogleSansCode-Medium.ttf",
124
+ "assets/v2.2/fonts/GoogleSansCode-Regular.ttf",
125
+ "assets/v2.2/fonts/GoogleSansCode-SemiBold.ttf",
126
+ "assets/v2.2/js/synteq-ui-v2.js",
127
+ "deploy-dashboard.sh",
128
+ "favicon.svg",
129
+ "index.html",
130
+ "nginx-octra.conf",
131
+ "octra-dashboard.service",
132
+ "octra-node-prune.cron",
133
+ "octra-node-prune.service",
134
+ "octra-node-prune.timer",
135
+ "prune-node-junk.sh",
136
+ "server.py",
137
+ "ssl-certbot.sh",
138
+ "styles.css"
139
+ ],
140
+ "entrypoint": "assets/manage/dashboard_monitor/deploy-dashboard.sh"
141
+ }
142
+ }
143
+ }
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env bash
2
+ # ===================================================================
3
+ # 00-backup-identity.sh — snapshot everything that cannot be recreated
4
+ #
5
+ # Run this on a node BEFORE anything destructive (migration switch, VM
6
+ # reinstall, disk swap). The archive is a few KB but it holds the only
7
+ # copy of the validator key that the bond is attached to.
8
+ #
9
+ # Contents:
10
+ # .keys/validator/{wallet.json,enrollment.json,node.env,network.env}
11
+ # devnet/{ready_to_vote.json,HEAD.json}
12
+ # a stat.sh snapshot (context at backup time)
13
+ #
14
+ # Usage:
15
+ # sudo bash 00-backup-identity.sh # → /root/migration/backups/
16
+ # sudo bash 00-backup-identity.sh /root/safe # custom destination
17
+ #
18
+ # Restore (on a freshly copied tree, same absolute paths):
19
+ # tar xzf octra-identity-*.tar.gz -C /
20
+ # ===================================================================
21
+
22
+ set -euo pipefail
23
+ source "$(dirname "$0")/lib.sh"
24
+
25
+ require_root
26
+
27
+ DEST="${1:-${MIG_DIR}/backups}"
28
+ STAMP=$(date -u +%Y%m%dT%H%M%SZ)
29
+ HOST=$(hostname)
30
+ OUT="${DEST}/octra-identity-${HOST}-${STAMP}.tar.gz"
31
+
32
+ mkdir -p "${DEST}"
33
+
34
+ KEY_DIR="${NODE_SRC_DIR}/.keys/validator"
35
+ [[ -f "${KEY_DIR}/wallet.json" ]] || die "wallet.json not found at ${KEY_DIR} — is this the node host?"
36
+
37
+ REL=()
38
+ for f in wallet.json enrollment.json node.env network.env node.env.pre-migration.bak; do
39
+ [[ -f "${KEY_DIR}/${f}" ]] && REL+=("${KEY_DIR#/}/${f}")
40
+ done
41
+ for f in ready_to_vote.json HEAD.json validator-control/last.json; do
42
+ [[ -f "${NODE_DATA_DIR}/${f}" ]] && REL+=("${NODE_DATA_DIR#/}/${f}")
43
+ done
44
+ (( ${#REL[@]} > 0 )) || die "no identity files found to archive"
45
+
46
+ # context snapshot (node may be stopped — node_stat tolerates that)
47
+ node_stat > "${DEST}/stat-${HOST}-${STAMP}.txt" 2>/dev/null || true
48
+ if [[ -f "${KEY_DIR}/wallet.json" ]]; then
49
+ {
50
+ echo "host = ${HOST}"
51
+ echo "taken_at = ${STAMP}"
52
+ echo "address = $(python3 -c 'import json,sys;print(json.load(open(sys.argv[1]))["address"])' "${KEY_DIR}/wallet.json" 2>/dev/null || echo unknown)"
53
+ echo "wallet_sha256 = $(sha256sum "${KEY_DIR}/wallet.json" | awk '{print $1}')"
54
+ } > "${DEST}/identity-${HOST}-${STAMP}.txt"
55
+ fi
56
+
57
+ ( cd / && tar czf "${OUT}" "${REL[@]}" )
58
+ chmod 600 "${OUT}"
59
+ sha256sum "${OUT}" | tee "${OUT}.sha256" >/dev/null
60
+
61
+ log "=== identity backup written ==="
62
+ log "archive : ${OUT} ($(du -h "${OUT}" | cut -f1))"
63
+ log "sha256 : $(awk '{print $1}' "${OUT}.sha256")"
64
+ log "files :"
65
+ tar tzf "${OUT}" | sed 's/^/ /'
66
+
67
+ set_status DONE "backup-identity" "${OUT}"