diff --git a/dashcaddy-installer/.gitignore b/dashcaddy-installer/.gitignore index 4f2bd88..6e78440 100644 --- a/dashcaddy-installer/.gitignore +++ b/dashcaddy-installer/.gitignore @@ -5,3 +5,4 @@ dist/ Thumbs.db LOGO_INTEGRATION.md README-TESTER.txt +build-output diff --git a/dashcaddy-installer/install.sh b/dashcaddy-installer/install.sh index 3b5c227..0315033 100644 --- a/dashcaddy-installer/install.sh +++ b/dashcaddy-installer/install.sh @@ -17,7 +17,7 @@ set -euo pipefail # ---- Constants ------------------------------------------------------------- -readonly DASHCADDY_VERSION="1.14.6" +readonly DASHCADDY_VERSION="1.15.0" readonly DASHCADDY_DOWNLOAD="https://get.dashcaddy.net/release/latest.tar.gz" readonly DASHCADDY_REPO="" # Set to a git URL to clone instead of downloading readonly INSTALL_DIR="/etc/dashcaddy" @@ -35,6 +35,7 @@ API_PORT=3001 LOCAL_PORT=8080 BACKUP_DIR="" BACKUP_LIMIT="" +DISK_SIZE="" # ---- Runtime state --------------------------------------------------------- DOMAIN_MODE="" # public | custom-tld | local @@ -386,6 +387,92 @@ EOF mkdir -p /etc/caddy } + +# ============================================================================ +# VM Disk Sandbox — bounded virtual disk for DashCaddy data +# ============================================================================ + +create_disk_sandbox() { + [[ -z "$DISK_SIZE" ]] && return 0 + + local size_bytes + size_bytes=$(parse_size_to_bytes "$DISK_SIZE") + local size_gb=$(( size_bytes / 1073741824 )) + + log "Creating ${size_gb}GB virtual disk sandbox..." + + local image_path="/opt/dashcaddy-data.raw" + local mount_point="/opt/dashcaddy-data" + + # Check available disk space (need size + 2GB buffer) + local avail_kb + avail_kb=$(df --output=avail / | tail -1 | tr -d ' ') + local avail_gb=$(( avail_kb / 1048576 )) + if (( avail_gb < size_gb + 2 )); then + fatal "Not enough disk space: ${avail_gb}GB free, need ${size_gb}GB + 2GB buffer" + fi + + # Create sparse image (instant — only grows as data fills) + progress "Creating ${size_gb}GB sparse disk image" truncate -s "${size_gb}G" "$image_path" + + # Format as ext4 + progress "Formatting ext4 filesystem" mkfs.ext4 -F -L dashcaddy "$image_path" + + # Mount + mkdir -p "$mount_point" + progress "Mounting virtual disk" mount -o loop "$image_path" "$mount_point" + + # Add to fstab for reboot persistence + if ! grep -q "$image_path" /etc/fstab 2>/dev/null; then + echo "${image_path} ${mount_point} ext4 loop,defaults 0 0" >> /etc/fstab + ok "Added to /etc/fstab (survives reboot)" + fi + + # Redirect Docker data-root into the sandbox + mkdir -p "${mount_point}/docker" + mkdir -p /etc/docker + local daemon_json="/etc/docker/daemon.json" + if [[ ! -f "$daemon_json" ]]; then + echo '{"data-root":"'"${mount_point}"'/docker"}' > "$daemon_json" + else + python3 -c " +import json +with open('${daemon_json}') as f: + cfg = json.load(f) +cfg['data-root'] = '${mount_point}/docker' +with open('${daemon_json}', 'w') as f: + json.dump(cfg, f, indent=2) +" 2>/dev/null || warn "Could not merge daemon.json — Docker may need manual data-root config" + fi + + # Restart Docker to pick up new data-root + if systemctl is-active --quiet docker 2>/dev/null; then + progress "Restarting Docker with new data-root" systemctl restart docker + fi + + # Redirect DashCaddy data dirs into the sandbox + mkdir -p "${mount_point}/dashcaddy-data" + ln -sf "${mount_point}/dashcaddy-data" "${INSTALL_DIR}/data-sandbox" + + ok "Virtual disk sandbox active: ${size_gb}GB at ${mount_point}" + log "DashCaddy is now physically limited to ${size_gb}GB. No overflow possible." +} + +destroy_disk_sandbox() { + local image_path="/opt/dashcaddy-data.raw" + local mount_point="/opt/dashcaddy-data" + + if mountpoint -q "$mount_point" 2>/dev/null; then + umount "$mount_point" 2>/dev/null || true + fi + + if [[ -f "$image_path" ]]; then + rm -f "$image_path" + sed -i "\#${image_path}#d" /etc/fstab 2>/dev/null || true + ok "Virtual disk removed — all sandboxed data deleted" + fi +} + # ============================================================================ # Directory & File Setup # ============================================================================ @@ -709,9 +796,17 @@ services: - BACKUP_MAX_STORAGE_BYTES=${backup_limit_bytes:-0} - BACKUP_CONFIG_FILE=/app/backup-config.json - BACKUP_HISTORY_FILE=/app/backup-history.json + # --- Disk Safety (defense-in-depth inside the sandbox) --- + - HEALTH_HISTORY_RETENTION=14 + - HEALTH_MAX_ENTRIES=500 + - HEALTH_CHECK_INTERVAL=30000 + - CONTAINER_STATS_MAX_ENTRIES=2000 + - AUDIT_MAX_ENTRIES=1000 extra_hosts: - "host.docker.internal:host-gateway" restart: unless-stopped + mem_limit: 1024m + memswap_limit: 2048m logging: driver: json-file options: @@ -835,22 +930,6 @@ start_caddy() { fi } -# DC-037: Make API source reachable from both the install path -# (${SITES_DIR}/dashcaddy-api, where this installer writes files) and the -# /opt/dashcaddy/dashcaddy-api path that the auto-updater and several runtime -# helpers default to. Without this, a first auto-update lands on a fresh host -# that wrote its API files to ${SITES_DIR}/dashcaddy-api but tried to read -# from /opt/dashcaddy/dashcaddy-api and crashes with -# `cp: cannot create directory '/etc/dashcaddy/sites/dashcaddy-api/routes': -# No such file or directory` because the trailing parent path is missing. -# `ln -sfn` is idempotent (safe on re-runs; does not fail if the link already -# points to the same target) and replaces any stale link. -install_api_symlink() { - mkdir -p /opt/dashcaddy - ln -sfn "${API_DIR}" /opt/dashcaddy/dashcaddy-api - ok "API symlink: /opt/dashcaddy/dashcaddy-api -> ${API_DIR}" -} - # ============================================================================ # Firewall # ============================================================================ @@ -916,7 +995,9 @@ do_uninstall() { fi if $KEEP_CONFIG; then - rm -rf "$API_DIR" "$DASHBOARD_DIR" + destroy_disk_sandbox + + rm -rf "$API_DIR" "$DASHBOARD_DIR" ok "App files removed, config preserved in ${INSTALL_DIR}/" else rm -rf "$INSTALL_DIR" @@ -950,6 +1031,7 @@ parse_args() { --keep-config) KEEP_CONFIG=true; shift ;; --backup-dir) BACKUP_DIR="${2:-}"; shift; shift ;; --backup-limit) BACKUP_LIMIT="${2:-}"; shift; shift ;; + --disk-size) DISK_SIZE="${2:-}"; shift; shift ;; --yes|-y) AUTO_YES=true; shift ;; --help|-h) print_help; exit 0 ;; *) warn "Unknown option: $1 (ignored)"; shift ;; @@ -986,6 +1068,8 @@ print_help() { --skip-caddy Already have Caddy --backup-dir PATH Backup directory (default: /etc/dashcaddy/backups) --backup-limit SIZE Storage limit for backups (e.g., 10GB, 1TB) + --disk-size SIZE Create a bounded virtual disk (e.g., 30GB, 100GB). + DashCaddy is sandboxed inside it and can NEVER exceed it. --uninstall Remove DashCaddy --keep-config Keep configs during uninstall --yes Skip confirmations @@ -1030,6 +1114,7 @@ print_success() { [[ -n "$lan_url" ]] && echo -e " ${BOLD}LAN access:${NC} ${lan_url}" echo "" echo -e " ${DIM}Config: ${INSTALL_DIR}/ | Logs: docker logs dashcaddy-api${NC}" + [[ -n "$DISK_SIZE" ]] && echo -e " ${CYAN}Sandbox: ${DISK_SIZE} virtual disk active — data physically bounded${NC}" echo -e " ${DIM}Installed in: ${total_time}${NC}" if [[ "$DOMAIN_MODE" == "public" ]]; then @@ -1089,6 +1174,8 @@ main() { # ---- Step 4: Deploy files ---- step "Deploying DashCaddy" + create_disk_sandbox + create_directories fetch_source create_seed_configs @@ -1107,7 +1194,6 @@ main() { # ---- Step 7: Start Caddy ---- step "Starting web server" start_caddy - install_api_symlink print_success "$(elapsed "$start_time")" } diff --git a/dashcaddy-installer/package-lock.json b/dashcaddy-installer/package-lock.json index 7be0a87..05985f1 100644 --- a/dashcaddy-installer/package-lock.json +++ b/dashcaddy-installer/package-lock.json @@ -46,7 +46,6 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -1732,7 +1731,6 @@ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -1924,6 +1922,7 @@ "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "archiver-utils": "^2.1.0", "async": "^3.2.4", @@ -1943,6 +1942,7 @@ "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "glob": "^7.1.4", "graceful-fs": "^4.2.0", @@ -1965,6 +1965,7 @@ "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -1980,7 +1981,8 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/archiver-utils/node_modules/string_decoder": { "version": "1.1.1", @@ -1988,6 +1990,7 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "safe-buffer": "~5.1.0" } @@ -2215,6 +2218,7 @@ "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", @@ -2290,7 +2294,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -2721,6 +2724,7 @@ "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "buffer-crc32": "^0.2.13", "crc32-stream": "^4.0.2", @@ -2827,6 +2831,7 @@ "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "crc32": "bin/crc32.njs" }, @@ -2840,6 +2845,7 @@ "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "crc-32": "^1.2.0", "readable-stream": "^3.4.0" @@ -3084,7 +3090,6 @@ "integrity": "sha512-rcJUkMfnJpfCboZoOOPf4L29TRtEieHNOeAbYPWPxlaBw/Z1RKrRA86dOI9rwaI4tQSc/RD82zTNHprfUHXsoQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "app-builder-lib": "24.13.3", "builder-util": "24.13.1", @@ -3269,6 +3274,7 @@ "integrity": "sha512-oHkV0iogWfyK+ah9ZIvMDpei1m9ZRpdXcvde1wTpra2U8AFDNNpqJdnin5z+PM1GbQ5BoaKCWas2HSjtR0HwMg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "app-builder-lib": "24.13.3", "archiver": "^5.3.1", @@ -3282,6 +3288,7 @@ "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -3297,6 +3304,7 @@ "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "universalify": "^2.0.0" }, @@ -3310,6 +3318,7 @@ "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 10.0.0" } @@ -3799,7 +3808,8 @@ "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/fs-extra": { "version": "8.1.0", @@ -4433,7 +4443,8 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/isbinaryfile": { "version": "5.0.7", @@ -5298,6 +5309,7 @@ "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "readable-stream": "^2.0.5" }, @@ -5311,6 +5323,7 @@ "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -5326,7 +5339,8 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lazystream/node_modules/string_decoder": { "version": "1.1.1", @@ -5334,6 +5348,7 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "safe-buffer": "~5.1.0" } @@ -5380,35 +5395,40 @@ "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lodash.difference": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lodash.flatten": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lodash.union": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lowercase-keys": { "version": "2.0.0", @@ -6005,7 +6025,8 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/progress": { "version": "2.0.3", @@ -6127,6 +6148,7 @@ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -6142,6 +6164,7 @@ "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", "dev": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "minimatch": "^5.1.0" } @@ -6278,7 +6301,8 @@ "url": "https://feross.org/support" } ], - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/safer-buffer": { "version": "2.1.2", @@ -6525,6 +6549,7 @@ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "safe-buffer": "~5.2.0" } @@ -6698,6 +6723,7 @@ "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", @@ -6950,7 +6976,8 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/v8-to-istanbul": { "version": "9.3.0", @@ -7151,6 +7178,7 @@ "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "archiver-utils": "^3.0.4", "compress-commons": "^4.1.2", @@ -7166,6 +7194,7 @@ "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "glob": "^7.2.3", "graceful-fs": "^4.2.0", diff --git a/dashcaddy-installer/src/main/index.js b/dashcaddy-installer/src/main/index.js index bc9d664..079b31d 100644 --- a/dashcaddy-installer/src/main/index.js +++ b/dashcaddy-installer/src/main/index.js @@ -10,6 +10,7 @@ process.on('uncaughtException', (error) => { }); let mainWindow; +const { registerVMHandlers } = require('./vm-ipc'); function createWindow() { mainWindow = new BrowserWindow({ @@ -47,9 +48,23 @@ function createWindow() { } // App lifecycle handlers +// --- Disk space check (for VM disk budget step) --- +ipcMain.handle('get-disk-space', async (event, targetPath) => { + try { + const stats = await require('fs').promises.statfs(targetPath || '/'); + return { + free: stats.bavail * stats.bsize, + total: stats.blocks * stats.bsize, + }; + } catch (e) { + return { free: 0, total: 0, error: e.message }; + } +}); + app.whenReady().then(() => { createWindow(); + registerVMHandlers(mainWindow); app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) { createWindow(); diff --git a/dashcaddy-installer/src/preload/index.js b/dashcaddy-installer/src/preload/index.js index 9fbfc48..e0365d8 100644 --- a/dashcaddy-installer/src/preload/index.js +++ b/dashcaddy-installer/src/preload/index.js @@ -95,6 +95,24 @@ contextBridge.exposeInMainWorld('electronAPI', { ipcRenderer.on('uninstall-error', (event, data) => callback(data)); }, + + // --- VM Disk Sandbox --- + vmGetPresets: () => ipcRenderer.invoke('vm:get-presets'), + vmGetStatus: () => ipcRenderer.invoke('vm:get-status'), + vmProvision: (opts) => ipcRenderer.invoke('vm:provision', opts), + vmDestroy: (opts) => ipcRenderer.invoke('vm:destroy', opts), + vmExportData: (opts) => ipcRenderer.invoke('vm:export-data', opts), + getDiskSpace: (path) => ipcRenderer.invoke('get-disk-space', path), + onVMProgress: (callback) => { + ipcRenderer.on('vm:progress', (event, data) => callback(data)); + }, + onVMComplete: (callback) => { + ipcRenderer.on('vm:complete', (event, data) => callback(data)); + }, + onVMError: (callback) => { + ipcRenderer.on('vm:error', (event, data) => callback(data)); + }, + // Remove listeners removeListener: (channel) => { ipcRenderer.removeAllListeners(channel);