@fastybird/smart-panel-backend 1.1.0-alpha.21 → 1.1.0-alpha.23
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.
|
@@ -44,6 +44,12 @@ DB_BACKUP_PATH="${DB_BACKUP_PATH:-}"
|
|
|
44
44
|
BACKUP_TMP_PATH=""
|
|
45
45
|
SOURCE_DB_PATH=""
|
|
46
46
|
STOPPED_SERVICE_BASELINE=""
|
|
47
|
+
CGROUP_PROBE_MOUNT_ID=""
|
|
48
|
+
CGROUP_PROBE_PARENT_ID=""
|
|
49
|
+
CGROUP_PROBE_DIR_ID=""
|
|
50
|
+
CGROUP_PROBE_EVENTS_ID=""
|
|
51
|
+
CGROUP_PROBE_NAMESPACE_ID=""
|
|
52
|
+
CGROUP_PROBE_ABSENT="false"
|
|
47
53
|
|
|
48
54
|
json_escape() {
|
|
49
55
|
printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g; s/[[:cntrl:]]//g'
|
|
@@ -200,35 +206,104 @@ service_property() {
|
|
|
200
206
|
}
|
|
201
207
|
|
|
202
208
|
manager_processes_are_empty() {
|
|
203
|
-
local output
|
|
209
|
+
local output
|
|
204
210
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
211
|
+
command -v busctl >/dev/null 2>&1 || return 2
|
|
212
|
+
output="$(busctl --system --json=short call org.freedesktop.systemd1 /org/freedesktop/systemd1 \
|
|
213
|
+
org.freedesktop.systemd1.Manager GetUnitProcesses s "$SERVICE_UNIT" 2>/dev/null)" || return 2
|
|
214
|
+
|
|
215
|
+
# busctl's JSON preserves the a(sus) signature and tuple boundaries. Never infer an empty
|
|
216
|
+
# process set from omitted digits, a truncated reply, or a successful command with no payload.
|
|
217
|
+
[ "${#output}" -le 65536 ] || return 2
|
|
218
|
+
printf '%s' "$output" | MANAGER_REPLY=true node -e '
|
|
219
|
+
let input = "";
|
|
220
|
+
process.stdin.setEncoding("utf8");
|
|
221
|
+
process.stdin.on("data", (chunk) => { input += chunk; });
|
|
222
|
+
process.stdin.on("end", () => {
|
|
223
|
+
try {
|
|
224
|
+
const reply = JSON.parse(input);
|
|
225
|
+
if (reply?.type !== "a(sus)" || !Array.isArray(reply.data) ||
|
|
226
|
+
reply.data.length !== 1 || !Array.isArray(reply.data[0])) process.exit(2);
|
|
227
|
+
const members = reply.data[0];
|
|
228
|
+
if (members.length > 4096) process.exit(2);
|
|
229
|
+
for (const tuple of members) {
|
|
230
|
+
if (!Array.isArray(tuple) || tuple.length !== 3 ||
|
|
231
|
+
typeof tuple[0] !== "string" || typeof tuple[2] !== "string" ||
|
|
232
|
+
!Number.isInteger(tuple[1]) || tuple[1] < 1 || tuple[1] > 4294967295) process.exit(2);
|
|
233
|
+
}
|
|
234
|
+
process.exit(members.length === 0 ? 0 : 1);
|
|
235
|
+
} catch {
|
|
236
|
+
process.exit(2);
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
'
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
service_unit_object() {
|
|
243
|
+
local output
|
|
244
|
+
command -v busctl >/dev/null 2>&1 || return 2
|
|
245
|
+
output="$(busctl --system --json=short call org.freedesktop.systemd1 /org/freedesktop/systemd1 \
|
|
246
|
+
org.freedesktop.systemd1.Manager GetUnit s "$SERVICE_UNIT" 2>/dev/null)" || return 2
|
|
247
|
+
[ "${#output}" -le 4096 ] || return 2
|
|
248
|
+
printf '%s' "$output" | MANAGER_REPLY=true node -e '
|
|
249
|
+
let input = "";
|
|
250
|
+
process.stdin.setEncoding("utf8");
|
|
251
|
+
process.stdin.on("data", (chunk) => { input += chunk; });
|
|
252
|
+
process.stdin.on("end", () => {
|
|
253
|
+
try {
|
|
254
|
+
const reply = JSON.parse(input);
|
|
255
|
+
const object = reply?.data?.[0];
|
|
256
|
+
if (reply.type !== "o" || reply.data.length !== 1 || typeof object !== "string" ||
|
|
257
|
+
!object.startsWith("/org/freedesktop/systemd1/unit/")) process.exit(2);
|
|
258
|
+
process.stdout.write(object);
|
|
259
|
+
} catch { process.exit(2); }
|
|
260
|
+
});
|
|
261
|
+
'
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
systemd_manager_identity() {
|
|
265
|
+
local stat_line start_time
|
|
266
|
+
if [ "${SERVICE_PROCESS_ROOT:-/proc}" != "/proc" ]; then
|
|
267
|
+
process_identity 1
|
|
268
|
+
return $?
|
|
214
269
|
fi
|
|
270
|
+
stat_line="$(cat /proc/1/stat 2>/dev/null)" || return 2
|
|
271
|
+
case "$stat_line" in '1 (systemd) '*) ;; *) return 2 ;; esac
|
|
272
|
+
start_time="$(printf '%s\n' "$stat_line" | awk '{ sub(/^.*\) /, ""); print $20 }')" || return 2
|
|
273
|
+
case "$start_time" in ''|*[!0-9]*) return 2 ;; esac
|
|
274
|
+
[ "$start_time" -gt 0 ] 2>/dev/null || return 2
|
|
275
|
+
printf '1|%s\n' "$start_time"
|
|
276
|
+
}
|
|
215
277
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
$(
|
|
226
|
-
|
|
278
|
+
stopped_service_identity() {
|
|
279
|
+
local boot_id manager_identity unit_object property value fragment_path exec_start slice delegate kill_mode unit_id load_state
|
|
280
|
+
|
|
281
|
+
boot_id="$(cat "${SERVICE_BOOT_ID_FILE:-/proc/sys/kernel/random/boot_id}" 2>/dev/null)" || return 2
|
|
282
|
+
[ -n "$boot_id" ] || return 2
|
|
283
|
+
manager_identity="$(systemd_manager_identity)" || return 2
|
|
284
|
+
unit_object="$(service_unit_object)" || return 2
|
|
285
|
+
unit_id="$(service_property Id 2>/dev/null)" || return 2
|
|
286
|
+
load_state="$(service_property LoadState 2>/dev/null)" || return 2
|
|
287
|
+
fragment_path="$(service_property FragmentPath 2>/dev/null)" || return 2
|
|
288
|
+
exec_start="$(service_property ExecStart 2>/dev/null)" || return 2
|
|
289
|
+
slice="$(service_property Slice 2>/dev/null)" || return 2
|
|
290
|
+
delegate="$(service_property Delegate 2>/dev/null)" || return 2
|
|
291
|
+
kill_mode="$(service_property KillMode 2>/dev/null)" || return 2
|
|
292
|
+
[ "$unit_id" = "$SERVICE_UNIT" ] && [ "$load_state" = "loaded" ] || return 2
|
|
293
|
+
[ "$fragment_path" = "/etc/systemd/system/${SERVICE_UNIT}" ] || return 2
|
|
294
|
+
[[ "$exec_start" == *"${IMAGE_BASE_DIR}/current"* ]] || return 2
|
|
295
|
+
[ "$slice" = "system.slice" ] && [ "$delegate" = "no" ] && [ "$kill_mode" = "process" ] || return 2
|
|
296
|
+
printf 'boot=%s\nmanager=%s\nobject=%s\n' "$boot_id" "$manager_identity" "$unit_object"
|
|
297
|
+
for property in FragmentPath ExecStart Slice Delegate KillMode InvocationID Result; do
|
|
298
|
+
value="$(service_property "$property" 2>/dev/null)" || return 2
|
|
299
|
+
[ -n "$value" ] || return 2
|
|
300
|
+
printf '%s=%s\n' "$property" "$value"
|
|
301
|
+
done
|
|
227
302
|
}
|
|
228
303
|
|
|
229
304
|
stopped_service_quiesced() {
|
|
230
305
|
local active_state main_pid control_pid job_id control_group cgroup_dir cgroup_procs cgroup_events
|
|
231
|
-
local member_status population_status probe_status members subtree_file baseline current_baseline
|
|
306
|
+
local member_status population_status probe_status members subtree_file baseline current_baseline cgroup_mount_type
|
|
232
307
|
|
|
233
308
|
active_state="$(service_property ActiveState 2>/dev/null)" || return 2
|
|
234
309
|
if [ -n "${SERVICE_CGROUP_MOUNT_TYPE:-}" ]; then
|
|
@@ -247,27 +322,22 @@ stopped_service_quiesced() {
|
|
|
247
322
|
[ -z "$job_id" ] || [ "$job_id" = "0" ] || return 1
|
|
248
323
|
|
|
249
324
|
manager_processes_are_empty || return $?
|
|
250
|
-
baseline=""
|
|
251
|
-
for property in FragmentPath ExecStart Slice Delegate KillMode InvocationID Result; do
|
|
252
|
-
value="$(service_property "$property" 2>/dev/null || true)"
|
|
253
|
-
[ -n "$value" ] || return 2
|
|
254
|
-
baseline="${baseline}${property}=${value}\n"
|
|
255
|
-
done
|
|
325
|
+
baseline="$(stopped_service_identity)" || return 2
|
|
256
326
|
if [ -n "$STOPPED_SERVICE_BASELINE" ]; then
|
|
257
|
-
|
|
258
|
-
[ "$current_baseline" = "$STOPPED_SERVICE_BASELINE" ] || return 1
|
|
327
|
+
[ "$baseline" = "$STOPPED_SERVICE_BASELINE" ] || return 1
|
|
259
328
|
else
|
|
260
329
|
STOPPED_SERVICE_BASELINE="$baseline"
|
|
261
330
|
fi
|
|
262
331
|
|
|
263
|
-
control_group="$(service_property ControlGroup 2>/dev/null ||
|
|
332
|
+
control_group="$(service_property ControlGroup 2>/dev/null)" || return 2
|
|
264
333
|
# systemd may clear ControlGroup after a stopped unit is reaped. The canonical unit cgroup is
|
|
265
334
|
# still the only acceptable fallback; it is checked against the cgroup-v2 mount below.
|
|
266
|
-
[ -
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
335
|
+
if [ -z "$control_group" ]; then
|
|
336
|
+
[ "$(service_property Slice 2>/dev/null)" = "system.slice" ] || return 2
|
|
337
|
+
[ "$(service_property Delegate 2>/dev/null)" = "no" ] || return 2
|
|
338
|
+
control_group="/system.slice/${SERVICE_UNIT}"
|
|
339
|
+
fi
|
|
340
|
+
[ "$control_group" = "/system.slice/${SERVICE_UNIT}" ] || return 2
|
|
271
341
|
cgroup_procs="${SERVICE_CGROUP_PROCS_FILE:-/sys/fs/cgroup${control_group}/cgroup.procs}"
|
|
272
342
|
cgroup_dir="${SERVICE_CGROUP_DIR:-${cgroup_procs%/cgroup.procs}}"
|
|
273
343
|
cgroup_events="${SERVICE_CGROUP_EVENTS_FILE:-${cgroup_dir}/cgroup.events}"
|
|
@@ -282,11 +352,19 @@ stopped_service_quiesced() {
|
|
|
282
352
|
[ "$CGROUP_POPULATED" = "0" ] || return 1
|
|
283
353
|
# Check descendants as well as the unit's direct cgroup. A delegated child subtree is not
|
|
284
354
|
# quiescent merely because the parent cgroup.procs is empty.
|
|
355
|
+
local subtree_files symlinks
|
|
356
|
+
symlinks="$(find "$cgroup_dir" -type l -print 2>/dev/null)" || return 2
|
|
357
|
+
[ -z "$symlinks" ] || return 2
|
|
358
|
+
subtree_files="$(find "$cgroup_dir" -type f -name cgroup.procs -print 2>/dev/null)" || return 2
|
|
285
359
|
while IFS= read -r subtree_file; do
|
|
360
|
+
[ -n "$subtree_file" ] || continue
|
|
286
361
|
[ "$subtree_file" = "$cgroup_procs" ] && continue
|
|
287
362
|
read_cgroup_members "$subtree_file" || return $?
|
|
288
363
|
[ -z "$CGROUP_MEMBERS" ] || return 1
|
|
289
|
-
done
|
|
364
|
+
done <<< "$subtree_files"
|
|
365
|
+
current_baseline="$(stopped_service_identity)" || return 2
|
|
366
|
+
[ "$current_baseline" = "$baseline" ] || return 1
|
|
367
|
+
manager_processes_are_empty || return $?
|
|
290
368
|
return 0
|
|
291
369
|
else
|
|
292
370
|
probe_status=$?
|
|
@@ -295,8 +373,9 @@ stopped_service_quiesced() {
|
|
|
295
373
|
|
|
296
374
|
# A removed original cgroup is valid only when the cgroup-v2 mount and its stable parent remain
|
|
297
375
|
# readable; manager enumeration above is the independent proof that no unit process survived.
|
|
298
|
-
|
|
299
|
-
[
|
|
376
|
+
current_baseline="$(stopped_service_identity)" || return 2
|
|
377
|
+
[ "$current_baseline" = "$baseline" ] || return 1
|
|
378
|
+
manager_processes_are_empty || return $?
|
|
300
379
|
return 0
|
|
301
380
|
}
|
|
302
381
|
|
|
@@ -353,7 +432,7 @@ process_identity() {
|
|
|
353
432
|
local pid="$1"
|
|
354
433
|
local proc_root="${SERVICE_PROCESS_ROOT:-/proc}"
|
|
355
434
|
local proc_dir="${proc_root}/${pid}"
|
|
356
|
-
local stat_line start_time executable
|
|
435
|
+
local stat_line start_time executable
|
|
357
436
|
|
|
358
437
|
[ "$pid" != "0" ] && [ -n "$pid" ] || return 1
|
|
359
438
|
|
|
@@ -365,21 +444,44 @@ process_identity() {
|
|
|
365
444
|
|
|
366
445
|
if [ -r "${proc_dir}/stat" ]; then
|
|
367
446
|
stat_line="$(cat "${proc_dir}/stat")" || return 2
|
|
368
|
-
start_time="$(printf '%s\n' "$stat_line" | awk '{ sub(
|
|
369
|
-
|
|
447
|
+
start_time="$(printf '%s\n' "$stat_line" | awk '{ sub(/^.*\) /, ""); print $20 }')" || return 2
|
|
448
|
+
case "$start_time" in ''|*[!0-9]*) return 2 ;; esac
|
|
449
|
+
[ "$start_time" -gt 0 ] 2>/dev/null || return 2
|
|
370
450
|
executable="$(readlink "${proc_dir}/exe" 2>/dev/null)" || return 2
|
|
371
451
|
[ -n "$executable" ] || return 2
|
|
372
452
|
printf '%s|%s|%s\n' "$pid" "$start_time" "$executable"
|
|
373
453
|
return 0
|
|
374
454
|
fi
|
|
455
|
+
[ "$proc_root" = "/proc" ] || return 1
|
|
375
456
|
|
|
376
457
|
if ! kill -0 "$pid" 2>/dev/null; then
|
|
377
458
|
return 1
|
|
378
459
|
fi
|
|
460
|
+
# A live process without readable /proc start ticks and executable has unknown identity.
|
|
461
|
+
return 2
|
|
462
|
+
}
|
|
379
463
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
464
|
+
read_cgroup_file() {
|
|
465
|
+
CGROUP_FILE_PATH="$1" node -e '
|
|
466
|
+
const fs = require("node:fs");
|
|
467
|
+
let fd;
|
|
468
|
+
try {
|
|
469
|
+
fd = fs.openSync(process.env.CGROUP_FILE_PATH, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW | fs.constants.O_NONBLOCK);
|
|
470
|
+
if (!fs.fstatSync(fd).isFile()) process.exit(2);
|
|
471
|
+
const chunks = [];
|
|
472
|
+
let size = 0;
|
|
473
|
+
while (true) {
|
|
474
|
+
const buffer = Buffer.allocUnsafe(4096);
|
|
475
|
+
const count = fs.readSync(fd, buffer, 0, buffer.length, null);
|
|
476
|
+
if (count === 0) break;
|
|
477
|
+
size += count;
|
|
478
|
+
if (size > 65536) process.exit(2);
|
|
479
|
+
chunks.push(buffer.subarray(0, count));
|
|
480
|
+
}
|
|
481
|
+
process.stdout.write(Buffer.concat(chunks));
|
|
482
|
+
} catch { process.exitCode = 2; }
|
|
483
|
+
finally { if (fd !== undefined) fs.closeSync(fd); }
|
|
484
|
+
'
|
|
383
485
|
}
|
|
384
486
|
|
|
385
487
|
read_cgroup_members() {
|
|
@@ -387,7 +489,7 @@ read_cgroup_members() {
|
|
|
387
489
|
local contents members
|
|
388
490
|
|
|
389
491
|
CGROUP_MEMBERS=""
|
|
390
|
-
if ! contents="$(
|
|
492
|
+
if ! contents="$(read_cgroup_file "$cgroup_procs" 2>/dev/null)"; then
|
|
391
493
|
return 2
|
|
392
494
|
fi
|
|
393
495
|
|
|
@@ -410,7 +512,7 @@ read_cgroup_population() {
|
|
|
410
512
|
local contents populated
|
|
411
513
|
|
|
412
514
|
CGROUP_POPULATED=""
|
|
413
|
-
if ! contents="$(
|
|
515
|
+
if ! contents="$(read_cgroup_file "$cgroup_events" 2>/dev/null)"; then
|
|
414
516
|
return 2
|
|
415
517
|
fi
|
|
416
518
|
|
|
@@ -438,32 +540,87 @@ read_cgroup_population() {
|
|
|
438
540
|
|
|
439
541
|
probe_cgroup_directory() {
|
|
440
542
|
local cgroup_dir="$1"
|
|
441
|
-
local
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
543
|
+
local output result mount_id parent_id dir_id events_id namespace_id
|
|
544
|
+
|
|
545
|
+
# lstat keeps a dangling link, permission error, and a genuinely absent cgroup distinct.
|
|
546
|
+
# The mount and parent are identified both inside the probe and across all worker checks.
|
|
547
|
+
# Compare the events node too: kernfs can reinitialize directory timestamps without replacing the cgroup.
|
|
548
|
+
# JavaScript template literals are intentionally passed verbatim.
|
|
549
|
+
# shellcheck disable=SC2016
|
|
550
|
+
if output="$(CGROUP_PROBE_PATH="$cgroup_dir" CGROUP_PROBE_EVENTS="${SERVICE_CGROUP_EVENTS_FILE:-${cgroup_dir}/cgroup.events}" CGROUP_PROBE_MOUNT="${SERVICE_CGROUP_MOUNT:-/sys/fs/cgroup}" node -e '
|
|
551
|
+
const fs = require("node:fs");
|
|
552
|
+
const path = require("node:path");
|
|
553
|
+
const mount = process.env.CGROUP_PROBE_MOUNT;
|
|
554
|
+
const dir = process.env.CGROUP_PROBE_PATH;
|
|
555
|
+
if (!path.isAbsolute(mount) || !path.isAbsolute(dir) || !dir.startsWith(`${mount}/`)) process.exit(2);
|
|
556
|
+
const parent = path.dirname(dir);
|
|
557
|
+
function directoryIdentity(name) {
|
|
558
|
+
const stat = fs.lstatSync(name, { bigint: true });
|
|
559
|
+
if (!stat.isDirectory()) process.exit(2);
|
|
560
|
+
return `${stat.dev}:${stat.ino}`;
|
|
561
|
+
}
|
|
562
|
+
function fileIdentity(name) {
|
|
563
|
+
const stat = fs.lstatSync(name, { bigint: true });
|
|
564
|
+
if (!stat.isFile()) process.exit(2);
|
|
565
|
+
return `${stat.dev}:${stat.ino}`;
|
|
566
|
+
}
|
|
567
|
+
try {
|
|
568
|
+
const mountId = directoryIdentity(mount);
|
|
569
|
+
let namespaceId;
|
|
570
|
+
try { namespaceId = `${fs.readlinkSync("/proc/self/ns/mnt")}:${fs.readlinkSync("/proc/self/ns/cgroup")}`; }
|
|
571
|
+
catch {
|
|
572
|
+
if (!process.env.SERVICE_CGROUP_MOUNT_TYPE) process.exit(2);
|
|
573
|
+
namespaceId = "fixture-namespace";
|
|
574
|
+
}
|
|
575
|
+
if (process.env.SERVICE_CGROUP_MOUNT_TYPE) {
|
|
576
|
+
if (process.env.SERVICE_CGROUP_MOUNT_TYPE !== "cgroup2fs") process.exit(2);
|
|
577
|
+
} else if (fs.statfsSync(mount, { bigint: true }).type !== 0x63677270n) process.exit(2);
|
|
578
|
+
const parentId = directoryIdentity(parent);
|
|
579
|
+
let dirId = "-";
|
|
580
|
+
let eventsId = "-";
|
|
581
|
+
let absent = false;
|
|
582
|
+
try { dirId = directoryIdentity(dir); }
|
|
583
|
+
catch (error) {
|
|
584
|
+
if (error.code !== "ENOENT") process.exit(2);
|
|
585
|
+
absent = true;
|
|
586
|
+
}
|
|
587
|
+
if (!absent) eventsId = fileIdentity(process.env.CGROUP_PROBE_EVENTS);
|
|
588
|
+
if (directoryIdentity(mount) !== mountId || directoryIdentity(parent) !== parentId) process.exit(2);
|
|
589
|
+
if (absent) {
|
|
590
|
+
try { fs.lstatSync(dir); process.exit(2); }
|
|
591
|
+
catch (error) { if (error.code !== "ENOENT") process.exit(2); }
|
|
592
|
+
}
|
|
593
|
+
process.stdout.write(`${mountId}|${parentId}|${dirId}|${eventsId}|${namespaceId}`);
|
|
594
|
+
process.exit(absent ? 1 : 0);
|
|
595
|
+
} catch { process.exit(2); }
|
|
596
|
+
' 2>/dev/null)"; then
|
|
597
|
+
result=0
|
|
598
|
+
else
|
|
599
|
+
result=$?
|
|
446
600
|
fi
|
|
447
|
-
[ -
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
601
|
+
[ "$result" -eq 0 ] || [ "$result" -eq 1 ] || return 2
|
|
602
|
+
IFS='|' read -r mount_id parent_id dir_id events_id namespace_id <<< "$output"
|
|
603
|
+
[ -n "$mount_id" ] && [ -n "$parent_id" ] && [ -n "$dir_id" ] && [ -n "$events_id" ] && [ -n "$namespace_id" ] || return 2
|
|
604
|
+
if [ -n "$CGROUP_PROBE_MOUNT_ID" ]; then
|
|
605
|
+
[ "$CGROUP_PROBE_MOUNT_ID" = "$mount_id" ] && [ "$CGROUP_PROBE_PARENT_ID" = "$parent_id" ] && \
|
|
606
|
+
[ "$CGROUP_PROBE_NAMESPACE_ID" = "$namespace_id" ] || return 2
|
|
607
|
+
else
|
|
608
|
+
CGROUP_PROBE_MOUNT_ID="$mount_id"
|
|
609
|
+
CGROUP_PROBE_PARENT_ID="$parent_id"
|
|
610
|
+
CGROUP_PROBE_NAMESPACE_ID="$namespace_id"
|
|
454
611
|
fi
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
Directory|directory) return 0 ;;
|
|
459
|
-
*) return 2 ;;
|
|
460
|
-
esac
|
|
612
|
+
if [ "$result" -eq 1 ]; then
|
|
613
|
+
CGROUP_PROBE_ABSENT="true"
|
|
614
|
+
return 1
|
|
461
615
|
fi
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
616
|
+
[ "$CGROUP_PROBE_ABSENT" = "false" ] || return 2
|
|
617
|
+
if [ -n "$CGROUP_PROBE_DIR_ID" ]; then
|
|
618
|
+
[ "$CGROUP_PROBE_DIR_ID" = "$dir_id" ] && [ "$CGROUP_PROBE_EVENTS_ID" = "$events_id" ] || return 2
|
|
619
|
+
else
|
|
620
|
+
CGROUP_PROBE_DIR_ID="$dir_id"
|
|
621
|
+
CGROUP_PROBE_EVENTS_ID="$events_id"
|
|
622
|
+
fi
|
|
623
|
+
return 0
|
|
467
624
|
}
|
|
468
625
|
|
|
469
626
|
capture_service_identity() {
|
|
@@ -1046,6 +1203,12 @@ if [ "$INSTALL_TYPE" = "image" ]; then
|
|
|
1046
1203
|
rm -f "$BACKUP_TMP_PATH"
|
|
1047
1204
|
BACKUP_TMP_PATH=""
|
|
1048
1205
|
fi
|
|
1206
|
+
if [ "$UPDATE_START_MODE" = "stopped-maintenance" ] && ! stopped_service_quiesced; then
|
|
1207
|
+
hold_attempt "quiesced" "Stopped service changed after backup before switch"
|
|
1208
|
+
update_status "failed" "failed" "Stopped service changed after backup; update is held for recovery"
|
|
1209
|
+
FINALIZED="true"
|
|
1210
|
+
exit 1
|
|
1211
|
+
fi
|
|
1049
1212
|
|
|
1050
1213
|
# ── Switch symlink (atomic on same filesystem) ──
|
|
1051
1214
|
sudo -n ln -sfn "$NEW_VERSION_DIR" "$CURRENT_LINK" || {
|
|
@@ -1075,6 +1238,12 @@ if [ "$INSTALL_TYPE" = "image" ]; then
|
|
|
1075
1238
|
FINALIZED="true"
|
|
1076
1239
|
exit 1
|
|
1077
1240
|
fi
|
|
1241
|
+
if [ "$UPDATE_START_MODE" = "stopped-maintenance" ] && ! stopped_service_quiesced; then
|
|
1242
|
+
hold_attempt "migrating" "Stopped service changed before migration entry"
|
|
1243
|
+
update_status "failed" "failed" "Stopped service changed before migration entry; update is held for recovery"
|
|
1244
|
+
FINALIZED="true"
|
|
1245
|
+
exit 1
|
|
1246
|
+
fi
|
|
1078
1247
|
|
|
1079
1248
|
MIGRATION_ENTERED="true"
|
|
1080
1249
|
if ! write_attempt "active" "migrating" "" "false" "true"; then
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fastybird/smart-panel-backend",
|
|
3
|
-
"version": "1.1.0-alpha.
|
|
3
|
+
"version": "1.1.0-alpha.23",
|
|
4
4
|
"description": "Powerful backend for administrating and integrating the FastyBird IoT Smart Panel",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"fastybird",
|
|
@@ -112,7 +112,7 @@
|
|
|
112
112
|
"ws": "^8.19.0",
|
|
113
113
|
"yaml": "^2.8.2",
|
|
114
114
|
"zod": "4.4.3",
|
|
115
|
-
"@fastybird/smart-panel-extension-sdk": "1.1.0-alpha.
|
|
115
|
+
"@fastybird/smart-panel-extension-sdk": "1.1.0-alpha.23"
|
|
116
116
|
},
|
|
117
117
|
"devDependencies": {
|
|
118
118
|
"@eslint/eslintrc": "^3.3.5",
|