clonebox 1.1.13__py3-none-any.whl → 1.1.15__py3-none-any.whl
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.
- clonebox/audit.py +452 -0
- clonebox/cli.py +966 -10
- clonebox/cloner.py +221 -135
- clonebox/orchestrator.py +568 -0
- clonebox/plugins/__init__.py +24 -0
- clonebox/plugins/base.py +319 -0
- clonebox/plugins/manager.py +523 -0
- clonebox/remote.py +511 -0
- clonebox/secrets.py +9 -6
- clonebox/validator.py +113 -41
- {clonebox-1.1.13.dist-info → clonebox-1.1.15.dist-info}/METADATA +5 -1
- {clonebox-1.1.13.dist-info → clonebox-1.1.15.dist-info}/RECORD +16 -10
- {clonebox-1.1.13.dist-info → clonebox-1.1.15.dist-info}/WHEEL +0 -0
- {clonebox-1.1.13.dist-info → clonebox-1.1.15.dist-info}/entry_points.txt +0 -0
- {clonebox-1.1.13.dist-info → clonebox-1.1.15.dist-info}/licenses/LICENSE +0 -0
- {clonebox-1.1.13.dist-info → clonebox-1.1.15.dist-info}/top_level.txt +0 -0
clonebox/cloner.py
CHANGED
|
@@ -35,6 +35,7 @@ from clonebox.logging import get_logger, log_operation
|
|
|
35
35
|
from clonebox.resources import ResourceLimits
|
|
36
36
|
from clonebox.rollback import vm_creation_transaction
|
|
37
37
|
from clonebox.secrets import SecretsManager, SSHKeyPair
|
|
38
|
+
from clonebox.audit import get_audit_logger, AuditEventType, AuditOutcome
|
|
38
39
|
|
|
39
40
|
log = get_logger(__name__)
|
|
40
41
|
|
|
@@ -365,121 +366,131 @@ class SelectiveVMCloner:
|
|
|
365
366
|
Returns:
|
|
366
367
|
UUID of created VM
|
|
367
368
|
"""
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
369
|
+
audit = get_audit_logger()
|
|
370
|
+
with audit.operation(
|
|
371
|
+
AuditEventType.VM_CREATE,
|
|
372
|
+
target_type="vm",
|
|
373
|
+
target_name=config.name,
|
|
374
|
+
) as audit_ctx:
|
|
375
|
+
audit_ctx.add_detail("ram_mb", config.ram_mb)
|
|
376
|
+
audit_ctx.add_detail("vcpus", config.vcpus)
|
|
377
|
+
audit_ctx.add_detail("disk_size_gb", config.disk_size_gb)
|
|
378
|
+
|
|
379
|
+
with log_operation(
|
|
380
|
+
log, "vm.create", vm_name=config.name, ram_mb=config.ram_mb
|
|
381
|
+
):
|
|
382
|
+
with vm_creation_transaction(self, config, console) as ctx:
|
|
383
|
+
# If VM already exists, optionally replace it
|
|
384
|
+
existing_vm = None
|
|
385
|
+
try:
|
|
386
|
+
candidate_vm = self.conn.lookupByName(config.name)
|
|
387
|
+
if candidate_vm is not None:
|
|
388
|
+
try:
|
|
389
|
+
if hasattr(candidate_vm, "name") and callable(candidate_vm.name):
|
|
390
|
+
if candidate_vm.name() == config.name:
|
|
391
|
+
existing_vm = candidate_vm
|
|
392
|
+
else:
|
|
380
393
|
existing_vm = candidate_vm
|
|
381
|
-
|
|
394
|
+
except Exception:
|
|
382
395
|
existing_vm = candidate_vm
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
existing_vm
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
396
|
+
except Exception:
|
|
397
|
+
existing_vm = None
|
|
398
|
+
|
|
399
|
+
if existing_vm is not None:
|
|
400
|
+
if not replace:
|
|
401
|
+
raise RuntimeError(
|
|
402
|
+
f"VM '{config.name}' already exists.\n\n"
|
|
403
|
+
f"🔧 Solutions:\n"
|
|
404
|
+
f" 1. Reuse existing VM: clonebox start {config.name}\n"
|
|
405
|
+
f" 2. Replace it: clonebox clone . --name {config.name} --replace\n"
|
|
406
|
+
f" 3. Delete it: clonebox delete {config.name}\n"
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
log.info(f"VM '{config.name}' already exists - replacing...")
|
|
410
|
+
self.delete_vm(config.name, delete_storage=True, console=console, ignore_not_found=True)
|
|
411
|
+
|
|
412
|
+
# Determine images directory
|
|
413
|
+
images_dir = self.get_images_dir()
|
|
414
|
+
try:
|
|
415
|
+
vm_dir = ctx.add_directory(images_dir / config.name)
|
|
416
|
+
vm_dir.mkdir(parents=True, exist_ok=True)
|
|
417
|
+
except PermissionError as e:
|
|
418
|
+
raise PermissionError(
|
|
419
|
+
f"Cannot create VM directory: {images_dir / config.name}\n\n"
|
|
392
420
|
f"🔧 Solutions:\n"
|
|
393
|
-
f" 1.
|
|
394
|
-
f"
|
|
395
|
-
f"
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
f"
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
cmd =
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
config.
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
cloudinit_iso = ctx.add_file(self._create_cloudinit_iso(vm_dir, config))
|
|
458
|
-
log.info(f"Created cloud-init ISO with {len(config.packages)} packages")
|
|
459
|
-
|
|
460
|
-
# Generate VM XML
|
|
461
|
-
vm_xml = self._generate_vm_xml(config, root_disk, cloudinit_iso)
|
|
462
|
-
ctx.add_libvirt_domain(self.conn, config.name)
|
|
463
|
-
|
|
464
|
-
# Define VM
|
|
465
|
-
log.info(f"Defining VM '{config.name}'...")
|
|
466
|
-
try:
|
|
467
|
-
vm = self.conn.defineXML(vm_xml)
|
|
468
|
-
except Exception as e:
|
|
469
|
-
raise RuntimeError(
|
|
470
|
-
f"Failed to define VM '{config.name}'.\n"
|
|
471
|
-
f"Error: {e}\n\n"
|
|
472
|
-
f"If the VM already exists, try: clonebox clone . --name {config.name} --replace\n"
|
|
473
|
-
) from e
|
|
421
|
+
f" 1. Use --user flag to run in user session (recommended):\n"
|
|
422
|
+
f" clonebox clone . --user\n\n"
|
|
423
|
+
f" 2. Run with sudo (not recommended):\n"
|
|
424
|
+
f" sudo clonebox clone .\n\n"
|
|
425
|
+
f" 3. Fix directory permissions:\n"
|
|
426
|
+
f" sudo mkdir -p {images_dir}\n"
|
|
427
|
+
f" sudo chown -R $USER:libvirt {images_dir}\n\n"
|
|
428
|
+
f"Original error: {e}"
|
|
429
|
+
) from e
|
|
430
|
+
|
|
431
|
+
# Create root disk
|
|
432
|
+
root_disk = ctx.add_file(vm_dir / "root.qcow2")
|
|
433
|
+
|
|
434
|
+
if not config.base_image:
|
|
435
|
+
config.base_image = str(self._ensure_default_base_image(console=console))
|
|
436
|
+
|
|
437
|
+
if config.base_image and Path(config.base_image).exists():
|
|
438
|
+
# Use backing file for faster creation
|
|
439
|
+
log.debug(f"Creating disk with backing file: {config.base_image}")
|
|
440
|
+
cmd = [
|
|
441
|
+
"qemu-img",
|
|
442
|
+
"create",
|
|
443
|
+
"-f",
|
|
444
|
+
"qcow2",
|
|
445
|
+
"-b",
|
|
446
|
+
config.base_image,
|
|
447
|
+
"-F",
|
|
448
|
+
"qcow2",
|
|
449
|
+
str(root_disk),
|
|
450
|
+
f"{config.disk_size_gb}G",
|
|
451
|
+
]
|
|
452
|
+
else:
|
|
453
|
+
# Create empty disk
|
|
454
|
+
log.debug(f"Creating empty {config.disk_size_gb}GB disk...")
|
|
455
|
+
cmd = ["qemu-img", "create", "-f", "qcow2", str(root_disk), f"{config.disk_size_gb}G"]
|
|
456
|
+
|
|
457
|
+
subprocess.run(cmd, check=True, capture_output=True)
|
|
458
|
+
|
|
459
|
+
# Create cloud-init ISO if packages/services specified
|
|
460
|
+
cloudinit_iso = None
|
|
461
|
+
if (
|
|
462
|
+
config.packages
|
|
463
|
+
or config.services
|
|
464
|
+
or config.snap_packages
|
|
465
|
+
or config.post_commands
|
|
466
|
+
or config.gui
|
|
467
|
+
):
|
|
468
|
+
cloudinit_iso = ctx.add_file(self._create_cloudinit_iso(vm_dir, config, self.user_session))
|
|
469
|
+
log.info(f"Created cloud-init ISO with {len(config.packages)} packages")
|
|
470
|
+
|
|
471
|
+
# Generate VM XML
|
|
472
|
+
vm_xml = self._generate_vm_xml(config, root_disk, cloudinit_iso)
|
|
473
|
+
ctx.add_libvirt_domain(self.conn, config.name)
|
|
474
|
+
|
|
475
|
+
# Define VM
|
|
476
|
+
log.info(f"Defining VM '{config.name}'...")
|
|
477
|
+
try:
|
|
478
|
+
vm = self.conn.defineXML(vm_xml)
|
|
479
|
+
except Exception as e:
|
|
480
|
+
raise RuntimeError(
|
|
481
|
+
f"Failed to define VM '{config.name}'.\n"
|
|
482
|
+
f"Error: {e}\n\n"
|
|
483
|
+
f"If the VM already exists, try: clonebox clone . --name {config.name} --replace\n"
|
|
484
|
+
) from e
|
|
474
485
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
486
|
+
# Start if autostart requested
|
|
487
|
+
if getattr(config, "autostart", False):
|
|
488
|
+
self.start_vm(config.name, open_viewer=True)
|
|
478
489
|
|
|
479
|
-
|
|
480
|
-
|
|
490
|
+
# All good - commit transaction
|
|
491
|
+
ctx.commit()
|
|
481
492
|
|
|
482
|
-
|
|
493
|
+
return vm.UUIDString()
|
|
483
494
|
|
|
484
495
|
def _generate_vm_xml(
|
|
485
496
|
self, config: VMConfig = None, root_disk: Path = None, cloudinit_iso: Optional[Path] = None
|
|
@@ -706,6 +717,9 @@ class SelectiveVMCloner:
|
|
|
706
717
|
for snap, ifaces in SNAP_INTERFACES.items()
|
|
707
718
|
)
|
|
708
719
|
|
|
720
|
+
mount_points_bash = "\n".join(str(p) for p in (config.paths or {}).values())
|
|
721
|
+
copy_paths_bash = "\n".join(str(p) for p in (config.copy_paths or {}).values())
|
|
722
|
+
|
|
709
723
|
script = f"""#!/bin/bash
|
|
710
724
|
set -uo pipefail
|
|
711
725
|
LOG="/var/log/clonebox-boot.log"
|
|
@@ -1000,12 +1014,23 @@ for app in "${{APPS_TO_TEST[@]}}"; do
|
|
|
1000
1014
|
fi
|
|
1001
1015
|
done
|
|
1002
1016
|
|
|
1003
|
-
section "5/7" "Checking
|
|
1004
|
-
write_status "checking_mounts" "checking
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1017
|
+
section "5/7" "Checking mounts & imported paths..."
|
|
1018
|
+
write_status "checking_mounts" "checking mounts & imported paths"
|
|
1019
|
+
|
|
1020
|
+
MOUNT_POINTS=$(cat <<'EOF'
|
|
1021
|
+
{mount_points_bash}
|
|
1022
|
+
EOF
|
|
1023
|
+
)
|
|
1024
|
+
|
|
1025
|
+
COPIED_PATHS=$(cat <<'EOF'
|
|
1026
|
+
{copy_paths_bash}
|
|
1027
|
+
EOF
|
|
1028
|
+
)
|
|
1029
|
+
|
|
1030
|
+
# Bind mounts (shared live)
|
|
1031
|
+
if [ -n "$(echo "$MOUNT_POINTS" | tr -d '[:space:]')" ]; then
|
|
1032
|
+
while IFS= read -r mp; do
|
|
1033
|
+
[ -z "$mp" ] && continue
|
|
1009
1034
|
if mountpoint -q "$mp" 2>/dev/null; then
|
|
1010
1035
|
ok "$mp mounted"
|
|
1011
1036
|
else
|
|
@@ -1018,8 +1043,24 @@ while IFS= read -r line; do
|
|
|
1018
1043
|
fail "$mp mount FAILED"
|
|
1019
1044
|
fi
|
|
1020
1045
|
fi
|
|
1021
|
-
|
|
1022
|
-
|
|
1046
|
+
done <<< "$MOUNT_POINTS"
|
|
1047
|
+
else
|
|
1048
|
+
log " (no bind mounts configured)"
|
|
1049
|
+
fi
|
|
1050
|
+
|
|
1051
|
+
# Imported/copied paths (one-time import)
|
|
1052
|
+
if [ -n "$(echo "$COPIED_PATHS" | tr -d '[:space:]')" ]; then
|
|
1053
|
+
while IFS= read -r p; do
|
|
1054
|
+
[ -z "$p" ] && continue
|
|
1055
|
+
if [ -d "$p" ]; then
|
|
1056
|
+
ok "$p copied"
|
|
1057
|
+
else
|
|
1058
|
+
fail "$p missing (copy)"
|
|
1059
|
+
fi
|
|
1060
|
+
done <<< "$COPIED_PATHS"
|
|
1061
|
+
else
|
|
1062
|
+
log " (no copied paths configured)"
|
|
1063
|
+
fi
|
|
1023
1064
|
|
|
1024
1065
|
section "6/7" "Checking services..."
|
|
1025
1066
|
write_status "checking_services" "checking services"
|
|
@@ -1095,6 +1136,10 @@ REPORT_FILE="/var/log/clonebox-health.log"
|
|
|
1095
1136
|
PASSED=0
|
|
1096
1137
|
FAILED=0
|
|
1097
1138
|
WARNINGS=0
|
|
1139
|
+
SETUP_IN_PROGRESS=0
|
|
1140
|
+
if [ ! -f /var/lib/cloud/instance/boot-finished ]; then
|
|
1141
|
+
SETUP_IN_PROGRESS=1
|
|
1142
|
+
fi
|
|
1098
1143
|
|
|
1099
1144
|
# Colors for output
|
|
1100
1145
|
RED='\\033[0;31m'
|
|
@@ -1113,22 +1158,36 @@ check_apt_package() {{
|
|
|
1113
1158
|
((PASSED++))
|
|
1114
1159
|
return 0
|
|
1115
1160
|
else
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1161
|
+
if [ $SETUP_IN_PROGRESS -eq 1 ]; then
|
|
1162
|
+
log "[WARN] APT package '$pkg' is not installed yet"
|
|
1163
|
+
((WARNINGS++))
|
|
1164
|
+
return 1
|
|
1165
|
+
else
|
|
1166
|
+
log "[FAIL] APT package '$pkg' is NOT installed"
|
|
1167
|
+
((FAILED++))
|
|
1168
|
+
return 1
|
|
1169
|
+
fi
|
|
1119
1170
|
fi
|
|
1120
1171
|
}}
|
|
1121
1172
|
|
|
1122
1173
|
check_snap_package() {{
|
|
1123
1174
|
local pkg="$1"
|
|
1124
|
-
|
|
1175
|
+
local out
|
|
1176
|
+
out=$(snap list "$pkg" 2>&1)
|
|
1177
|
+
if [ $? -eq 0 ]; then
|
|
1125
1178
|
log "[PASS] Snap package '$pkg' is installed"
|
|
1126
1179
|
((PASSED++))
|
|
1127
1180
|
return 0
|
|
1128
1181
|
else
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1182
|
+
if [ $SETUP_IN_PROGRESS -eq 1 ]; then
|
|
1183
|
+
log "[WARN] Snap package '$pkg' is not installed yet"
|
|
1184
|
+
((WARNINGS++))
|
|
1185
|
+
return 1
|
|
1186
|
+
else
|
|
1187
|
+
log "[FAIL] Snap package '$pkg' is NOT installed"
|
|
1188
|
+
((FAILED++))
|
|
1189
|
+
return 1
|
|
1190
|
+
fi
|
|
1132
1191
|
fi
|
|
1133
1192
|
}}
|
|
1134
1193
|
|
|
@@ -1221,20 +1280,30 @@ log "Warnings: $WARNINGS"
|
|
|
1221
1280
|
if [ $FAILED -eq 0 ]; then
|
|
1222
1281
|
log ""
|
|
1223
1282
|
log "[SUCCESS] All critical checks passed!"
|
|
1224
|
-
|
|
1225
|
-
|
|
1283
|
+
if [ $SETUP_IN_PROGRESS -eq 1 ]; then
|
|
1284
|
+
echo "HEALTH_STATUS=PENDING" > /var/log/clonebox-health-status
|
|
1285
|
+
exit 0
|
|
1286
|
+
else
|
|
1287
|
+
echo "HEALTH_STATUS=OK" > /var/log/clonebox-health-status
|
|
1288
|
+
exit 0
|
|
1289
|
+
fi
|
|
1226
1290
|
else
|
|
1227
1291
|
log ""
|
|
1228
1292
|
log "[ERROR] Some checks failed. Review log for details."
|
|
1229
|
-
|
|
1230
|
-
|
|
1293
|
+
if [ $SETUP_IN_PROGRESS -eq 1 ]; then
|
|
1294
|
+
echo "HEALTH_STATUS=PENDING" > /var/log/clonebox-health-status
|
|
1295
|
+
exit 0
|
|
1296
|
+
else
|
|
1297
|
+
echo "HEALTH_STATUS=FAILED" > /var/log/clonebox-health-status
|
|
1298
|
+
exit 1
|
|
1299
|
+
fi
|
|
1231
1300
|
fi
|
|
1232
1301
|
"""
|
|
1233
1302
|
# Encode script to base64 for safe embedding in cloud-init
|
|
1234
1303
|
encoded = base64.b64encode(script.encode()).decode()
|
|
1235
1304
|
return encoded
|
|
1236
1305
|
|
|
1237
|
-
def _create_cloudinit_iso(self, vm_dir: Path, config: VMConfig) -> Path:
|
|
1306
|
+
def _create_cloudinit_iso(self, vm_dir: Path, config: VMConfig, user_session: bool = False) -> Path:
|
|
1238
1307
|
"""Create cloud-init ISO with secure credential handling."""
|
|
1239
1308
|
secrets_mgr = SecretsManager()
|
|
1240
1309
|
|
|
@@ -1259,6 +1328,17 @@ fi
|
|
|
1259
1328
|
ssh_authorized_keys = [key_pair.public_key]
|
|
1260
1329
|
log.info(f"SSH key generated and saved to: {ssh_key_path}")
|
|
1261
1330
|
|
|
1331
|
+
local_password = getattr(config, "password", None)
|
|
1332
|
+
if getattr(config, "gui", False) and local_password:
|
|
1333
|
+
chpasswd_config = (
|
|
1334
|
+
"chpasswd:\n"
|
|
1335
|
+
" list: |\n"
|
|
1336
|
+
f" {config.username}:{local_password}\n"
|
|
1337
|
+
" expire: False"
|
|
1338
|
+
)
|
|
1339
|
+
lock_passwd = "false"
|
|
1340
|
+
ssh_pwauth = "true"
|
|
1341
|
+
|
|
1262
1342
|
elif auth_method == "one_time_password":
|
|
1263
1343
|
otp, chpasswd_raw = SecretsManager.generate_one_time_password()
|
|
1264
1344
|
chpasswd_config = chpasswd_raw
|
|
@@ -1511,7 +1591,7 @@ Comment=CloneBox autostart
|
|
|
1511
1591
|
)
|
|
1512
1592
|
runcmd_lines.append(" - chmod +x /usr/local/bin/clonebox-health")
|
|
1513
1593
|
runcmd_lines.append(
|
|
1514
|
-
" - /usr/local/bin/clonebox-health >> /var/log/clonebox-health.log 2>&1"
|
|
1594
|
+
" - /usr/local/bin/clonebox-health >> /var/log/clonebox-health.log 2>&1 || true"
|
|
1515
1595
|
)
|
|
1516
1596
|
runcmd_lines.append(" - echo 'CloneBox VM ready!' > /var/log/clonebox-ready")
|
|
1517
1597
|
|
|
@@ -2245,19 +2325,25 @@ if __name__ == "__main__":
|
|
|
2245
2325
|
# Note: The bash monitor is already installed above, no need to install Python monitor
|
|
2246
2326
|
|
|
2247
2327
|
# Create logs disk for host access
|
|
2328
|
+
# Use different paths based on session type
|
|
2329
|
+
if user_session:
|
|
2330
|
+
logs_disk_path = str(Path.home() / ".local/share/libvirt/images/clonebox-logs.qcow2")
|
|
2331
|
+
else:
|
|
2332
|
+
logs_disk_path = "/var/lib/libvirt/images/clonebox-logs.qcow2"
|
|
2333
|
+
|
|
2248
2334
|
runcmd_lines.extend(
|
|
2249
2335
|
[
|
|
2250
2336
|
" - mkdir -p /mnt/logs",
|
|
2251
|
-
" - truncate -s 1G
|
|
2252
|
-
" - mkfs.ext4 -F
|
|
2253
|
-
" - echo '
|
|
2337
|
+
f" - truncate -s 1G {logs_disk_path}",
|
|
2338
|
+
f" - mkfs.ext4 -F {logs_disk_path}",
|
|
2339
|
+
f" - echo '{logs_disk_path} /mnt/logs ext4 loop,defaults 0 0' >> /etc/fstab",
|
|
2254
2340
|
" - mount -a",
|
|
2255
2341
|
" - mkdir -p /mnt/logs/var/log",
|
|
2256
2342
|
" - mkdir -p /mnt/logs/tmp",
|
|
2257
2343
|
" - cp -r /var/log/clonebox*.log /mnt/logs/var/log/ 2>/dev/null || true",
|
|
2258
2344
|
" - cp -r /tmp/*-error.log /mnt/logs/tmp/ 2>/dev/null || true",
|
|
2259
|
-
" - echo 'Logs disk mounted at /mnt/logs - accessible from host as
|
|
2260
|
-
" - \"echo 'To view logs on host: sudo mount -o loop
|
|
2345
|
+
f" - echo 'Logs disk mounted at /mnt/logs - accessible from host as {logs_disk_path}'",
|
|
2346
|
+
f" - \"echo 'To view logs on host: sudo mount -o loop {logs_disk_path} /mnt/clonebox-logs'\"",
|
|
2261
2347
|
]
|
|
2262
2348
|
)
|
|
2263
2349
|
|