underpost 3.2.28 → 3.2.70

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.
@@ -26,7 +26,7 @@ services:
26
26
  mongodb:
27
27
  image: ${MONGO_IMAGE:-mongo:latest}
28
28
  container_name: dd-mongodb
29
- entrypoint: ["bash", "/docker-init/entrypoint.sh"]
29
+ entrypoint: ['bash', '/docker-init/entrypoint.sh']
30
30
  environment:
31
31
  MONGO_INITDB_ROOT_USERNAME: ${MONGO_INITDB_ROOT_USERNAME:?set MONGO_INITDB_ROOT_USERNAME in docker/compose.env}
32
32
  MONGO_INITDB_ROOT_PASSWORD: ${MONGO_INITDB_ROOT_PASSWORD:?set MONGO_INITDB_ROOT_PASSWORD in docker/compose.env}
@@ -38,9 +38,9 @@ services:
38
38
  networks:
39
39
  - dd-internal
40
40
  expose:
41
- - "27017"
41
+ - '27017'
42
42
  ports:
43
- - "${MONGO_HOST_PORT:-27017}:27017"
43
+ - '${MONGO_HOST_PORT:-27017}:27017'
44
44
  healthcheck:
45
45
  test:
46
46
  - CMD-SHELL
@@ -58,18 +58,18 @@ services:
58
58
  valkey-service:
59
59
  image: ${VALKEY_IMAGE:-valkey/valkey:latest}
60
60
  container_name: dd-valkey
61
- command: ["valkey-server", "--port", "6379", "--bind", "0.0.0.0", "--protected-mode", "no"]
61
+ command: ['valkey-server', '--port', '6379', '--bind', '0.0.0.0', '--protected-mode', 'no']
62
62
  volumes:
63
63
  - valkey-data:/data
64
64
  networks:
65
65
  - dd-internal
66
66
  expose:
67
- - "6379"
67
+ - '6379'
68
68
  # NodePort equivalent (manifests/valkey/valkey-nodeport.yaml nodePort 32079).
69
69
  ports:
70
- - "${VALKEY_NODEPORT:-32079}:6379"
70
+ - '${VALKEY_NODEPORT:-32079}:6379'
71
71
  healthcheck:
72
- test: ["CMD", "valkey-cli", "-p", "6379", "ping"]
72
+ test: ['CMD', 'valkey-cli', '-p', '6379', 'ping']
73
73
  interval: 10s
74
74
  timeout: 5s
75
75
  retries: 10
@@ -81,7 +81,7 @@ services:
81
81
  # (dd-default-development-blue). Connection targets use Docker service
82
82
  # discovery instead of hardcoded localhost.
83
83
  app:
84
- image: ${APP_IMAGE:-underpost/underpost-engine}:${APP_TAG:-v3.2.22}
84
+ image: ${APP_IMAGE:-underpost/underpost-engine}:${APP_TAG:-v3.2.70}
85
85
  container_name: dd-app
86
86
  # The start command is supplied by the generated override docker/compose.app.yml
87
87
  # (see `underpost docker-compose --generate --deploy-id <id>`), keeping this
@@ -100,18 +100,18 @@ services:
100
100
  networks:
101
101
  - dd-internal
102
102
  expose:
103
- - "4001"
104
- - "4002"
105
- - "4003"
106
- - "4004"
103
+ - '4001'
104
+ - '4002'
105
+ - '4003'
106
+ - '4004'
107
107
  # Direct access to the LoadBalancer-equivalent ports (manifest 4001-4004).
108
108
  ports:
109
- - "${APP_PORT_4001:-4001}:4001"
110
- - "${APP_PORT_4002:-4002}:4002"
111
- - "${APP_PORT_4003:-4003}:4003"
112
- - "${APP_PORT_4004:-4004}:4004"
109
+ - '${APP_PORT_4001:-4001}:4001'
110
+ - '${APP_PORT_4002:-4002}:4002'
111
+ - '${APP_PORT_4003:-4003}:4003'
112
+ - '${APP_PORT_4004:-4004}:4004'
113
113
  healthcheck:
