Zálohy na Cloudflare R2 přes restic

Mám rád zálohy, kterým nemusím rozumět, abych je obnovil. Restic je přesně takový nástroj: jeden binárka v Go, jeden šifrovaný repozitář, deduplikace, snapshoty, a S3-kompatibilní backend, který se dá hostovat prakticky kdekoli. Cloudflare R2 je v tomhle ohledu příjemný cíl – S3 API, žádné poplatky za egress, dostupné z čehokoliv, co umí mluvit přes HTTPS.

Tenhle zápis je obecný návod. Nepředpokládá konkrétní server ani strukturu dat – jen Linux box, který chce posílat svoje důležité věci někam, kde přežijí požár v serverovně.

Proč zrovna restic

Tři vlastnosti, kvůli kterým za tím stojí jít:

  • Šifrování na straně klienta. Repozitář je AES-256 + Poly1305. Server (R2, S3, B2, lokální disk, SFTP) nevidí obsah ani metadata souborů. Klíč drží jen ten, kdo zná RESTIC_PASSWORD.
  • Content-defined chunking. Restic data nerozseká po pevně dlouhých blocích, ale podle obsahu (rolling hash). Když vložíš do souboru jeden byte uprostřed, znova se nahraje jen pár chunků, ne celý soubor. V praxi to znamená, že druhý a další snapshot trvá vteřiny a zabere desítky kilobytů.
  • Snapshoty místo verzí. Každé spuštění backup je samostatný snapshot s časovým razítkem a tagem. Restore není o „obnov soubor X z verze 3″, ale o „namountuj snapshot z minulého úterý a vezmi si z něj, co potřebuješ.“

Co restic není: real-time replikace, journalovaný file system, ani náhrada za RAID. Je to backup, ne failover.

Proč R2

R2 je objektové úložiště od Cloudflare s S3-kompatibilním API. Hlavní důvody, proč ho zvážit pro zálohy:

  • Nulový egress. Stahování dat zpátky nestojí nic. U AWS S3 platíš za každý gigabyt, který opustí region; u restoru terabajtových záloh to bolí.
  • Cena za storage. Aktuálně řádově 0.015 USD za GB-měsíc, levnější než S3 Standard, srovnatelné s B2 Backblaze.
  • S3 API. Restic ho umí out-of-the-box, žádný custom plugin.

Co je třeba si pohlídat: R2 nepoužívá virtual-hosted URLs (tj. bucket.endpoint), restic to ostatně ani nechce – spoléhá na path-style (endpoint/bucket). U R2 to vychází přirozeně, endpoint vypadá https://<account-id>.r2.cloudflarestorage.com a bucket se připojuje za lomítko.

Příprava na straně R2

V Cloudflare dashboardu, sekce R2:

  1. Založ bucket. Jméno je globální v rámci tvého účtu, kratší je lepší.
  2. V sekci Manage R2 API Tokens vytvoř token typu User API Token s oprávněním Object Read & Write omezeným na konkrétní bucket. Token jde svázat s jediným bucketem – dělej to, omezený blast radius za to stojí.
  3. Po vytvoření dostaneš tři údaje: Access Key ID, Secret Access Keyendpoint URL. Secret se ukáže jenom jednou, ulož si ho hned.

Verzování ani lifecycle pravidla na úrovni R2 nepotřebuješ – retenci si restic řeší sám přes forget.

Instalace restic

Na Debianu/Ubuntu:

sudo apt install restic

Verze v Debianu stable bývá o pár měsíců pozadu. Pokud chceš aktuální release, stáhni binárku z github.com/restic/restic/releases a hoď ji do /usr/local/bin/. Restic je single static binary, žádné runtime závislosti.

restic version

Konfigurace přes environment

Restic čte přihlašovací údaje a cestu k repozitáři z proměnných prostředí. Tahle čtveřice je všechno, co potřebuje:

export AWS_ACCESS_KEY_ID="<access-key-z-r2>"
export AWS_SECRET_ACCESS_KEY="<secret-z-r2>"
export RESTIC_REPOSITORY="s3:https://<account-id>.r2.cloudflarestorage.com/<bucket>"
export RESTIC_PASSWORD="<silne-heslo-na-sifrovani>"

Pár poznámek k téhle čtveřici:

  • AWS_* jsou jen názvy – restic používá AWS SDK, ale jde o credentials pro R2.
  • RESTIC_REPOSITORY má prefix s3: a pak plnou URL endpointu i s bucketem.
  • RESTIC_PASSWORD jde nahradit RESTIC_PASSWORD_FILE (cesta k souboru s heslem) nebo RESTIC_PASSWORD_COMMAND (libovolný příkaz, jehož stdout je heslo – typicky pass, gopass, secret-tool, ap.). Pro automatizaci preferuj soubor s právy 600.

