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,98 @@
1
+ #!/usr/bin/env bash
2
+ # ===================================================================
3
+ # 10-prepare-host.sh - turn a fresh VPS into a deploy target
4
+ #
5
+ # * swap + node data store (delegates to the shared /root/pre-run.sh, which
6
+ # detects the volume, formats it when it holds no Octra data set, mounts it
7
+ # and records its decision in prerun.env)
8
+ # * the small tooling every later step needs (git/curl/jq/rsync/psmisc)
9
+ # * the directory layout, and /root/deploy/host.env with the facts the
10
+ # orchestrator and the GUI display
11
+ #
12
+ # Usage: sudo bash 10-prepare-host.sh
13
+ # Env: WIPE_VOLUME=1 reformat the data volume even if it holds a data set
14
+ # Idempotent.
15
+ # ===================================================================
16
+
17
+ set -euo pipefail
18
+ source "$(dirname "$0")/lib.sh"
19
+
20
+ require_root
21
+ set_status RUNNING "prepare-host"
22
+
23
+ log "=== preparing $(hostname) ==="
24
+
25
+ # --- 1. swap + data store -------------------------------------------
26
+ if [[ -f /root/pre-run.sh ]]; then
27
+ log "running /root/pre-run.sh (swap + data store: auto detect/format/mount)"
28
+ # keep this program's state separate: the decision file goes to /root/deploy
29
+ # Project settings decide the swap size and which block device is the data store
30
+ PRERUN_STATE="${DEPLOY_DIR}/prerun.env" \
31
+ WIPE_VOLUME="${WIPE_VOLUME:-0}" \
32
+ SWAP_GB="${SWAP_GB:-8}" \
33
+ BLOCK_DEVICE="${BLOCK_DEVICE:-/dev/sdb}" \
34
+ DATA_MIN_GB="${DATA_MIN_GB:-1000}" \
35
+ bash /root/pre-run.sh
36
+ else
37
+ warn "/root/pre-run.sh is missing - run the deploy step again after the script push"
38
+ fi
39
+
40
+ # --- 2. tooling: check -> install -> continue ------------------------
41
+ # A re-installed VPS is a bare image, so nothing here is assumed: every binary
42
+ # this and the later steps call is checked first and only installed when it is
43
+ # really missing (apt is skipped completely when everything is present, which
44
+ # makes a retry after a dropped SSH cheap).
45
+ export DEBIAN_FRONTEND=noninteractive
46
+ export TERM="${TERM:-dumb}"
47
+ log "checking the base tooling (git, curl, wget, rsync, jq, psmisc, procps)"
48
+ ensure_package ca-certificates || die "cannot install ca-certificates"
49
+ ensure_tool git git || die "git is missing and could not be installed"
50
+ ensure_tool curl curl || die "curl is missing and could not be installed"
51
+ ensure_tool wget wget || die "wget is missing and could not be installed"
52
+ ensure_tool rsync rsync || die "rsync is missing and could not be installed"
53
+ ensure_tool jq jq || die "jq is missing and could not be installed"
54
+ ensure_tool pgrep psmisc || die "pgrep (psmisc) is missing and could not be installed"
55
+ ensure_tool pkill psmisc || die "pkill (psmisc) is missing and could not be installed"
56
+ ensure_package procps || die "cannot install procps"
57
+ ensure_package gnupg || die "cannot install gnupg"
58
+ ensure_package lsb-release || die "cannot install lsb-release"
59
+
60
+ # --- 3. layout + facts ----------------------------------------------
61
+ if ! id "${NODE_USER}" >/dev/null 2>&1; then
62
+ log "creating system user '${NODE_USER}'"
63
+ useradd -r -m -d "/home/${NODE_USER}" -s /bin/bash "${NODE_USER}"
64
+ else
65
+ log "user '${NODE_USER}' already exists"
66
+ fi
67
+ ensure_dirs
68
+ mkdir -p /root/deploy
69
+
70
+ PUBLIC_HOST=$(curl -s --max-time 5 https://api.ipify.org 2>/dev/null || true)
71
+ if [[ -z "${PUBLIC_HOST}" ]]; then
72
+ PUBLIC_HOST=$(hostname -I | awk '{print $1}')
73
+ fi
74
+
75
+ STORE_DEV=$(findmnt -n -o SOURCE "${NODE_DATA_ROOT}" 2>/dev/null || echo none)
76
+ STORE_FREE_GB=$(df -BG --output=avail "${NODE_DATA_ROOT}" 2>/dev/null | tail -1 | tr -dc '0-9')
77
+ STORE_TOTAL_GB=$(df -BG --output=size "${NODE_DATA_ROOT}" 2>/dev/null | tail -1 | tr -dc '0-9')
78
+ {
79
+ echo "PUBLIC_HOST=${PUBLIC_HOST}"
80
+ echo "HOSTNAME=$(hostname)"
81
+ echo "CORES=$(nproc)"
82
+ echo "MEM_MB=$(free -m | awk '/^Mem:/{print $2}')"
83
+ echo "SWAP=$(free -h | awk '/^Swap:/{print $2}')"
84
+ echo "STORE_DEV=${STORE_DEV}"
85
+ echo "STORE_TOTAL_GB=${STORE_TOTAL_GB:-0}"
86
+ echo "STORE_FREE_GB=${STORE_FREE_GB:-0}"
87
+ echo "PREPARED_AT=$(date -u +%Y-%m-%dT%H:%M:%SZ)"
88
+ } > "${DEPLOY_HOST_ENV}"
89
+ log "host facts:"
90
+ sed 's/^/ /' "${DEPLOY_HOST_ENV}"
91
+
92
+ if [[ -f "${DEPLOY_DIR}/prerun.env" ]]; then
93
+ log "data store decision:"
94
+ sed 's/^/ /' "${DEPLOY_DIR}/prerun.env"
95
+ fi
96
+
97
+ log "=== host ready: ${PUBLIC_HOST} ($(nproc) cores, ${STORE_FREE_GB:-?} GB free on ${STORE_DEV}) ==="
98
+ set_status DONE "prepare-host" "${PUBLIC_HOST}"
@@ -0,0 +1,93 @@
1
+ #!/usr/bin/env bash
2
+ # ===================================================================
3
+ # 20-install-toolchain.sh - build the Octra runtime (apt deps + OCaml + dune)
4
+ #
5
+ # Runs the project's own /root/install-deps.sh DETACHED as a systemd unit:
6
+ # it compiles for 15-30 minutes, and a dropped SSH must not kill it. The exit
7
+ # code lands in /root/deploy/octra-deploy-toolchain.rc, so re-running this step
8
+ # after a reconnect does not repeat a finished build.
9
+ #
10
+ # Usage: sudo bash 20-install-toolchain.sh
11
+ # Env: TOOLCHAIN_TIMEOUT (default 5400s)
12
+ # ===================================================================
13
+
14
+ set -euo pipefail
15
+ source "$(dirname "$0")/lib.sh"
16
+
17
+ require_root
18
+ set_status RUNNING "install-toolchain"
19
+
20
+ # tools this step calls itself (install-deps.sh installs its own compiler deps)
21
+ ensure_tool git git || die "git is missing and could not be installed"
22
+ ensure_tool curl curl || die "curl is missing and could not be installed"
23
+ ensure_tool tar tar || die "tar is missing and could not be installed"
24
+
25
+ UNIT="octra-deploy-toolchain"
26
+ RC_FILE="${DEPLOY_DIR}/${UNIT}.rc"
27
+
28
+ # A cached "exit 0" is NOT proof the toolchain is usable: a failed
29
+ # `opam switch link` empties the switch prefix (opam 2.3, see config.sh) and the
30
+ # unit still reports success - 2026-11-02 the deploy then reported "toolchain
31
+ # ready" and the next step found no switch at all.
32
+ if unit_done "${UNIT}" && [[ "$(unit_rc ${UNIT})" == "0" ]]; then
33
+ if ocaml_switch_built; then
34
+ log "toolchain already installed (unit exit 0, OCaml switch present) - nothing to do"
35
+ set_status DONE "install-toolchain" "cached"
36
+ exit 0
37
+ fi
38
+ warn "the toolchain unit exited 0 but the OCaml switch is missing/incomplete"
39
+ warn " (opam 2.3 empties the switch when 'opam switch link' is called on an existing _opam)"
40
+ drop_broken_ocaml_switch "${RC_FILE}"
41
+ log "the stale result was dropped - rebuilding the runtime"
42
+ fi
43
+
44
+ if ! [[ -f /root/install-deps.sh ]]; then
45
+ die "install-deps.sh is missing at /root/install-deps.sh - push the scripts first"
46
+ fi
47
+
48
+ if [[ "$(unit_active ${UNIT})" == "yes" ]]; then
49
+ log "toolchain build already running (unit ${UNIT}) - re-attaching"
50
+ else
51
+ log "=== building the runtime (install-deps.sh, 15-30 min on 4 cores) ==="
52
+ # install.sh itself calls `opam switch link`: opam 2.3 dies on an existing
53
+ # <repo>/_opam and empties the switch prefix, so clear both leftovers first
54
+ # (drop_broken_ocaml_switch keeps a real switch untouched).
55
+ drop_broken_ocaml_switch "${RC_FILE}"
56
+ unlink_ocaml_switch_link "install.sh runs opam switch link next"
57
+ unit_start "${UNIT}" "/root" \
58
+ "DEBIAN_FRONTEND=noninteractive" "TERM=dumb" \
59
+ -- "bash /root/install-deps.sh"
60
+ fi
61
+
62
+ unit_wait "${UNIT}" "${TOOLCHAIN_TIMEOUT:-5400}" "install-deps.sh (toolchain)" \
63
+ "Cloning|Checking out|Building|Compiling|success|error" \
64
+ || die "the toolchain build failed - see ${DEPLOY_DIR}/${UNIT}.log"
65
+
66
+ # --- proof the build is usable ---------------------------------------
67
+ if [[ ! -d "${NODE_SRC_DIR}/.git" ]]; then
68
+ die "the node repository is missing at ${NODE_SRC_DIR}"
69
+ fi
70
+ if [[ ! -f "${NODE_SRC_DIR}/controls/check.sh" ]]; then
71
+ die "controls/check.sh is missing - the repository looks incomplete"
72
+ fi
73
+ COMMIT=$(node_repo_commit)
74
+ BRANCH=$(node_repo_branch)
75
+ REPO_URL=$(node_repo_url)
76
+ if [[ -n "${COMMIT}" ]]; then
77
+ log "repository: ${COMMIT}${BRANCH:+ on ${BRANCH}} -> ${NODE_SRC_DIR}${REPO_URL:+ (${REPO_URL})}"
78
+ else
79
+ # never print a bare "unknown": name what is wrong and what git said instead
80
+ REPO_ERR=$(node_repo_error)
81
+ warn "repository: no readable commit at ${NODE_SRC_DIR}${REPO_ERR:+ - git said: ${REPO_ERR}}"
82
+ fi
83
+ # install-deps.sh can exit 0 while leaving no usable OCaml switch behind (the
84
+ # opam 2.3 `switch link` wipe). Drop the empty prefix and the cached result, so
85
+ # the NEXT run really rebuilds instead of reporting "already installed".
86
+ if ! assert_opam_switch; then
87
+ warn "the build did not leave a usable OCaml switch - dropping the leftovers so the next run rebuilds"
88
+ drop_broken_ocaml_switch "${RC_FILE}"
89
+ set_status WAITING-BUILD "no usable OCaml switch - Start deploy again rebuilds the runtime"
90
+ exit 2
91
+ fi
92
+ log "toolchain ready"
93
+ set_status DONE "install-toolchain"
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env bash
2
+ # ===================================================================
3
+ # 30-setup-node.sh - configure the node: identity, binary, chain state
4
+ #
5
+ # Runs the project's own /root/node-setup.sh DETACHED (config_val.sh --build
6
+ # compiles the node, --sync downloads ~38 GB). Both take a long time, so the
7
+ # unit + rc-file pattern is used here too.
8
+ #
9
+ # Result: /opt/octra/libv_litecore/.keys/validator/{wallet.json,node.env}
10
+ #
11
+ # Usage: sudo bash 30-setup-node.sh
12
+ # Env: NODE_NAME (default octra-node-<hostname>)
13
+ # PUBLIC_HOST (default: from host.env, else auto-detected)
14
+ # SETUP_TIMEOUT (default 5400s)
15
+ # ===================================================================
16
+
17
+ set -euo pipefail
18
+ source "$(dirname "$0")/lib.sh"
19
+
20
+ require_root
21
+ set_status RUNNING "setup-node"
22
+
23
+ # tools used below and by node-setup.sh: check first, install when missing
24
+ ensure_tool curl curl || die "curl is missing and could not be installed"
25
+ ensure_tool jq jq || die "jq is missing and could not be installed"
26
+ ensure_tool git git || die "git is missing and could not be installed"
27
+ ensure_tool rsync rsync || die "rsync is missing and could not be installed"
28
+
29
+ # host.env from the prep step carries the public address the node advertises
30
+ if [[ -f "${DEPLOY_HOST_ENV}" ]]; then
31
+ # shellcheck disable=SC1090
32
+ source "${DEPLOY_HOST_ENV}"
33
+ fi
34
+ PUBLIC_HOST="${PUBLIC_HOST:-$(curl -s --max-time 5 https://api.ipify.org 2>/dev/null || hostname -I | awk '{print $1}')}"
35
+ NODE_NAME="${NODE_NAME:-octra-node-$(hostname)}"
36
+ UNIT="octra-deploy-setup"
37
+
38
+ # --- already configured? ---------------------------------------------
39
+ if [[ -f "${NODE_ENV}" && -f "${NODE_WALLET}" ]]; then
40
+ log "node is already configured (node.env + wallet.json present)"
41
+ log " wallet: $(wallet_address || echo unknown)"
42
+ log " role: $(grep -m1 '^OCTRA_OPERATOR_ROLE=' "${NODE_ENV}" | cut -d= -f2)"
43
+ set_status DONE "setup-node" "cached"
44
+ exit 0
45
+ fi
46
+
47
+ if [[ ! -f /root/node-setup.sh ]]; then
48
+ die "node-setup.sh is missing at /root/node-setup.sh - push the scripts first"
49
+ fi
50
+ if [[ ! -d "${NODE_SRC_DIR}/_build" && ! -d "${NODE_SRC_DIR}/runtime_data" ]]; then
51
+ warn "the toolchain does not look installed yet - run the toolchain step first"
52
+ fi
53
+
54
+ if [[ "$(unit_active ${UNIT})" == "yes" ]]; then
55
+ log "node setup already running (unit ${UNIT}) - re-attaching"
56
+ else
57
+ log "=== configuring ${NODE_NAME} @ ${PUBLIC_HOST} (build + 38 GB sync, 10-30 min) ==="
58
+ # a missing/broken OCaml switch would fail deep inside config_val.sh: say so
59
+ # here instead (soft stop - the operator can re-run the toolchain step)
60
+ if ! assert_opam_switch; then
61
+ set_status WAITING-BUILD "OCaml switch missing - re-run the toolchain step (see the log)"
62
+ exit 2
63
+ fi
64
+ unit_start "${UNIT}" "/root" \
65
+ "PUBLIC_HOST=${PUBLIC_HOST}" "NODE_NAME=${NODE_NAME}" "ROLE=observer" \
66
+ "DEBIAN_FRONTEND=noninteractive" "TERM=dumb" \
67
+ -- "bash /root/node-setup.sh"
68
+ fi
69
+
70
+ # the sync prints progress percentages; show the last one on the heartbeat
71
+ unit_wait "${UNIT}" "${SETUP_TIMEOUT:-5400}" "node-setup.sh (build + sync)" \
72
+ "[0-9]+%|Downloaded|sync|Building|Compiling" \
73
+ || die "node setup failed - see ${DEPLOY_DIR}/${UNIT}.log"
74
+
75
+ # --- proof the identity exists ---------------------------------------
76
+ [[ -f "${NODE_WALLET}" ]] || die "wallet.json was not created (${NODE_WALLET})"
77
+ [[ -f "${NODE_ENV}" ]] || die "node.env was not created (${NODE_ENV})"
78
+ log "wallet address : $(wallet_address || echo unknown)"
79
+ log "role : $(grep -m1 '^OCTRA_OPERATOR_ROLE=' "${NODE_ENV}" | cut -d= -f2)"
80
+ log "data dir : $(du -sh "${NODE_DATA_DIR}" 2>/dev/null | awk '{print $1}') in ${NODE_DATA_DIR}"
81
+ log "advertise : $(grep -m1 '^OCTRA_ADVERTISE_ENDPOINT=' "${NODE_ENV}" | cut -d= -f2)"
82
+ set_status DONE "setup-node"
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env bash
2
+ # ===================================================================
3
+ # 40-start-node.sh - start the node and wait until its RPC answers
4
+ #
5
+ # The first start has to open the whole chain store and replay the epoch log, so
6
+ # "started" is not enough: this step waits for `rpc = ready` (bounded) and prints
7
+ # one status line a minute while it waits.
8
+ #
9
+ # Usage: sudo bash 40-start-node.sh
10
+ # Env: WAIT_SEC (default 1800)
11
+ # ===================================================================
12
+
13
+ set -euo pipefail
14
+ source "$(dirname "$0")/lib.sh"
15
+
16
+ require_root
17
+ set_status RUNNING "start-node"
18
+
19
+ # tools this step calls (node_stat -> controls/stat.sh, pm2, curl, jq)
20
+ ensure_tool curl curl || die "curl is missing and could not be installed"
21
+ ensure_tool jq jq || die "jq is missing and could not be installed"
22
+ ensure_tool pgrep psmisc || die "pgrep (psmisc) is missing and could not be installed"
23
+
24
+ [[ -f "${NODE_ENV}" ]] || die "node.env is missing - run the setup step first"
25
+ [[ -f /root/node-start.sh ]] || die "node-start.sh is missing at /root - push the scripts first"
26
+
27
+ if [[ -n "$(node_processes)" ]]; then
28
+ log "node already running - not restarting"
29
+ else
30
+ log "=== starting the node ($(wallet_address || echo 'no wallet yet')) ==="
31
+ # node-start.sh verifies pm2 itself and fails fast with the node's own log when
32
+ # the process does not come up (waiting 30 min for rpc would tell nobody why)
33
+ bash /root/node-start.sh ||
34
+ die "the node did not start - see the node log above (pm2 list, ${NODE_SRC_DIR}/data/node.log)"
35
+ fi
36
+
37
+ WAIT_SEC="${WAIT_SEC:-3600}" # 2-core hosts need 30-40 min (4-core ~7-9 min)
38
+ DEADLINE=$(( $(date +%s) + WAIT_SEC ))
39
+ START=$(date +%s)
40
+ LAST=$START
41
+ RPC=""
42
+ # read the real pm2 process name the node registered itself under (OCTRA_OPERATOR_PM2_NAME
43
+ # in node.env); the lib.sh default (octra-node-1) is a fallback and often differs
44
+ REAL_PM2=$(grep -m1 '^OCTRA_OPERATOR_PM2_NAME=' "${NODE_ENV:-${NODE_SRC_DIR}/.keys/validator/node.env}" 2>/dev/null | cut -d= -f2- || true)
45
+ REAL_PM2="${REAL_PM2:-${NODE_PM2_NAME}}"
46
+ log "waiting for rpc = ready (up to $(elapsed_minutes "${WAIT_SEC}")) - the first start replays the epoch log..."
47
+ while (( $(date +%s) < DEADLINE )); do
48
+ SNAP=$(node_stat)
49
+ RPC=$(printf '%s\n' "${SNAP}" | stat_field rpc)
50
+ PROC=$(printf '%s\n' "${SNAP}" | stat_field process)
51
+ HEAD=$(printf '%s\n' "${SNAP}" | stat_field head_epoch)
52
+ if [[ "${RPC}" == "ready" ]]; then
53
+ log "rpc is ready after $(( $(date +%s) - START ))s (process=${PROC} head_epoch=${HEAD})"
54
+ break
55
+ fi
56
+ if (( $(date +%s) - LAST >= 60 )); then
57
+ log " waiting for rpc: $(elapsed_minutes $(( $(date +%s) - START ))) of $(elapsed_minutes "${WAIT_SEC}") (rpc=${RPC:-?} process=${PROC:-?} head=${HEAD:-?})"
58
+ LAST=$(date +%s)
59
+ fi
60
+ sleep 5
61
+ done
62
+
63
+ if [[ "${RPC}" != "ready" ]]; then
64
+ warn "rpc did not become ready in $(elapsed_minutes "${WAIT_SEC}") - pm2 log (process: ${REAL_PM2}):"
65
+ as_octra "pm2 logs ${REAL_PM2} --lines 15 --nostream" 2>/dev/null | sed 's/^/ /' || true
66
+ die "node rpc is not ready after ${WAIT_SEC}s (set WAIT_SEC=7200 in the env to double the limit)"
67
+ fi
68
+
69
+ log "--- pm2 ---"
70
+ as_octra "pm2 list" 2>/dev/null | sed 's/^/ /' || warn "pm2 list failed"
71
+ log "--- status ---"
72
+ node_stat | tee "${DEPLOY_DIR}/start-stat.txt" | \
73
+ grep -E '^(node|role|address|process|pid|rpc|epoch|head_epoch|voting|p2p_connected|validator_active|validator_enrollment) =' || true
74
+
75
+ set_status DONE "start-node"
@@ -0,0 +1,259 @@
1
+ #!/usr/bin/env bash
2
+ # ===================================================================
3
+ # 50-verify.sh - prove the freshly deployed node works
4
+ #
5
+ # For ROLE=observer (a fresh deploy): process online, rpc ready, role correct,
6
+ # p2p peers > 0, and the epoch advancing between samples. A brand-new node needs
7
+ # a moment to find peers, so it WAITS (PEER_WAIT) instead of failing on the first
8
+ # sample - that wait is what lets the enrollment popup appear on a healthy node.
9
+ # For ROLE=validator (already enrolled): also voting enabled, validator_active
10
+ # true and validator_enrollment ready - the same bar the migration uses. The
11
+ # validator only starts voting after the set fold, so this waits (VOTE_WAIT)
12
+ # instead of failing: the step ends as PASS exactly when voting = enabled.
13
+ #
14
+ # Usage: sudo SAMPLES=3 INTERVAL=20 bash 50-verify.sh
15
+ # Env: SAMPLES (default 3), INTERVAL (default 20), ROLE (default observer),
16
+ # PEER_WAIT (default 300 s), VOTE_WAIT (default 900 s),
17
+ # VERIFY_RETRY (default 2), VERIFY_RETRY_WAIT (default 8 s)
18
+ #
19
+ # A sample that fails the bar is re-read (VERIFY_RETRY times, VERIFY_RETRY_WAIT
20
+ # apart) before it counts: the node's own state flaps for one window right after
21
+ # a set fold (`voting=disabled`) or while it replays an epoch (`rpc=unavailable`),
22
+ # and one flapping sample must not turn a healthy validator into a FAILED deploy
23
+ # (2026-11-04). A transient blip is counted, logged and named in the result; more
24
+ # than half of the samples blipping is still a failure.
25
+ #
26
+ # Running out of the wait budget is NOT a broken deploy either: a node that is
27
+ # still replaying (`rpc != ready`) or a validator the network has not admitted /
28
+ # activated yet is a matter of minutes, not of a wrong command. Those two end as
29
+ # soft stops (`WAITING-RPC` / `WAITING-FOLD`, exit 2) so the console asks for
30
+ # another run instead of showing a red failure. A dead process, a wrong role or
31
+ # an observer that never finds a peer is still a real failure.
32
+ # ===================================================================
33
+
34
+ set -euo pipefail
35
+ source "$(dirname "$0")/lib.sh"
36
+
37
+ require_root
38
+ # the verify bar reads the node through curl/jq and watches the process with pgrep
39
+ ensure_tool curl curl || die "curl is missing and could not be installed"
40
+ ensure_tool jq jq || die "jq is missing and could not be installed"
41
+ ensure_tool pgrep psmisc || die "pgrep (psmisc) is missing and could not be installed"
42
+ SAMPLES="${SAMPLES:-3}"
43
+ INTERVAL="${INTERVAL:-20}"
44
+ ROLE="${ROLE:-observer}"
45
+ # How long to keep waiting for the state a healthy node reaches on its own:
46
+ # peers for an observer, the set fold + voting for a validator.
47
+ PEER_WAIT="${PEER_WAIT:-300}"
48
+ VOTE_WAIT="${VOTE_WAIT:-900}"
49
+ EXPECTED_ADDRESS="${EXPECTED_ADDRESS:-$(wallet_address || true)}"
50
+
51
+ set_status RUNNING "verify"
52
+ # per-sample dumps of earlier attempts (a test host carried eleven
53
+ # verify-sample-*.txt from older runs): this run writes its own, so drop the old
54
+ rm -f "${DEPLOY_DIR}"/verify-sample-*.txt 2>/dev/null || true
55
+ log "=== verifying the node on $(hostname) - ${SAMPLES} samples every ${INTERVAL}s (role=${ROLE}) ==="
56
+ [[ "${ROLE}" == "validator" ]] && log "=== the step only passes once voting = enabled (waiting up to ${VOTE_WAIT}s for the fold) ==="
57
+
58
+ FAIL=0
59
+ PREV_EPOCH=""
60
+ ADDR=""
61
+ P2P=""
62
+ VOTING=""
63
+ ACTIVE=""
64
+ ENROLL=""
65
+
66
+ epoch_num() { # epoch_num <value> -> a number, 0 when empty/not numeric
67
+ case "${1:-}" in
68
+ ''|*[!0-9]*) printf '0' ;;
69
+ *) printf '%s' "$1" ;;
70
+ esac
71
+ }
72
+
73
+ # --- a flapping sample is not a broken node ---------------------------
74
+ # The node's own state flaps by design: `voting` can read `disabled` for one
75
+ # window right after a set fold, and the RPC answers `unavailable` while it
76
+ # replays an epoch log (observed on both). 2026-11-04: two deploys of a perfectly
77
+ # healthy ACTIVE validator were reported FAILED because exactly ONE of five
78
+ # samples flapped (sample 3 `voting=disabled`, resp. sample 2 `rpc=unavailable`),
79
+ # so the console showed a red verify although every other sample proved voting,
80
+ # active=true, enrollment=ready and an advancing epoch. A sample that fails the
81
+ # bar is therefore RE-READ up to VERIFY_RETRY times (VERIFY_RETRY_WAIT apart):
82
+ # any healthy re-read makes it a TRANSIENT BLIP (counted, logged, named in the
83
+ # result). Only a sample that stays bad - or a run where more than half of the
84
+ # samples blipped - fails.
85
+ BLIPS=0
86
+ VERIFY_RETRY="${VERIFY_RETRY:-2}"
87
+ VERIFY_RETRY_WAIT="${VERIFY_RETRY_WAIT:-8}"
88
+
89
+ sample_reason() { # why the current field values fail the bar ("" = healthy)
90
+ if [[ "${PROC}" != "online" ]]; then printf 'process is %s (not online)' "${PROC:-?}"; return 0; fi
91
+ if [[ "${RPC}" != "ready" ]]; then printf 'rpc is %s' "${RPC:-?}"; return 0; fi
92
+ if [[ "${DEV_ROLE}" != "${ROLE}" ]]; then
93
+ printf 'role is %s, expected %s' "${DEV_ROLE:-?}" "${ROLE}"; return 0
94
+ fi
95
+ if [[ "${P2P:-0}" -le 0 ]]; then printf 'no p2p peers connected'; return 0; fi
96
+ if [[ -n "${EXPECTED_ADDRESS}" && "${ADDR}" != "${EXPECTED_ADDRESS}" ]]; then
97
+ printf 'address is %s, expected %s' "${ADDR:-?}" "${EXPECTED_ADDRESS}"; return 0
98
+ fi
99
+ if [[ "${ROLE}" == "validator" ]]; then
100
+ if [[ "${VOTING}" != "enabled" ]]; then printf "voting is %s (must be 'enabled')" "${VOTING:-?}"; return 0; fi
101
+ if [[ "${ACTIVE}" != "true" ]]; then printf 'validator_active is %s' "${ACTIVE:-?}"; return 0; fi
102
+ if [[ "${ACTIVE}" != "true" && "${ENROLL}" != "ready" ]]; then printf 'validator_enrollment is %s' "${ENROLL:-?}"; return 0; fi
103
+ fi
104
+ printf ''
105
+ return 0
106
+ }
107
+
108
+ sample_state() { # read the node once into the field variables
109
+ STAT=$(node_stat)
110
+ printf '%s\n' "${STAT}" > "${DEPLOY_DIR}/verify-sample-${SAMPLE_N}.txt"
111
+ FIELD() { printf '%s\n' "${STAT}" | stat_field "$1"; }
112
+ DEV_ROLE=$(FIELD role); ADDR=$(FIELD address); PROC=$(FIELD process)
113
+ RPC=$(FIELD rpc); EPOCH=$(FIELD epoch); HEAD=$(FIELD head_epoch)
114
+ VOTING=$(FIELD voting); ACTIVE=$(FIELD validator_active)
115
+ ENROLL=$(FIELD validator_enrollment); P2P=$(FIELD p2p_connected)
116
+ NEXT_SET=$(FIELD validator_next_set_epoch)
117
+ UPTIME=$(FIELD uptime); RESTARTS=$(FIELD restarts)
118
+ }
119
+
120
+ # A validator only votes after the set fold; a fresh observer needs a moment for
121
+ # its first peers. Waiting here (instead of failing on the first sample) is what
122
+ # makes the run end exactly when the node is healthy - and therefore what lets the
123
+ # enrollment popup follow a *verified* node.
124
+ WAIT_BUDGET="${PEER_WAIT}"
125
+ [[ "${ROLE}" == "validator" ]] && WAIT_BUDGET="${VOTE_WAIT}"
126
+ WAITED=0
127
+ SAMPLE_N=0
128
+ READY=0
129
+ while :; do
130
+ SAMPLE_N=$(( SAMPLE_N + 1 ))
131
+ sample_state
132
+ log "waiting ${SAMPLE_N}: process=${PROC} rpc=${RPC} role=${DEV_ROLE} p2p=${P2P} voting=${VOTING} active=${ACTIVE} enrollment=${ENROLL}${NEXT_SET:+ - set fold at epoch ${NEXT_SET}}"
133
+ if [[ "${PROC}" == "online" && "${RPC}" == "ready" && "${P2P:-0}" -gt 0 ]]; then
134
+ if [[ "${ROLE}" != "validator" ]]; then READY=1; fi
135
+ if [[ "${ROLE}" == "validator" \
136
+ && "${VOTING}" == "enabled" && "${ACTIVE}" == "true" ]]; then
137
+ # active + voting is authoritative; the enrollment projection can still say
138
+ # 'unavailable' while the RPC warms up (observed on live migrations)
139
+ if [[ "${ENROLL}" != "ready" ]]; then
140
+ warn "enrollment projection reads '${ENROLL:-unavailable}' while the validator is active and voting - accepting active membership"
141
+ fi
142
+ READY=1
143
+ fi
144
+ fi
145
+ (( READY == 1 )) && break
146
+ if (( WAITED >= WAIT_BUDGET )); then
147
+ # The budget is out. Only a node that is really broken fails here: the two
148
+ # states a healthy node has not reached yet are timing (see the header).
149
+ if [[ "${PROC}" != "online" ]]; then
150
+ err "the node process is ${PROC:-?} after ${WAIT_BUDGET}s - it is not running"
151
+ err " check on the host: pm2 status ; tail -n 50 ${NODE_SRC_DIR}/data/operator_logs/node.log"
152
+ set_status FAILED "verify-not-ready" "process=${PROC:-?} after ${WAIT_BUDGET}s"
153
+ exit 1
154
+ fi
155
+ if [[ "${RPC}" != "ready" ]]; then
156
+ warn "the node is still starting or catching up after ${WAIT_BUDGET}s (rpc=${RPC:-?})"
157
+ warn " nothing is broken: the rpc answers 'ready' once the node replayed its state"
158
+ set_status WAITING-RPC "rpc=${RPC:-?} after ${WAIT_BUDGET}s - the node is still catching up, re-run verify"
159
+ exit 2
160
+ fi
161
+ if [[ "${ROLE}" == "validator" && "${P2P:-0}" -gt 0 && "${VOTING}" != "enabled" ]]; then
162
+ warn "the validator is not voting after ${WAIT_BUDGET}s (voting=${VOTING:-?} active=${ACTIVE:-?} enrollment=${ENROLL:-?})"
163
+ warn " the network admits a new validator at the set fold and starts its duty epoch after that;"
164
+ warn " the node keeps running and votes on its own - re-run verify in a few minutes"
165
+ set_status WAITING-FOLD "voting=${VOTING:-?} active=${ACTIVE:-?} enrollment=${ENROLL:-?} after ${WAIT_BUDGET}s${NEXT_SET:+ - set fold at epoch ${NEXT_SET}} - re-run verify later"
166
+ exit 2
167
+ fi
168
+ err "the node did not reach the ${ROLE} state within ${WAIT_BUDGET}s"
169
+ err "process=${PROC} rpc=${RPC} p2p=${P2P:-0} voting=${VOTING:-?} active=${ACTIVE:-?} enrollment=${ENROLL:-?}"
170
+ [[ "${ROLE}" == "validator" ]] && err "voting must be 'enabled' - check: sh controls/stat.sh on the host"
171
+ set_status FAILED "verify-not-ready"
172
+ exit 1
173
+ fi
174
+ log " not there yet - waiting ${INTERVAL}s (${WAITED}s of ${WAIT_BUDGET}s)"
175
+ WAITED=$(( WAITED + INTERVAL ))
176
+ sleep "${INTERVAL}"
177
+ done
178
+ if [[ "${ROLE}" == "validator" ]]; then
179
+ log "=== the node is an ACTIVE validator and voting (voting=${VOTING} active=${ACTIVE} enrollment=${ENROLL}) ==="
180
+ else
181
+ log "=== the node is online with ${P2P} peers ==="
182
+ fi
183
+
184
+ for ((i = 1; i <= SAMPLES; i++)); do
185
+ SAMPLE_N="${i}"
186
+ sample_state
187
+
188
+ log "sample ${i}/${SAMPLES}: epoch=${EPOCH} head=${HEAD} role=${DEV_ROLE} process=${PROC} rpc=${RPC} p2p=${P2P} voting=${VOTING} active=${ACTIVE} enrollment=${ENROLL} uptime=${UPTIME} restarts=${RESTARTS}"
189
+
190
+ WHY=$(sample_reason)
191
+ if [[ -n "${WHY}" ]]; then
192
+ RECOVERED=""
193
+ for ((r = 1; r <= VERIFY_RETRY; r++)); do
194
+ log " sample ${i}/${SAMPLES} read '${WHY}' - re-reading in ${VERIFY_RETRY_WAIT}s (${r}/${VERIFY_RETRY})"
195
+ sleep "${VERIFY_RETRY_WAIT}"
196
+ sample_state
197
+ WHY=$(sample_reason)
198
+ if [[ -z "${WHY}" ]]; then RECOVERED="yes"; break; fi
199
+ done
200
+ if [[ -n "${RECOVERED}" ]]; then
201
+ BLIPS=$(( BLIPS + 1 ))
202
+ log " transient blip ${BLIPS}: recovered - process=${PROC} rpc=${RPC} role=${DEV_ROLE} p2p=${P2P} voting=${VOTING} active=${ACTIVE} enrollment=${ENROLL}"
203
+ else
204
+ err "sample ${i}/${SAMPLES}: ${WHY}"
205
+ FAIL=1
206
+ fi
207
+ fi
208
+
209
+ if [[ -n "${PREV_EPOCH}" ]]; then
210
+ PREV_NUM=$(epoch_num "${PREV_EPOCH}")
211
+ NOW_NUM=$(epoch_num "${EPOCH}")
212
+ if (( NOW_NUM > PREV_NUM )); then
213
+ log " epoch advanced ${PREV_EPOCH} -> ${EPOCH} (+$((NOW_NUM - PREV_NUM)))"
214
+ elif (( NOW_NUM == 0 )); then
215
+ warn " the node does not report an epoch yet - it is probably still catching up"
216
+ else
217
+ warn " epoch did not advance (${PREV_EPOCH} -> ${EPOCH}) - the node may still be catching up"
218
+ fi
219
+ fi
220
+ PREV_EPOCH="${EPOCH}"
221
+
222
+ if (( i < SAMPLES )); then
223
+ log " sleeping ${INTERVAL}s before the next sample"
224
+ sleep "${INTERVAL}"
225
+ fi
226
+ done
227
+
228
+ # A node that is mostly unhealthy must not pass: one or two blips are noise, a
229
+ # majority of bad samples is not (a node that flaps most of the time is broken).
230
+ if (( BLIPS * 2 > SAMPLES )); then
231
+ err "most samples (${BLIPS}/${SAMPLES}) had to be re-read before they looked healthy - that is not a stable node"
232
+ FAIL=1
233
+ fi
234
+
235
+ echo
236
+ if (( FAIL == 0 )); then
237
+ if (( BLIPS > 0 )); then
238
+ log "=== RESULT: PASS - the node is online and following the chain (${BLIPS}/${SAMPLES} transient blip(s) recovered) ==="
239
+ else
240
+ log "=== RESULT: PASS - the node is online and following the chain ==="
241
+ fi
242
+ log "wallet address: ${ADDR}"
243
+ if [[ "${ROLE}" == "validator" ]]; then
244
+ log "voting = enabled - this node is an ACTIVE validator"
245
+ else
246
+ log "this node is an observer. Next: fund ${ADDR} and run the enroll step,"
247
+ log "then the node can be promoted to a validator (ROLE=validator)."
248
+ fi
249
+ if (( BLIPS > 0 )); then
250
+ set_status DONE "verify-pass" "address=${ADDR}" "transient blips=${BLIPS}/${SAMPLES} (recovered)"
251
+ else
252
+ set_status DONE "verify-pass" "address=${ADDR}"
253
+ fi
254
+ else
255
+ err "=== RESULT: FAIL - see the errors above ==="
256
+ set_status FAILED "verify-failed"
257
+ exit 1
258
+ fi
259
+