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,76 @@
1
+ # ===================================================================
2
+ # Octra Live Monitor - Nginx site
3
+ # -------------------------------------------------------------------
4
+ # Domain : node.example.com (the deploy script rewrites server_name from -Domain)
5
+ # SSL : Cloudflare Origin Certificate (wildcard *.example.com)
6
+ # Static : /opt/octra/dashboard
7
+ # Backend : python dashboard on 127.0.0.1:9090 (proxy_pass /api/)
8
+ # ===================================================================
9
+
10
+ server {
11
+ listen 443 ssl;
12
+ listen [::]:443 ssl;
13
+ http2 on;
14
+ server_name node.example.com;
15
+
16
+ ssl_certificate /etc/ssl/octra/fullchain.pem;
17
+ ssl_certificate_key /etc/ssl/octra/privkey.pem;
18
+
19
+ ssl_protocols TLSv1.2 TLSv1.3;
20
+ ssl_prefer_server_ciphers on;
21
+ ssl_ciphers HIGH:!aNULL:!MD5;
22
+ ssl_session_cache shared:SSL:10m;
23
+ ssl_session_timeout 1d;
24
+
25
+ add_header X-Frame-Options "SAMEORIGIN" always;
26
+ add_header X-Content-Type-Options "nosniff" always;
27
+ add_header Referrer-Policy "no-referrer" always;
28
+
29
+ root /opt/octra/dashboard;
30
+ index index.html;
31
+
32
+ # -- Dashboard shell --------------------------------------------
33
+ location = /index.html {
34
+ add_header Cache-Control "no-cache, must-revalidate" always;
35
+ try_files $uri =404;
36
+ }
37
+
38
+ location / {
39
+ try_files $uri $uri/ /index.html;
40
+ }
41
+
42
+ # -- Framework + app assets (cacheable, but revalidated on release) --
43
+ location ~* \.(css|js|ttf|woff2?|svg|ico|png)$ {
44
+ expires 1d;
45
+ add_header Cache-Control "public, max-age=86400";
46
+ try_files $uri =404;
47
+ }
48
+
49
+ # -- Telemetry API -> Python backend -----------------------------
50
+ location /api/ {
51
+ proxy_pass http://127.0.0.1:9090/api/;
52
+ proxy_http_version 1.1;
53
+ proxy_set_header Host $host;
54
+ proxy_set_header X-Real-IP $remote_addr;
55
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
56
+ proxy_set_header X-Forwarded-Proto $scheme;
57
+ proxy_connect_timeout 5s;
58
+ proxy_read_timeout 25s;
59
+ proxy_send_timeout 10s;
60
+ proxy_buffering off; # live JSON should never be buffered
61
+ add_header Cache-Control "no-store" always;
62
+ }
63
+
64
+
65
+ gzip on;
66
+ gzip_types text/plain text/css application/json application/javascript text/javascript image/svg+xml;
67
+ gzip_min_length 256;
68
+ }
69
+
70
+ # -- HTTP -> HTTPS ---------------------------------------------------
71
+ server {
72
+ listen 80;
73
+ listen [::]:80;
74
+ server_name node.example.com;
75
+ return 301 https://$host$request_uri;
76
+ }
@@ -0,0 +1,29 @@
1
+ [Unit]
2
+ Description=Octra Node Live Monitor (dashboard backend)
3
+ Documentation=https://github.com/octra-labs/lite_node
4
+ After=network-online.target
5
+ Wants=network-online.target
6
+
7
+ [Service]
8
+ Type=simple
9
+ User=root
10
+ WorkingDirectory=/opt/octra/dashboard
11
+ ExecStart=/usr/bin/python3 /opt/octra/dashboard/server.py --port 9090
12
+ Restart=always
13
+ RestartSec=3
14
+ # Read-only telemetry collector: keep it cheap and predictable
15
+ Nice=5
16
+ TimeoutStopSec=10
17
+ StandardOutput=journal
18
+ StandardError=journal
19
+ SyslogIdentifier=octra-dashboard
20
+
21
+ # Optional overrides
22
+ #Environment=OCTRA_API_PORT=8080
23
+
24
+ # Hardening (the backend only needs to read /proc, pm2 state and log files)
25
+ NoNewPrivileges=true
26
+ PrivateTmp=true
27
+
28
+ [Install]
29
+ WantedBy=multi-user.target
@@ -0,0 +1,12 @@
1
+ # /etc/cron.d/octra-node-prune - installed by dashboard/deploy-dashboard.sh
2
+ # Daily cleanup of node-generated junk (operator log rotation, core dumps,
3
+ # dashboard __pycache__, journal vacuum). The program itself carries the
4
+ # policy; this file only says "once a day".
5
+ # Keep this file ASCII-only and LF-only: Debian cron reads it as bytes and
6
+ # rejects a CRLF or non-ASCII line with "bad minute", ignoring the whole file.
7
+ SHELL=/bin/bash
8
+ PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
9
+ MAILTO=""
10
+
11
+ # m h dom mon dow user command
12
+ 40 3 * * * root /usr/local/sbin/octra-node-prune >> /var/log/octra-node-prune.cron.log 2>&1
@@ -0,0 +1,16 @@
1
+ [Unit]
2
+ Description=Prune Octra node logs and junk files (daily)
3
+ Documentation=file:/usr/local/sbin/octra-node-prune
4
+ After=local-fs.target
5
+
6
+ [Service]
7
+ Type=oneshot
8
+ ExecStart=/usr/local/sbin/octra-node-prune
9
+ Nice=10
10
+ IOSchedulingClass=idle
11
+ # The program never fails the unit for a skipped step; a non-zero exit means
12
+ # it could not even start (permissions / missing binary).
13
+ SuccessExitStatus=0
14
+
15
+ [Install]
16
+ WantedBy=multi-user.target
@@ -0,0 +1,12 @@
1
+ [Unit]
2
+ Description=Daily Octra node prune (log rotation + junk cleanup)
3
+ Documentation=file:/usr/local/sbin/octra-node-prune
4
+
5
+ [Timer]
6
+ OnCalendar=*-*-* 03:40:00
7
+ RandomizedDelaySec=600
8
+ Persistent=true
9
+ Unit=octra-node-prune.service
10
+
11
+ [Install]
12
+ WantedBy=timers.target
@@ -0,0 +1,329 @@
1
+ #!/usr/bin/env bash
2
+ # ===================================================================
3
+ # octra-node-prune — daily cleanup of node-generated junk
4
+ #
5
+ # Installed as /usr/local/sbin/octra-node-prune by
6
+ # dashboard/deploy-dashboard.sh and scheduled once a day (cron, or a
7
+ # systemd timer on hosts without cron).
8
+ #
9
+ # What it prunes (safe, node-scoped):
10
+ # • the pm2 operator log <node>/data/operator_logs/node.log
11
+ # (rotated with copy-truncate once it exceeds OCTRA_NODE_LOG_MAX_MB;
12
+ # the node keeps its file handle, the dashboard re-indexes itself)
13
+ # • rotated copies older than OCTRA_PRUNE_KEEP_DAYS
14
+ # • the pm2 daemon log, core dumps, dashboard __pycache__ / stale *.tmp
15
+ # • old systemd journal entries (kept OCTRA_PRUNE_JOURNAL_DAYS days)
16
+ # • the preserved states of the previous epochs:
17
+ # node/controls/storage.sh --prune-prior --yes
18
+ # (the upstream README's "Remove preserved states"; the node keeps the current
19
+ # state and rebuilds what a lagging peer needs, so this only frees disk)
20
+ # • its own log file (trimmed to the last 2000 lines)
21
+ #
22
+ # What it NEVER touches: irmin_store, chaindata, pvac, vote_log,
23
+ # commit_journal.log, finality/state-sync data, .keys, node.env.
24
+ #
25
+ # Usage
26
+ # octra-node-prune # prune now
27
+ # octra-node-prune --dry-run # report only, change nothing
28
+ # octra-node-prune --status # show the schedule + last runs
29
+ # ===================================================================
30
+ set -uo pipefail
31
+
32
+ VERSION="1.0"
33
+ NODE_HOME="${OCTRA_NODE_HOME:-/opt/octra/libv_litecore}"
34
+ NODE_LOG="${OCTRA_NODE_LOG:-$NODE_HOME/data/operator_logs/node.log}"
35
+ NODE_LOG_DIR="$(dirname "$NODE_LOG")"
36
+ NODE_LOG_MAX_MB="${OCTRA_NODE_LOG_MAX_MB:-200}"
37
+ KEEP_DAYS="${OCTRA_PRUNE_KEEP_DAYS:-7}"
38
+ KEEP_ROTATIONS="${OCTRA_PRUNE_KEEP_ROTATIONS:-2}"
39
+ JOURNAL_DAYS="${OCTRA_PRUNE_JOURNAL_DAYS:-7}"
40
+ DASH_DIR="${OCTRA_DASH_DIR:-/opt/octra/dashboard}"
41
+ PM2_LOG="${OCTRA_PM2_LOG:-/home/octra/.pm2/pm2.log}"
42
+ PM2_LOG_MAX_MB="${OCTRA_PM2_LOG_MAX_MB:-20}"
43
+ PRUNE_LOG="${OCTRA_PRUNE_LOG:-/var/log/octra-node-prune.log}"
44
+ LOCK_FILE="${OCTRA_PRUNE_LOCK:-/run/octra-node-prune.lock}"
45
+ DATA_ROOT="${OCTRA_DATA_ROOT:-/var/lib/octra}"
46
+ CRON_FILE="/etc/cron.d/octra-node-prune"
47
+ TIMER_FILE="/etc/systemd/system/octra-node-prune.timer"
48
+
49
+ DRY_RUN=0
50
+ STATUS_ONLY=0
51
+ QUIET=0
52
+ # the node's own storage tool (upstream README: "Remove preserved states")
53
+ STORAGE_TOOL="${OCTRA_STORAGE_TOOL:-$NODE_HOME/controls/storage.sh}"
54
+ STORAGE_TIMEOUT="${OCTRA_STORAGE_TIMEOUT:-900}"
55
+ NODE_USER="${OCTRA_NODE_USER:-octra}"
56
+
57
+ usage() {
58
+ sed -n '2,26p' "$0" | sed 's/^# \{0,1\}//'
59
+ exit 0
60
+ }
61
+
62
+ for arg in "$@"; do
63
+ case "$arg" in
64
+ --dry-run) DRY_RUN=1 ;;
65
+ --status) STATUS_ONLY=1 ;;
66
+ -q|--quiet) QUIET=1 ;;
67
+ -h|--help) usage ;;
68
+ -V|--version) echo "octra-node-prune $VERSION"; exit 0 ;;
69
+ *) echo "octra-node-prune: unknown argument '$arg' (try --help)" >&2; exit 2 ;;
70
+ esac
71
+ done
72
+
73
+ say() {
74
+ local line
75
+ line="$(date -u +%Y-%m-%dT%H:%M:%SZ) $*"
76
+ if [ -w "$(dirname "$PRUNE_LOG")" ] 2>/dev/null; then
77
+ printf '%s\n' "$line" >>"$PRUNE_LOG" 2>/dev/null || true
78
+ fi
79
+ [ "$QUIET" -eq 1 ] || printf '%s\n' "$line"
80
+ }
81
+
82
+ human() {
83
+ awk -v b="${1:-0}" 'BEGIN{
84
+ split("B KB MB GB TB", unit, " ");
85
+ i = 1;
86
+ while (b >= 1024 && i < 5) { b = b / 1024; i++ }
87
+ printf "%.1f %s", b, unit[i]
88
+ }'
89
+ }
90
+
91
+ size_of() { stat -c %s "$1" 2>/dev/null || echo 0; }
92
+
93
+ free_bytes() {
94
+ df -B1 --output=avail "$NODE_LOG_DIR" 2>/dev/null | tail -1 | tr -d ' ' || echo 0
95
+ }
96
+
97
+ rotate_log() {
98
+ local path="$1" max_mb="$2" label="$3"
99
+ [ -f "$path" ] || return 0
100
+ local bytes mb stamp rotated
101
+ bytes=$(size_of "$path")
102
+ mb=$(( bytes / 1024 / 1024 ))
103
+ if [ "$mb" -lt "$max_mb" ]; then
104
+ say "keep $label: $(human "$bytes") (limit ${max_mb} MB)"
105
+ return 0
106
+ fi
107
+ if [ "$DRY_RUN" -eq 1 ]; then
108
+ say "would rotate $label: $(human "$bytes") >= ${max_mb} MB"
109
+ return 0
110
+ fi
111
+ stamp=$(date -u +%Y%m%dT%H%M%SZ)
112
+ rotated="$path.$stamp"
113
+ # copy-truncate: the node (via pm2) holds this file open in append mode, so
114
+ # the content is copied aside and the live file is truncated in place - the
115
+ # writer keeps working and the dashboard notices the truncation and
116
+ # re-indexes by itself.
117
+ if cp --reflink=auto --sparse=never "$path" "$rotated" 2>/dev/null || cp "$path" "$rotated" 2>/dev/null; then
118
+ if : > "$path"; then
119
+ local gz="${rotated}.gz" gz_size
120
+ gzip -1 -f "$rotated" 2>/dev/null || true
121
+ if [ -f "$gz" ]; then
122
+ gz_size=$(size_of "$gz")
123
+ RECLAIMED=$((RECLAIMED + bytes - gz_size))
124
+ say "rotated $label: $(human "$bytes") -> $(basename "$rotated").gz ($(human "$gz_size"))"
125
+ else
126
+ say "rotated $label: $(human "$bytes") -> $(basename "$rotated") (left uncompressed)"
127
+ fi
128
+ else
129
+ say "FAILED $label: copy kept, live file not truncated (left as is)"
130
+ fi
131
+ else
132
+ say "FAILED $label: could not copy the log, nothing changed"
133
+ fi
134
+ }
135
+
136
+ prune_rotations() {
137
+ local pattern="$1" label="$2" days="$3"
138
+ while IFS= read -r old; do
139
+ [ -n "$old" ] || continue
140
+ if [ "$DRY_RUN" -eq 1 ]; then
141
+ say "would drop $label rotation: $(basename "$old") ($(human "$(size_of "$old")"))"
142
+ else
143
+ local sz
144
+ sz=$(size_of "$old")
145
+ rm -f -- "$old" && RECLAIMED=$((RECLAIMED + sz)) && \
146
+ say "dropped $label rotation: $(basename "$old") ($(human "$sz"))"
147
+ fi
148
+ done < <(find "$NODE_LOG_DIR" -maxdepth 1 -type f -name "$pattern" -mtime +"$days" 2>/dev/null)
149
+
150
+ # Keep at most KEEP_ROTATIONS newest copies. Sort by mtime: a timestamped
151
+ # name ("node.log.20261031T0727Z.gz") sorts *below* "node.log.500.gz", so a
152
+ # plain name sort would drop the freshest rotation instead of the oldest.
153
+ local extra
154
+ extra=$(find "$NODE_LOG_DIR" -maxdepth 1 -type f -name "$pattern" -printf '%T@ %p\n' 2>/dev/null \
155
+ | sort -rn | tail -n +"$((KEEP_ROTATIONS + 1))" | cut -d' ' -f2-)
156
+ while IFS= read -r old; do
157
+ [ -n "$old" ] || continue
158
+ if [ "$DRY_RUN" -eq 1 ]; then
159
+ say "would drop $label rotation beyond ${KEEP_ROTATIONS}: $(basename "$old")"
160
+ else
161
+ local sz
162
+ sz=$(size_of "$old")
163
+ rm -f -- "$old" && RECLAIMED=$((RECLAIMED + sz)) && \
164
+ say "dropped $label rotation beyond ${KEEP_ROTATIONS}: $(basename "$old")"
165
+ fi
166
+ done <<<"$extra"
167
+ }
168
+
169
+ trim_in_place() {
170
+ local path="$1" max_mb="$2" label="$3"
171
+ [ -f "$path" ] || return 0
172
+ local mb
173
+ mb=$(( $(size_of "$path") / 1024 / 1024 ))
174
+ [ "$mb" -ge "$max_mb" ] || return 0
175
+ if [ "$DRY_RUN" -eq 1 ]; then
176
+ say "would truncate $label (${mb} MB >= ${max_mb} MB)"
177
+ return 0
178
+ fi
179
+ if : > "$path"; then
180
+ say "truncated $label (${mb} MB)"
181
+ else
182
+ say "FAILED $label: not writable"
183
+ fi
184
+ }
185
+
186
+ show_status() {
187
+ echo "octra-node-prune $VERSION"
188
+ local self
189
+ self=$(command -v octra-node-prune 2>/dev/null || echo "$0")
190
+ echo " program : $self"
191
+ if [ -f "$CRON_FILE" ]; then
192
+ echo " schedule: cron ($CRON_FILE)"
193
+ grep -vE '^[[:space:]]*(#|$)' "$CRON_FILE" 2>/dev/null | sed 's/^/ /'
194
+ if command -v systemctl >/dev/null 2>&1; then
195
+ echo " cron : $(systemctl is-active cron 2>/dev/null || echo unknown)"
196
+ fi
197
+ elif [ -f "$TIMER_FILE" ]; then
198
+ echo " schedule: systemd timer ($TIMER_FILE)"
199
+ command -v systemctl >/dev/null 2>&1 && systemctl list-timers octra-node-prune.timer --no-pager 2>/dev/null | sed 's/^/ /'
200
+ else
201
+ echo " schedule: NOT INSTALLED (run dashboard/deploy-dashboard.sh)"
202
+ fi
203
+ echo " node log: $NODE_LOG ($(human "$(size_of "$NODE_LOG")"))"
204
+ echo " limits : rotate > ${NODE_LOG_MAX_MB} MB, keep ${KEEP_ROTATIONS} copies / ${KEEP_DAYS} days"
205
+ if [ -f "$STORAGE_TOOL" ]; then
206
+ echo " preserved: prune-prior --yes each run ($STORAGE_TOOL)"
207
+ else
208
+ echo " preserved: controls/storage.sh not found - the step is skipped"
209
+ fi
210
+ echo " log : $PRUNE_LOG"
211
+ if [ -s "$PRUNE_LOG" ]; then
212
+ echo " last runs:"
213
+ grep -E 'prune start|prune done' "$PRUNE_LOG" | tail -4 | sed 's/^/ /'
214
+ fi
215
+ }
216
+
217
+
218
+ if [ "$STATUS_ONLY" -eq 1 ]; then
219
+ show_status
220
+ exit 0
221
+ fi
222
+
223
+ # ── single instance ────────────────────────────────────────────────
224
+ if [ -n "$LOCK_FILE" ] && command -v flock >/dev/null 2>&1; then
225
+ exec 9>"$LOCK_FILE" 2>/dev/null || true
226
+ if ! flock -n 9; then
227
+ say "prune skip: another run is still active"
228
+ exit 0
229
+ fi
230
+ fi
231
+
232
+ start_free=$(free_bytes)
233
+ RECLAIMED=0
234
+ say "prune start (dry-run=${DRY_RUN} max=${NODE_LOG_MAX_MB}MB keep=${KEEP_ROTATIONS}/${KEEP_DAYS}d)"
235
+
236
+ # ── operator log (the big one) ────────────────────────────────────
237
+ rotate_log "$NODE_LOG" "$NODE_LOG_MAX_MB" "operator log"
238
+ prune_rotations "$(basename "$NODE_LOG").*.gz" "operator log" "$KEEP_DAYS"
239
+
240
+ # ── pm2 daemon log ────────────────────────────────────────────────
241
+ trim_in_place "$PM2_LOG" "$PM2_LOG_MAX_MB" "pm2 daemon log"
242
+
243
+ # ── core dumps (kept 1 day in case someone wants to inspect them) ──
244
+ for dir in "$NODE_HOME" "$DATA_ROOT" /tmp; do
245
+ [ -d "$dir" ] || continue
246
+ while IFS= read -r dump; do
247
+ [ -n "$dump" ] || continue
248
+ if [ "$DRY_RUN" -eq 1 ]; then
249
+ say "would drop core dump: $dump ($(human "$(size_of "$dump")"))"
250
+ else
251
+ rm -f -- "$dump" && say "dropped core dump: $dump ($(human "$(size_of "$dump")"))"
252
+ fi
253
+ done < <(find "$dir" -maxdepth 3 -type f \( -name 'core' -o -name 'core.*' -o -name '*.core' \) -mtime +1 2>/dev/null | head -20)
254
+ done
255
+
256
+ # ── dashboard junk ────────────────────────────────────────────────
257
+ if [ -d "$DASH_DIR" ]; then
258
+ if [ "$DRY_RUN" -eq 1 ]; then
259
+ pyc=$(find "$DASH_DIR" -name '__pycache__' -type d 2>/dev/null | wc -l)
260
+ tmp=$(find "$DASH_DIR" -maxdepth 1 -name '*.tmp' -mtime +1 2>/dev/null | wc -l)
261
+ say "would drop dashboard junk: ${pyc} __pycache__ dir(s), ${tmp} stale .tmp file(s)"
262
+ else
263
+ find "$DASH_DIR" -name '__pycache__' -type d -prune -exec rm -rf {} + 2>/dev/null
264
+ find "$DASH_DIR" -maxdepth 1 -name '*.tmp' -mtime +1 -delete 2>/dev/null
265
+ say "cleaned dashboard __pycache__ / stale .tmp"
266
+ fi
267
+ fi
268
+
269
+ # ── systemd journal (system hygiene, bounded) ─────────────────────
270
+ if [ "${OCTRA_PRUNE_SKIP_JOURNAL:-0}" != "1" ] && command -v journalctl >/dev/null 2>&1; then
271
+ if [ "$DRY_RUN" -eq 1 ]; then
272
+ say "would vacuum systemd journal beyond ${JOURNAL_DAYS}d"
273
+ else
274
+ before=$(du -sk /var/log/journal 2>/dev/null | cut -f1)
275
+ journalctl --vacuum-time="${JOURNAL_DAYS}d" --quiet >/dev/null 2>&1 || true
276
+ after=$(du -sk /var/log/journal 2>/dev/null | cut -f1)
277
+ if [ -n "${before:-}" ] && [ -n "${after:-}" ] && [ "$before" -gt "$after" ]; then
278
+ say "journal vacuumed ${JOURNAL_DAYS}d: $(( (before - after) / 1024 )) MB freed"
279
+ else
280
+ say "journal within ${JOURNAL_DAYS}d, nothing to vacuum"
281
+ fi
282
+ fi
283
+ fi
284
+
285
+ # ── preserved states (the node's own storage tool) ────────────────
286
+ # Upstream README: "Remove preserved states"
287
+ # sh controls/storage.sh
288
+ # sh controls/storage.sh --prune-prior --yes
289
+ # A node keeps the states of the previous epochs so it can serve peers that lag
290
+ # behind; they grow until the store is oversized. This is the documented
291
+ # maintenance and it only frees disk - the current state is untouched.
292
+ if [ "${OCTRA_PRUNE_SKIP_STORAGE:-0}" = "1" ]; then
293
+ say "storage preserved states: skipped (OCTRA_PRUNE_SKIP_STORAGE=1)"
294
+ elif [ ! -f "$STORAGE_TOOL" ]; then
295
+ say "storage preserved states: controls/storage.sh not found (old node version) - skipped"
296
+ elif [ "$DRY_RUN" -eq 1 ]; then
297
+ say "would run controls/storage.sh --prune-prior --yes (preserved states)"
298
+ else
299
+ before_storage=$(df -B1 --output=avail "$NODE_HOME" 2>/dev/null | tail -1 | tr -d ' ' || echo 0)
300
+ out=$(timeout "$STORAGE_TIMEOUT" su - "$NODE_USER" -c \
301
+ "cd $NODE_HOME && sh controls/storage.sh --prune-prior --yes" 2>&1)
302
+ rc=$?
303
+ if [ "$rc" -eq 0 ]; then
304
+ line=$(printf '%s\n' "$out" | grep -iE 'reclaim|freed|removed|pruned|preserved|prior' | tail -1)
305
+ after_storage=$(df -B1 --output=avail "$NODE_HOME" 2>/dev/null | tail -1 | tr -d ' ' || echo 0)
306
+ freed_disk=0
307
+ if [ -n "${before_storage:-}" ] && [ -n "${after_storage:-}" ] \
308
+ && [ "${after_storage}" -gt "${before_storage}" ] 2>/dev/null; then
309
+ freed_disk=$(( after_storage - before_storage ))
310
+ fi
311
+ say "storage preserved states pruned${freed_disk:+ : $(human "$freed_disk") freed}${line:+ - $line}"
312
+ RECLAIMED=$(( RECLAIMED + freed_disk ))
313
+ else
314
+ # never fail the whole prune over the node's own tool: report and continue
315
+ say "storage prune-prior skipped (exit $rc): $(printf '%s\n' "$out" | tail -1)"
316
+ fi
317
+ fi
318
+
319
+ # ── keep our own log small ────────────────────────────────────────
320
+ if [ -f "$PRUNE_LOG" ] && [ "$(size_of "$PRUNE_LOG")" -gt 1048576 ] && [ "$DRY_RUN" -eq 0 ]; then
321
+ tail -n 2000 "$PRUNE_LOG" >"$PRUNE_LOG.tmp" 2>/dev/null && mv -f "$PRUNE_LOG.tmp" "$PRUNE_LOG"
322
+ fi
323
+
324
+ end_free=$(free_bytes)
325
+ freed=$(( end_free - start_free ))
326
+ [ "$freed" -lt 0 ] && freed=0
327
+ say "prune done: reclaimed $(human "$RECLAIMED") · operator log now $(human "$(size_of "$NODE_LOG")") · free $(human "$end_free")"
328
+ exit 0
329
+