ovtools 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (198) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +121 -2
  3. package/assets/common/bond-inspect.sh +368 -0
  4. package/assets/common/config.sh +263 -0
  5. package/assets/common/extract-data.sh +197 -0
  6. package/assets/common/harden-host.sh +37 -0
  7. package/assets/common/install-deps.sh +93 -0
  8. package/assets/common/node-enroll.sh +65 -0
  9. package/assets/common/node-setup.sh +150 -0
  10. package/assets/common/node-start.sh +90 -0
  11. package/assets/common/node-stop.sh +18 -0
  12. package/assets/common/node-upgrade.sh +45 -0
  13. package/assets/common/pre-run.sh +497 -0
  14. package/assets/common/promote-validator.sh +104 -0
  15. package/assets/deploy/10-prepare-host.sh +98 -0
  16. package/assets/deploy/20-install-toolchain.sh +93 -0
  17. package/assets/deploy/30-setup-node.sh +82 -0
  18. package/assets/deploy/40-start-node.sh +75 -0
  19. package/assets/deploy/50-verify.sh +259 -0
  20. package/assets/deploy/60-enroll.sh +304 -0
  21. package/assets/deploy/70-promote.sh +152 -0
  22. package/assets/deploy/lib.sh +398 -0
  23. package/assets/manage/10-bond-increase.sh +149 -0
  24. package/assets/manage/10-diagnose-node.sh +169 -0
  25. package/assets/manage/20-exit-validator.sh +259 -0
  26. package/assets/manage/25-recover-node.sh +291 -0
  27. package/assets/manage/30-status.sh +64 -0
  28. package/assets/manage/40-exit-withdraw.sh +133 -0
  29. package/assets/manage/bond_inc.sh +318 -0
  30. package/assets/manage/dashboard_monitor/app.js +1544 -0
  31. package/assets/manage/dashboard_monitor/assets/v2.2/css/base.css +113 -0
  32. package/assets/manage/dashboard_monitor/assets/v2.2/css/components/content.css +133 -0
  33. package/assets/manage/dashboard_monitor/assets/v2.2/css/components/controls.css +229 -0
  34. package/assets/manage/dashboard_monitor/assets/v2.2/css/components/data.css +374 -0
  35. package/assets/manage/dashboard_monitor/assets/v2.2/css/components/icons.css +35 -0
  36. package/assets/manage/dashboard_monitor/assets/v2.2/css/components/motion.css +57 -0
  37. package/assets/manage/dashboard_monitor/assets/v2.2/css/components/shell.css +51 -0
  38. package/assets/manage/dashboard_monitor/assets/v2.2/css/components/sidebar.css +242 -0
  39. package/assets/manage/dashboard_monitor/assets/v2.2/css/components/topbar.css +138 -0
  40. package/assets/manage/dashboard_monitor/assets/v2.2/css/fonts.css +30 -0
  41. package/assets/manage/dashboard_monitor/assets/v2.2/css/synteq-ui-v2.css +25 -0
  42. package/assets/manage/dashboard_monitor/assets/v2.2/css/theme-dark.css +38 -0
  43. package/assets/manage/dashboard_monitor/assets/v2.2/css/theme-light.css +36 -0
  44. package/assets/manage/dashboard_monitor/assets/v2.2/css/tokens.css +61 -0
  45. package/assets/manage/dashboard_monitor/assets/v2.2/fonts/GoogleSansCode-Medium.ttf +0 -0
  46. package/assets/manage/dashboard_monitor/assets/v2.2/fonts/GoogleSansCode-Regular.ttf +0 -0
  47. package/assets/manage/dashboard_monitor/assets/v2.2/fonts/GoogleSansCode-SemiBold.ttf +0 -0
  48. package/assets/manage/dashboard_monitor/assets/v2.2/js/synteq-ui-v2.js +869 -0
  49. package/assets/manage/dashboard_monitor/deploy-dashboard.sh +230 -0
  50. package/assets/manage/dashboard_monitor/favicon.svg +13 -0
  51. package/assets/manage/dashboard_monitor/index.html +1197 -0
  52. package/assets/manage/dashboard_monitor/nginx-octra.conf +76 -0
  53. package/assets/manage/dashboard_monitor/octra-dashboard.service +29 -0
  54. package/assets/manage/dashboard_monitor/octra-node-prune.cron +12 -0
  55. package/assets/manage/dashboard_monitor/octra-node-prune.service +16 -0
  56. package/assets/manage/dashboard_monitor/octra-node-prune.timer +12 -0
  57. package/assets/manage/dashboard_monitor/prune-node-junk.sh +329 -0
  58. package/assets/manage/dashboard_monitor/server.py +1887 -0
  59. package/assets/manage/dashboard_monitor/ssl-certbot.sh +90 -0
  60. package/assets/manage/dashboard_monitor/styles.css +327 -0
  61. package/assets/manage/lib.sh +347 -0
  62. package/assets/manifest.json +143 -0
  63. package/assets/migrate/00-backup-identity.sh +67 -0
  64. package/assets/migrate/01-prepare-target.sh +145 -0
  65. package/assets/migrate/02-authorize-pull.sh +43 -0
  66. package/assets/migrate/03-pull-state.sh +102 -0
  67. package/assets/migrate/04-stop-source.sh +85 -0
  68. package/assets/migrate/05-finalize-target.sh +140 -0
  69. package/assets/migrate/06-start-target.sh +57 -0
  70. package/assets/migrate/07-verify.sh +237 -0
  71. package/assets/migrate/08-rollback.sh +54 -0
  72. package/assets/migrate/lib.sh +149 -0
  73. package/assets/upgrade/10-preflight.sh +120 -0
  74. package/assets/upgrade/20-upgrade.sh +142 -0
  75. package/assets/upgrade/30-manual-upgrade.sh +129 -0
  76. package/assets/upgrade/40-storage-prune.sh +65 -0
  77. package/assets/upgrade/50-status.sh +58 -0
  78. package/assets/upgrade/lib.sh +232 -0
  79. package/bin/ovtools.js +32 -0
  80. package/dist/api/checks.d.ts +6 -0
  81. package/dist/api/checks.d.ts.map +1 -0
  82. package/dist/api/checks.js +173 -0
  83. package/dist/api/checks.js.map +1 -0
  84. package/dist/api/hostkeys.d.ts +10 -0
  85. package/dist/api/hostkeys.d.ts.map +1 -0
  86. package/dist/api/hostkeys.js +58 -0
  87. package/dist/api/hostkeys.js.map +1 -0
  88. package/dist/api/jobs.d.ts +33 -0
  89. package/dist/api/jobs.d.ts.map +1 -0
  90. package/dist/api/jobs.js +483 -0
  91. package/dist/api/jobs.js.map +1 -0
  92. package/dist/api/nodes.d.ts +16 -0
  93. package/dist/api/nodes.d.ts.map +1 -0
  94. package/dist/api/nodes.js +126 -0
  95. package/dist/api/nodes.js.map +1 -0
  96. package/dist/api/server.d.ts +3 -0
  97. package/dist/api/server.d.ts.map +1 -0
  98. package/dist/api/server.js +443 -0
  99. package/dist/api/server.js.map +1 -0
  100. package/dist/cli.d.ts +3 -0
  101. package/dist/cli.d.ts.map +1 -0
  102. package/dist/cli.js +301 -0
  103. package/dist/cli.js.map +1 -0
  104. package/dist/constants.d.ts +76 -0
  105. package/dist/constants.d.ts.map +1 -0
  106. package/dist/constants.js +150 -0
  107. package/dist/constants.js.map +1 -0
  108. package/dist/index.d.ts +18 -0
  109. package/dist/index.d.ts.map +1 -0
  110. package/dist/index.js +60 -0
  111. package/dist/index.js.map +1 -0
  112. package/dist/orchestrator/base.d.ts +52 -0
  113. package/dist/orchestrator/base.d.ts.map +1 -0
  114. package/dist/orchestrator/base.js +219 -0
  115. package/dist/orchestrator/base.js.map +1 -0
  116. package/dist/orchestrator/deploy.d.ts +33 -0
  117. package/dist/orchestrator/deploy.d.ts.map +1 -0
  118. package/dist/orchestrator/deploy.js +313 -0
  119. package/dist/orchestrator/deploy.js.map +1 -0
  120. package/dist/orchestrator/manage.d.ts +28 -0
  121. package/dist/orchestrator/manage.d.ts.map +1 -0
  122. package/dist/orchestrator/manage.js +267 -0
  123. package/dist/orchestrator/manage.js.map +1 -0
  124. package/dist/orchestrator/migrate.d.ts +31 -0
  125. package/dist/orchestrator/migrate.d.ts.map +1 -0
  126. package/dist/orchestrator/migrate.js +442 -0
  127. package/dist/orchestrator/migrate.js.map +1 -0
  128. package/dist/ssh/client.d.ts +39 -0
  129. package/dist/ssh/client.d.ts.map +1 -0
  130. package/dist/ssh/client.js +418 -0
  131. package/dist/ssh/client.js.map +1 -0
  132. package/dist/test/core.test.d.ts +2 -0
  133. package/dist/test/core.test.d.ts.map +1 -0
  134. package/dist/test/core.test.js +188 -0
  135. package/dist/test/core.test.js.map +1 -0
  136. package/dist/types.d.ts +267 -0
  137. package/dist/types.d.ts.map +1 -0
  138. package/dist/types.js +3 -0
  139. package/dist/types.js.map +1 -0
  140. package/dist/utils/assets.d.ts +28 -0
  141. package/dist/utils/assets.d.ts.map +1 -0
  142. package/dist/utils/assets.js +583 -0
  143. package/dist/utils/assets.js.map +1 -0
  144. package/dist/utils/browser.d.ts +2 -0
  145. package/dist/utils/browser.d.ts.map +1 -0
  146. package/dist/utils/browser.js +23 -0
  147. package/dist/utils/browser.js.map +1 -0
  148. package/dist/utils/dashboard.d.ts +16 -0
  149. package/dist/utils/dashboard.d.ts.map +1 -0
  150. package/dist/utils/dashboard.js +177 -0
  151. package/dist/utils/dashboard.js.map +1 -0
  152. package/dist/utils/helpers.d.ts +29 -0
  153. package/dist/utils/helpers.d.ts.map +1 -0
  154. package/dist/utils/helpers.js +204 -0
  155. package/dist/utils/helpers.js.map +1 -0
  156. package/dist/utils/scripts.d.ts +60 -0
  157. package/dist/utils/scripts.d.ts.map +1 -0
  158. package/dist/utils/scripts.js +174 -0
  159. package/dist/utils/scripts.js.map +1 -0
  160. package/dist/utils/state.d.ts +12 -0
  161. package/dist/utils/state.d.ts.map +1 -0
  162. package/dist/utils/state.js +44 -0
  163. package/dist/utils/state.js.map +1 -0
  164. package/dist/utils/tar.d.ts +11 -0
  165. package/dist/utils/tar.d.ts.map +1 -0
  166. package/dist/utils/tar.js +93 -0
  167. package/dist/utils/tar.js.map +1 -0
  168. package/dist/utils/update.d.ts +9 -0
  169. package/dist/utils/update.d.ts.map +1 -0
  170. package/dist/utils/update.js +134 -0
  171. package/dist/utils/update.js.map +1 -0
  172. package/dist/utils/websocket.d.ts +10 -0
  173. package/dist/utils/websocket.d.ts.map +1 -0
  174. package/dist/utils/websocket.js +203 -0
  175. package/dist/utils/websocket.js.map +1 -0
  176. package/package.json +55 -4
  177. package/public/app.js +2712 -0
  178. package/public/assets/v2.2/css/base.css +113 -0
  179. package/public/assets/v2.2/css/components/content.css +133 -0
  180. package/public/assets/v2.2/css/components/controls.css +229 -0
  181. package/public/assets/v2.2/css/components/data.css +373 -0
  182. package/public/assets/v2.2/css/components/icons.css +35 -0
  183. package/public/assets/v2.2/css/components/motion.css +57 -0
  184. package/public/assets/v2.2/css/components/shell.css +51 -0
  185. package/public/assets/v2.2/css/components/sidebar.css +242 -0
  186. package/public/assets/v2.2/css/components/topbar.css +138 -0
  187. package/public/assets/v2.2/css/fonts.css +30 -0
  188. package/public/assets/v2.2/css/synteq-ui-v2.css +25 -0
  189. package/public/assets/v2.2/css/theme-dark.css +38 -0
  190. package/public/assets/v2.2/css/theme-light.css +36 -0
  191. package/public/assets/v2.2/css/tokens.css +61 -0
  192. package/public/assets/v2.2/fonts/GoogleSansCode-Medium.ttf +0 -0
  193. package/public/assets/v2.2/fonts/GoogleSansCode-Regular.ttf +0 -0
  194. package/public/assets/v2.2/fonts/GoogleSansCode-SemiBold.ttf +0 -0
  195. package/public/assets/v2.2/js/synteq-ui-v2.js +869 -0
  196. package/public/favicon.svg +7 -0
  197. package/public/index.html +1080 -0
  198. package/public/styles.css +552 -0
@@ -0,0 +1,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