#!/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=() # ameno hosts+=("ameno") ameno_includes=( "/" "/boot/firmware" ) ameno_excludes=( "/var/cache/" "/var/lib/snapd/" "/var/log/journal/" ) # 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/" "/var/cache/" "/var/db/freebsd-update/files/" ) # sleipner hosts+=("sleipner") sleipner_includes=( "/" ) sleipner_excludes=( "/scratch/" "/var/cache/" ) # tom hosts+=("tom") tom_includes=( "/" "/boot/efi" ) tom_excludes=( "/var/cache/" ) # balduzius hosts+=("balduzius") balduzius_includes=( "/" ) balduzius_excludes=( "/var/cache/" ) host_ssh_hostname["balduzius"]="balduzius-backup" # isvegg hosts+=("isvegg") isvegg_includes=( "/" ) isvegg_excludes=( "/home" "/nix" "/scratch" "/usr/bin/ollama" "/usr/share/ollama" "/var/cache/" "/var/lib/plocate" "/var/log/journal" ) 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" # 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" # === 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