Fix backup failing on tmpfs /tmp: stage in backup store, check free space

On Ubuntu 24.10+ /tmp is a size-limited tmpfs, so mktemp -d staging could
not hold the uncompressed copy of the panel, web roots and certs. Stage
backup/restore work dirs inside /var/backups/x-ui instead, and verify
free space (2x estimated size + 100 MB margin) before touching anything.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Ivan Razin
2026-07-13 14:31:01 +03:00
co-authored by Claude Fable 5
parent 906822b42b
commit 9638cf5b47
+31 -3
View File
@@ -33,6 +33,17 @@ die() { red "ERROR: $*" >&2; exit 1; }
require_root() { [[ $EUID -eq 0 ]] || die "Run as root (sudo $0 $*)"; } require_root() { [[ $EUID -eq 0 ]] || die "Run as root (sudo $0 $*)"; }
# free space in KB on the filesystem containing $1
avail_kb() { df -Pk "$1" | awk 'NR==2 {print $4}'; }
# staging must NOT live in /tmp: on Ubuntu 24.10+ /tmp is a size-limited tmpfs
# and a full uncompressed copy of the panel + web roots does not fit there
make_staging() {
local prefix="$1"
mkdir -p "${BACKUP_STORE}"
mktemp -d "${BACKUP_STORE}/.${prefix}-XXXXXX"
}
# ── backup ──────────────────────────────────────────────────────────────────── # ── backup ────────────────────────────────────────────────────────────────────
cmd_backup() { cmd_backup() {
require_root require_root
@@ -40,10 +51,19 @@ cmd_backup() {
local ts name staging dest local ts name staging dest
ts=$(date +%Y%m%d-%H%M%S) ts=$(date +%Y%m%d-%H%M%S)
name="x-ui-backup-${ts}" name="x-ui-backup-${ts}"
staging=$(mktemp -d)
# estimate size and verify free space before touching anything:
# staging holds an uncompressed copy, the archive lands next to it
local est_kb need_kb have_kb
est_kb=$(du -skc "${BACKUP_PATHS[@]}" 2>/dev/null | awk 'END {print $1}')
need_kb=$(( est_kb * 2 + 102400 )) # copy + archive + 100 MB margin
mkdir -p "${BACKUP_STORE}"
have_kb=$(avail_kb "${BACKUP_STORE}")
(( have_kb >= need_kb )) || die "Not enough free space in ${BACKUP_STORE}: need ~$(( need_kb / 1024 )) MB, have $(( have_kb / 1024 )) MB"
staging=$(make_staging staging)
trap 'rm -rf "${staging}"' EXIT trap 'rm -rf "${staging}"' EXIT
mkdir -p "${BACKUP_STORE}"
dest="${BACKUP_STORE}/${name}.tar.gz" dest="${BACKUP_STORE}/${name}.tar.gz"
blue "==> Stopping x-ui for consistent DB snapshot..." blue "==> Stopping x-ui for consistent DB snapshot..."
@@ -106,8 +126,16 @@ cmd_restore() {
[[ -n "${backup_file}" ]] || die "Usage: $0 restore <backup.tar.gz>" [[ -n "${backup_file}" ]] || die "Usage: $0 restore <backup.tar.gz>"
[[ -f "${backup_file}" ]] || die "File not found: ${backup_file}" [[ -f "${backup_file}" ]] || die "File not found: ${backup_file}"
# verify free space for the extracted copy before unpacking
local unpacked_kb have_kb
unpacked_kb=$(( $(gzip -l "${backup_file}" | awk 'NR==2 {print $2}') / 1024 ))
mkdir -p "${BACKUP_STORE}"
have_kb=$(avail_kb "${BACKUP_STORE}")
(( have_kb >= unpacked_kb * 2 + 102400 )) || \
die "Not enough free space in ${BACKUP_STORE}: need ~$(( (unpacked_kb * 2 + 102400) / 1024 )) MB, have $(( have_kb / 1024 )) MB"
local staging local staging
staging=$(mktemp -d) staging=$(make_staging restore)
trap 'rm -rf "${staging}"' EXIT trap 'rm -rf "${staging}"' EXIT
blue "==> Extracting backup: ${backup_file}" blue "==> Extracting backup: ${backup_file}"