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.
- package/LICENSE +21 -0
- package/README.md +121 -2
- package/assets/common/bond-inspect.sh +368 -0
- package/assets/common/config.sh +263 -0
- package/assets/common/extract-data.sh +197 -0
- package/assets/common/harden-host.sh +37 -0
- package/assets/common/install-deps.sh +93 -0
- package/assets/common/node-enroll.sh +65 -0
- package/assets/common/node-setup.sh +150 -0
- package/assets/common/node-start.sh +90 -0
- package/assets/common/node-stop.sh +18 -0
- package/assets/common/node-upgrade.sh +45 -0
- package/assets/common/pre-run.sh +497 -0
- package/assets/common/promote-validator.sh +104 -0
- package/assets/deploy/10-prepare-host.sh +98 -0
- package/assets/deploy/20-install-toolchain.sh +93 -0
- package/assets/deploy/30-setup-node.sh +82 -0
- package/assets/deploy/40-start-node.sh +75 -0
- package/assets/deploy/50-verify.sh +259 -0
- package/assets/deploy/60-enroll.sh +304 -0
- package/assets/deploy/70-promote.sh +152 -0
- package/assets/deploy/lib.sh +398 -0
- package/assets/manage/10-bond-increase.sh +149 -0
- package/assets/manage/10-diagnose-node.sh +169 -0
- package/assets/manage/20-exit-validator.sh +259 -0
- package/assets/manage/25-recover-node.sh +291 -0
- package/assets/manage/30-status.sh +64 -0
- package/assets/manage/40-exit-withdraw.sh +133 -0
- package/assets/manage/bond_inc.sh +318 -0
- package/assets/manage/dashboard_monitor/app.js +1544 -0
- package/assets/manage/dashboard_monitor/assets/v2.2/css/base.css +113 -0
- package/assets/manage/dashboard_monitor/assets/v2.2/css/components/content.css +133 -0
- package/assets/manage/dashboard_monitor/assets/v2.2/css/components/controls.css +229 -0
- package/assets/manage/dashboard_monitor/assets/v2.2/css/components/data.css +374 -0
- package/assets/manage/dashboard_monitor/assets/v2.2/css/components/icons.css +35 -0
- package/assets/manage/dashboard_monitor/assets/v2.2/css/components/motion.css +57 -0
- package/assets/manage/dashboard_monitor/assets/v2.2/css/components/shell.css +51 -0
- package/assets/manage/dashboard_monitor/assets/v2.2/css/components/sidebar.css +242 -0
- package/assets/manage/dashboard_monitor/assets/v2.2/css/components/topbar.css +138 -0
- package/assets/manage/dashboard_monitor/assets/v2.2/css/fonts.css +30 -0
- package/assets/manage/dashboard_monitor/assets/v2.2/css/synteq-ui-v2.css +25 -0
- package/assets/manage/dashboard_monitor/assets/v2.2/css/theme-dark.css +38 -0
- package/assets/manage/dashboard_monitor/assets/v2.2/css/theme-light.css +36 -0
- package/assets/manage/dashboard_monitor/assets/v2.2/css/tokens.css +61 -0
- package/assets/manage/dashboard_monitor/assets/v2.2/fonts/GoogleSansCode-Medium.ttf +0 -0
- package/assets/manage/dashboard_monitor/assets/v2.2/fonts/GoogleSansCode-Regular.ttf +0 -0
- package/assets/manage/dashboard_monitor/assets/v2.2/fonts/GoogleSansCode-SemiBold.ttf +0 -0
- package/assets/manage/dashboard_monitor/assets/v2.2/js/synteq-ui-v2.js +869 -0
- package/assets/manage/dashboard_monitor/deploy-dashboard.sh +230 -0
- package/assets/manage/dashboard_monitor/favicon.svg +13 -0
- package/assets/manage/dashboard_monitor/index.html +1197 -0
- package/assets/manage/dashboard_monitor/nginx-octra.conf +76 -0
- package/assets/manage/dashboard_monitor/octra-dashboard.service +29 -0
- package/assets/manage/dashboard_monitor/octra-node-prune.cron +12 -0
- package/assets/manage/dashboard_monitor/octra-node-prune.service +16 -0
- package/assets/manage/dashboard_monitor/octra-node-prune.timer +12 -0
- package/assets/manage/dashboard_monitor/prune-node-junk.sh +329 -0
- package/assets/manage/dashboard_monitor/server.py +1887 -0
- package/assets/manage/dashboard_monitor/ssl-certbot.sh +90 -0
- package/assets/manage/dashboard_monitor/styles.css +327 -0
- package/assets/manage/lib.sh +347 -0
- package/assets/manifest.json +143 -0
- package/assets/migrate/00-backup-identity.sh +67 -0
- package/assets/migrate/01-prepare-target.sh +145 -0
- package/assets/migrate/02-authorize-pull.sh +43 -0
- package/assets/migrate/03-pull-state.sh +102 -0
- package/assets/migrate/04-stop-source.sh +85 -0
- package/assets/migrate/05-finalize-target.sh +140 -0
- package/assets/migrate/06-start-target.sh +57 -0
- package/assets/migrate/07-verify.sh +237 -0
- package/assets/migrate/08-rollback.sh +54 -0
- package/assets/migrate/lib.sh +149 -0
- package/assets/upgrade/10-preflight.sh +120 -0
- package/assets/upgrade/20-upgrade.sh +142 -0
- package/assets/upgrade/30-manual-upgrade.sh +129 -0
- package/assets/upgrade/40-storage-prune.sh +65 -0
- package/assets/upgrade/50-status.sh +58 -0
- package/assets/upgrade/lib.sh +232 -0
- package/bin/ovtools.js +32 -0
- package/dist/api/checks.d.ts +6 -0
- package/dist/api/checks.d.ts.map +1 -0
- package/dist/api/checks.js +173 -0
- package/dist/api/checks.js.map +1 -0
- package/dist/api/hostkeys.d.ts +10 -0
- package/dist/api/hostkeys.d.ts.map +1 -0
- package/dist/api/hostkeys.js +58 -0
- package/dist/api/hostkeys.js.map +1 -0
- package/dist/api/jobs.d.ts +33 -0
- package/dist/api/jobs.d.ts.map +1 -0
- package/dist/api/jobs.js +486 -0
- package/dist/api/jobs.js.map +1 -0
- package/dist/api/nodes.d.ts +16 -0
- package/dist/api/nodes.d.ts.map +1 -0
- package/dist/api/nodes.js +126 -0
- package/dist/api/nodes.js.map +1 -0
- package/dist/api/server.d.ts +3 -0
- package/dist/api/server.d.ts.map +1 -0
- package/dist/api/server.js +443 -0
- package/dist/api/server.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +301 -0
- package/dist/cli.js.map +1 -0
- package/dist/constants.d.ts +76 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +150 -0
- package/dist/constants.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +60 -0
- package/dist/index.js.map +1 -0
- package/dist/orchestrator/base.d.ts +52 -0
- package/dist/orchestrator/base.d.ts.map +1 -0
- package/dist/orchestrator/base.js +219 -0
- package/dist/orchestrator/base.js.map +1 -0
- package/dist/orchestrator/deploy.d.ts +33 -0
- package/dist/orchestrator/deploy.d.ts.map +1 -0
- package/dist/orchestrator/deploy.js +313 -0
- package/dist/orchestrator/deploy.js.map +1 -0
- package/dist/orchestrator/manage.d.ts +28 -0
- package/dist/orchestrator/manage.d.ts.map +1 -0
- package/dist/orchestrator/manage.js +267 -0
- package/dist/orchestrator/manage.js.map +1 -0
- package/dist/orchestrator/migrate.d.ts +31 -0
- package/dist/orchestrator/migrate.d.ts.map +1 -0
- package/dist/orchestrator/migrate.js +442 -0
- package/dist/orchestrator/migrate.js.map +1 -0
- package/dist/ssh/client.d.ts +39 -0
- package/dist/ssh/client.d.ts.map +1 -0
- package/dist/ssh/client.js +418 -0
- package/dist/ssh/client.js.map +1 -0
- package/dist/test/core.test.d.ts +2 -0
- package/dist/test/core.test.d.ts.map +1 -0
- package/dist/test/core.test.js +188 -0
- package/dist/test/core.test.js.map +1 -0
- package/dist/types.d.ts +267 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/assets.d.ts +28 -0
- package/dist/utils/assets.d.ts.map +1 -0
- package/dist/utils/assets.js +583 -0
- package/dist/utils/assets.js.map +1 -0
- package/dist/utils/browser.d.ts +2 -0
- package/dist/utils/browser.d.ts.map +1 -0
- package/dist/utils/browser.js +23 -0
- package/dist/utils/browser.js.map +1 -0
- package/dist/utils/dashboard.d.ts +16 -0
- package/dist/utils/dashboard.d.ts.map +1 -0
- package/dist/utils/dashboard.js +177 -0
- package/dist/utils/dashboard.js.map +1 -0
- package/dist/utils/helpers.d.ts +29 -0
- package/dist/utils/helpers.d.ts.map +1 -0
- package/dist/utils/helpers.js +204 -0
- package/dist/utils/helpers.js.map +1 -0
- package/dist/utils/scripts.d.ts +60 -0
- package/dist/utils/scripts.d.ts.map +1 -0
- package/dist/utils/scripts.js +174 -0
- package/dist/utils/scripts.js.map +1 -0
- package/dist/utils/state.d.ts +12 -0
- package/dist/utils/state.d.ts.map +1 -0
- package/dist/utils/state.js +44 -0
- package/dist/utils/state.js.map +1 -0
- package/dist/utils/tar.d.ts +11 -0
- package/dist/utils/tar.d.ts.map +1 -0
- package/dist/utils/tar.js +93 -0
- package/dist/utils/tar.js.map +1 -0
- package/dist/utils/update.d.ts +9 -0
- package/dist/utils/update.d.ts.map +1 -0
- package/dist/utils/update.js +134 -0
- package/dist/utils/update.js.map +1 -0
- package/dist/utils/websocket.d.ts +10 -0
- package/dist/utils/websocket.d.ts.map +1 -0
- package/dist/utils/websocket.js +203 -0
- package/dist/utils/websocket.js.map +1 -0
- package/package.json +55 -4
- package/public/app.js +2736 -0
- package/public/assets/v2.2/css/base.css +113 -0
- package/public/assets/v2.2/css/components/content.css +133 -0
- package/public/assets/v2.2/css/components/controls.css +229 -0
- package/public/assets/v2.2/css/components/data.css +373 -0
- package/public/assets/v2.2/css/components/icons.css +35 -0
- package/public/assets/v2.2/css/components/motion.css +57 -0
- package/public/assets/v2.2/css/components/shell.css +51 -0
- package/public/assets/v2.2/css/components/sidebar.css +242 -0
- package/public/assets/v2.2/css/components/topbar.css +138 -0
- package/public/assets/v2.2/css/fonts.css +30 -0
- package/public/assets/v2.2/css/synteq-ui-v2.css +25 -0
- package/public/assets/v2.2/css/theme-dark.css +38 -0
- package/public/assets/v2.2/css/theme-light.css +36 -0
- package/public/assets/v2.2/css/tokens.css +61 -0
- package/public/assets/v2.2/fonts/GoogleSansCode-Medium.ttf +0 -0
- package/public/assets/v2.2/fonts/GoogleSansCode-Regular.ttf +0 -0
- package/public/assets/v2.2/fonts/GoogleSansCode-SemiBold.ttf +0 -0
- package/public/assets/v2.2/js/synteq-ui-v2.js +869 -0
- package/public/favicon.svg +13 -0
- package/public/index.html +1083 -0
- 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
|
+
|