114
- test: ["CMD-SHELL", "timeout 2 bash -c '</dev/tcp/127.0.0.1/4001' || exit 1"]
114
+ test: ['CMD-SHELL', "timeout 2 bash -c '</dev/tcp/127.0.0.1/4001' || exit 1"]
115
115
  interval: 15s
116
116
  timeout: 5s
117
117
  retries: 5
@@ -134,9 +134,9 @@ services:
134
134
  networks:
135
135
  - dd-internal
136
136
  ports:
137
- - "${PROXY_HTTP_PORT:-80}:80"
137
+ - '${PROXY_HTTP_PORT:-80}:80'
138
138
  healthcheck:
139
- test: ["CMD", "wget", "-q", "-O", "-", "http://127.0.0.1/healthz"]
139
+ test: ['CMD', 'wget', '-q', '-O', '-', 'http://127.0.0.1/healthz']
140
140
  interval: 15s
141
141
  timeout: 5s
142
142
  retries: 5
@@ -162,11 +162,11 @@ services:
162
162
  networks:
163
163
  - dd-internal
164
164
  expose:
165
- - "9090"
165
+ - '9090'
166
166
  ports:
167
- - "${PROMETHEUS_PORT:-9090}:9090"
167
+ - '${PROMETHEUS_PORT:-9090}:9090'
168
168
  healthcheck:
169
- test: ["CMD", "wget", "-q", "-O", "-", "http://127.0.0.1:9090/-/healthy"]
169
+ test: ['CMD', 'wget', '-q', '-O', '-', 'http://127.0.0.1:9090/-/healthy']
170
170
  interval: 15s
171
171
  timeout: 5s
172
172
  retries: 5
@@ -185,18 +185,18 @@ services:
185
185
  environment:
186
186
  GF_SECURITY_ADMIN_USER: ${GRAFANA_ADMIN_USER:-admin}
187
187
  GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_ADMIN_PASSWORD:-admin}
188
- GF_USERS_ALLOW_SIGN_UP: "false"
188
+ GF_USERS_ALLOW_SIGN_UP: 'false'
189
189
  volumes:
190
190
  - ./docker/grafana/provisioning:/etc/grafana/provisioning:ro
191
191
  - grafana-data:/var/lib/grafana
192
192
  networks:
193
193
  - dd-internal
194
194
  expose:
195
- - "3000"
195
+ - '3000'
196
196
  ports:
197
- - "${GRAFANA_PORT:-3000}:3000"
197
+ - '${GRAFANA_PORT:-3000}:3000'
198
198
  healthcheck:
199
- test: ["CMD", "wget", "-q", "-O", "-", "http://127.0.0.1:3000/api/health"]
199
+ test: ['CMD', 'wget', '-q', '-O', '-', 'http://127.0.0.1:3000/api/health']
200
200
  interval: 15s
201
201
  timeout: 5s
202
202
  retries: 5
@@ -23,7 +23,7 @@ spec:
23
23
  spec:
24
24
  containers:
25
25
  - name: dd-cron-backup
26
- image: underpost/underpost-engine:v3.2.28
26
+ image: underpost/underpost-engine:v3.2.70
27
27
  command:
28
28
  - /bin/sh
29
29
  - -c
@@ -23,7 +23,7 @@ spec:
23
23
  spec:
24
24
  containers:
25
25
  - name: dd-cron-dns
26
- image: underpost/underpost-engine:v3.2.28
26
+ image: underpost/underpost-engine:v3.2.70
27
27
  command:
28
28
  - /bin/sh
29
29
  - -c
@@ -17,7 +17,7 @@ spec:
17
17
  spec:
18
18
  containers:
19
19
  - name: dd-default-development-blue
20
- image: underpost/underpost-engine:v3.2.28
20
+ image: underpost/underpost-engine:v3.2.70
21
21
  # resources:
22
22
  # requests:
23
23
  # memory: "124Ki"
@@ -98,7 +98,7 @@ spec:
98
98
  spec:
99
99
  containers:
100
100
  - name: dd-default-development-green
