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,145 @@
1
+ #!/usr/bin/env bash
2
+ # ===================================================================
3
+ # 01-prepare-target.sh — prepare the NEW VPS for the data pull
4
+ #
5
+ # * installs the tools needed by the migration (rsync, jq, git, ...)
6
+ # * creates the `octra` system user (same as scripts/install-deps.sh)
7
+ # * creates the node directories on the 1 TB volume
8
+ # * generates an SSH key for root so it can pull from the OLD VPS
9
+ #
10
+ # Run on the NEW VPS AFTER scripts/pre-run.sh:
11
+ # sudo bash 01-prepare-target.sh
12
+ # Optional:
13
+ # FORCE=1 keep an existing data set (resumable copy of this migration)
14
+ # RESET_TARGET=1 drop a stale/foreign node state and start clean
15
+ #
16
+ # Idempotent - safe to re-run.
17
+ # ===================================================================
18
+
19
+ set -euo pipefail
20
+ source "$(dirname "$0")/lib.sh"
21
+
22
+ require_root
23
+ set_status RUNNING "prepare-target"
24
+ rm -f "${MIG_DIR}/prep.blocked"
25
+
26
+ # --- Freshness guard -------------------------------------------------
27
+ # Never prepare over an existing node: a second copy of the same wallet is
28
+ # exactly what must not happen.
29
+ #
30
+ # A provider block volume SURVIVES an OS re-install, so "I just reinstalled the
31
+ # VPS" does not guarantee an empty chain state. pre-run.sh formats any volume
32
+ # that does not carry an Octra data set (automatic, no checkbox) and records
33
+ # what it did in /root/migration/prerun.env. The orchestrator reads that file and
34
+ # passes the matching flag, so this script never contradicts pre-run.sh:
35
+ #
36
+ # (nothing) fresh target - nothing to protect
37
+ # FORCE=1 the data set is a resumable copy of THIS migration -
38
+ # keep it and prepare anyway
39
+ # RESET_TARGET=1 the data set is stale/foreign - drop the node state
40
+ # (chain state, sync stage, source tree + keys) and start
41
+ # clean; the pull copies the source's state right after
42
+ # WIPE_VOLUME=1 ... bash /root/pre-run.sh (reformat the volume instead)
43
+ FRESH=1
44
+ if [[ -f "${NODE_SRC_DIR}/.keys/validator/wallet.json" ]]; then
45
+ FRESH=0
46
+ fi
47
+ if [[ -f "${NODE_DATA_DIR}/HEAD.json" ]]; then
48
+ FRESH=0
49
+ fi
50
+
51
+ reset_target_state() {
52
+ local path
53
+ warn "RESET_TARGET=1 - dropping the stale node state on $(hostname):"
54
+ for path in "${NODE_DATA_DIR}" "${NODE_SYNC_STAGE}" "${NODE_SRC_DIR}"; do
55
+ [[ -e "${path}" ]] || continue
56
+ warn " rm -rf ${path}"
57
+ rm -rf "${path}"
58
+ done
59
+ log "node state cleared - the pull will copy the source state next"
60
+ }
61
+
62
+ if [[ "${RESET_TARGET:-0}" == "1" ]]; then
63
+ if [[ -n "$(node_processes)" ]]; then
64
+ die "RESET_TARGET=1 refused: a node is already running on this host ($(node_processes | awk '{print $1}' | head -1)) - stop it first, this host would be a live validator"
65
+ fi
66
+ if (( FRESH == 0 )); then
67
+ reset_target_state
68
+ else
69
+ log "RESET_TARGET=1 requested, but the target is already fresh - nothing to clear"
70
+ fi
71
+ elif [[ "${FORCE:-0}" == "1" ]]; then
72
+ if (( FRESH == 0 )); then
73
+ warn "FORCE=1 - keeping the existing data set on this host (resuming the same migration)"
74
+ fi
75
+ elif (( FRESH == 0 )); then
76
+ if [[ -f "${NODE_SRC_DIR}/.keys/validator/wallet.json" ]]; then
77
+ FOUND="${NODE_SRC_DIR}/.keys/validator/wallet.json"
78
+ else
79
+ FOUND="${NODE_DATA_DIR}/HEAD.json"
80
+ fi
81
+ # Machine-readable marker: the orchestrator turns it into a self-heal
82
+ # (reformat the volume, or RESET_TARGET=1) instead of retrying the same call.
83
+ printf 'target-not-fresh %s\n' "${FOUND}" > "${MIG_DIR}/prep.blocked"
84
+ die "${FOUND} already exists - the target still holds a node (nothing of this migration has been copied yet): pre-run.sh with WIPE_VOLUME=1 reformats the volume, RESET_TARGET=1 drops just the node state, FORCE=1 keeps it (resume)"
85
+ fi
86
+
87
+ log "Checking the migration tooling (rsync, jq, git, curl, psmisc)..."
88
+ export DEBIAN_FRONTEND=noninteractive
89
+ export TERM="${TERM:-dumb}"
90
+ # check -> install -> continue: a fresh VPS may have none of these, and a retry
91
+ # must not re-run a full apt install for tools that are already there
92
+ ensure_package ca-certificates || die "cannot install ca-certificates"
93
+ ensure_tool rsync rsync || die "rsync is missing and could not be installed"
94
+ ensure_tool jq jq || die "jq is missing and could not be installed"
95
+ ensure_tool git git || die "git is missing and could not be installed"
96
+ ensure_tool curl curl || die "curl is missing and could not be installed"
97
+ ensure_tool wget wget || die "wget is missing and could not be installed"
98
+ ensure_tool pgrep psmisc || die "pgrep (psmisc) is missing and could not be installed"
99
+ ensure_tool pkill psmisc || die "pkill (psmisc) is missing and could not be installed"
100
+ ensure_package procps || die "cannot install procps"
101
+ log "Packages ready: $(rsync --version | head -1)"
102
+
103
+ # --- octra user (same convention as scripts/install-deps.sh) --------
104
+ if ! id "${NODE_USER}" &>/dev/null; then
105
+ log "Creating system user '${NODE_USER}'..."
106
+ useradd -r -m -d "/home/${NODE_USER}" -s /bin/bash "${NODE_USER}"
107
+ else
108
+ log "User '${NODE_USER}' already exists ($(id -u "${NODE_USER}"))"
109
+ fi
110
+
111
+ # --- Data store must exist before we write anything -----------------
112
+ # pre-run.sh has already mounted a 1 TB volume, or (no volume attached) created
113
+ # the directory on the largest filesystem. Either way the path must exist and be
114
+ # writable, and every sub-directory is created here when missing.
115
+ ensure_dirs
116
+ if ! mountpoint -q "${NODE_DATA_ROOT}" 2>/dev/null; then
117
+ warn "${NODE_DATA_ROOT} is not a separate mount point - the node data lives on the root filesystem"
118
+ fi
119
+ FREE_GB=$(df -BG --output=avail "${NODE_DATA_ROOT}" 2>/dev/null | tail -1 | tr -dc '0-9')
120
+ if [[ -n "${FREE_GB}" ]] && (( FREE_GB < 60 )); then
121
+ die "not enough free space on ${NODE_DATA_ROOT} (${FREE_GB}G free, ~41 GB is copied)"
122
+ fi
123
+ log "Data root: ${NODE_DATA_ROOT} ($(df -h "${NODE_DATA_ROOT}" | tail -1 | awk '{print $2}' ) total, $(df -h "${NODE_DATA_ROOT}" | tail -1 | awk '{print $4}') free)"
124
+
125
+ # --- Root SSH key used to pull data from the OLD VPS ----------------
126
+ # NOTE: we deliberately do NOT touch sshd_config. Password login is the
127
+ # operator's out-of-band access path; pubkey auth is already enabled by
128
+ # default on Debian, which is all the pull needs.
129
+ install -d -m 700 /root/.ssh
130
+ if [[ ! -f /root/.ssh/id_ed25519 ]]; then
131
+ log "Generating root SSH key for the data pull..."
132
+ ssh-keygen -t ed25519 -N "" -C "octra-migration@$(hostname)" -f /root/.ssh/id_ed25519 >/dev/null
133
+ fi
134
+
135
+ cp /root/.ssh/id_ed25519.pub "${MIG_DIR}/target_ssh_key.pub"
136
+ chmod 644 "${MIG_DIR}/target_ssh_key.pub"
137
+
138
+ log "Target prepared."
139
+ log "Disk: $(df -h "${NODE_DATA_ROOT}" | tail -1 | awk '{print $2" total, "$4" free"}')"
140
+ log "Swap: $(free -h | awk '/^Swap:/ {print $2}')"
141
+ echo
142
+ log "PUBLIC KEY (must be appended to ${OLD_USER}@${OLD_HOST:-<old-vps>}:~/.ssh/authorized_keys):"
143
+ cat "${MIG_DIR}/target_ssh_key.pub"
144
+
145
+ set_status DONE "prepare-target"
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env bash
2
+ # ===================================================================
3
+ # 02-authorize-pull.sh — run ON THE OLD VPS
4
+ #
5
+ # Appends the NEW VPS root public key to root's authorized_keys so the
6
+ # target can rsync the node state directly from the source machine.
7
+ #
8
+ # Usage (on the OLD vps):
9
+ # bash 02-authorize-pull.sh "ssh-ed25519 AAAA... octra-migration@onode"
10
+ # bash 02-authorize-pull.sh /root/migration/target_ssh_key.pub
11
+ #
12
+ # Idempotent — the key is only added once.
13
+ # ===================================================================
14
+
15
+ set -euo pipefail
16
+ source "$(dirname "$0")/lib.sh"
17
+
18
+ require_root
19
+
20
+ KEY_INPUT="${1:-${MIG_DIR}/target_ssh_key.pub}"
21
+ [[ -n "${KEY_INPUT}" ]] || die "usage: $0 <pubkey-line|pubkey-file>"
22
+
23
+ if [[ -f "${KEY_INPUT}" ]]; then
24
+ PUBKEY=$(tr -d '\r\n' < "${KEY_INPUT}")
25
+ else
26
+ PUBKEY=$(printf '%s' "${KEY_INPUT}" | tr -d '\r\n')
27
+ fi
28
+
29
+ [[ "${PUBKEY}" == ssh-* ]] || die "not a public key: ${PUBKEY:0:40}..."
30
+
31
+ install -d -m 700 /root/.ssh
32
+ touch /root/.ssh/authorized_keys
33
+ chmod 600 /root/.ssh/authorized_keys
34
+
35
+ if grep -qxF "${PUBKEY}" /root/.ssh/authorized_keys; then
36
+ log "Key already authorized — nothing to do"
37
+ else
38
+ printf '%s\n' "${PUBKEY}" >> /root/.ssh/authorized_keys
39
+ log "Key authorized for root"
40
+ fi
41
+
42
+ log "authorized_keys now holds $(wc -l < /root/.ssh/authorized_keys) key(s)"
43
+ set_status DONE "authorize-pull"
@@ -0,0 +1,102 @@
1
+ #!/usr/bin/env bash
2
+ # ===================================================================
3
+ # 03-pull-state.sh — copy the whole validator from OLD VPS to this one
4
+ #
5
+ # pull 1 (initial) : while the old node is STILL RUNNING
6
+ # → whole-file mode (-W), fastest for a cold copy
7
+ # pull 2 (final) : after 04-stop-source.sh has stopped the old node
8
+ # → rolling-checksum delta, so only appended bytes
9
+ # of the live store suffixes are sent
10
+ #
11
+ # Usage (on the NEW vps):
12
+ # OLD_HOST=203.0.113.27 bash 03-pull-state.sh # initial
13
+ # OLD_HOST=203.0.113.27 bash 03-pull-state.sh --final # delta
14
+ #
15
+ # Copies:
16
+ # /opt/octra/libv_litecore/ (source tree, toolchain, binaries, .keys)
17
+ # /var/lib/octra/devnet/ (chain state, 39 GB)
18
+ # /var/lib/octra/devnet.state_sync/
19
+ # ===================================================================
20
+
21
+ set -euo pipefail
22
+ source "$(dirname "$0")/lib.sh"
23
+
24
+ require_root
25
+ require_old_host
26
+ require_cmd rsync
27
+
28
+ MODE="initial"
29
+ [[ "${1:-}" == "--final" ]] && MODE="final"
30
+
31
+ RSYNC_OPTS=(-aH --numeric-ids --info=progress2 --stats --no-inc-recursive)
32
+ if [[ "${MODE}" == "initial" ]]; then
33
+ RSYNC_OPTS+=(-W)
34
+ else
35
+ # delta mode: list everything so appended store bytes are found quickly
36
+ RSYNC_OPTS=(-aH --numeric-ids --info=progress2 --stats --delete --delete-delay)
37
+ fi
38
+
39
+ FREE_GB=$(df -BG --output=avail "${NODE_DATA_ROOT}" | tail -1 | tr -dc '0-9')
40
+ [[ "${FREE_GB}" -ge 60 ]] || die "not enough free space on ${NODE_DATA_ROOT} (${FREE_GB}G)"
41
+
42
+ # While the source node is RUNNING its logs/WAL/receipt files vanish under
43
+ # rsync's feet, which makes rsync exit 23/24. That is expected: the final
44
+ # delta pass (run with the source stopped) reconciles whatever moved.
45
+ RSYNC_TOLERANT=(23 24)
46
+ run_rsync() { # run_rsync <label> <src> <dst>
47
+ local label="$1" src="$2" dst="$3" rc=0
48
+ log "--- ${label} ---"
49
+ # rsync 3.5 prints one progress line per parallel transfer thread, which
50
+ # produced multi-megabyte single-line log entries. Collapse each update to
51
+ # its last "bytes N% rate" field and keep byte totals / warnings, so the
52
+ # log stays readable while the orchestrator can still poll the percentage.
53
+ # set +e / PIPESTATUS: grep returns 1 when nothing matches, and we must not
54
+ # lose rsync's own exit code.
55
+ set +e
56
+ rsync "${RSYNC_OPTS[@]}" \
57
+ -e "ssh ${SSH_OPTS[*]} -o Compression=no" \
58
+ "${src}" "${dst}" 2>&1 \
59
+ | tr '\r' '\n' \
60
+ | grep -a --line-buffered -E '[0-9]+%|^sent |^total size|^rsync' \
61
+ | sed -u -E 's/^.* ([0-9,]+ +[0-9]+% +[0-9.]+[kMG]?B\/s).*$/\1/'
62
+ rc=${PIPESTATUS[0]}
63
+ set -e
64
+ if (( rc == 0 )); then
65
+ return 0
66
+ fi
67
+ local tolerated=0
68
+ for code in "${RSYNC_TOLERANT[@]}"; do
69
+ [[ "${rc}" -eq "${code}" ]] && tolerated=1
70
+ done
71
+ if (( tolerated == 1 )); then
72
+ warn "${label}: rsync exit ${rc} (files changed during the copy) — tolerated on a live node; the --final pass fixes it"
73
+ return 0
74
+ fi
75
+ die "${label}: rsync failed with exit ${rc}"
76
+ }
77
+
78
+ set_status RUNNING "pull-${MODE}"
79
+ START=$(date +%s)
80
+ log "=== pull (${MODE}) from ${OLD_USER}@${OLD_HOST} ==="
81
+
82
+ run_rsync "1/3 source tree ${NODE_SRC_DIR}" \
83
+ "${OLD_USER}@${OLD_HOST}:${NODE_SRC_DIR}/" "${NODE_SRC_DIR}/"
84
+
85
+ mkdir -p "${NODE_DATA_DIR}"
86
+ run_rsync "2/3 chain state ${NODE_DATA_DIR}" \
87
+ "${OLD_USER}@${OLD_HOST}:${NODE_DATA_DIR}/" "${NODE_DATA_DIR}/"
88
+
89
+ mkdir -p "${NODE_SYNC_STAGE}"
90
+ if on_old "test -d ${NODE_SYNC_STAGE}"; then
91
+ run_rsync "3/3 state-sync stage ${NODE_SYNC_STAGE}" \
92
+ "${OLD_USER}@${OLD_HOST}:${NODE_SYNC_STAGE}/" "${NODE_SYNC_STAGE}/"
93
+ else
94
+ warn "state-sync stage does not exist on the source — skipped"
95
+ fi
96
+
97
+ ELAPSED=$(( $(date +%s) - START ))
98
+ log "=== pull (${MODE}) finished in $((ELAPSED / 60))m$((ELAPSED % 60))s ==="
99
+ log "Local size: src $(du -sh "${NODE_SRC_DIR}" | awk '{print $1}'), data $(du -sh "${NODE_DATA_DIR}" | awk '{print $1}')"
100
+ log "Source size: $(on_old "du -sh ${NODE_SRC_DIR} ${NODE_DATA_DIR} 2>/dev/null | tr '\n' ' '")"
101
+
102
+ set_status DONE "pull-${MODE}" "${ELAPSED}s"
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env bash
2
+ # ===================================================================
3
+ # 04-stop-source.sh — stop the validator on the OLD VPS (decommission)
4
+ #
5
+ # A validator wallet must NEVER be active on two machines at once
6
+ # (double-signing / equivocation). This step:
7
+ # 1. records the last status of the old node
8
+ # 2. stops it via the node's own controls/stop.sh (pm2)
9
+ # 3. proves no octra_node process is left
10
+ # 4. disables the pm2 boot unit so it cannot resurrect after a reboot
11
+ # 5. drops a decommission note in the old node's home
12
+ #
13
+ # Usage (on the NEW vps):
14
+ # OLD_HOST=203.0.113.27 bash 04-stop-source.sh
15
+ # ===================================================================
16
+
17
+ set -euo pipefail
18
+ source "$(dirname "$0")/lib.sh"
19
+
20
+ require_root
21
+ require_old_host
22
+ set_status RUNNING "stop-source"
23
+ log "=== stopping node on ${OLD_HOST} ==="
24
+
25
+ # --- 1. record final status of the source node ----------------------
26
+ # Keep the first (running) snapshot: on a re-run the node is already down
27
+ # and the status no longer carries the live epoch/voting fields.
28
+ if [[ ! -s "${MIG_DIR}/source-final-stat.txt" ]] || ! grep -q '^address = ' "${MIG_DIR}/source-final-stat.txt"; then
29
+ log "Capturing final status of the old node..."
30
+ on_old "su - ${NODE_USER} -c 'cd ${NODE_SRC_DIR} && sh controls/stat.sh' 2>&1" \
31
+ | tee "${MIG_DIR}/source-final-stat.txt" | grep -E '^(role|address|epoch|head_epoch|voting|validator_active) =' || true
32
+ else
33
+ log "Keeping existing source status snapshot ($(grep -m1 '^address = ' "${MIG_DIR}/source-final-stat.txt"))"
34
+ fi
35
+
36
+ WALLET_ADDRESS=$(grep -m1 '^address = ' "${MIG_DIR}/source-final-stat.txt" | awk -F' = ' '{print $2}' || true)
37
+ [[ -n "${WALLET_ADDRESS}" ]] || warn "could not read wallet address from source status"
38
+ log "Source wallet: ${WALLET_ADDRESS:-unknown}"
39
+
40
+ # --- 2. graceful stop ----------------------------------------------
41
+ log "Running controls/stop.sh on the source..."
42
+ on_old "su - ${NODE_USER} -c 'cd ${NODE_SRC_DIR} && sh controls/stop.sh' 2>&1" | tee -a "${MIG_LOG}"
43
+
44
+ # --- 3. verify nothing is left running -----------------------------
45
+ LEFT=$(on_old_node_processes)
46
+ for i in {1..20}; do
47
+ [[ -z "${LEFT}" ]] && break
48
+ sleep 1
49
+ LEFT=$(on_old_node_processes)
50
+ done
51
+
52
+ if [[ -n "${LEFT}" ]]; then
53
+ warn "node processes still alive after stop — sending SIGTERM"
54
+ on_old "pkill -TERM -f '${NODE_PROC_RE}' || true"
55
+ sleep 5
56
+ LEFT=$(on_old_node_processes)
57
+ fi
58
+ if [[ -n "${LEFT}" ]]; then
59
+ die "could not stop the source node — DO NOT start the new node (wallet would be double-active)"
60
+ fi
61
+ log "Source node processes are gone (none left)"
62
+
63
+ # --- 4. make sure it cannot come back on reboot ---------------------
64
+ if on_old "systemctl is-enabled --quiet pm2-${NODE_USER} 2>/dev/null"; then
65
+ on_old "systemctl disable pm2-${NODE_USER} 2>/dev/null || true"
66
+ log "Disabled pm2-${NODE_USER} boot unit on the source"
67
+ else
68
+ log "pm2 boot unit on the source already disabled"
69
+ fi
70
+ on_old "su - ${NODE_USER} -c 'pm2 save --force' >/dev/null 2>&1 || true"
71
+
72
+ # --- 5. decommission note ------------------------------------------
73
+ on_old "cat > /root/MIGRATED-AWAY.txt <<'EOF'
74
+ This VPS no longer runs the Octra validator.
75
+ The validator (wallet ${WALLET_ADDRESS:-unknown}) was migrated on $(date -u +%Y-%m-%dT%H:%M:%SZ).
76
+ New host: $(hostname) / see the operator's records.
77
+ DO NOT start the node here again while the new host is live: the same wallet
78
+ would be active twice and the validator would equivocate.
79
+ EOF"
80
+ log "Decommission note written to ${OLD_HOST}:/root/MIGRATED-AWAY.txt"
81
+
82
+ log "=== source stopped — safe to run the final delta pull ==="
83
+ date -u +%Y-%m-%dT%H:%M:%SZ > "${MIG_DIR}/downtime-start.txt"
84
+ log "downtime clock started: $(cat "${MIG_DIR}/downtime-start.txt")"
85
+ set_status DONE "stop-source"
@@ -0,0 +1,140 @@
1
+ #!/usr/bin/env bash
2
+ # ===================================================================
3
+ # 05-finalize-target.sh — make the copied tree bootable on this host
4
+ #
5
+ # 1. back up node.env and point OCTRA_ADVERTISE_ENDPOINT at this VPS
6
+ # 2. verify the binary hashes still match node.env (integrity gate)
7
+ # 3. fix ownership (the copy may carry the source's numeric uid)
8
+ # 4. run controls/install.sh (runtime libs + nodejs + pm2 + boot unit)
9
+ # 5. run controls/lib/validator_config.py --check-runtime
10
+ #
11
+ # Nothing here touches config/network.env, the chain data or the wallet:
12
+ # a migrated validator must NOT re-sync and must NOT re-enroll.
13
+ #
14
+ # Usage (on the NEW vps):
15
+ # NEW_HOST=203.0.113.133 bash 05-finalize-target.sh
16
+ # ===================================================================
17
+
18
+ set -euo pipefail
19
+ source "$(dirname "$0")/lib.sh"
20
+
21
+ require_root
22
+ NEW_HOST="${NEW_HOST:-${1:-}}"
23
+ [[ -n "${NEW_HOST}" ]] || NEW_HOST=$(curl -s --max-time 5 https://api.ipify.org 2>/dev/null || hostname -I | awk '{print $1}')
24
+
25
+ [[ -f "${NODE_ENV}" ]] || die "node.env missing at ${NODE_ENV} — did the pull complete?"
26
+ [[ -f "${NODE_SRC_DIR}/.keys/validator/wallet.json" ]] || die "wallet.json missing"
27
+ set_status RUNNING "finalize-target"
28
+
29
+ # --- 1. advertise endpoint (the only host-specific field) -----------
30
+ log "=== finalize (advertise → ${NEW_HOST}:${CONSENSUS_PORT}) ==="
31
+ CURRENT_ADV=$(grep -m1 '^OCTRA_ADVERTISE_ENDPOINT=' "${NODE_ENV}" | cut -d= -f2-)
32
+ log "current advertise: ${CURRENT_ADV}"
33
+
34
+ if [[ "${CURRENT_ADV}" == "${NEW_HOST}:${CONSENSUS_PORT}" ]]; then
35
+ log "advertise already correct"
36
+ else
37
+ cp -a "${NODE_ENV}" "${NODE_ENV}.pre-migration.bak"
38
+ sed -i "s|^OCTRA_ADVERTISE_ENDPOINT=.*|OCTRA_ADVERTISE_ENDPOINT=${NEW_HOST}:${CONSENSUS_PORT}|" "${NODE_ENV}"
39
+ log "advertise updated: $(grep -m1 '^OCTRA_ADVERTISE_ENDPOINT=' "${NODE_ENV}")"
40
+ log "backup kept at ${NODE_ENV}.pre-migration.bak"
41
+ fi
42
+
43
+ # --- 2. integrity gate: binaries must still match node.env ---------
44
+ log "Verifying binary hashes against node.env..."
45
+ check_hash() { # check_hash <env_key> <file>
46
+ local key="$1" file="$2" want got
47
+ want=$(grep -m1 "^${key}=" "${NODE_ENV}" | cut -d= -f2-)
48
+ got=$(sha256sum "${file}" | awk '{print $1}')
49
+ if [[ "${want}" != "${got}" ]]; then
50
+ die "${key} mismatch: env=${want} file=${got} (${file})"
51
+ fi
52
+ log " ${key} OK"
53
+ }
54
+ check_hash OCTRA_BINARY_HASH "${NODE_SRC_DIR}/_build/default/bin/octra_node.exe"
55
+ check_hash OCTRA_PVAC_VERIFY_WORKER_HASH "${NODE_SRC_DIR}/_build/default/bin/octra_pvac_worker.exe"
56
+ check_hash OCTRA_OPERATOR_SYNC_BINARY_HASH "${NODE_SRC_DIR}/_build/default/bin/octra_state_sync_client.exe"
57
+ check_hash OCTRA_OPERATOR_CONTROL_BINARY_HASH "${NODE_SRC_DIR}/_build/default/bin/bft_control_tx.exe"
58
+
59
+ NET_WANT=$(grep -m1 '^OCTRA_OPERATOR_NETWORK_SHA256=' "${NODE_ENV}" | cut -d= -f2-)
60
+ for f in "${NODE_SRC_DIR}/.keys/validator/network.env" "${NODE_SRC_DIR}/config/network.env"; do
61
+ [[ "$(sha256sum "$f" | awk '{print $1}')" == "${NET_WANT}" ]] || die "network bundle hash mismatch: $f"
62
+ done
63
+ log " network bundle OK ($(grep -m1 '^OCTRA_CHAIN_ID=' "${NODE_ENV}"))"
64
+
65
+ # --- 3. ownership ---------------------------------------------------
66
+ log "Fixing ownership (${NODE_USER})..."
67
+ chown -R "${NODE_USER}:${NODE_USER}" "${NODE_SRC_DIR}" "${NODE_DATA_ROOT}"
68
+ chmod 700 "${NODE_SRC_DIR}/.keys/validator" "${NODE_DATA_DIR}" 2>/dev/null || true
69
+ chmod 600 "${NODE_SRC_DIR}/.keys/validator/"*.json "${NODE_ENV}" 2>/dev/null || true
70
+
71
+ # --- 4. runtime packages + process manager --------------------------
72
+ # `install.sh` runs apt-get + npm, which can take minutes. Holding a single SSH
73
+ # session open for that long is what killed the 2026-10-30 run ("Network error:
74
+ # Software caused connection abort" mid-apt), so it runs detached in a systemd
75
+ # unit and this script only polls a status file. A dropped SSH is then harmless:
76
+ # re-running this step picks up the finished install.
77
+ if command -v pm2 >/dev/null 2>&1 && dpkg -s libev4 libgmp10 liblmdb0 nodejs npm >/dev/null 2>&1; then
78
+ log "Runtime packages + pm2 already installed - skipping install.sh"
79
+ else
80
+ INSTALL_UNIT="octra-finalize-install"
81
+ INSTALL_LOG="${MIG_DIR}/install-target.log"
82
+ INSTALL_STATUS="${MIG_DIR}/install-target.status"
83
+ INSTALL_TIMEOUT="${INSTALL_TIMEOUT:-1800}"
84
+ rm -f "${INSTALL_LOG}" "${INSTALL_STATUS}"
85
+ systemctl reset-failed "${INSTALL_UNIT}" 2>/dev/null || true
86
+
87
+ log "Running controls/install.sh detached (runtime libs, nodejs, pm2, boot unit)..."
88
+ systemd-run --unit="${INSTALL_UNIT}" --collect --no-block \
89
+ --working-directory="${NODE_SRC_DIR}" \
90
+ --setenv=OCTRA_OPERATOR_USER="${NODE_USER}" \
91
+ --setenv=OCTRA_DATA_ROOT="${NODE_DATA_ROOT}" \
92
+ --setenv=DEBIAN_FRONTEND=noninteractive \
93
+ --setenv=TERM=dumb \
94
+ bash -c "sh controls/install.sh > '${INSTALL_LOG}' 2>&1; echo \$? > '${INSTALL_STATUS}'"
95
+
96
+ log "install.sh runs as systemd unit ${INSTALL_UNIT} - polling (SSH may drop safely)"
97
+ START_TS=$(date +%s)
98
+ LAST_LOG=$START_TS
99
+ while [[ ! -s "${INSTALL_STATUS}" ]]; do
100
+ ELAPSED=$(( $(date +%s) - START_TS ))
101
+ if (( ELAPSED > INSTALL_TIMEOUT )); then
102
+ die "install.sh did not finish in ${INSTALL_TIMEOUT}s (see ${INSTALL_LOG})"
103
+ fi
104
+ # One line every 30 s: a \r progress line becomes its own row in the console,
105
+ # so a 5-minute apt run used to print ~30 near-identical rows.
106
+ if (( $(date +%s) - LAST_LOG >= 30 )); then
107
+ log " install.sh still running... ${ELAPSED}s of ${INSTALL_TIMEOUT}s (unit ${INSTALL_UNIT})"
108
+ LAST_LOG=$(date +%s)
109
+ fi
110
+ sleep 10
111
+ done
112
+ log "install.sh finished in $(( $(date +%s) - START_TS ))s"
113
+
114
+ INSTALL_RC=$(tr -dc '0-9' < "${INSTALL_STATUS}")
115
+ tail -n 8 "${INSTALL_LOG}" 2>/dev/null | sed 's/^/ /' || true
116
+ [[ "${INSTALL_RC}" == "0" ]] || die "install.sh failed (exit ${INSTALL_RC}) - log: ${INSTALL_LOG}"
117
+ fi
118
+ command -v pm2 >/dev/null || die "pm2 was not installed by install.sh"
119
+ log "pm2: $(pm2 -v)"
120
+
121
+ # --- 5. runtime binding check --------------------------------------
122
+ log "Checking runtime binding (--check-runtime)..."
123
+ as_octra "cd ${NODE_SRC_DIR} && python3 controls/lib/validator_config.py --check-runtime --config ${NODE_ENV}" \
124
+ || die "runtime binding check failed"
125
+ log "runtime binding OK"
126
+
127
+ ADDRESS=$(jq -r '.address' "${NODE_SRC_DIR}/.keys/validator/wallet.json")
128
+ log "wallet address on target: ${ADDRESS}"
129
+ if [[ -f "${MIG_DIR}/source-final-stat.txt" ]]; then
130
+ SRC_ADDR=$(grep -m1 '^address = ' "${MIG_DIR}/source-final-stat.txt" | awk -F' = ' '{print $2}' || true)
131
+ if [[ -n "${SRC_ADDR}" && "${SRC_ADDR}" != "${ADDRESS}" ]]; then
132
+ die "wallet mismatch! source=${SRC_ADDR} target=${ADDRESS}"
133
+ fi
134
+ log "wallet matches the source validator: ${SRC_ADDR:-unknown}"
135
+ fi
136
+ # recorded for 07-verify.sh (and for the runbook)
137
+ printf '%s\n' "${ADDRESS}" > "${MIG_DIR}/source-address.txt"
138
+
139
+ log "=== finalize done — target is ready to start ==="
140
+ set_status DONE "finalize-target" "${ADDRESS}"
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env bash
2
+ # ===================================================================
3
+ # 06-start-target.sh — start the migrated validator on the NEW vps
4
+ #
5
+ # Uses the node's own controls/run.sh, which performs:
6
+ # --check-runtime → validator_recover → validator_guard →
7
+ # validator_process → pm2 start
8
+ #
9
+ # Usage (on the NEW vps):
10
+ # bash 06-start-target.sh
11
+ # ===================================================================
12
+
13
+ set -euo pipefail
14
+ source "$(dirname "$0")/lib.sh"
15
+
16
+ require_root
17
+ [[ -f "${NODE_ENV}" ]] || die "node.env missing — run 05-finalize-target.sh first"
18
+ set_status RUNNING "start-target"
19
+
20
+ # --- Double-signing guard -------------------------------------------
21
+ # Two live copies of one validator wallet would equivocate. If OLD_HOST is
22
+ # given, refuse to start unless the source node is provably stopped.
23
+ if [[ -n "${OLD_HOST}" ]]; then
24
+ require_old_host
25
+ if [[ -n "$(on_old_node_processes)" ]]; then
26
+ die "the source node on ${OLD_HOST} is STILL RUNNING — refusing to start a second copy of the wallet"
27
+ fi
28
+ log "source node on ${OLD_HOST} confirmed stopped"
29
+ fi
30
+
31
+ log "=== starting the node on $(hostname) ==="
32
+ # Idempotent: never restart a node that is already up. A restart costs a full
33
+ # ~38 GB store open plus RPC catch-up, so "start" must mean "ensure running".
34
+ # A node that is already online is normal here: controls/install.sh (finalize)
35
+ # starts it, and this step only has to prove it is up.
36
+ if [[ -n "$(node_processes)" ]]; then
37
+ log "node already running - not restarting: $(node_processes | awk '{print $1, $2}' | head -1)"
38
+ else
39
+ as_octra "cd ${NODE_SRC_DIR} && sh controls/run.sh" 2>&1 | tee -a "${MIG_LOG}"
40
+ fi
41
+
42
+ log "Waiting for the process to come up..."
43
+ sleep 10
44
+
45
+ log "--- pm2 ---"
46
+ as_octra "pm2 list" 2>/dev/null || warn "pm2 list failed"
47
+
48
+ log "--- status ---"
49
+ node_stat | tee "${MIG_DIR}/target-initial-stat.txt" | \
50
+ grep -E '^(node|role|address|process|pid|rpc|epoch|head_epoch|voting|validator_active|validator_enrollment|p2p_connected) =' || true
51
+
52
+ if grep -q '^process = online' "${MIG_DIR}/target-initial-stat.txt"; then
53
+ log "=== node is up ==="
54
+ set_status DONE "start-target"
55
+ else
56
+ die "node did not reach process = online — check pm2 logs: pm2 logs ${NODE_PM2_NAME:-octra-node-1}"
57
+ fi