@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 token
209
+ local output
204
210
 
205
- # Tests may provide a deterministic manager enumeration. Production uses the systemd Manager
206
- # D-Bus method instead of treating MainPID=0 as an independent process proof.
207
- if [ -n "${SERVICE_MANAGER_PROCS_FILE:-}" ]; then
208
- [ -r "$SERVICE_MANAGER_PROCS_FILE" ] || return 2
209
- output="$(cat "$SERVICE_MANAGER_PROCS_FILE" 2>/dev/null)" || return 2
210
- else
211
- command -v busctl >/dev/null 2>&1 || return 2
212
- output="$(busctl --system call org.freedesktop.systemd1 /org/freedesktop/systemd1 \
213
- org.freedesktop.systemd1.Manager GetUnitProcesses s "$SERVICE_UNIT" 2>/dev/null)" || return 2
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
- # busctl returns an array header followed by tuples. Any positive integer in the tuple stream
217
- # is a process owned by the unit; zero is the empty-array count/fixture sentinel.
218
- while IFS= read -r token; do
219
- case "$token" in
220
- ''|0) ;;
221
- *[!0-9]*) ;;
222
- *) [ "$token" -eq 0 ] || return 1 ;;
223
- esac
224
- done <<EOF
225
- $(printf '%s\n' "$output" | grep -Eo '[0-9]+' || true)
226
- EOF
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 property value cgroup_mount_type
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
- current_baseline="$baseline"
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 || true)"
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
- [ -n "$control_group" ] || control_group="/system.slice/${SERVICE_NAME}.service"
267
- case "$control_group" in
268
- /*) ;;
269
- *) return 2 ;;
270
- esac
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 < <(find "$cgroup_dir" -type f -name cgroup.procs -print 2>/dev/null)
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
- [ -d "${SERVICE_CGROUP_MOUNT:-/sys/fs/cgroup}" ] || return 2
299
- [ -d "$(dirname "$cgroup_dir")" ] || return 2
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 command
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(/^.*\\) /, ""); print $20 }')" || return 2
369
- [ -n "$start_time" ] || return 2
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
- command="$(ps -p "$pid" -o command= 2>/dev/null)" || return 2
381
- [ -n "$command" ] || return 2
382
- printf '%s|%s\n' "$pid" "$command"
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="$(cat "$cgroup_procs" 2>/dev/null)"; then
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="$(cat "$cgroup_events" 2>/dev/null)"; then
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 gnu_stat bsd_stat
442
-
443
- if [ ! -e "$cgroup_dir" ]; then
444
- [ -d "$(dirname "$cgroup_dir")" ] || return 2
445
- return 1
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
- [ -d "$cgroup_dir" ] || return 2
448
-
449
- if gnu_stat="$(LC_ALL=C stat -c '%F' "$cgroup_dir" 2>&1)"; then
450
- case "$gnu_stat" in
451
- directory) return 0 ;;
452
- *) return 2 ;;
453
- esac
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
- if bsd_stat="$(LC_ALL=C stat -f '%HT' "$cgroup_dir" 2>&1)"; then
457
- case "$bsd_stat" in
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
- case "$gnu_stat\n$bsd_stat" in
464
- *"No such file or directory"*|*"no such file or directory"*) return 1 ;;
465
- *) return 2 ;;
466
- esac
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.21",
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.21"
115
+ "@fastybird/smart-panel-extension-sdk": "1.1.0-alpha.23"
116
116
  },
117
117
  "devDependencies": {
118
118
  "@eslint/eslintrc": "^3.3.5",