101
- image: underpost/underpost-engine:v3.2.28
101
+ image: underpost/underpost-engine:v3.2.70
102
102
  # resources:
103
103
  # requests:
104
104
  # memory: "124Ki"
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "type": "module",
3
3
  "main": "src/index.js",
4
4
  "name": "underpost",
5
- "version": "3.2.28",
5
+ "version": "3.2.70",
6
6
  "description": "Underpost Platform — end-to-end CI/CD and application-delivery toolchain CLI. Covers bare metal, Kubernetes, K3s, kubeadm, LXD, container/image orchestration, secrets, databases, cron jobs, monitoring, SSH, runners, PWA + Workbox delivery, and release orchestration. Extensible via downstream CLIs.",
7
7
  "scripts": {
8
8
  "start": "node --max-old-space-size=8192 src/server",
@@ -71,14 +71,14 @@
71
71
  },
72
72
  "homepage": "https://github.com/underpostnet/pwa-microservices-template#readme",
73
73
  "dependencies": {
74
- "@fortawesome/fontawesome-free": "^7.2.0",
74
+ "@fortawesome/fontawesome-free": "^7.3.0",
75
75
  "@fullcalendar/rrule": "^6.1.21",
76
76
  "@grpc/grpc-js": "^1.14.4",
77
77
  "@grpc/proto-loader": "^0.8.1",
78
78
  "@neodrag/vanilla": "^2.3.1",
79
- "adm-zip": "^0.5.17",
80
- "ag-grid-community": "^35.3.1",
81
- "axios": "^1.18.0",
79
+ "adm-zip": "^0.5.18",
80
+ "ag-grid-community": "^36.0.0",
81
+ "axios": "^1.18.1",
82
82
  "bumpp": "^11.1.0",
83
83
  "chai": "^6.2.2",
84
84
  "clipboardy": "^5.3.1",
@@ -100,11 +100,11 @@
100
100
  "express-slow-down": "^3.1.0",
101
101
  "fast-json-stable-stringify": "^2.1.0",
102
102
  "favicons": "^7.3.0",
103
- "fs-extra": "^11.3.5",
103
+ "fs-extra": "^11.3.6",
104
104
  "fullcalendar": "^6.1.21",
105
105
  "helmet": "^8.2.0",
106
106
  "html-minifier-terser": "^7.2.0",
107
- "http-proxy-middleware": "^4.1.1",
107
+ "http-proxy-middleware": "^4.2.0",
108
108
  "ignore-walk": "^9.0.0",
109
109
  "iovalkey": "^0.3.3",
110
110
  "json-colorizer": "^3.0.1",
@@ -112,9 +112,9 @@
112
112
  "mariadb": "^3.5.3",
113
113
  "marked": "^18.0.5",
114
114
  "mocha": "^11.7.6",
115
- "mongoose": "^9.7.1",
115
+ "mongoose": "^9.7.3",
116
116
  "morgan": "^1.11.0",
117
- "nodemailer": "^9.0.0",
117
+ "nodemailer": "^9.0.3",
118
118
  "nodemon": "^3.0.1",
119
119
  "peer": "^1.0.2",
120
120
  "peerjs": "^1.5.5",
@@ -128,7 +128,7 @@
128
128
  "split-file": "^2.3.0",
129
129
  "swagger-autogen": "^2.23.7",
130
130
  "swagger-ui-express": "^5.0.0",
131
- "typedoc": "^0.28.19",
131
+ "typedoc": "^0.28.20",
132
132
  "typescript": "^6.0.3",
133
133
  "validator": "^13.15.35",
134
134
  "vanilla-jsoneditor": "^3.12.0",
@@ -7,20 +7,20 @@ set -euo pipefail
7
7
 
8
8
  VACUUM_SIZE="200M" # journalctl vacuum target
9
9
  LARGE_LOG_SIZE="+100M" # threshold for truncation (find syntax)
10
- TMP_AGE_DAYS=3 # remove files older than this from /tmp and /var/tmp
11
- CACHE_AGE_DAYS=30 # remove caches older than this from /var/cache
10
+ TMP_AGE_DAYS=1 # remove files older than this from /tmp and /var/tmp
11
+ CACHE_AGE_DAYS=5 # remove caches older than this from /var/cache
12
12
  KEEP_KERNELS=2 # keep this many latest kernels
13
13
  REMOVE_OPT=0 # ALWAYS allow removing /opt entries (user requested)
14
14
 
15
15
  timestamp() { date +"%F %T"; }
16
16
  log() {
17
- echo "$(timestamp) $*"
17
+ echo "$(timestamp) $*"
18
18
  }
19
19
 
20
20
  # must be root
21
21
  if [ "$EUID" -ne 0 ]; then
22
- echo "This script must be run as root. Exiting."
23
- exit 1
22
+ echo "This script must be run as root. Exiting."
23
+ exit 1
24
24
  fi
25
25
 
26
26
  log "=== Starting Rocky Linux 9 automated cleanup (batch mode) ==="
@@ -46,8 +46,8 @@ journalctl --vacuum-size="$VACUUM_SIZE"
46
46
  # 3) Truncate very large logs in /var/log (safer than delete)
47
47
  log "Truncating logs in /var/log larger than ${LARGE_LOG_SIZE} (keeps zero-sized file so services don't break)..."
48
48
  find /var/log -type f -size "$LARGE_LOG_SIZE" -print0 2>/dev/null | while IFS= read -r -d '' f; do
49
- echo "Truncating $f"
50
- : > "$f" || echo "Failed to truncate $f"
49
+ echo "Truncating $f"
50
+ : > "$f" || echo "Failed to truncate $f"
51
51
  done
52
52
 
53
53
  # 4) Clean /tmp and /var/tmp older than TMP_AGE_DAYS
@@ -62,87 +62,110 @@ find /var/cache -mindepth 1 -mtime +"$CACHE_AGE_DAYS" -print0 2>/dev/null | xarg
62
62
  # 6) Clean user caches (~/.cache) and Downloads older than CACHE_AGE_DAYS
63
63
  log "Cleaning user caches and old downloads (home dirs)..."
64
64
  for homedir in /home/* /root; do
65
- [ -d "$homedir" ] || continue
66
- usercache="$homedir/.cache"
67
- userdownloads="$homedir/Downloads"
68
- usertrash="$homedir/.local/share/Trash"
69
-
70
- if [ -d "$usercache" ]; then
71
- log "Removing contents of $usercache"
72
- rm -rf "${usercache:?}/"* 2>/dev/null
73
- fi
74
- if [ -d "$userdownloads" ]; then
75
- log "Removing files older than ${CACHE_AGE_DAYS} days from $userdownloads"
76
- find "$userdownloads" -type f -mtime +"$CACHE_AGE_DAYS" -print0 2>/dev/null | xargs -0r rm -f -- 2>/dev/null
77
- fi
78
- if [ -d "$usertrash" ]; then
79
- log "Emptying trash in $usertrash"
80
- rm -rf "${usertrash:?}/"* 2>/dev/null
81
- fi
65
+ [ -d "$homedir" ] || continue
66
+ usercache="$homedir/.cache"
67
+ userdownloads="$homedir/Downloads"
68
+ usertrash="$homedir/.local/share/Trash"
69
+
70
+ if [ -d "$usercache" ]; then
71
+ log "Removing contents of $usercache"
72
+ rm -rf "${usercache:?}/"* 2>/dev/null
73
+ fi
74
+ if [ -d "$userdownloads" ]; then
75
+ log "Removing files older than ${CACHE_AGE_DAYS} days from $userdownloads"
76
+ find "$userdownloads" -type f -mtime +"$CACHE_AGE_DAYS" -print0 2>/dev/null | xargs -0r rm -f -- 2>/dev/null
77
+ fi
78
+ if [ -d "$usertrash" ]; then
79
+ log "Emptying trash in $usertrash"
80
+ rm -rf "${usertrash:?}/"* 2>/dev/null
81
+ fi
82
82
  done
83
83
 
84
- # 7) Docker / Podman cleanup (if present)
84
+ # 7) Docker / Podman cleanup (if present) — this is what actually reclaims the
85
+ # overlay storage behind those duplicated `overlay` df lines.
85
86
  if command -v docker >/dev/null 2>&1; then
86
- log "Docker detected: pruning images/containers/volumes (aggressive)..."
87
- docker system df
88
- docker system prune -a --volumes -f
87
+ log "Docker detected: pruning images/containers/volumes/build cache (aggressive)..."
88
+ docker system df || true
89
+ docker system prune -a --volumes -f || true
90
+ docker builder prune -a -f || true
89
91
  fi
90
92
 
91
93
  if command -v podman >/dev/null 2>&1; then
92
- log "Podman detected: pruning images/containers (aggressive)..."
93
- podman system df
94
- podman system prune -a -f
94
+ log "Podman detected: pruning images/containers/volumes (aggressive)..."
95
+ podman system df || true
96
+ podman system prune -a --volumes -f || true
95
97
  fi
96
98
 
99
+ if command -v buildah >/dev/null 2>&1; then
100
+ log "Buildah detected: removing leftover working containers..."
101
+ buildah rm --all || true
102
+ fi
103
+
104
+ # 7b) Unmount leaked container overlay 'merged' mounts. Pruning frees the data
105
+ # but leaves these mountpoints attached, so they keep flooding `df -h` with
106
+ # identical `overlay ... /merged` rows. Only unmount overlays not backing a
107
+ # still-existing container, so running workloads are never disturbed.
108
+ log "Unmounting orphaned container overlay mounts..."
109
+ if command -v podman >/dev/null 2>&1; then
110
+ podman umount --all >/dev/null 2>&1 || true
111
+ fi
112
+ active_merged="$(podman ps -aq 2>/dev/null | xargs -r -I{} podman inspect --format '{{.GraphDriver.Data.MergedDir}}' {} 2>/dev/null || true)"
113
+ findmnt -rn -o TARGET 2>/dev/null | grep -E '/var/lib/(containers/storage|docker)/overlay.*/merged' | sort -r | while IFS= read -r m; do
114
+ if ! printf '%s\n' "$active_merged" | grep -qxF "$m"; then
115
+ echo "Unmounting orphaned overlay: $m"
116
+ umount -l "$m" 2>/dev/null || true
117
+ fi
118
+ done
119
+
97
120
  # 8) Flatpak unused runtimes/apps (if present)
98
121
  if command -v flatpak >/dev/null 2>&1; then
99
- log "Flatpak detected: trying to uninstall unused runtimes/apps..."
100
- flatpak uninstall --unused -y || flatpak uninstall --unused
101
- flatpak repair
122
+ log "Flatpak detected: trying to uninstall unused runtimes/apps..."
123
+ flatpak uninstall --unused -y || flatpak uninstall --unused
124
+ flatpak repair
102
125
  fi
103
126
 
104
127
  # 9) Python Pip cleanup
105
128
  if command -v pip3 >/dev/null 2>&1 || command -v pip >/dev/null 2>&1; then
106
- log "Python Pip detected: cleaning cache..."
107
- if command -v pip3 >/dev/null 2>&1; then
108
- pip3 cache purge
109
- elif command -v pip >/dev/null 2>&1; then
110
- pip cache purge
111
- fi
129
+ log "Python Pip detected: cleaning cache..."
130
+ if command -v pip3 >/dev/null 2>&1; then
131
+ pip3 cache purge
132
+ elif command -v pip >/dev/null 2>&1; then
133
+ pip cache purge
134
+ fi
112
135
  fi
113
136
 
114
137
  # 10) Conda cleanup
115
138
  if command -v conda >/dev/null 2>&1; then
116
- log "Conda detected: cleaning all..."
117
- conda clean --all -y
139
+ log "Conda detected: cleaning all..."
140
+ conda clean --all -y
118
141
  fi
119
142
 
120
143
  # 11) Remove old kernels but keep the last KEEP_KERNELS (safe)
121
144
  log "Removing old kernels, keeping the last $KEEP_KERNELS kernels..."
122
145
  OLD_KERNELS=$(dnf repoquery --installonly --latest-limit=-$KEEP_KERNELS -q 2>/dev/null)
123
146
  if [ -n "$OLD_KERNELS" ]; then
124
- log "Old kernels to remove: $OLD_KERNELS"
125
- dnf -y remove $OLD_KERNELS
147
+ log "Old kernels to remove: $OLD_KERNELS"
148
+ dnf -y remove $OLD_KERNELS
126
149
  else
127
- log "No old kernels found by DNF repoquery. Only $KEEP_KERNELS or fewer are currently installed."
150
+ log "No old kernels found by DNF repoquery. Only $KEEP_KERNELS or fewer are currently installed."
128
151
  fi
129
152
 
130
153
  # 12) /opt review and removal (ON by default now)
131
154
  if [ -d /opt ]; then
132
- log "/opt usage (top entries):"
133
- du -sh /opt/* 2>/dev/null | sort -h | head -n 20
134
-
135
- if [ "$REMOVE_OPT" = "1" ]; then
136
- OPT_TARGETS=(/opt/local-path-provisioner)
137
- for t in "${OPT_TARGETS[@]}"; do
138
- if [ -d "$t" ]; then
139
- log "Removing $t as REMOVE_OPT=1"
140
- rm -rf "$t"
141
- fi
142
- done
143
- else
144
- log "Automatic /opt removals disabled (set REMOVE_OPT=1 to enable)."
145
- fi
155
+ log "/opt usage (top entries):"
156
+ du -sh /opt/* 2>/dev/null | sort -h | head -n 20
157
+
158
+ if [ "$REMOVE_OPT" = "1" ]; then
159
+ OPT_TARGETS=(/opt/local-path-provisioner)
160
+ for t in "${OPT_TARGETS[@]}"; do
161
+ if [ -d "$t" ]; then
162
+ log "Removing $t as REMOVE_OPT=1"
163
+ rm -rf "$t"
164
+ fi
165
+ done
166
+ else
167
+ log "Automatic /opt removals disabled (set REMOVE_OPT=1 to enable)."
168
+ fi
146
169
  fi
147
170
 
148
171
  # 13) Find large files > 100MB (report only)
@@ -150,8 +173,10 @@ log "Listing files larger than 100MB (report only):"
150
173
  find / -xdev -type f -size +100M -printf '%s\t%p\n' 2>/dev/null | sort -nr | head -n 50 | awk '{printf "%.1fMB\t%s\n",$1/1024/1024,$2}'
151
174
 
152
175
  # Final disk usage
153
- log "Disk usage after cleanup:"
176
+ log "Disk usage cleanup:"
154
177
  df -h
178
+ log "Top-level usage ( / ):"
179
+ du -xh --max-depth=1 / | sort -rh | head -n 20
155
180
 
156
181
  log "=== Cleanup finished ==="
157
182
  exit 0
@@ -2,9 +2,7 @@
2
2
  #
3
3
  # test-monitor.sh — end-to-end deploy + two-phase monitor smoke test.
4
4
  #
5
- # Two deployment shapes are supported (see
6
- # src/client/public/nexodev/docs/references/Deploy-Monitor-PRD.md and
7
- # 'Deploy custom instance to K8S.md'):
5
+ # Two deployment shapes are supported:
8
6
  #
9
7
  # --mode runtime `underpost start` deploy (e.g. dd-test). Monitored with the
10
8
  # HTTP gate: /_internal/ready probes + port-forward status.
@@ -26,11 +24,11 @@ MODE=runtime # runtime | instance
26
24
  ENV=development # development | production
27
25
  DEPLOY_ID=dd-test # deploy id (instance mode: parent of conf.instances.json)
28
26
  INSTANCE_IDS= # instance mode: csv of ids (default: all in conf.instances.json)
29
- IMAGE=underpost/wp:v3.2.14 # runtime mode image (instance mode reads image from conf)
27
+ IMAGE=underpost/wp:v3.2.70 # runtime mode image (instance mode reads image from conf)
30
28
  VERSIONS=green # csv of blue/green versions
31
29
  REPLICAS=1 # replicas per deployment
32
30
  NAMESPACE=default # k8s namespace
33
- CLUSTER= # kind | kubeadm | k3s | "" (auto/none)
31
+ CLUSTER=kind # kind | kubeadm | k3s | "" (auto/none)
34
32
  TIMEOUT_RESPONSE=300000ms # HTTPProxy per-route response timeout
35
33
  TEMPLATE_REPO=underpostnet/pwa-microservices-template-private # runtime mode link repo
36
34
  ENVOY_NAMESPACE=projectcontour # ingress namespace (instance TLS exposure)
@@ -876,6 +876,74 @@ net.ipv4.ip_forward = 1' | sudo tee /etc/sysctl.d/99-k3s.conf > /dev/null`,
876
876
  }
877
877
  },
878
878
 
879
+ /**
880
+ * @method _pruneContainerCaches
881
+ * @description Reclaims container-runtime disk left behind after a cluster
882
+ * teardown: stopped containers, unused images, build cache and anonymous
883
+ * volumes. Best-effort across every runtime present on the host (docker,
884
+ * podman, and optionally the CRI runtime via crictl). Each block is guarded
885
+ * by a command-existence check, so it is safe to call when a runtime is
886
+ * absent. This is what turns a "cluster deleted" into "disk actually freed":
887
+ * `kind delete` / `kubeadm reset` remove the cluster but leave gigabytes of
888
+ * images and overlay layers under /var/lib/{docker,containers}.
889
+ * @param {object} [options]
890
+ * @param {boolean} [options.all=true] - Remove all unused images, not just dangling ones.
891
+ * @param {boolean} [options.crictl=false] - Also prune the CRI runtime via crictl.
892
+ * @param {string} [options.criSocket] - Optional crictl --runtime-endpoint override.
893
+ * @private
894
+ */
895
+ _pruneContainerCaches(options = {}) {
896
+ const all = options.all !== false;
897
+ const a = all ? '-a ' : '';
898
+ logger.info(` -> Pruning container-runtime caches (all=${all})...`);
899
+ // Docker (also matches the podman-docker shim when docker is symlinked to podman).
900
+ shellExec(
901
+ `if command -v docker >/dev/null 2>&1; then sudo docker system prune ${a}--volumes -f; sudo docker builder prune ${a}-f; fi`,
902
+ { silentOnError: true },
903
+ );
904
+ // Podman native — on this host images/overlays live under /var/lib/containers/storage.
905
+ shellExec(
906
+ `if command -v podman >/dev/null 2>&1; then sudo podman system prune ${a}--volumes -f; fi`,
907
+ { silentOnError: true },
908
+ );
909
+ if (options.crictl) {
910
+ const ep = options.criSocket ? `--runtime-endpoint ${options.criSocket} ` : '';
911
+ shellExec(
912
+ `if command -v crictl >/dev/null 2>&1; then sudo env PATH="$PATH:/usr/local/bin:/usr/bin" crictl ${ep}rmi --prune; fi`,
913
+ { silentOnError: true },
914
+ );
915
+ }
916
+ Underpost.cluster._unmountOrphanContainerOverlays();
917
+ },
918
+
919
+ /**
920
+ * @method _unmountOrphanContainerOverlays
921
+ * @description Unmounts leaked container overlay 'merged' mounts under
922
+ * /var/lib/{containers/storage,docker}/overlay. Pruning images/containers
923
+ * frees the backing data but leaves these mountpoints attached, so they
924
+ * keep showing up as identical `overlay ... /merged` rows in `df -h` long
925
+ * after the containers are gone. Only overlays NOT backing a still-existing
926
+ * container are unmounted, so running workloads are never disturbed.
927
+ * Best-effort; safe to call when no runtime is present.
928
+ * @private
929
+ */
930
+ _unmountOrphanContainerOverlays() {
931
+ logger.info(' -> Unmounting orphaned container overlay mounts...');
932
+ shellExec(`if command -v podman >/dev/null 2>&1; then sudo podman umount --all >/dev/null 2>&1 || true; fi`, {
933
+ silentOnError: true,
934
+ });
935
+ shellExec(
936
+ `active="$(sudo podman ps -aq 2>/dev/null | xargs -r -I{} sudo podman inspect --format '{{.GraphDriver.Data.MergedDir}}' {} 2>/dev/null || true)"
937
+ findmnt -rn -o TARGET 2>/dev/null | grep -E '/var/lib/(containers/storage|docker)/overlay.*/merged' | sort -r | while IFS= read -r m; do
938
+ if ! printf '%s\\n' "$active" | grep -qxF "$m"; then
939
+ echo "Unmounting orphaned overlay: $m"
940
+ sudo umount -l "$m" 2>/dev/null || true
941
+ fi
942
+ done`,
943
+ { silentOnError: true },
944
+ );
945
+ },
946
+
879
947
  /**
880
948
  * @method _lazyUmountKubeletMounts
881
949
  * @description Lazy-unmounts every mount under /var/lib/kubelet so a
@@ -910,7 +978,7 @@ net.ipv4.ip_forward = 1' | sudo tee /etc/sysctl.d/99-k3s.conf > /dev/null`,
910
978
  if (options.removeVolumeHostPaths) Underpost.cluster._cleanHostPathPvs();
911
979
  else logger.info(' -> Skipping (pass --remove-volume-host-paths to enable).');
912
980
 
913
- logger.info('Phase 3/5: Deleting all Kind clusters...');
981
+ logger.info('Phase 3/6: Deleting all Kind clusters...');
914
982
  shellExec(`clusters=$(kind get clusters)
915
983
  if [ -n "$clusters" ]; then
916
984
  for c in $clusters; do
@@ -919,11 +987,14 @@ if [ -n "$clusters" ]; then
919
987
  done
920
988
  fi`);
921
989
 
922
- logger.info('Phase 4/5: Cleaning kubeconfig and Kind Docker networks...');
990
+ logger.info('Phase 4/6: Cleaning kubeconfig and Kind Docker networks...');
923
991
  shellExec(`rm -rf "$HOME/.kube"`);
924
992
  Underpost.cluster.recoverKindDockerNetworks();
925
993
 
926
- logger.info('Phase 5/5: Re-applying host configuration (Docker, containerd, sysctl).');
994
+ logger.info('Phase 5/6: Pruning container-runtime caches (kindest/node images, build cache, volumes)...');
995
+ Underpost.cluster._pruneContainerCaches({ all: true });
996
+
997
+ logger.info('Phase 6/6: Re-applying host configuration (Docker, containerd, sysctl).');
927
998
  Underpost.cluster.config();
928
999
 
929
1000
  logger.info('=== KIND SAFE RESET COMPLETE ===');
@@ -995,7 +1066,7 @@ fi`);
995
1066
  shellExec(`if ip link show tunl0 >/dev/null 2>&1; then sudo ip link del tunl0; fi`);
996
1067
  shellExec(`sudo iptables -F`);
997
1068
  shellExec(`sudo iptables -t nat -F`);
998
- shellExec(`if command -v crictl >/dev/null 2>&1; then sudo crictl rmi --prune; fi`);
1069
+ Underpost.cluster._pruneContainerCaches({ all: true, crictl: true });
999
1070
 
1000
1071
  logger.info('Phase 7/7: Re-applying host configuration (Docker, containerd, sysctl).');
1001
1072
  Underpost.cluster.config();
@@ -1042,6 +1113,8 @@ fi`);
1042
1113
  shellExec(`if [ -d /etc/rancher/k3s ]; then sudo rm -rf /etc/rancher/k3s; fi`);
1043
1114
  shellExec(`if ip link show flannel.1 >/dev/null 2>&1; then sudo ip link del flannel.1; fi`);
1044
1115
  shellExec(`if ip link show cni0 >/dev/null 2>&1; then sudo ip link del cni0; fi`);
1116
+ // k3s-uninstall.sh removes /var/lib/rancher/k3s; still prune any host docker/podman leftovers.
1117
+ Underpost.cluster._pruneContainerCaches({ all: true });
1045
1118
 
1046
1119
  logger.info('Phase 5/5: Re-applying minimal K3s host config.');
1047
1120
  Underpost.cluster.configMinimalK3s();