{"id":1432,"date":"2025-12-08T17:07:49","date_gmt":"2025-12-08T16:07:49","guid":{"rendered":"https:\/\/pcmacb.de\/?page_id=1432"},"modified":"2025-12-08T18:19:54","modified_gmt":"2025-12-08T17:19:54","slug":"inkrementelles-backup-mit-rsync","status":"publish","type":"page","link":"https:\/\/pcmacb.de\/?page_id=1432","title":{"rendered":"Inkrementelles Backup mit rsync"},"content":{"rendered":"\n<p>auf eine zweite interne Festplatte, die nur beim Backup eingebunden wird.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Variablen und Funktionen festlegen<\/h4>\n\n\n\n<pre class=\"wp-block-code\"><code>#!\/bin\/bash\n# 0_functions.sh\n\nBAK=\"\/BACKUP_EXT\"\nBASE=\"00_BASE\"\n\n# Timestamp\nDATE=$(LC_ALL=C date '+%d. %b %H:%M:%S')\nDWE=$(date +%Y-%m-%d)                 # 2025-11-29\nHOMIN=$(date +%H%M)                   # 1430\n\n# What?\nSRC0=(\n    '\/etc'\n    '\/root'\n    '\/var\/www'\n    '\/home\/user\/Desktop'\n)\n\n# Check Mount\nonmount() {\n    &#91; -f \"$BAK\/.mounted\" ] &amp;&amp; return 0\n    echo \"$DATE - Mounting $BAK ...\"\n    mount \/dev\/vdb1 \"$BAK\" || echo \"$DATE ist schon gemounted\"\n    touch \"$BAK\/.mounted\"\n}\n\noffmount() {\n    &#91; ! -f \"$BAK\/.mounted\" ] &amp;&amp; return 0\n    echo \"$DATE - Unmounting $BAK ...\"\n    sleep 2\n    umount \"$BAK\" 2>\/dev\/null || true\n    rm -f \"$BAK\/.mounted\"\n}\n\n# L\u00e4uft das Script schon - dann Killyourself\nselfchk() {\n    exec 9&lt;\"$0\"\n    flock -n 9 || { echo \"$DATE - Bereits am Laufen!\"; exit 1; }\n}\n\n# Optional SQL Datenbanken sichern\ndumpbase() {\n    local dumpdir=\"$BAK\/SQL\"  # mit auf die externe Platte in Unterverzeichnis\n    mkdir -p \"$dumpdir\"\n    for db in  database_sql0 database_sql1; do\n        local file=\"$dumpdir\/${db}-$DWE.sql\"\n        echo \"$DATE - Dumping $db \u2192 $file\"\n        mysqldump \"$db\" > \"$file\" || echo \"mysqldump for $db failed!\"\n    done\n}<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Basis erstellen<\/h4>\n\n\n\n<p>um von hier ausgehend Hardlinks zu generieren<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>#!\/bin\/bash\n# 1_base.sh \u2013 00_BASE neu anlegen\/aktualisieren, etwa alle zwei Wochen ausf\u00fchren\n\n# In welchem Verzeichnis befinde ich mich und finde ich hier meine 0_functions??\nDEAR=$(cd -- \"$(dirname -- \"${BASH_SOURCE&#91;0]}\")\" &amp;>\/dev\/null &amp;&amp; pwd)\nsource \"$DEAR\/0_functions.sh\"\n\nselfchk\nonmount\n\necho \"$DATE - Erstelle\/aktualisiere die Basis 00_BASE auf externer Platte\"\n\n# Alte Basis ggf. sichern (optional)\n#&#91; -d \"$BAK\/$BASE\" ] &amp;&amp; mv \"$BAK\/$BASE\" \"$BAK\/${BASE}_old_$(date +%Y%m%d)\" 2>\/dev\/null\n\nmkdir -p \"$BAK\/$BASE\"\n\nfor SRC in \"${SRC0&#91;@]}\"; do\n    echo \"$DATE - BASE: $SRC \u2192 $BAK\/$BASE$SRC\"\n    mkdir -p \"$BAK\/$BASE$SRC\"\n    rsync -aP --delete \"$SRC\/\" \"$BAK\/$BASE$SRC\/\"\ndone\n\necho \"$DATE - BASE fertig. Gr\u00f6\u00dfe: $(du -sh \"$BAK\/$BASE\")\"\noffmount\nexit 0<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">T\u00e4gliche inkrementelle Backups erstellen<\/h4>\n\n\n\n<p>mit zus\u00e4tzlichen w\u00f6chentlichen Tarballs und Aufr\u00e4umfunktion mit Sicherheit, dass die Platte nicht voll l\u00e4uft<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>#!\/bin\/bash\n# 2_daily.sh \u2192 t\u00e4gliches Backup mit Hardlinks zu 00_BASE\n# F\u00fcr st\u00fcndliche Backups die Datei nach 3_hourly.sh kopieren und DWE durch HOMIN ersetzen\n\nDEAR=$(cd -- \"$(dirname -- \"${BASH_SOURCE&#91;0]}\")\" &amp;>\/dev\/null &amp;&amp; pwd)\nsource \"$DEAR\/0_functions.sh\"\n\nselfchk\nonmount\n\n# Zielordner\nTARGET=\"$BAK\/DAILY\/$DWE\"          # st\u00fcndlich? Dann: \"$BAK\/HOURLY\/$HOMIN\"\nmkdir -p \"$TARGET\"\n\necho \"$DATE - Starte Backup \u2192 $TARGET (Hardlinks zu 00_BASE)\"\n\nfor SRC in \"${SRC0&#91;@]}\"; do\n    DEST=\"$TARGET$SRC\"\n    PREV=\"$BAK\/$BASE$SRC\"\n\n    mkdir -p \"$DEST\"\n    echo \"$DATE - $SRC \u2192 Hardlinks zur BASE\"\n    rsync -aP --delete --stats --link-dest=\"$PREV\" \"$SRC\/\" \"$DEST\/\"\ndone\n\n# Rotation: \u00e4lter als 22 Tage l\u00f6schen\nfind \"$BAK\/DAILY\" -maxdepth 1 -type d -mtime +22 -exec rm -rf {} + 2>\/dev\/null\n\n# SQL Daten dumpen und \u00e4lter als 22 Tage l\u00f6schen\ndumpbase\nfind \"$BAK\/SQL\" -maxdepth 1 -type f -mtime +22 -exec rm -rf {} + 2>\/dev\/null\n\n# Regelm\u00e4\u00dfige Tarballs\n# \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500  T\u00e4glicher SQL-Dump-Tarball \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\necho \"$DATE - Erstelle SQL-Dump-Tarball...\"\nif &#91; -d \"$BAK\/SQL\" ] &amp;&amp; &#91; \"$(ls -A \"$BAK\/SQL\" 2>\/dev\/null)\" ]; then\n    tar -czf \"$BAK\/SQL-$DWE.tar.gz\" -C \"$BAK\/SQL\" . \\\n        &amp;&amp; echo \"$DATE - SQL-Tarball fertig: $(du -h \"$BAK\/SQL-$DWE.tar.gz\")\" \\\n        || echo \"$DATE - SQL-Tarball fehlgeschlagen!\"\nelse\n    echo \"$DATE - Keine SQL-Dumps vorhanden, ergo kein SQL-Tarball\"\nfi\n\n# \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 Tarball DAILY \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\necho \"$DATE - Erstelle offsite Tarball von $DWE ...\"\ntar -czf \"$BAK\/offsite_daily_$DWE.tar.gz\" -C \"$BAK\/DAILY\/$DWE\" . \\\n    &amp;&amp; echo \"$DATE - Offsite-Tarball fertig: $(du -h \"$BAK\/offsite_daily_$DWE.tar.gz\")\" \\\n    || echo \"$DATE - Offsite-Tarball fehlgeschlagen!\"\n\n# \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 Retention: Platz &lt; 45 GB halten \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# Nur die letzten 4 t\u00e4glichen Offsite-Tarballs behalten\nfind \"$BAK\" -name \"offsite_daily_*.tar.gz\" -type f -printf '%T@\\t%p\\n' 2>\/dev\/null | \\\n    sort -nr | tail -n +5 | cut -f2- | xargs rm -f 2>\/dev\/null\n\n# Nur die letzten 10 SQL-Tarballs behalten\nfind \"$BAK\" -name \"SQL-*.tar.gz\" -type f -printf '%T@\\t%p\\n' 2>\/dev\/null | \\\n    sort -nr | tail -n +11 | cut -f2- | xargs rm -f 2>\/dev\/null\n\n# Am 1. monatlich die Tarballs durch Umbenennung sichern\nif &#91; \"$(date +%d)\" -eq 01 ]; then\n    &#91; -f \"$BAK\/offsite_daily_$DWE.tar.gz\" ] &amp;&amp; \\\n        mv \"$BAK\/offsite_daily_$DWE.tar.gz\" \"$BAK\/offsite_MONTHLY_$(date +%Y-%m).tar.gz\"\n    &#91; -f \"$BAK\/SQL-$DWE.tar.gz\" ] &amp;&amp; \\\n        mv \"$BAK\/SQL-$DWE.tar.gz\" \"$BAK\/SQL_MONTHLY_$(date +%Y-%m).tar.gz\"\nfi\n\n# Nur letzte 8 Monats-Tarballs (offsite + SQL) behalten\nfind \"$BAK\" -name \"offsite_MONTHLY_*.tar.gz\" -type f -printf '%T@\\t%p\\n' 2>\/dev\/null | \\\n    sort -nr | tail -n +9 | cut -f2- | xargs rm -f 2>\/dev\/null\nfind \"$BAK\" -name \"SQL_MONTHLY_*.tar.gz\" -type f -printf '%T@\\t%p\\n' 2>\/dev\/null | \\\n    sort -nr | tail -n +9 | cut -f2- | xargs rm -f 2>\/dev\/null\n\n# Hardlink-Ordner: letzte 14 Tage behalten (kostet fast keinen Speicherplatz)\nfind \"$BAK\/DAILY\" -mindepth 1 -maxdepth 1 -type d -mtime +13 -exec rm -rf {} + 2>\/dev\/null\n\n# NOTFALL: wenn > 90 % der Platte voll sind, \u00e4lteste daily-Tarballs l\u00f6schen\nwhile &#91; \"$(df --output=pcent \"$BAK\" | tail -1 | tr -d ' %')\" -gt 90 ]; do\n    oldest=$(ls -1t \"$BAK\"\/offsite_daily_*.tar.gz \"$BAK\"\/SQL-*.tar.gz 2>\/dev\/null | tail -1)\n    &#91; -z \"$oldest\" ] &amp;&amp; break\n    echo \"$DATE - NOTFALL: Platte voll \u2192 l\u00f6sche $oldest\"\n    rm -f \"$oldest\"\ndone\n\necho \"$DATE - Backup + Tarballs + Aufr\u00e4umen abgeschlossen.\"\necho \"$DATE - Belegung: $(df -h \"$BAK\" | tail -1)\"\n\n# Hardlink-Statistik, Kontrolle, ob tats\u00e4chlich Hardlinks und nicht Kopien erstellt werden\necho \"=== Hardlink-Statistik $DWE ===\"\nfind \"$BAK\/$BASE\" -type f -printf '%i\\n' | sort -u > \/tmp\/base_inodes\nfind \"$TARGET\"     -type f -printf '%i\\n' | sort -u > \/tmp\/new_inodes\necho \"Hardlinks (gemeinsam): $(comm -12 \/tmp\/base_inodes \/tmp\/new_inodes | wc -l)\"\necho \"Echte Kopien (neu):   $(comm -23 \/tmp\/new_inodes \/tmp\/base_inodes | wc -l)\"\nrm -f \/tmp\/*_inodes\n\noffmount\necho \"$DATE - Backup fertig!\"\nexit 0<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">St\u00fcndlich, so zum Spa\u00df<\/h4>\n\n\n\n<pre class=\"wp-block-code\"><code>#!\/bin\/bash\n# 3_hourly.sh \u2192 st\u00fcndliches Backup mit Hardlinks zu 00_BASE\n\nDEAR=$(cd -- \"$(dirname -- \"${BASH_SOURCE&#91;0]}\")\" &amp;>\/dev\/null &amp;&amp; pwd)\nsource \"$DEAR\/0_functions.sh\"\n\nselfchk\nonmount\n\n# Zielordner st\u00fcndlich\nTARGET=\"$BAK\/HOURLY\/$HOMIN\"\nmkdir -p \"$TARGET\"\n\necho \"$DATE - Starte Backup \u2192 $TARGET (Hardlinks zu 00_BASE)\"\n\nfor SRC in \"${SRC0&#91;@]}\"; do\n    DEST=\"$TARGET$SRC\"\n    PREV=\"$BAK\/$BASE$SRC\"\n\n    mkdir -p \"$DEST\"\n    echo \"$DATE - $SRC \u2192 Hardlinks zu BASE\"\n    rsync -avP --delete --stats --link-dest=\"$PREV\" \"$SRC\/\" \"$DEST\/\"\ndone\n\n# Hardlink-Statistik\necho \"=== Hardlink-Statistik $HOMIN ===\"\nfind \"$BAK\/$BASE\" -type f -printf '%i\\n' | sort -u > \/tmp\/base_inodes\nfind \"$TARGET\"     -type f -printf '%i\\n' | sort -u > \/tmp\/new_inodes\necho \"Hardlinks (gemeinsam): $(comm -12 \/tmp\/base_inodes \/tmp\/new_inodes | wc -l)\"\necho \"Echte Kopien (neu):   $(comm -23 \/tmp\/new_inodes \/tmp\/base_inodes | wc -l)\"\nrm -f \/tmp\/*_inodes\n\noffmount\necho \"$DATE - Backup fertig!\"\nexit 0<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Zu zu guter Letzt noch ein passender Cronjob<\/h4>\n\n\n\n<pre class=\"wp-block-code\"><code># m h  dom mon dow   command\n# 4:12 Uhr am 3.&amp;17. jeden Monats\n# 1:12 Uhr t\u00e4glich\n# zwischen 8:07 und 20:07 Uhr t\u00e4glich jede Stunde\nSHELL=\/bin\/bash\n12 04 3,17 * * \/root\/scripts\/bak\/1_base.sh >\/dev\/null 2>&amp;1\n12 01 * * * \/root\/scripts\/bak\/2_daily.sh >\/dev\/null 2>&amp;1\n7 8-20 * * * \/root\/scripts\/bak\/3_hourly.sh >\/dev\/null 2>&amp;1<\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"<p>auf eine zweite interne Festplatte, die nur beim Backup eingebunden wird. Variablen und Funktionen festlegen Basis erstellen um von hier ausgehend Hardlinks zu generieren T\u00e4gliche inkrementelle Backups erstellen mit zus\u00e4tzlichen w\u00f6chentlichen Tarballs und Aufr\u00e4umfunktion mit Sicherheit, dass die Platte nicht <a class=\"more-link\" href=\"https:\/\/pcmacb.de\/?page_id=1432\">weiterlesen&#8230;<\/a><\/p>\n","protected":false},"author":3,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"_lmt_disableupdate":"","_lmt_disable":"","footnotes":""},"class_list":["post-1432","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/pcmacb.de\/index.php?rest_route=\/wp\/v2\/pages\/1432","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/pcmacb.de\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/pcmacb.de\/index.php?rest_route=\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/pcmacb.de\/index.php?rest_route=\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"https:\/\/pcmacb.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1432"}],"version-history":[{"count":5,"href":"https:\/\/pcmacb.de\/index.php?rest_route=\/wp\/v2\/pages\/1432\/revisions"}],"predecessor-version":[{"id":1443,"href":"https:\/\/pcmacb.de\/index.php?rest_route=\/wp\/v2\/pages\/1432\/revisions\/1443"}],"wp:attachment":[{"href":"https:\/\/pcmacb.de\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1432"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}