Merge: resolve conflict in routes/backups.js, keep storage-info + maxStorageBytes

This commit is contained in:
Krystie
2026-05-28 15:00:41 -07:00
parent ea9bdf9598
commit ad9400490d
6 changed files with 387 additions and 14 deletions
+56
View File
@@ -27,10 +27,14 @@ readonly API_DIR="${SITES_DIR}/dashcaddy-api"
readonly DASHBOARD_DIR="${SITES_DIR}/status"
readonly CONTAINER_NAME="dashcaddy-api"
readonly CADDY_ADMIN_PORT=2019
readonly BACKUP_DIR="${BACKUP_DIR:-${INSTALL_DIR}/backups}"
readonly DEFAULT_MAX_STORAGE_BYTES=""
# ---- Tunables (overridable via flags) --------------------------------------
API_PORT=3001
LOCAL_PORT=8080
BACKUP_DIR=""
BACKUP_LIMIT=""
# ---- Runtime state ---------------------------------------------------------
DOMAIN_MODE="" # public | custom-tld | local
@@ -389,6 +393,7 @@ EOF
create_directories() {
mkdir -p "$INSTALL_DIR" "$DOCKER_DATA" "$SITES_DIR" "$API_DIR" "$DASHBOARD_DIR" "${DASHBOARD_DIR}/assets"
mkdir -p /opt/dashcaddy/updates /opt/dashcaddy/scripts
mkdir -p "${BACKUP_DIR}"
ok "Directories created"
}
@@ -626,7 +631,41 @@ CEOF
# Docker Compose
# ============================================================================
# Parse size string like "10GB" or "1TB" to bytes
parse_size_to_bytes() {
local size="$1"
local value unit
# Strip whitespace
size=$(echo "$size" | tr -d ' ')
# Extract numeric value and unit
if [[ $size =~ ^([0-9.]+)([kmgtKMGT][bb]?|[bB]?)$ ]]; then
value="${BASH_REMATCH[1]}"
unit="${BASH_REMATCH[2]}"
# Normalize unit to uppercase without 'B' suffix for simplicity
unit=$(echo "$unit" | tr '[:lower:]' '[:upper:]')
case "$unit" in
K|KB) echo $((value * 1024)) ;;
M|MB) echo $((value * 1024 * 1024)) ;;
G|GB) echo $((value * 1024 * 1024 * 1024)) ;;
T|TB) echo $((value * 1024 * 1024 * 1024 * 1024)) ;;
*) echo "$value" ;;
esac
else
# Not recognized, treat as raw bytes
echo "$size"
fi
}
generate_docker_compose() {
# Convert BACKUP_LIMIT to bytes if set (e.g., "10GB" -> 10737418240)
local backup_limit_bytes=""
if [[ -n "$BACKUP_LIMIT" ]]; then
backup_limit_bytes=$(parse_size_to_bytes "$BACKUP_LIMIT")
fi
cat > "${API_DIR}/docker-compose.yml" <<DCEOF
services:
dashcaddy-api:
@@ -648,6 +687,7 @@ services:
- ${DASHBOARD_DIR}:/app/dashboard:rw
- /opt/dashcaddy/updates:/app/updates:rw
- /var/run/docker.sock:/var/run/docker.sock
- dashcaddy-backups:/app/backups
environment:
- CADDYFILE_PATH=/caddyfile
- CADDY_ADMIN_URL=http://host.docker.internal:${CADDY_ADMIN_PORT}
@@ -665,6 +705,10 @@ services:
- DASHCADDY_HOST_UPDATES_DIR=/opt/dashcaddy/updates
- DASHCADDY_API_SOURCE_DIR=${API_DIR}
- DASHCADDY_FRONTEND_DIR=/app/dashboard
- BACKUP_DIR=/app/backups
- BACKUP_MAX_STORAGE_BYTES=${backup_limit_bytes:-0}
- BACKUP_CONFIG_FILE=/app/backup-config.json
- BACKUP_HISTORY_FILE=/app/backup-history.json
extra_hosts:
- "host.docker.internal:host-gateway"
restart: unless-stopped
@@ -673,6 +717,14 @@ services:
options:
max-size: "10m"
max-file: "3"
volumes:
dashcaddy-backups:
driver: local
driver_opts:
type: none
o: bind
device: ${BACKUP_DIR}
DCEOF
ok "docker-compose.yml generated"
@@ -880,6 +932,8 @@ parse_args() {
--skip-caddy) SKIP_CADDY=true; shift ;;
--uninstall) UNINSTALL=true; shift ;;
--keep-config) KEEP_CONFIG=true; shift ;;
--backup-dir) BACKUP_DIR="${2:-}"; shift; shift ;;
--backup-limit) BACKUP_LIMIT="${2:-}"; shift; shift ;;
--yes|-y) AUTO_YES=true; shift ;;
--help|-h) print_help; exit 0 ;;
*) warn "Unknown option: $1 (ignored)"; shift ;;
@@ -914,6 +968,8 @@ print_help() {
--source PATH Use local source files
--skip-docker Already have Docker
--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)
--uninstall Remove DashCaddy
--keep-config Keep configs during uninstall
--yes Skip confirmations
+21 -4
View File
@@ -226,17 +226,34 @@ class ConfigManager {
* @returns {Promise<Object>} Disk space info
*/
async getDiskSpace(testPath) {
// Note: This is a simplified version. In production, you'd use a library like 'check-disk-space'
try {
const stats = await fs.stat(testPath);
const fsPromises = require('fs').promises;
const pathModule = require('path');
// Ensure directory exists
await fsPromises.mkdir(testPath, { recursive: true });
// Use statfs for true disk space (works on all filesystems: ext4, Btrfs, XFS, ZFS, APFS, NTFS)
const stats = await fsPromises.statfs(testPath);
const totalBytes = stats.blocks * stats.bsize;
const freeBytes = stats.bfree * stats.bsize;
const availableBytes = stats.bavail * stats.bsize; // Available to non-root users
const usedBytes = totalBytes - freeBytes;
return {
available: true,
path: testPath
path: testPath,
total: totalBytes,
used: usedBytes,
free: freeBytes,
availableBytes: availableBytes,
usagePercent: parseFloat(((usedBytes / totalBytes) * 100).toFixed(2))
};
} catch (error) {
return {
available: false,
path: testPath,
error: error.message
};
}
@@ -62,6 +62,11 @@ const state = {
installPath: '',
health: null
},
// Backup configuration
backup: {
maxStorageGB: 10,
backupDir: ''
},
// Uninstall mode
uninstallMode: false,
uninstall: {
@@ -373,6 +378,24 @@ function updateBranding(field, value) {
if (field === 'primaryColor') render();
}
// Backup functions
function updateBackup(field, value) {
state.backup[field] = value;
render();
}
async function selectBackupDir() {
try {
const result = await window.electronAPI.selectFolder();
if (result.success && result.path) {
state.backup.backupDir = result.path;
render();
}
} catch (err) {
console.error('Backup dir selection failed:', err);
}
}
async function selectLogo() {
try {
const result = await window.electronAPI.selectFile({
@@ -420,6 +443,10 @@ async function startInstallation() {
password: state.dns.password,
token: state.dns.token
} : null,
backup: {
maxStorageGB: state.backup.maxStorageGB,
backupDir: state.backup.backupDir || null
},
autoStart: true
});
} catch (err) {
@@ -963,6 +990,31 @@ function renderDashboardSetup() {
<p class="hint">Port for the DashCaddy API server (default: 3001)</p>
</div>
` : ''}
<div class="folder-input">
<label>Backup Storage Limit (GB)</label>
<div class="input-row">
<input type="number"
value="${state.backup.maxStorageGB}"
min="1" max="10240"
oninput="updateBackup('maxStorageGB', parseInt(this.value) || 10)">
</div>
<p class="hint">Maximum storage for backups in GB (default: 10, max: 10TB)</p>
</div>
${state.tier !== 'basic' ? `
<div class="folder-input">
<label>Backup Directory</label>
<div class="input-row">
<input type="text"
value="${escapeHtml(state.backup.backupDir)}"
readonly
placeholder="Default: $INSTALL_DIR/backups">
<button class="btn-browse" onclick="selectBackupDir()">Browse...</button>
</div>
<p class="hint">Where backup files are stored on the host</p>
</div>
` : ''}
</div>
</div>
`;
@@ -7,15 +7,28 @@ services:
volumes:
- {{API_PATH}}:/app
- /var/run/docker.sock:/var/run/docker.sock
- dashcaddy-backups:/app/backups
environment:
- NODE_ENV=production
- PORT={{API_PORT}}
- SERVICES_FILE=/app/services.json
- CADDY_ADMIN_URL=http://host.docker.internal:2019
- BACKUP_DIR=/app/backups
- BACKUP_MAX_STORAGE_BYTES={{BACKUP_MAX_STORAGE_BYTES}}
- BACKUP_CONFIG_FILE=/app/backup-config.json
- BACKUP_HISTORY_FILE=/app/backup-history.json
restart: unless-stopped
networks:
- dashcaddy
volumes:
dashcaddy-backups:
driver: local
driver_opts:
type: none
o: bind
device: {{BACKUP_DIR}}
networks:
dashcaddy:
driver: bridge