Heslo k repozitáři si zapiš mimo server. Bez něj jsou data nečitelná i pro tebe. Hardware password manager, papírová záloha v šuplíku, šifrovaný USB stick u rodičů – cokoli, co přežije ztrátu serveru.

Inicializace repozitáře

S exportovanými proměnnými stačí:

restic init

Restic v bucketu vytvoří strukturu config, keys/, data/, index/, snapshots/, locks/. config je šifrovaný JSON s parametry repozitáře, keys/ drží wrappované master klíče (jeden per heslo – ano, repozitář může mít víc hesel).

Init udělej jen jednou. Při dalších voláních proti existujícímu repozitáři restic poznám, že je inicializovaný, a odmítne.

První záloha

Záloha adresáře:

restic backup /home /etc --tag system

Co se stane:

  1. Restic projde strom, načte soubory, rozseká je content-defined chunkingem, každý chunk zašifruje a nahraje, pokud ho repozitář ještě nemá.
  2. Vytvoří snapshot – malý objekt, který říká „v čase T na hostu H z cesty P měl strom tenhle obsah.“
  3. Vypíše souhrn: kolik souborů, kolik nových chunků, kolik bytů se nahrálo.

První záloha je vždy „drahá“ -- nahrává se prakticky všechno. Druhá a každá další jsou levné: nahrávají se jen rozdíly. Mám zkušenost, že denní inkrement domovského adresáře s pár gigabyty kódu, fotek a mailu se vejde do desítek megabytů.

Výluky

Skoro nikdy nechceš zálohovat všechno. Cache, build artefakty, dočasné soubory, virtuální prostředí – to všechno radši ne.

restic backup /home \
  --exclude='**/node_modules' \
  --exclude='**/.cache' \
  --exclude='**/target' \
  --exclude='**/__pycache__' \
  --exclude-caches

Komfortnější je exclude file:

restic backup /home --exclude-file /etc/restic/excludes.txt

Kde soubor vypadá podobně jako .gitignore – jeden pattern na řádek, komentáře #, negace !. Patterns používají filepath-style matching (*, **, ?, [abc]). --exclude-caches přeskočí adresáře, ve kterých je CACHEDIR.TAG – moderní cache to umí (Cargo, npm i jiné).

Tagy

restic backup /home --tag home --tag daily

Tagy se hodí pro forget (retence per-tag) a pro filtrování při snapshotsrestore.

Snapshoty

restic snapshots

Vypíše seznam: ID, čas, host, paths, tagy. Filtruj přes --tag, --host, --path.

restic snapshots --tag daily --host myhost

Pro detaily konkrétního snapshotu:

restic ls <id>           # výpis souborů ve snapshotu
restic diff <id1> <id2>  # rozdíl mezi snapshoty
restic stats <id>        # velikost a počty

Restore

Tři způsoby, jak se k datům dostat:

Klasický restore do adresáře:

restic restore latest --target /tmp/restore --path /home

latest vybere nejnovější snapshot, --path ho zúží na ten, který zálohoval danou cestu (užitečné, když pod jedním repozitářem děláš zálohy víc systémů).

Selektivní restore:

restic restore <id> --target /tmp/restore --include '/home/jirka/.gnupg'

FUSE mount – nejpohodlnější pro jednorázové dohledávky:

mkdir /mnt/restic
restic mount /mnt/restic

/mnt/restic se objeví strom snapshots/<host>/<datum>/, v něm přesně to, co bylo zálohované. Procházíš to ls, kopíruješ cp, hledáš grep. Při ukončení (Ctrl+C) se filesystem odpojí.

Mount je read-only, takže neexistuje cesta, jak si při restoru rozbít repozitář.

Retence – forget a prune

Záloha bez retence po čase přeroste. Restic řeší retenci dvoufázově:

  • forget odstraní referenci na snapshot z indexu. Snapshot zmizí z výpisu, ale jeho data jsou pořád v repozitáři (sdílí je jiné snapshoty).
  • prune prochází repozitář a maže chunky, na které už nikdo neukazuje.

Typická politika – 7 denních, 4 týdenní, 12 měsíčních a 3 roční:

restic forget \
  --keep-daily 7 \
  --keep-weekly 4 \
  --keep-monthly 12 \
  --keep-yearly 3 \
  --prune

Co dělá --keep-daily 7: vezme 7 nejnovějších dní, ve kterých byl snapshot, a z každého dne nechá ten nejnovější. Ne posledních 7 dní v kalendáři – 7 dní s daty. Kalendáře (týden, měsíc, rok) restic počítá podle skutečných hranic (týden = pondělí--neděle), takže „měsíční“ snapshot je první ze začátku měsíce.

