cba157518e
Disse mappene inneholder kjøretidsdata som endrer seg ofte, og vil uansett bli slettet når maskinen blir restartet.
525 lines
12 KiB
Bash
Executable File
525 lines
12 KiB
Bash
Executable File
#!/usr/local/bin/bash
|
|
|
|
#
|
|
# Hjemmesnekret script som tar backup av PVV-servere.
|
|
#
|
|
# orjane - 2008.11.10
|
|
#
|
|
# Oppdatert av pederbs og yorinad 2017.02.04
|
|
#
|
|
|
|
# For å legge til en backup jobb, lag et nyt datasett under principal/backupz:
|
|
#
|
|
# zfs create principal/backupz/%name%
|
|
#
|
|
# Sørg for at principal kan logge inn som brukerene på boksen uten passord.
|
|
# Det ligger et skript for å lage nye nøkkelpar ved './ssh_extra_keys/generate_keypair.sh',
|
|
# men du kan alternativt bruke nøkkelen som ligger i '~/.ssh/id_ed25519.pub'.
|
|
# I førstnevnte tilfelle må du også registrere boksen i './ssh_config_backup_targets'.
|
|
|
|
|
|
if ! command -v git >/dev/null 2>&1; then
|
|
echo "PVV; hjemmelaget backupscript (\$ git commit sha: ??? \$)"
|
|
else
|
|
echo "PVV; hjemmelaget backupscript (\$ git commit sha: $(cd /backupz && git rev-parse HEAD) \$)"
|
|
fi
|
|
echo
|
|
date
|
|
|
|
# === Sjekk at nødvendige verktøy er installert. ===
|
|
|
|
required_tools=(
|
|
rsync
|
|
ssh
|
|
zfs
|
|
zpool
|
|
awk
|
|
)
|
|
for tool in "${required_tools[@]}"; do
|
|
if ! command -v "$tool" >/dev/null 2>&1; then
|
|
echo "Mangler påkrevd verktøy: $tool"
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
if [ "$(id -u)" -ne 0 ]; then
|
|
echo "Må kjøres som root!"
|
|
exit 1
|
|
fi
|
|
|
|
# 5.1 er minimum pga. wait '-p' flagget.
|
|
minimum_bash_major_version="5"
|
|
minimum_bash_minor_version="1"
|
|
if [ \
|
|
"${BASH_VERSINFO[0]}" -lt "$minimum_bash_major_version" ] || \
|
|
{ [ "${BASH_VERSINFO[0]}" -eq "$minimum_bash_major_version" ] && \
|
|
[ "${BASH_VERSINFO[1]}" -lt "$minimum_bash_minor_version" ]; }; then
|
|
echo "Må kjøres med bash versjon $minimum_bash_major_version eller nyere!"
|
|
exit 1
|
|
fi
|
|
|
|
|
|
# === Sjekk for pågående backup eller avbrutt backup, sett låsfil og signalhåndtering. ===
|
|
|
|
# Blir brukt som navn på ZFS-snapshot og logger.
|
|
snapshot_date="$(date +%Y%m%d)"
|
|
if [ "$1" = "full" ]; then # Fullbackupen starter før midnatt, legg på en dag
|
|
snapshot_date=$(date -v +1d +%Y%m%d)
|
|
fi
|
|
|
|
# Sjekk om en annen backup kjører eller om forrige backup ble avbrutt.
|
|
lockfile="/backupz/backup.sh.lock"
|
|
if [ -e $lockfile ]; then
|
|
existing_pid="$(cat $lockfile)"
|
|
|
|
# TODO: Bruk bedre metode for å finne prosess.
|
|
if ps -p "$existing_pid"; then
|
|
echo "Backup kjører allerede: $existing_pid"
|
|
exit 1
|
|
fi
|
|
|
|
echo "Forrige backup ble avbrutt, rydder opp..."
|
|
rm "$lockfile"
|
|
|
|
zfs snapshot -r "principal/backupz@avbrutt_${snapshot_date}" && \
|
|
echo "ZFS-snapshot OK." || echo "ZFS-snapshot FEILET!"
|
|
|
|
# TODO: zfs rollback til forrige komplette backup.
|
|
|
|
echo "Ferdig med oppryddingen."
|
|
fi
|
|
|
|
# Fang SIGINT, vi vil rydde opp om vi blir avbrutt.
|
|
on_sigint() {
|
|
rm "$lockfile"
|
|
echo 'Avbrutt, sletter låsfil...'
|
|
exit 2
|
|
}
|
|
trap on_sigint SIGINT
|
|
|
|
echo "$$" > "$lockfile"
|
|
|
|
|
|
# === Rydd opp gamle backups hvis det trengs plass. ===
|
|
|
|
echo "Sjekker at det er nok ledig plass på disken..."
|
|
zfs get available principal/backupz
|
|
|
|
# Dette tilsvarer 50 GB, juster etter behov.
|
|
min_free_space=50000000000
|
|
while [ "$(zfs get -Hp available principal/backupz | cut -f3)" -lt "$min_free_space" ] ; do
|
|
echo;
|
|
echo "Disken er nesten full, rydder";
|
|
echo
|
|
|
|
min_backups=5
|
|
if [ "$(zfs list -t snapshot | grep -c "backupz@")" -lt "$min_backups" ]; then
|
|
echo;
|
|
echo "Mindre enn $min_backups backups lagret, feiger ut fra sletting";
|
|
echo
|
|
break;
|
|
fi
|
|
|
|
oldest_snapshot=$(zfs list -t snapshot | grep backupz@ | head -n1 | tr @ ' ' | awk '{print $2}')
|
|
echo "Kjører zfs destroy på alle disker @$oldest_snapshot"
|
|
for d in $(zfs list | grep ^principal/backupz | awk '{print $1}'); do
|
|
zfs destroy "$d@$oldest_snapshot"
|
|
done
|
|
|
|
zfs get available principal/backupz
|
|
echo
|
|
done
|
|
|
|
|
|
# === Liste over vertsmaskiner og hva som skal tas backup av. ===
|
|
|
|
# TODO: kanskje noe av dette enklere kunne blitt uttrykt
|
|
# med en egen json-fil og litt jq-magi?
|
|
|
|
# Liste over vertsmaskiner som skal tas backup av.
|
|
declare -a hosts=()
|
|
|
|
# SSH-vertsnavn hvis det er forskjellig fra vertsnavnet i listen.
|
|
declare -A host_ssh_hostname=()
|
|
|
|
# Backup-katalog hvis den er forskjellig fra vertsnavnet i listen.
|
|
declare -A hosts_output_dir=()
|
|
|
|
# microbel (hjemmeområder)
|
|
hosts+=("homepvv")
|
|
homepvv_includes=(
|
|
"/"
|
|
"/boot"
|
|
"/export/home/pvv"
|
|
"/var"
|
|
)
|
|
homepvv_excludes=(
|
|
# Se ./homepvv.exclude - den skal bli plukket opp automatisk
|
|
)
|
|
|
|
# innovation
|
|
# Minecraft-verden kopieres fra /var/backups/minecraft/current/ istf.
|
|
# /srv/minecraft-pvv/.
|
|
hosts+=("innovation")
|
|
innovation_includes=(
|
|
"/"
|
|
"/boot/efi"
|
|
)
|
|
innovation_excludes=(
|
|
"/srv/minecraft-pvv/"
|
|
"/tmp"
|
|
"/var/cache/"
|
|
"/var/db/freebsd-update/files/"
|
|
"/var/run"
|
|
"/var/tmp"
|
|
)
|
|
|
|
# sleipner
|
|
hosts+=("sleipner")
|
|
sleipner_includes=(
|
|
"/"
|
|
)
|
|
sleipner_excludes=(
|
|
"/run"
|
|
"/scratch/"
|
|
"/tmp"
|
|
"/var/cache/"
|
|
"/var/run"
|
|
"/var/spool/rwho"
|
|
"/var/tmp"
|
|
)
|
|
|
|
# tom
|
|
hosts+=("tom")
|
|
tom_includes=(
|
|
"/"
|
|
"/boot/efi"
|
|
)
|
|
tom_excludes=(
|
|
"/run"
|
|
"/tmp"
|
|
"/var/cache/"
|
|
"/var/run"
|
|
"/var/spool/rwho"
|
|
"/var/tmp"
|
|
)
|
|
|
|
# balduzius
|
|
hosts+=("balduzius")
|
|
balduzius_includes=(
|
|
"/"
|
|
)
|
|
balduzius_excludes=(
|
|
"/run"
|
|
"/tmp"
|
|
"/var/cache/"
|
|
"/var/run"
|
|
"/var/tmp"
|
|
)
|
|
host_ssh_hostname["balduzius"]="balduzius-backup"
|
|
|
|
# knutsen
|
|
hosts+=("knutsen")
|
|
knutsen_includes=(
|
|
"/"
|
|
"/var"
|
|
"/usr"
|
|
)
|
|
knutsen_excludes=(
|
|
"/dev"
|
|
"/proc"
|
|
"/tmp"
|
|
"/usr/obj"
|
|
"/usr/ports"
|
|
"/var/cache"
|
|
"/var/db/freebsd-update/files"
|
|
"/var/run"
|
|
"/var/tmp"
|
|
)
|
|
# hosts_output_dir["knutsen"]="knutsen/current"
|
|
|
|
# ludvigsen
|
|
hosts+=("ludvigsen")
|
|
ludvigsen_includes=(
|
|
"/"
|
|
"/var"
|
|
"/usr"
|
|
)
|
|
ludvigsen_excludes=(
|
|
"/dev"
|
|
"/proc"
|
|
"/tmp"
|
|
"/usr/obj"
|
|
"/usr/ports"
|
|
"/var/cache"
|
|
"/var/db/freebsd-update/files"
|
|
"/var/run"
|
|
"/var/tmp"
|
|
)
|
|
host_ssh_hostname["ludvigsen"]="ludvigsen-tap"
|
|
# hosts_output_dir["ludvigsen"]="ludvigsen/current"
|
|
|
|
# isvegg
|
|
hosts+=("isvegg")
|
|
isvegg_includes=(
|
|
"/"
|
|
)
|
|
isvegg_excludes=(
|
|
"/home"
|
|
"/nix"
|
|
"/run"
|
|
"/scratch"
|
|
"/tmp"
|
|
"/usr/bin/ollama"
|
|
"/usr/share/ollama"
|
|
"/var/cache/"
|
|
"/var/lib/plocate"
|
|
"/var/log/journal"
|
|
"/var/run"
|
|
"/var/spool/rwho"
|
|
"/var/tmp"
|
|
)
|
|
host_ssh_hostname["isvegg"]="isvegg-backup"
|
|
|
|
# gitea (kommode)
|
|
hosts+=("gitea")
|
|
gitea_includes=(
|
|
"/"
|
|
)
|
|
host_ssh_hostname["gitea"]="gitea-backup"
|
|
|
|
# mediawiki (bekkalokk)
|
|
hosts+=("mediawiki")
|
|
mediawiki_includes=(
|
|
"/"
|
|
)
|
|
host_ssh_hostname["mediawiki"]="mediawiki-backup"
|
|
|
|
# matrix media store (bicep)
|
|
hosts+=("matrix_media_store")
|
|
matrix_media_store_includes=(
|
|
"/"
|
|
)
|
|
matrix_media_store_excludes=(
|
|
"/local_thumbnails"
|
|
"/remote_thumbnail"
|
|
"/url_cache"
|
|
"/url_cache_thumbnails"
|
|
)
|
|
host_ssh_hostname["matrix_media_store"]="matrix-media-store-backup"
|
|
|
|
# matrix ooye
|
|
hosts+=("matrix_ooye")
|
|
matrix_ooye_includes=(
|
|
"/"
|
|
)
|
|
matrix_media_store_excludes=(
|
|
"/registration.yaml"
|
|
)
|
|
host_ssh_hostname["matrix_ooye"]="matrix-ooye-backup"
|
|
|
|
# mysql (bicep)
|
|
hosts+=("mysql")
|
|
mysql_includes=(
|
|
"/mysql-dump-latest.sql.zst"
|
|
)
|
|
host_ssh_hostname["mysql"]="mysql-backup"
|
|
|
|
# postgresql (bicep)
|
|
hosts+=("postgresql")
|
|
postgresql_includes=(
|
|
"/postgresql-dump-latest.sql.zst"
|
|
)
|
|
host_ssh_hostname["postgresql"]="postgresql-backup"
|
|
|
|
# vaultwarden (bekkalokk)
|
|
hosts+=("vaultwarden")
|
|
vaultwarden_includes=(
|
|
"/"
|
|
)
|
|
vaultwarden_excludes=(
|
|
"/icon_cache"
|
|
"/tmp"
|
|
)
|
|
host_ssh_hostname["vaultwarden"]="vaultwarden-backup"
|
|
|
|
# snappymail (bekkalokk)
|
|
hosts+=("snappymail")
|
|
snappymail_includes=(
|
|
"/_data_"
|
|
)
|
|
host_ssh_hostname["snappymail"]="snappymail-backup"
|
|
|
|
# uptime-kuma (ildkule)
|
|
hosts+=("uptime_kuma")
|
|
uptime_kuma_includes=(
|
|
"/"
|
|
)
|
|
host_ssh_hostname["uptime_kuma"]="uptime-kuma-backup"
|
|
|
|
|
|
# === Sjekk at alle backup-kataloger har tilhørende datasett, og at datasettene er montert. ===
|
|
|
|
for host in "${hosts[@]}"; do
|
|
katalog="/backupz/${hosts_output_dir[$host]:-$host}"
|
|
dataset="principal/backupz/${hosts_output_dir[$host]:-$host}"
|
|
|
|
# Sjekk at ZFS-datasettet finnes
|
|
if ! zfs list "$dataset" >/dev/null 2>&1; then
|
|
echo "Feil: ZFS-datasettet '$dataset' finnes ikke. Opprett det med: zfs create $dataset" >&2
|
|
rm "$lockfile"
|
|
exit 1
|
|
fi
|
|
|
|
if [ ! -d "$katalog" ]; then
|
|
echo "Feil: katalogen '$katalog' finnes ikke. Opprett den med: mkdir '$katalog'" >&2
|
|
rm "$lockfile"
|
|
exit 1
|
|
fi
|
|
|
|
# Sjekk at datasettet er montert, og forsøk å mounte om nødvendig
|
|
is_mounted="$(zfs get -H -o value mounted "$dataset" 2>/dev/null || echo "no")"
|
|
if [ "${is_mounted:-no}" != "yes" ]; then
|
|
echo "Datasettet '$dataset' er ikke montert på '$katalog'. Forsøker å mounte..."
|
|
if ! zfs mount "$dataset" >/dev/null 2>&1; then
|
|
echo "Kunne ikke mounte '$dataset'. Avbryter." >&2
|
|
rm "$lockfile"
|
|
exit 1
|
|
fi
|
|
fi
|
|
done
|
|
|
|
|
|
# === Start rsync-jobber ===
|
|
|
|
echo "Starter backup..."
|
|
echo "Snapshot ID: $snapshot_date"
|
|
echo
|
|
|
|
start_time="$(date +%s)"
|
|
rsync="/usr/local/bin/rsync"
|
|
rsync_flags=(
|
|
--archive
|
|
--hard-links
|
|
--compress
|
|
--delete
|
|
--numeric-ids
|
|
--one-file-system
|
|
--relative
|
|
--rsh="ssh -v"
|
|
--stats
|
|
--inplace
|
|
--exclude=/.zfs/
|
|
)
|
|
logdir="/backupz/log"
|
|
declare -A venteproc=()
|
|
|
|
echo "Starter rsync for følgende verter: ${hosts[*]}"
|
|
echo
|
|
for host in "${hosts[@]}"; do
|
|
command=(
|
|
"$rsync"
|
|
"${rsync_flags[@]}"
|
|
"--log-file=${logdir}/${host}.log.$snapshot_date"
|
|
)
|
|
|
|
extra_array_name="${host}_extra_rsync_flags"
|
|
if declare -p "$extra_array_name" >/dev/null 2>&1; then
|
|
extra_rsync_flags_var="${host}_extra_rsync_flags[@]"
|
|
extra_rsync_flags=( "${!extra_rsync_flags_var}" )
|
|
for ef in "${extra_rsync_flags[@]}"; do
|
|
command+=( "$ef" )
|
|
done
|
|
fi
|
|
|
|
exclude_array_name="${host}_excludes"
|
|
if declare -p "$exclude_array_name" >/dev/null 2>&1; then
|
|
exclude_paths_var="${host}_excludes[@]"
|
|
exclude_paths=( "${!exclude_paths_var}" )
|
|
for exclude in "${exclude_paths[@]}"; do
|
|
command+=( "--exclude=${exclude}" )
|
|
done
|
|
fi
|
|
|
|
if [ -f "/backupz/${host}.exclude" ]; then
|
|
command+=("--exclude-from=/backupz/${host}.exclude")
|
|
fi
|
|
|
|
include_array_name="${host}_includes"
|
|
if declare -p "$include_array_name" >/dev/null 2>&1; then
|
|
include_paths_var="${host}_includes[@]"
|
|
include_paths=( "${!include_paths_var}" )
|
|
for include in "${include_paths[@]}"; do
|
|
command+=("${host_ssh_hostname[$host]:-$host}:$include")
|
|
done
|
|
else
|
|
echo "Ingen inkluderingsstier for vert $host, hopper over."
|
|
continue
|
|
fi
|
|
|
|
command+=("/backupz/${hosts_output_dir[$host]:-$host}/")
|
|
|
|
echo "Starter backup for vert: $host"
|
|
"${command[@]}" >"${logdir}/${host}.out.$snapshot_date" 2>&1 &
|
|
venteproc[$!]="$host"
|
|
echo "Startet $!:"
|
|
echo "${command[0]} \\"
|
|
# Ikke print de første rsync-flagga i kommandoen
|
|
echo " # ...standard rsync-flagg... \\"
|
|
start_index="$((1 + ${#rsync_flags[@]}))"
|
|
for ((i="$start_index"; i<${#command[@]}-1; i++)); do
|
|
echo " ${command[i]} \\"
|
|
done
|
|
echo " ${command[-1]}"
|
|
echo
|
|
done
|
|
echo "Rsync er i gang."
|
|
|
|
|
|
# === Vent på at alle rsync-jobbene skal bli ferdige ===
|
|
|
|
echo "Venter til rsync er ferdig: ${!venteproc[*]}"
|
|
echo
|
|
while true; do
|
|
wait -n -p pid; code=$?
|
|
[[ -z "${pid}" ]] && break
|
|
|
|
if [ $code -eq 0 ]; then
|
|
echo "${pid} (${venteproc["$pid"]:-???}): OK"
|
|
else
|
|
echo "${pid} (${venteproc["$pid"]:-???}): Rsync returnerte feil (${code})"
|
|
echo "Se loggfilene her for mer informasjon:"
|
|
echo "- ${logdir}/${venteproc["$pid"]}.log.$snapshot_date"
|
|
echo "- ${logdir}/${venteproc["$pid"]}.out.$snapshot_date"
|
|
fi
|
|
|
|
unset 'venteproc["$pid"]'
|
|
|
|
echo "${#venteproc[@]} jobber gjenstår..."
|
|
echo
|
|
done
|
|
echo "Rsync er ferdig."
|
|
|
|
|
|
# === Oppsummering, ta ZFS-snapshot og avslutt ===
|
|
|
|
end_time="$(date +%s)"
|
|
elapsed_time="$((end_time - start_time))"
|
|
printf 'Tid brukt på rsync: %03d:%02d:%02d\n' $((elapsed_time/3600)) $(((elapsed_time/60)%60)) $((elapsed_time%60))
|
|
|
|
# Touch home slik at timestamp på snapshot blir når backup var ferdig.
|
|
touch /backupz/homepvv/export/home
|
|
|
|
echo "Tar ZFS-snapshot..."
|
|
zfs snapshot -r "principal/backupz@${snapshot_date}" && \
|
|
echo "ZFS-snapshot ferdig." || echo "ZFS-snapshot FEILET!"
|
|
echo
|
|
echo Ledig plass: "$(zfs list -H -o avail principal/backupz)"
|
|
echo
|
|
|
|
# TODO: Slett enkelte gamle snapshots?
|
|
|
|
echo "Backup ferdig: $(date)"
|
|
rm "$lockfile"
|
|
zpool status -xv principal
|