Přeskočit na obsah

git clean smaže uživatelské uploady (untracked soubory v repu)

import { Aside } from ‘@astrojs/starlight/components’;

Klientské fotky a dokumenty nahrané uživateli (vč. fotek z mobilu) přestaly být dostupné — odkazy vracely 404 / soubory chyběly na disku. Databázové záznamy o souborech existovaly dál, ale fyzické soubory byly pryč. Odhaleno náhodně až po týdnech, protože nic neupozornilo.

Uživatelské uploady se ukládaly do adresáře uvnitř git working tree aplikace (<APP_DIR>/uploads/) a tento adresář nebyl v .gitignore. Soubory tedy existovaly jako untracked.

Při ladění/údržbě někdo spustil git clean -fdx (běžný příkaz na „uveď repo do čistého stavu”) — ten smaže všechny untracked soubory a adresáře, tedy i uploady.

Stopa po smazání nebyla v shell historii: příkazy spouštěné neinteraktivně přes ssh server "<cmd>" (typicky deploy a automatizace) se nezapisují do .bash_history, takže destruktivní jednorázový příkaz nezanechá dohledatelnou stopu.

Terminál
# Smrtící kombinace:
# - uploads/ je untracked (není v .gitignore)
# - uploads/ leží uvnitř git working tree
git clean -fdx # smaže VŠE untracked → včetně uploads/

Data zachránila jediná věc: offsite záloha běžela rsyncem BEZ --delete, takže soubory smazané lokálně na záloze zůstaly. S --delete by byla nenávratně pryč.

  1. Okamžitá obnova ze zálohy (additivně, aby se nepřepsaly novější lokální verze):
    Terminál
    rsync -az --ignore-existing -e "ssh ..." <REMOTE>:<PATH>/uploads/ <APP_DIR>/uploads/
  2. Gitignore uploadů — aby git clean ani omylem nemohl uploady smazat a aby se nezacommitovaly:
    # Uživatelské nahrané soubory — NIKDY do gitu
    uploads/
  3. Monitoring — hodinový cron porovná počet souborů lokálně vs. záloha a při propadu pošle e-mail (viz níže).

Jak se tomu vyvarovat v jiných systémech

Sekce “Jak se tomu vyvarovat v jiných systémech”
  • Detection:
    • git check-ignore <UPLOADS_DIR> musí vrátit cestu (= je ignorováno). Pokud nevrátí nic → díra.
    • Grep v repu: leží uploads/ (nebo media/, storage/, attachments/) uvnitř working tree a chybí v .gitignore?
  • Anti-pattern:
    • Uživatelská data v adresáři, který je v dosahu git clean / git reset --hard / rm -rf z deploy skriptu.
    • Záloha s --delete (zrcadlí i mazání → není to záloha proti smazání).
    • Spoléhání na .bash_history jako audit trail (neinteraktivní SSH příkazy tam nejsou).
  • Lepší přístup:
    • Uploady ukládej do adresáře mimo git working tree (např. /var/lib/<app>/uploads), cestu drž v ENV (UPLOADS_DIR). I tak je gitignoruj.
    • Offsite záloha vždy bez --delete (additivní = high-water mark).
    • Obnovovací příkaz měj zdokumentovaný v runbooku/README.
    • Guard cron s e-mail alertem na propad počtu souborů (alert přes lokální sendmail, ne přes SMTP auth, která může selhat/rotovat):
      Terminál
      MISSING=$(rsync -rn --ignore-existing --out-format="%n" -e "ssh ..." \
      <REMOTE>:<PATH>/uploads/<cenny_adresar>/ <UPLOADS_DIR>/<cenny_adresar>/ \
      | grep -vE '/$' | wc -l)
      [ "$MISSING" -ge 10 ] && echo "ALERT: chybí $MISSING souborů" | /usr/sbin/sendmail -t ...
      # regenerovatelné adresáře (cache) z kontroly vynech
    • Deploy skript nikdy nesmí sahat na UPLOADS_DIR (žádné git clean, reset --hard, rm -rf).

Sister bugs / související

Sekce “Sister bugs / související”
  • Prázdný mimeType u uložených souborů → servírují se jako application/octet-stream a místo zobrazení se stáhnou. Vždy ukládej mimeType, originalName, storagePath, fileSize.
  • HEIC/HEIF z mobilů prohlížeč nezobrazí → konvertuj na JPEG už při uploadu (ImageMagick/sharp), HEIC detekuj i podle přípony (prohlížeč posílá špatný/prázdný MIME).
  • Servírování souborů přes autentizovaný endpoint s RBAC, ne ze static složky bez kontroly oprávnění.
Přidal aiarchitekt.cz · 25. 5. 2026 17:41
Provozuje aiarchitekt.cz