Commit c9d9031d authored by Andrey Filippov's avatar Andrey Filippov

Add PBX USB backup setup

parent 3b7288db
BACKUP_MOUNT=/mnt/pbx-backup
BACKUP_SUBDIR=elphel-pbx
KEEP_COUNT=30
# MAILTO=andrey@elphel.com
# 2026-03-22 Backup And Reports
## Decision
The new PBX should not inherit the old "weekly restart" pattern.
Reason:
- current Debian 12 / FreePBX 17 / Asterisk 22 system does not have any PBX-specific weekly restart jobs
- only standard Debian timers are present
- routine restarts are a workaround for old instability, not a good operational policy for the replacement PBX
## Replacement Policy
Replace the old Sunday restart mail with:
- scheduled local backup to the USB stick
- optional short email report from the backup job
## USB
- device: `/dev/sda1`
- label: `PBX-BACKUP01`
- UUID: `068a3697-968d-40a9-8f03-c4bc46c2a511`
- planned mountpoint: `/mnt/pbx-backup`
## Backup Contents
The backup script is designed to capture the live state that is harder to reconstruct:
- MySQL dumps for `asterisk` and `asteriskcdrdb`
- `/etc/asterisk`
- `/etc/freepbx.conf`
- voicemail
- call recordings
- custom sounds
- music on hold
- PBX-specific host config:
- `/etc/fail2ban/jail.local`
- `/etc/network/interfaces`
- `/etc/hosts`
- `/usr/local/sbin/pbx-voip-whitelist.sh`
- `/etc/systemd/system/pbx-voip-whitelist.service`
- `/etc/postfix/main.cf`
## Schedule
- systemd timer: daily at `03:17`
- retention: keep the newest `30` runs by default
- optional mail: set `MAILTO` in `/etc/default/pbx-backup`
## Installed On Live PBX
Installed on `192.168.1.16`:
- `/usr/local/sbin/pbx-backup.sh`
- `/etc/default/pbx-backup`
- `/etc/systemd/system/pbx-backup.service`
- `/etc/systemd/system/pbx-backup.timer`
- `/etc/fstab` entry for `/mnt/pbx-backup`
Verified on `2026-03-22`:
- USB mounted at `/mnt/pbx-backup`
- timer active and scheduled for the next daily run
- manual backup run `20260322_125605` completed successfully
- resulting backup size: about `81M`
## Repo Artifacts
- `scripts/pbx-backup.sh`
- `config/pbx-backup.example`
- `systemd/pbx-backup.service`
- `systemd/pbx-backup.timer`
## Notes
The forwarded weekly restart emails were not present in the local fetched-mail cache when this note was written, so the decision above was made from the live PBX timer/cron state rather than from those old messages.
#!/bin/bash
set -euo pipefail
umask 077
if [[ -r /etc/default/pbx-backup ]]; then
# shellcheck disable=SC1091
. /etc/default/pbx-backup
fi
BACKUP_MOUNT="${BACKUP_MOUNT:-/mnt/pbx-backup}"
BACKUP_SUBDIR="${BACKUP_SUBDIR:-$(hostname -s)}"
KEEP_COUNT="${KEEP_COUNT:-30}"
MAILTO="${MAILTO:-}"
HOSTNAME_SHORT="$(hostname -s)"
STAMP="$(date +%Y%m%d_%H%M%S)"
ROOT_DIR="${BACKUP_MOUNT}/${BACKUP_SUBDIR}"
DEST_DIR="${ROOT_DIR}/${STAMP}"
LOG=""
send_report() {
local subject="$1"
if [[ -n "$MAILTO" ]] && command -v mail >/dev/null 2>&1 && [[ -f "$LOG" ]]; then
mail -s "$subject" "$MAILTO" < "$LOG" || true
fi
}
finish() {
local rc=$?
if [[ $rc -eq 0 ]]; then
send_report "[PBX backup] ${HOSTNAME_SHORT} OK ${STAMP}"
else
send_report "[PBX backup] ${HOSTNAME_SHORT} FAILED ${STAMP}"
fi
exit "$rc"
}
tar_if_present() {
local archive="$1"
shift
local -a items=()
local item
for item in "$@"; do
if [[ -e "/${item}" ]]; then
items+=("$item")
fi
done
if ((${#items[@]})); then
tar --ignore-failed-read --warning=no-file-changed -C / -czf "$archive" "${items[@]}"
fi
}
prune_old() {
local -a runs=()
local excess
mapfile -t runs < <(find "$ROOT_DIR" -mindepth 1 -maxdepth 1 -type d -printf '%f\n' | grep -E '^[0-9]{8}_[0-9]{6}$' | sort)
if ((${#runs[@]} <= KEEP_COUNT)); then
return
fi
excess=$((${#runs[@]} - KEEP_COUNT))
local old
for old in "${runs[@]:0:${excess}}"; do
rm -rf -- "${ROOT_DIR}/${old}"
done
}
trap finish EXIT
mkdir -p "$BACKUP_MOUNT"
if ! mountpoint -q "$BACKUP_MOUNT"; then
mount "$BACKUP_MOUNT"
fi
if ! mountpoint -q "$BACKUP_MOUNT"; then
echo "Backup mount ${BACKUP_MOUNT} is not available" >&2
exit 1
fi
mkdir -p "$DEST_DIR"
LOG="${DEST_DIR}/backup.log"
exec > >(tee -a "$LOG") 2>&1
echo "PBX backup started at $(date -Is)"
echo "Host: ${HOSTNAME_SHORT}"
echo "Mount: ${BACKUP_MOUNT}"
df -h "$BACKUP_MOUNT"
mysql -NBe 'SHOW DATABASES' > "${DEST_DIR}/mysql-databases.txt"
mysqldump --single-transaction --quick --routines --triggers --events asterisk | gzip -1 > "${DEST_DIR}/mysql-asterisk.sql.gz"
mysqldump --single-transaction --quick --routines --triggers --events asteriskcdrdb | gzip -1 > "${DEST_DIR}/mysql-asteriskcdrdb.sql.gz"
fwconsole ma list > "${DEST_DIR}/fwconsole-modules.txt" 2>&1 || true
fwconsole backup --list > "${DEST_DIR}/fwconsole-backup-jobs.txt" 2>&1 || true
dpkg-query -W > "${DEST_DIR}/dpkg-packages.txt"
ip -br a > "${DEST_DIR}/ip-addresses.txt"
ip route > "${DEST_DIR}/ip-routes.txt"
systemctl --no-pager --full status pbx-voip-whitelist.service > "${DEST_DIR}/pbx-voip-whitelist.status.txt" 2>&1 || true
systemctl --no-pager --full status fail2ban.service > "${DEST_DIR}/fail2ban.status.txt" 2>&1 || true
tar_if_present "${DEST_DIR}/etc-asterisk.tgz" \
etc/asterisk \
etc/freepbx.conf
tar_if_present "${DEST_DIR}/asterisk-state.tgz" \
var/spool/asterisk/voicemail \
var/spool/asterisk/monitor \
var/lib/asterisk/sounds/custom \
var/lib/asterisk/moh
tar_if_present "${DEST_DIR}/host-config.tgz" \
etc/fail2ban/jail.local \
etc/network/interfaces \
etc/hosts \
usr/local/sbin/pbx-voip-whitelist.sh \
etc/systemd/system/pbx-voip-whitelist.service \
etc/postfix/main.cf
manifest_tmp="$(mktemp)"
find "$DEST_DIR" -maxdepth 1 -type f ! -name 'manifest.tsv' ! -name 'SHA256SUMS' -printf '%f\t%s\n' | sort > "$manifest_tmp"
mv "$manifest_tmp" "${DEST_DIR}/manifest.tsv"
(cd "$DEST_DIR" && sha256sum ./* > SHA256SUMS)
ln -sfn "$DEST_DIR" "${ROOT_DIR}/latest"
prune_old
echo "PBX backup finished at $(date -Is)"
du -sh "$DEST_DIR"
[Unit]
Description=PBX backup to USB storage
Wants=network-online.target
After=network-online.target local-fs.target
[Service]
Type=oneshot
User=root
ExecStart=/usr/local/sbin/pbx-backup.sh
[Unit]
Description=Daily PBX backup to USB storage
[Timer]
OnCalendar=*-*-* 03:17:00
RandomizedDelaySec=10m
Persistent=true
Unit=pbx-backup.service
[Install]
WantedBy=timers.target
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment