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.
- 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 +483 -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 +2712 -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 +7 -0
- package/public/index.html +1080 -0
- package/public/styles.css +552 -0
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ===================================================================
|
|
3
|
+
# pre-run.sh - prepare a fresh VPS before installing Octra lite_node
|
|
4
|
+
#
|
|
5
|
+
# 0. Harden the host (mandatory): net.mptcp.enabled = 0
|
|
6
|
+
# 1. Add 8 GB swap (idempotent - skips if swap is already present)
|
|
7
|
+
# 2. Give the node a data store and mount it on ${NODE_DATA_ROOT}
|
|
8
|
+
# - /dev/sdb when it exists and is not the system disk
|
|
9
|
+
# - otherwise the largest non-system disk >= DATA_MIN_GB (1 TB)
|
|
10
|
+
# - otherwise the largest filesystem >= DATA_MIN_GB (main disk)
|
|
11
|
+
# Formatting + mounting is AUTOMATIC (no checkbox): an Octra data set that
|
|
12
|
+
# is already there is kept, anything else on that dedicated volume is
|
|
13
|
+
# formatted, so a retried migration never loses the copy it just made.
|
|
14
|
+
# WIPE_VOLUME=1 overrides that and reformats - including a volume that is
|
|
15
|
+
# already mounted on ${NODE_DATA_ROOT}, which used to be skipped.
|
|
16
|
+
# 3. Create every directory the node/migration needs
|
|
17
|
+
# 4. Record what was decided in /root/migration/prerun.env, so the migration
|
|
18
|
+
# orchestrator can adapt (keep the data set, format it, or reset the state)
|
|
19
|
+
# instead of contradicting this script and retrying blindly.
|
|
20
|
+
#
|
|
21
|
+
# Usage: sudo bash pre-run.sh
|
|
22
|
+
# Optional: WIPE_VOLUME=1 ... force a reformat even of an Octra data set
|
|
23
|
+
# DATA_MIN_GB=800 ... lower the size requirement
|
|
24
|
+
# BLOCK_DEVICE=/dev/nvme1n1 ... prefer another device
|
|
25
|
+
# PRERUN_STATE=<file> ... where the decision is recorded
|
|
26
|
+
# Safe to re-run - each step checks current state before acting.
|
|
27
|
+
# ===================================================================
|
|
28
|
+
|
|
29
|
+
set -euo pipefail
|
|
30
|
+
source "$(dirname "$0")/config.sh"
|
|
31
|
+
|
|
32
|
+
# Must be root
|
|
33
|
+
if [[ $EUID -ne 0 ]]; then
|
|
34
|
+
err "Run as root: sudo bash $0"
|
|
35
|
+
exit 1
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
# --- tools this script needs: check -> install -> continue ----------
|
|
39
|
+
# A bare image may come without e2fsprogs (mkfs.ext4), psmisc or rsync, and
|
|
40
|
+
# failing on `mkfs.ext4: command not found` AFTER the wipe is exactly the kind
|
|
41
|
+
# of half-done state this toolkit must not produce. Check before touching a disk.
|
|
42
|
+
ensure_tool lsblk util-linux || { err "lsblk (util-linux) is required"; exit 1; }
|
|
43
|
+
ensure_tool findmnt util-linux || { err "findmnt (util-linux) is required"; exit 1; }
|
|
44
|
+
ensure_tool mkfs.ext4 e2fsprogs || { err "mkfs.ext4 (e2fsprogs) is required to format the data volume"; exit 1; }
|
|
45
|
+
ensure_tool wipefs util-linux || { err "wipefs (util-linux) is required"; exit 1; }
|
|
46
|
+
|
|
47
|
+
# -------------------------------------------------------------------
|
|
48
|
+
# Data-store decision (recorded for the migration orchestrator)
|
|
49
|
+
# -------------------------------------------------------------------
|
|
50
|
+
# The migration's prep step must not demand a fresh data store that this script
|
|
51
|
+
# deliberately kept. So every decision lands in a small file the orchestrator
|
|
52
|
+
# reads right after pre-run.sh:
|
|
53
|
+
#
|
|
54
|
+
# PRERUN_ACTION = formatted | kept | mounted | fallback
|
|
55
|
+
# PRERUN_DATASET= yes | no an Octra data set is on the store
|
|
56
|
+
# PRERUN_CHAIN = yes | no that data set holds real chain state (HEAD.json)
|
|
57
|
+
#
|
|
58
|
+
# With those two facts the orchestrator can choose FORCE=1 (keep a resumable
|
|
59
|
+
# copy of the same migration), WIPE_VOLUME=1 (reformat a stale copy) or
|
|
60
|
+
# RESET_TARGET=1 (no volume to format - drop the stale node state).
|
|
61
|
+
PRERUN_STATE="${PRERUN_STATE:-/root/migration/prerun.env}"
|
|
62
|
+
PRERUN_DEV=""
|
|
63
|
+
PRERUN_ACTION=""
|
|
64
|
+
PRERUN_DATASET="no"
|
|
65
|
+
PRERUN_CHAIN="no"
|
|
66
|
+
PRERUN_FORCE="${WIPE_VOLUME:-${FORCE_FORMAT:-0}}"
|
|
67
|
+
|
|
68
|
+
write_prerun_state() {
|
|
69
|
+
mkdir -p "$(dirname "${PRERUN_STATE}")"
|
|
70
|
+
{
|
|
71
|
+
echo "PRERUN_DEV=${PRERUN_DEV}"
|
|
72
|
+
echo "PRERUN_MOUNT=${MOUNT_POINT}"
|
|
73
|
+
echo "PRERUN_ACTION=${PRERUN_ACTION}"
|
|
74
|
+
echo "PRERUN_DATASET=${PRERUN_DATASET}"
|
|
75
|
+
echo "PRERUN_CHAIN=${PRERUN_CHAIN}"
|
|
76
|
+
echo "PRERUN_FORCE=${PRERUN_FORCE}"
|
|
77
|
+
echo "PRERUN_AT=$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
78
|
+
} > "${PRERUN_STATE}"
|
|
79
|
+
log "data store decision: action=${PRERUN_ACTION} data-set=${PRERUN_DATASET} chain-state=${PRERUN_CHAIN}"
|
|
80
|
+
log " recorded in ${PRERUN_STATE}"
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
# Does a (mounted) data store still hold an Octra data set / chain state?
|
|
84
|
+
# The marker alone is NOT enough: pre-run.sh writes it on every run, including a
|
|
85
|
+
# freshly formatted volume. Only the node layout proves there is data here.
|
|
86
|
+
detect_dataset() { # detect_dataset <mountpoint>
|
|
87
|
+
local dir="$1"
|
|
88
|
+
if [[ -d "${dir}/devnet" ]] || [[ -d "${dir}/validator-control" ]]; then
|
|
89
|
+
PRERUN_DATASET="yes"
|
|
90
|
+
fi
|
|
91
|
+
if [[ -f "${dir}/devnet/HEAD.json" ]]; then
|
|
92
|
+
PRERUN_CHAIN="yes"
|
|
93
|
+
fi
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
# -------------------------------------------------------------------
|
|
97
|
+
# 0. Data-store helpers (auto detect / format / mount)
|
|
98
|
+
# -------------------------------------------------------------------
|
|
99
|
+
# Disks that carry the running system (/, /boot, /boot/efi): never format those.
|
|
100
|
+
system_disks() {
|
|
101
|
+
local mnt src disk
|
|
102
|
+
for mnt in / /boot /boot/efi; do
|
|
103
|
+
src=$(findmnt -n -o SOURCE "${mnt}" 2>/dev/null || true)
|
|
104
|
+
[[ -n "${src}" ]] || continue
|
|
105
|
+
disk=$(lsblk -no PKNAME "${src}" 2>/dev/null | head -1 || true)
|
|
106
|
+
[[ -n "${disk}" ]] || disk=$(basename "${src}")
|
|
107
|
+
echo "${disk}"
|
|
108
|
+
done | sort -u
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
is_system_disk() { # is_system_disk <device> (whole disk or partition)
|
|
112
|
+
local dev="$1" disk
|
|
113
|
+
disk=$(lsblk -no PKNAME "${dev}" 2>/dev/null | head -1 || true)
|
|
114
|
+
[[ -n "${disk}" ]] || disk=$(basename "${dev}")
|
|
115
|
+
system_disks | grep -qx "${disk}"
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
# Whole disks that are not the system disk, biggest first: "<name> <gb>"
|
|
119
|
+
list_data_disks() {
|
|
120
|
+
lsblk -ln -b -d -o NAME,SIZE,TYPE 2>/dev/null \
|
|
121
|
+
| awk '$3=="disk" {printf "%s %.0f\n", $1, $2/1000000000}' \
|
|
122
|
+
| while read -r name gb; do
|
|
123
|
+
case "${name}" in
|
|
124
|
+
loop*|sr*|ram*|fd*|dm-*|md*) continue ;;
|
|
125
|
+
esac
|
|
126
|
+
if (( gb < 1 )); then
|
|
127
|
+
continue
|
|
128
|
+
fi
|
|
129
|
+
if is_system_disk "/dev/${name}"; then
|
|
130
|
+
continue
|
|
131
|
+
fi
|
|
132
|
+
echo "${name} ${gb}"
|
|
133
|
+
done | sort -k2 -nr
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
# The device to format: its single partition when it has exactly one, otherwise
|
|
137
|
+
# the whole disk (a fresh provider volume usually has no partition table).
|
|
138
|
+
device_for() {
|
|
139
|
+
local disk="$1" parts count
|
|
140
|
+
parts=$(lsblk -ln -o NAME,TYPE "${disk}" 2>/dev/null | awk '$2=="part" {print $1}')
|
|
141
|
+
count=$(printf '%s\n' "${parts}" | grep -c . || true)
|
|
142
|
+
if [[ "${count}" == "1" ]]; then
|
|
143
|
+
echo "/dev/${parts}"
|
|
144
|
+
else
|
|
145
|
+
if [[ "${count}" -gt 1 ]]; then
|
|
146
|
+
warn "${disk} holds ${count} partitions - the whole disk will be formatted"
|
|
147
|
+
fi
|
|
148
|
+
echo "${disk}"
|
|
149
|
+
fi
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
# Does this device already carry an Octra data set?
|
|
153
|
+
# * our marker file (.octra-data-volume), or
|
|
154
|
+
# * the node layout itself (devnet/ or validator-control/) - volumes created by
|
|
155
|
+
# an older pre-run.sh have no marker, and reformatting them would destroy a
|
|
156
|
+
# migrated chain state.
|
|
157
|
+
volume_is_octra() {
|
|
158
|
+
local dev="$1" tmp rc=1
|
|
159
|
+
tmp=$(mktemp -d)
|
|
160
|
+
if mount -o ro "${dev}" "${tmp}" 2>/dev/null; then
|
|
161
|
+
if [[ -f "${tmp}/${DATA_MARKER}" ]]; then
|
|
162
|
+
rc=0
|
|
163
|
+
elif [[ -d "${tmp}/devnet" ]] || [[ -d "${tmp}/validator-control" ]]; then
|
|
164
|
+
rc=0
|
|
165
|
+
fi
|
|
166
|
+
umount "${tmp}" 2>/dev/null || true
|
|
167
|
+
fi
|
|
168
|
+
rmdir "${tmp}" 2>/dev/null || true
|
|
169
|
+
return ${rc}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
add_fstab() { # add_fstab <device> <mountpoint>
|
|
173
|
+
local dev="$1" target="$2" uuid
|
|
174
|
+
uuid=$(blkid -s UUID -o value "${dev}" 2>/dev/null || true)
|
|
175
|
+
sed -i "\|^[^#]*[[:space:]]${target}[[:space:]]|d" /etc/fstab
|
|
176
|
+
if [[ -n "${uuid}" ]]; then
|
|
177
|
+
# nofail: a missing volume must never block the boot of a validator host
|
|
178
|
+
echo "UUID=${uuid} ${target} ext4 defaults,noatime,nofail 0 2" >> /etc/fstab
|
|
179
|
+
log "fstab entry refreshed (UUID=${uuid} -> ${target})"
|
|
180
|
+
else
|
|
181
|
+
warn "no UUID for ${dev} - /etc/fstab was not updated"
|
|
182
|
+
fi
|
|
183
|
+
systemctl daemon-reload 2>/dev/null || true
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
size_gb_of() { # size_gb_of <device> -> integer GB
|
|
187
|
+
local bytes
|
|
188
|
+
bytes=$(blockdev --getsize64 "$1" 2>/dev/null || echo 0)
|
|
189
|
+
echo $(( ${bytes:-0} / 1000000000 ))
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
# Leave NOTHING behind. A test volume can still hold a previous node's cache, a
|
|
193
|
+
# foreign filesystem or a stale partition table, and the deploy program must
|
|
194
|
+
# start from an empty store. wipefs on the disk AND its partitions removes every
|
|
195
|
+
# signature the kernel could still pick up, zeroing the first and last 16 MiB
|
|
196
|
+
# kills the backup superblocks/GPT leftovers, and mkfs writes a clean ext4.
|
|
197
|
+
# No full-disk overwrite: this stays a few seconds even on 1 TB.
|
|
198
|
+
wipe_device() { # wipe_device <device>
|
|
199
|
+
local dev="$1" size_mb=0 last_seek=0 part
|
|
200
|
+
log "wiping every signature on ${dev} (partitions, filesystems, first/last 16 MiB)"
|
|
201
|
+
for part in $(lsblk -ln -o NAME "${dev}" 2>/dev/null | awk 'NR>1 {print "/dev/"$1}'); do
|
|
202
|
+
umount "${part}" 2>/dev/null || true
|
|
203
|
+
wipefs -a "${part}" >/dev/null 2>&1 || true
|
|
204
|
+
done
|
|
205
|
+
umount "${dev}" 2>/dev/null || true
|
|
206
|
+
wipefs -a "${dev}" >/dev/null 2>&1 || true
|
|
207
|
+
size_mb=$(blockdev --getsize64 "${dev}" 2>/dev/null | awk '{printf "%d", $1/1048576}')
|
|
208
|
+
[[ -n "${size_mb}" ]] || size_mb=0
|
|
209
|
+
dd if=/dev/zero of="${dev}" bs=1M count=16 conv=fsync 2>/dev/null || true
|
|
210
|
+
if (( size_mb > 32 )); then
|
|
211
|
+
last_seek=$(( size_mb - 16 ))
|
|
212
|
+
dd if=/dev/zero of="${dev}" bs=1M count=16 seek="${last_seek}" conv=fsync 2>/dev/null || true
|
|
213
|
+
fi
|
|
214
|
+
partprobe "${dev}" 2>/dev/null || true
|
|
215
|
+
udevadm settle 2>/dev/null || true
|
|
216
|
+
log " ${dev} is empty now ($(size_gb_of "${dev}") GB)"
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
mount_data_volume() { # mount_data_volume <device>
|
|
220
|
+
local dev="$1" fs_exists=0 force="${WIPE_VOLUME:-${FORCE_FORMAT:-0}}" has_dataset=0
|
|
221
|
+
if is_system_disk "${dev}"; then
|
|
222
|
+
err "refusing to format ${dev}: it carries the running system"
|
|
223
|
+
return 1
|
|
224
|
+
fi
|
|
225
|
+
|
|
226
|
+
# free anything mounted from this device (a previous node, a stale mount)
|
|
227
|
+
while read -r target; do
|
|
228
|
+
if [[ -z "${target}" ]]; then
|
|
229
|
+
continue
|
|
230
|
+
fi
|
|
231
|
+
if [[ "${target}" == "${MOUNT_POINT}" ]]; then
|
|
232
|
+
continue
|
|
233
|
+
fi
|
|
234
|
+
warn "unmounting ${target} (belongs to ${dev})"
|
|
235
|
+
umount "${target}" 2>/dev/null || true
|
|
236
|
+
done < <(findmnt -ln -o TARGET -S "${dev}" 2>/dev/null || true)
|
|
237
|
+
|
|
238
|
+
if [[ -n "$(blkid -o value -s TYPE "${dev}" 2>/dev/null || true)" ]]; then
|
|
239
|
+
fs_exists=1
|
|
240
|
+
fi
|
|
241
|
+
|
|
242
|
+
if (( fs_exists == 1 )) && volume_is_octra "${dev}"; then
|
|
243
|
+
has_dataset=1
|
|
244
|
+
fi
|
|
245
|
+
|
|
246
|
+
if (( fs_exists == 1 )) && [[ "${force}" != "1" ]] && (( has_dataset == 1 )); then
|
|
247
|
+
log "${dev} already holds an Octra data set - keeping it (no format)"
|
|
248
|
+
PRERUN_ACTION="kept"
|
|
249
|
+
else
|
|
250
|
+
if (( fs_exists == 1 )); then
|
|
251
|
+
if [[ "${force}" == "1" ]]; then
|
|
252
|
+
warn "WIPE_VOLUME=1 - reformatting ${dev} even though it holds an Octra data set"
|
|
253
|
+
PRERUN_ACTION="formatted"
|
|
254
|
+
else
|
|
255
|
+
warn "${dev} carries a filesystem but no Octra marker - formatting it now"
|
|
256
|
+
warn " (dedicated node volume: its content is not needed by the node)"
|
|
257
|
+
PRERUN_ACTION="formatted"
|
|
258
|
+
fi
|
|
259
|
+
else
|
|
260
|
+
log "No filesystem on ${dev} - formatting it now"
|
|
261
|
+
PRERUN_ACTION="formatted"
|
|
262
|
+
fi
|
|
263
|
+
wipe_device "${dev}"
|
|
264
|
+
log "Formatting ${dev} as ext4 (label ${DATA_LABEL})..."
|
|
265
|
+
# -q: mkfs prints a megabytes-sized "Discarding device blocks" progress line
|
|
266
|
+
# that arrives as one unreadable log row; discard stays enabled.
|
|
267
|
+
mkfs.ext4 -F -q -L "${DATA_LABEL}" "${dev}"
|
|
268
|
+
fi
|
|
269
|
+
|
|
270
|
+
mkdir -p "${MOUNT_POINT}"
|
|
271
|
+
if ! mountpoint -q "${MOUNT_POINT}" 2>/dev/null; then
|
|
272
|
+
log "Mounting ${dev} -> ${MOUNT_POINT}..."
|
|
273
|
+
mount "${dev}" "${MOUNT_POINT}"
|
|
274
|
+
fi
|
|
275
|
+
# a freshly formatted ext4 only has lost+found; anything else would be a
|
|
276
|
+
# leftover the operator asked us to remove (dot entries are left alone: they
|
|
277
|
+
# are bookkeeping, not node data)
|
|
278
|
+
if [[ "${PRERUN_ACTION}" == "formatted" ]]; then
|
|
279
|
+
find "${MOUNT_POINT}" -mindepth 1 -maxdepth 1 ! -name 'lost+found' ! -name '.*' \
|
|
280
|
+
-exec rm -rf {} + 2>/dev/null || true
|
|
281
|
+
fi
|
|
282
|
+
touch "${MOUNT_POINT}/${DATA_MARKER}"
|
|
283
|
+
add_fstab "${dev}" "${MOUNT_POINT}"
|
|
284
|
+
|
|
285
|
+
PRERUN_DEV="${dev}"
|
|
286
|
+
# has_dataset was measured BEFORE the format, so it only describes what is
|
|
287
|
+
# still there when the volume was kept
|
|
288
|
+
if [[ "${PRERUN_ACTION}" == "kept" ]] && (( has_dataset == 1 )); then
|
|
289
|
+
PRERUN_DATASET="yes"
|
|
290
|
+
fi
|
|
291
|
+
# what is really on the mounted volume now (a fresh format carries nothing)
|
|
292
|
+
detect_dataset "${MOUNT_POINT}"
|
|
293
|
+
|
|
294
|
+
log "Data volume ready: ${dev} -> ${MOUNT_POINT} ($(df -h "${MOUNT_POINT}" | tail -1 | awk '{print $2}') total)"
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
# No dedicated volume: keep the data on the biggest filesystem (usually /).
|
|
298
|
+
mount_data_fallback() {
|
|
299
|
+
local src size avail target best_src="" best_size=0 best_avail=0 best_target=""
|
|
300
|
+
while read -r src size avail target; do
|
|
301
|
+
[[ -z "${src}" ]] && continue
|
|
302
|
+
case "${src}" in
|
|
303
|
+
tmpfs|devtmpfs|overlay|none|udev|shm) continue ;;
|
|
304
|
+
esac
|
|
305
|
+
(( size >= DATA_MIN_GB )) || continue
|
|
306
|
+
if (( avail > best_avail )); then
|
|
307
|
+
best_src="${src}"; best_size="${size}"; best_avail="${avail}"; best_target="${target}"
|
|
308
|
+
fi
|
|
309
|
+
done < <(df -B1G --output=source,size,avail,target 2>/dev/null | tail -n +2 || true)
|
|
310
|
+
|
|
311
|
+
if [[ -z "${best_src}" ]]; then
|
|
312
|
+
err "no disk large enough for the node data store (need >= ${DATA_MIN_GB} GB)"
|
|
313
|
+
warn "Attach a 1 TB block volume (pre-run.sh picks it up automatically), or lower"
|
|
314
|
+
warn "the requirement with: DATA_MIN_GB=<gb> bash pre-run.sh"
|
|
315
|
+
lsblk || true
|
|
316
|
+
exit 1
|
|
317
|
+
fi
|
|
318
|
+
|
|
319
|
+
warn "no dedicated data volume found - the node data will live on ${best_src} (${best_target})"
|
|
320
|
+
log " filesystem: ${best_size} GB total, ${best_avail} GB free (nothing is formatted)"
|
|
321
|
+
mkdir -p "${MOUNT_POINT}"
|
|
322
|
+
PRERUN_DEV="${best_src}"
|
|
323
|
+
PRERUN_ACTION="fallback"
|
|
324
|
+
detect_dataset "${MOUNT_POINT}"
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
# -------------------------------------------------------------------
|
|
328
|
+
# Pick the data store, format/mount it and record the decision
|
|
329
|
+
# -------------------------------------------------------------------
|
|
330
|
+
# Split out of the main body so the test harness can drive every branch
|
|
331
|
+
# (mounted / mounted+force / dedicated volume / fallback) without touching a
|
|
332
|
+
# real disk: migrate-ui/dev/test-prerun.sh stubs lsblk/findmnt/mount/mkfs.
|
|
333
|
+
prepare_data_store() {
|
|
334
|
+
if mountpoint -q "${MOUNT_POINT}" 2>/dev/null; then
|
|
335
|
+
local current_dev
|
|
336
|
+
current_dev=$(findmnt -n -o SOURCE "${MOUNT_POINT}" 2>/dev/null || echo "${MOUNT_POINT}")
|
|
337
|
+
PRERUN_DEV="${current_dev}"
|
|
338
|
+
PRERUN_ACTION="mounted"
|
|
339
|
+
if [[ "${PRERUN_FORCE}" == "1" ]]; then
|
|
340
|
+
# A force that silently does nothing is worse than no force at all. On a
|
|
341
|
+
# retry the volume is already mounted, and the old code only touched the
|
|
342
|
+
# marker here - so WIPE_VOLUME=1 was ignored and the stale chain state
|
|
343
|
+
# survived every run.
|
|
344
|
+
if is_system_disk "${current_dev}"; then
|
|
345
|
+
warn "WIPE_VOLUME=1 refused: ${MOUNT_POINT} lives on the system disk (${current_dev})"
|
|
346
|
+
detect_dataset "${MOUNT_POINT}"
|
|
347
|
+
else
|
|
348
|
+
warn "WIPE_VOLUME=1 - reformatting the mounted data volume ${current_dev}"
|
|
349
|
+
if ! umount "${MOUNT_POINT}"; then
|
|
350
|
+
err "could not unmount ${MOUNT_POINT} - a process is still using it (lsof ${MOUNT_POINT})"
|
|
351
|
+
return 1
|
|
352
|
+
fi
|
|
353
|
+
mount_data_volume "${current_dev}"
|
|
354
|
+
return $?
|
|
355
|
+
fi
|
|
356
|
+
else
|
|
357
|
+
log "${MOUNT_POINT} is already a mount point (${current_dev}) - keeping it"
|
|
358
|
+
detect_dataset "${MOUNT_POINT}"
|
|
359
|
+
touch "${MOUNT_POINT}/${DATA_MARKER}"
|
|
360
|
+
fi
|
|
361
|
+
return 0
|
|
362
|
+
fi
|
|
363
|
+
|
|
364
|
+
local data_dev="" data_gb=0
|
|
365
|
+
|
|
366
|
+
# 1) the preferred volume (/dev/sdb by default)
|
|
367
|
+
if lsblk "${BLOCK_DEVICE}" >/dev/null 2>&1; then
|
|
368
|
+
if is_system_disk "${BLOCK_DEVICE}"; then
|
|
369
|
+
warn "${BLOCK_DEVICE} carries the running system - not usable as the data volume"
|
|
370
|
+
else
|
|
371
|
+
data_dev=$(device_for "${BLOCK_DEVICE}")
|
|
372
|
+
data_gb=$(size_gb_of "${data_dev}")
|
|
373
|
+
if (( data_gb < DATA_MIN_GB )); then
|
|
374
|
+
warn "${data_dev} is ${data_gb} GB - below the recommended ${DATA_MIN_GB} GB for the node data store"
|
|
375
|
+
fi
|
|
376
|
+
log "preferred data volume: ${BLOCK_DEVICE} -> ${data_dev} (${data_gb} GB)"
|
|
377
|
+
fi
|
|
378
|
+
else
|
|
379
|
+
warn "${BLOCK_DEVICE} not present - looking for another disk"
|
|
380
|
+
fi
|
|
381
|
+
|
|
382
|
+
# 2) the largest non-system disk >= DATA_MIN_GB
|
|
383
|
+
if [[ -z "${data_dev}" ]]; then
|
|
384
|
+
local disk_name disk_gb
|
|
385
|
+
while read -r disk_name disk_gb; do
|
|
386
|
+
if (( disk_gb < DATA_MIN_GB )); then
|
|
387
|
+
continue
|
|
388
|
+
fi
|
|
389
|
+
data_dev=$(device_for "/dev/${disk_name}")
|
|
390
|
+
log "using the largest free disk: /dev/${disk_name} (${disk_gb} GB)"
|
|
391
|
+
break
|
|
392
|
+
done < <(list_data_disks)
|
|
393
|
+
fi
|
|
394
|
+
|
|
395
|
+
# 3) mount (formatting only when there is no Octra data set on it)
|
|
396
|
+
if [[ -n "${data_dev}" ]]; then
|
|
397
|
+
mount_data_volume "${data_dev}"
|
|
398
|
+
else
|
|
399
|
+
warn "no dedicated volume >= ${DATA_MIN_GB} GB found"
|
|
400
|
+
mount_data_fallback
|
|
401
|
+
fi
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
# -------------------------------------------------------------------
|
|
406
|
+
# 0. HOST HARDENING - MPTCP off (mandatory, both programs)
|
|
407
|
+
# -------------------------------------------------------------------
|
|
408
|
+
# pre-run.sh is the first thing that touches a target machine in BOTH programs
|
|
409
|
+
# (deploy: 10-prepare-host.sh; migrate: 01-prepare-target.sh), so the
|
|
410
|
+
# baseline hardening lives here as well as in scripts/harden-host.sh.
|
|
411
|
+
# MPTCP is unused by the node and must not be listening: it is an avoidable
|
|
412
|
+
# vector of attack. disable_mptcp() writes
|
|
413
|
+
# /etc/sysctl.d/99-disable-mptcp.conf and verifies the running value.
|
|
414
|
+
log "Hardening the host (net.mptcp.enabled = 0)..."
|
|
415
|
+
if ! disable_mptcp; then
|
|
416
|
+
err "refusing to continue: MPTCP is still enabled on this host (fixed with sudo sysctl --system)"
|
|
417
|
+
exit 1
|
|
418
|
+
fi
|
|
419
|
+
|
|
420
|
+
# -------------------------------------------------------------------
|
|
421
|
+
# 1. SWAP - 8 GB swapfile
|
|
422
|
+
# -------------------------------------------------------------------
|
|
423
|
+
log "Checking swap configuration…"
|
|
424
|
+
|
|
425
|
+
CURRENT_SWAP=$(swapon --show --noheadings 2>/dev/null | wc -l)
|
|
426
|
+
if (( CURRENT_SWAP > 0 )); then
|
|
427
|
+
log "Swap already active ($(swapon --show | tail -n +2 | awk '{print $1, $3}')) — skipping"
|
|
428
|
+
else
|
|
429
|
+
log "Creating ${SWAP_SIZE} swap file at ${SWAP_FILE}…"
|
|
430
|
+
|
|
431
|
+
# Remove stale swap file if present
|
|
432
|
+
if [[ -f "${SWAP_FILE}" ]]; then
|
|
433
|
+
warn "${SWAP_FILE} already exists — recreating"
|
|
434
|
+
swapoff "${SWAP_FILE}" 2>/dev/null || true
|
|
435
|
+
rm -f "${SWAP_FILE}"
|
|
436
|
+
fi
|
|
437
|
+
|
|
438
|
+
# Create a dense swap file (dd writes real blocks, fallocate can create holes)
|
|
439
|
+
# Try fallocate first (fast), fall back to dd if the filesystem doesn't support it
|
|
440
|
+
if fallocate -l "${SWAP_SIZE}" "${SWAP_FILE}" 2>/dev/null; then
|
|
441
|
+
log "Swap file allocated with fallocate"
|
|
442
|
+
else
|
|
443
|
+
warn "fallocate failed — using dd (slower but reliable)"
|
|
444
|
+
dd if=/dev/zero of="${SWAP_FILE}" bs=1M count=$(( ${SWAP_GB:-8} * 1024 )) status=progress
|
|
445
|
+
fi
|
|
446
|
+
|
|
447
|
+
chmod 600 "${SWAP_FILE}"
|
|
448
|
+
mkswap "${SWAP_FILE}"
|
|
449
|
+
swapon "${SWAP_FILE}"
|
|
450
|
+
|
|
451
|
+
# Persist in fstab (remove old entry first)
|
|
452
|
+
if ! grep -q "${SWAP_FILE}" /etc/fstab; then
|
|
453
|
+
echo "${SWAP_FILE} none swap sw 0 0" >> /etc/fstab
|
|
454
|
+
log "Swap entry added to /etc/fstab"
|
|
455
|
+
fi
|
|
456
|
+
|
|
457
|
+
log "Swap activated: $(free -h | awk '/^Swap:/ {print $2}')"
|
|
458
|
+
fi
|
|
459
|
+
|
|
460
|
+
# -------------------------------------------------------------------
|
|
461
|
+
# 2. DATA STORE - automatic detect + format + mount
|
|
462
|
+
# -------------------------------------------------------------------
|
|
463
|
+
log "Preparing the node data store (${MOUNT_POINT}, needs >= ${DATA_MIN_GB} GB)..."
|
|
464
|
+
|
|
465
|
+
prepare_data_store || exit 1
|
|
466
|
+
write_prerun_state
|
|
467
|
+
|
|
468
|
+
# --- directories ----------------------------------------------------
|
|
469
|
+
# Node + migration paths must exist before anything writes into them; a fresh
|
|
470
|
+
# host has none of them, so they are always created here.
|
|
471
|
+
for dir in "${MOUNT_POINT}" "${NODE_DATA_DIR}" "${NODE_SYNC_STAGE}" \
|
|
472
|
+
"${MOUNT_POINT}/validator-control" "/root/migration" "/root/migration/backups"; do
|
|
473
|
+
mkdir -p "${dir}"
|
|
474
|
+
done
|
|
475
|
+
if id "${NODE_USER}" >/dev/null 2>&1; then
|
|
476
|
+
chown "${NODE_USER}:${NODE_USER}" "${NODE_DATA_DIR}" "${NODE_SYNC_STAGE}" 2>/dev/null || true
|
|
477
|
+
fi
|
|
478
|
+
log "Directories ready: ${NODE_DATA_DIR}, ${NODE_SYNC_STAGE}"
|
|
479
|
+
|
|
480
|
+
# -------------------------------------------------------------------
|
|
481
|
+
# Summary
|
|
482
|
+
# -------------------------------------------------------------------
|
|
483
|
+
|
|
484
|
+
echo
|
|
485
|
+
log "Pre-run complete:"
|
|
486
|
+
echo " MPTCP: $(sysctl net.mptcp.enabled 2>/dev/null || echo 'not available on this kernel')"
|
|
487
|
+
echo " Swap: $(free -h | awk '/^Swap:/ {print $2}')"
|
|
488
|
+
STORE_INFO=$(df -h "$MOUNT_POINT" 2>/dev/null | tail -1 | awk '{print $1" "$2", "$4" avail"}')
|
|
489
|
+
if mountpoint -q "$MOUNT_POINT" 2>/dev/null; then
|
|
490
|
+
STORE_KIND="dedicated volume, marker ${DATA_MARKER}"
|
|
491
|
+
else
|
|
492
|
+
STORE_KIND="main disk, no separate volume"
|
|
493
|
+
fi
|
|
494
|
+
echo " Store: ${STORE_INFO}"
|
|
495
|
+
echo " Mount: ${MOUNT_POINT} - ${STORE_KIND}"
|
|
496
|
+
echo " Store decision: ${PRERUN_ACTION} (Octra data set: ${PRERUN_DATASET}, chain state: ${PRERUN_CHAIN})"
|
|
497
|
+
echo " Dirs: ${NODE_DATA_DIR}, ${NODE_SYNC_STAGE}"
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ===================================================================
|
|
3
|
+
# promote-validator.sh — auto-promote observer node to validator
|
|
4
|
+
#
|
|
5
|
+
# Polls stat.sh every 30 seconds. Once validator_scheduled or
|
|
6
|
+
# validator_active = true, it:
|
|
7
|
+
# 1. Stops the node
|
|
8
|
+
# 2. Reconfigures node.env with --role validator
|
|
9
|
+
# 3. Restarts the node
|
|
10
|
+
# 4. Updates nodes_data.json (runs extract-data.sh)
|
|
11
|
+
#
|
|
12
|
+
# Usage:
|
|
13
|
+
# sudo bash promote-validator.sh # run in foreground
|
|
14
|
+
# sudo nohup bash promote-validator.sh > /root/promote.log 2>&1 & # background
|
|
15
|
+
#
|
|
16
|
+
# The script exits once promotion is complete (success or fatal error).
|
|
17
|
+
# ===================================================================
|
|
18
|
+
|
|
19
|
+
set -euo pipefail
|
|
20
|
+
source "$(dirname "$0")/config.sh"
|
|
21
|
+
|
|
22
|
+
if [[ $EUID -ne 0 ]]; then
|
|
23
|
+
err "Run as root: sudo bash $0"
|
|
24
|
+
exit 1
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
PUBLIC_HOST="${PUBLIC_HOST:-$(curl -s --max-time 5 https://api.ipify.org 2>/dev/null || hostname -I | awk '{print $1}')}"
|
|
28
|
+
NODE_NAME="${NODE_NAME:-octra-validator-01}"
|
|
29
|
+
|
|
30
|
+
log "Promotion watcher started — polling every 30s"
|
|
31
|
+
log "Node: ${NODE_NAME} @ ${PUBLIC_HOST}"
|
|
32
|
+
|
|
33
|
+
while true; do
|
|
34
|
+
STATUS=$(su - "${NODE_USER}" -c "cd ${NODE_SRC_DIR} && sh controls/stat.sh 2>/dev/null" 2>/dev/null || true)
|
|
35
|
+
|
|
36
|
+
ACTIVE=$(echo "${STATUS}" | grep '^validator_active' | awk -F' = ' '{print $2}')
|
|
37
|
+
SCHEDULED=$(echo "${STATUS}" | grep '^validator_scheduled' | awk -F' = ' '{print $2}')
|
|
38
|
+
HEAD=$(echo "${STATUS}" | grep '^head_epoch' | awk -F' = ' '{print $2}')
|
|
39
|
+
ENROLL=$(echo "${STATUS}" | grep '^validator_enrollment' | awk -F' = ' '{print $2}')
|
|
40
|
+
|
|
41
|
+
log "epoch=${HEAD} enrollment=${ENROLL} active=${ACTIVE:-?} scheduled=${SCHEDULED:-?}"
|
|
42
|
+
|
|
43
|
+
if [[ "${ACTIVE}" == "true" || "${SCHEDULED}" == "true" ]]; then
|
|
44
|
+
log "Membership ready! Promoting to validator…"
|
|
45
|
+
|
|
46
|
+
# Get network sha before stopping the node
|
|
47
|
+
NETWORK_SHA=$(awk '{print $1}' "${NODE_SRC_DIR}/config/network.env.sha256")
|
|
48
|
+
|
|
49
|
+
# Stop the node gracefully
|
|
50
|
+
log "Stopping node…"
|
|
51
|
+
su - "${NODE_USER}" -c "cd ${NODE_SRC_DIR} && sh controls/stop.sh" 2>&1 || true
|
|
52
|
+
sleep 3
|
|
53
|
+
|
|
54
|
+
# Reconfigure node.env to validator role
|
|
55
|
+
# NOTE: We bypass config_val.py's RPC membership check by editing node.env directly,
|
|
56
|
+
# since the RPC is unavailable after stopping. The key changes are:
|
|
57
|
+
# OCTRA_OPERATOR_ROLE: observer → validator
|
|
58
|
+
# OCTRA_CONSENSUS_MODE: observer → bft
|
|
59
|
+
log "Reconfiguring node.env — role=validator…"
|
|
60
|
+
|
|
61
|
+
NODE_ENV="${NODE_SRC_DIR}/.keys/validator/node.env"
|
|
62
|
+
if [[ ! -f "${NODE_ENV}" ]]; then
|
|
63
|
+
err "node.env not found at ${NODE_ENV}"
|
|
64
|
+
su - "${NODE_USER}" -c "cd ${NODE_SRC_DIR} && sh controls/run.sh" 2>&1 || true
|
|
65
|
+
exit 1
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
# Backup the current node.env
|
|
69
|
+
cp "${NODE_ENV}" "${NODE_ENV}.observer.bak"
|
|
70
|
+
log "Backed up observer config to ${NODE_ENV}.observer.bak"
|
|
71
|
+
|
|
72
|
+
# Patch the two role-related fields
|
|
73
|
+
sed -i 's/^OCTRA_OPERATOR_ROLE=.*/OCTRA_OPERATOR_ROLE=validator/' "${NODE_ENV}"
|
|
74
|
+
sed -i 's/^OCTRA_CONSENSUS_MODE=.*/OCTRA_CONSENSUS_MODE=bft/' "${NODE_ENV}"
|
|
75
|
+
|
|
76
|
+
# Verify the patch
|
|
77
|
+
ROLE_AFTER=$(grep '^OCTRA_OPERATOR_ROLE=' "${NODE_ENV}" | cut -d= -f2)
|
|
78
|
+
MODE_AFTER=$(grep '^OCTRA_CONSENSUS_MODE=' "${NODE_ENV}" | cut -d= -f2)
|
|
79
|
+
log "node.env updated: ROLE=${ROLE_AFTER} MODE=${MODE_AFTER}"
|
|
80
|
+
|
|
81
|
+
if [[ "${ROLE_AFTER}" != "validator" ]]; then
|
|
82
|
+
err "Failed to patch node.env — restoring backup"
|
|
83
|
+
cp "${NODE_ENV}.observer.bak" "${NODE_ENV}"
|
|
84
|
+
su - "${NODE_USER}" -c "cd ${NODE_SRC_DIR} && sh controls/run.sh" 2>&1 || true
|
|
85
|
+
exit 1
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
log "Config updated. Starting node as validator…"
|
|
89
|
+
su - "${NODE_USER}" -c "cd ${NODE_SRC_DIR} && sh controls/run.sh" 2>&1
|
|
90
|
+
|
|
91
|
+
sleep 5
|
|
92
|
+
log "Node status after restart:"
|
|
93
|
+
su - "${NODE_USER}" -c "cd ${NODE_SRC_DIR} && sh controls/stat.sh 2>/dev/null" 2>/dev/null | \
|
|
94
|
+
grep -E '^(role|process|uptime|rpc|voting|validator_active)' || true
|
|
95
|
+
|
|
96
|
+
log "Running extract-data.sh to update nodes_data.json…"
|
|
97
|
+
bash "$(dirname "$0")/extract-data.sh" 2>&1 || warn "extract-data.sh failed — run manually"
|
|
98
|
+
|
|
99
|
+
log "DONE — node is now running as validator"
|
|
100
|
+
exit 0
|
|
101
|
+
fi
|
|
102
|
+
|
|
103
|
+
sleep 30
|
|
104
|
+
done
|