--prune zařadí prune do stejného běhu. U velkých repozitářů (stovky GB) trvá prune dlouho a generuje hodně requestů – v takovém případě je rozumné dělat forget často, prune jednou týdně:

# denně
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 12

# v neděli
restic prune

Před produkčním nasazením politiky vyzkoušej --dry-run – ukáže, co by zmizelo, bez reálné akce.

Kontrola integrity

R2 (ani žádné jiné cloud storage) nepřidává záruku, že to, co jsi nahrál, je bit-perfektně to, co se ti později vrátí. Restic má proto vestavěnou verifikaci.

Levná kontrola (jen metadata a struktura):

restic check

Plná kontrola (stáhne všechna data a přepočítá hashe – drahé):

restic check --read-data

Pro velké repozitáře se hodí postupná verifikace – v každém běhu přečíst jen kus dat:

restic check --read-data-subset=10%

Za deset běhů jsi prošel celé úložiště. Tohle je rozumný kompromis mezi důvěrou a náklady – u R2 jsou náklady jen čas a CPU, egress je zdarma.

Automatizace přes systemd

Restic není daemon – spouští se externím schedulerem. Cron funguje, ale systemd timer dává lepší kontrolu nad výstupem a chybami.

/etc/systemd/system/restic-backup.service:

[Unit]
Description=Restic backup to R2
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
EnvironmentFile=/etc/restic/env
ExecStart=/usr/local/bin/restic backup /home /etc \
  --exclude-file /etc/restic/excludes.txt \
  --tag daily
ExecStartPost=/usr/local/bin/restic forget \
  --keep-daily 7 --keep-weekly 4 --keep-monthly 12 \
  --tag daily

/etc/systemd/system/restic-backup.timer:

[Unit]
Description=Daily restic backup

[Timer]
OnCalendar=daily
RandomizedDelaySec=1h
Persistent=true

[Install]
WantedBy=timers.target

/etc/restic/env:

AWS_ACCESS_KEY_ID=...
AWS_SECRET_ACCESS_KEY=...
RESTIC_REPOSITORY=s3:https://<account-id>.r2.cloudflarestorage.com/<bucket>
RESTIC_PASSWORD_FILE=/etc/restic/password

Soubor envpassword chmodni na 600, ownership root:root.

Aktivace:

sudo systemctl daemon-reload
sudo systemctl enable --now restic-backup.timer
systemctl list-timers restic-backup.timer

RandomizedDelaySec rozhází běhy v čase – užitečné, když máš víc strojů a nechceš, aby všechny narazily na R2 ve 03:00:00 současně. Persistent=true zajistí, že pokud byl stroj při plánovaném čase vypnutý, restic doběhne po startu.

Co když je server pryč

Tohle je test, který stojí za to udělat před tím, než ho budeš potřebovat. Na čerstvém stroji:

sudo apt install restic
export AWS_ACCESS_KEY_ID=...
export AWS_SECRET_ACCESS_KEY=...
export RESTIC_REPOSITORY=s3:https://<account-id>.r2.cloudflarestorage.com/<bucket>
export RESTIC_PASSWORD=...

restic snapshots
restic restore latest --target /restore

Pokud snapshots ukáže seznam a restore latest doběhne, máš funkční disaster recovery. Pokud něco z toho nejede, lepší zjistit teď.

Co si dát do popisku scénáře:

  • Heslo k repozitáři není v repozitáři. Musíš si ho pamatovat, nebo mít offline.
  • API credentials k R2 můžeš v Cloudflare regenerovat – ale jen z účtu, do kterého se dostaneš. Backup-of-backup pro Cloudflare přihlašování (2FA recovery codes, atd.) je samostatné téma.
  • Disk space na restore – nezkoušej restore terabajtu na 50GB systémový disk.

Co tady není

Multi-host repozitáře (jeden bucket, víc strojů – jde to, ale chce to opatrnost s --host a tagy), repository copy (restic copy pro zrcadlení do druhého úložiště – belt and suspenders), key management (více hesel k jednomu repo), append-only mód pro ransomware-resistant zálohy (přes B2 jde lépe než přes R2). To všechno stojí za vlastní zápis. Tady je základ – jednoduchý strom, jeden bucket, jeden host, plánovač v systemd. To dostane většinu provozu.

Restic má jednu velmi cennou vlastnost: skoro nikdy tě nepřekvapí. Když se ho budeš ptát hloupě, řekne ti to. Když mu zadáš nesmysl, nepustí ho. To je u zálohovacího nástroje vlastnost, kterou si nelze přeplatit.