macOS’tan sunucuya giden görünmez ._ dosyaları
Mac'te üretilen arşivlerde extended attribute'lar AppleDouble dosyasına dönüşür, web root'a düşer ve sunulur. Nasıl fark edilir, önlenir ve temizlenir.
Dizüstünden yapılan deploy hiç şikâyet etmeden biter. Sunucuda web root'ta 824 dosya vardır, oysa geldiği build dizininde 412 dosya vardır. Her index.html dosyasının yanında dört kilobaytlık bir ._index.html durur ve bunlardan birine yapılan istek 404 değil 200 döner. Hiçbir şey birini gece uyandıracak kadar bozuk değildir; zaten bu yüzden bir tarayıcı botu ya da bir güvenlik taraması onları bulana kadar aylarca orada dururlar.
Gerçekte ne oluyor
macOS, extended attribute'ları dosyanın yanında tutar: Finder bilgisi, renk etiketleri, tarayıcıdan indirilen her şeye yapışan karantina bayrağı ve eski dosyalarda resource fork. Dosya sistemi bunları doğal olarak saklar. Başka dosya sistemlerinin çoğu saklamaz, arşiv formatlarının çoğu da saklamaz.
Mac bir dosyayı, attribute'ları satır içinde taşıyamayan bir formata yazarken onları çöpe atmaz. Sistemin içindeki kopyalama mekanizmasıyla dosyayı ikiye böler: veri, orijinal adıyla girer; attribute'lar ise aynı adın başına ._ eklenmiş ikinci bir dosyaya girer. O ikinci dosya AppleDouble formatındadır, resource fork döneminden kalma bir kaptır ve başka her sistem açısından son derece sıradan bir dosyadır.
Bu dosyanın içinde ne olduğu da merak edilir. Başta bir imza ve sürüm numarası, ardından girdiler durur: Finder bilgisi, varsa resource fork ve çoğu zaman dosyanın orijinal adı. Yani sır değildir, ama sizin yazmadığınız ve hiç gözden geçirmediğiniz ikili bir içeriktir. Bir güvenlik taraması onu tanımadığı için bilinmeyen dosya türü diye işaretler ve siz hafta sonunuzu, dört kilobaytlık bir meta veri kabının ne olduğunu anlatarak geçirirsiniz.
Sorunu saklayan şey asimetridir:
- Arşivi Mac'te açmak çifti sessizce birleştirir ve tek dosya gösterir. Kendi makinenizde gidip gelen arşiv temiz görünür.
- Linux'ta açmak hiçbir şeyi birleştirmez. İki dosya da diske düşer ve
._olan orada kalır. - Finder nokta ile başlayan dosyaları gizler, yani build dizinine kendi gözünüzle bakmak da onları göstermez.
Sunucuya düştükten sonra web sunucusu onlara uzantısına göre, diğer her dosya gibi davranır. ._index.html isteğine text/html content type'ı ve ikili meta veriden oluşan bir gövdeyle yanıt verilebilir. İçinde nadiren gizli bir şey olur, ama bu hiç gözden geçirmediğiniz, yayına alınmış bir dosyadır. Bazı araçlar ise onu sunmaktan daha kötüsünü yapar: bir içerik dizinini gezen statik site üreticisi ._yazi.md dosyasını memnuniyetle bir yazı olarak basar, izleme kontrolündeki dosya sayısı da anlamını yitirir.
.DS_Store aynı yoldan daha kötü bir yükle gelir, çünkü bulunduğu dizini tarif eder: hiç deploy edilmemiş olanlar dahil dosya adları ve klasörün nasıl düzenlendiği. zip komutunun da aynı davranışın kendi sürümü vardır, aynı ._ kayıtlarıyla dolu bir __MACOSX/ dizini yazar; Finder'ın Sıkıştır komutu ise bunu her seferinde üretir.
Nasıl görülür
Arşiv makineden çıkmadan önce içine bakın:
tar -tzf site.tgz | grep -c '/\._'
# 412
tar -tzf site.tgz | grep '/\._' | head -3
# ./assets/._logo.svg
# ./assets/._app.css
# ./._index.htmlSonra nereden geldiklerini bulun. Uzun listelemedeki @ işareti attribute taşıyan dosyayı işaretler, xattr da içeriğini basar:
ls -l@ assets/logo.svg
# -rw-r--r--@ 1 user staff 18244 12 Nov 09:41 assets/logo.svg
# com.apple.quarantine 57
xattr -l assets/logo.svg
# com.apple.quarantine: 0083;690d4f1a;Safari;Karantina attribute'u her zamanki suçludur. Tarayıcıdan indirilip projeye sürüklenen bir ikon onu ömrünün sonuna kadar taşır ve o dizinden üretilen her arşiv ona bir ikiz ekler.
Sunucuda ise gezinmek yerine sayın ve karşılaştırın:
find /var/www/site -name '._*' | wc -l
# 412
find /var/www/site -name '.DS_Store' -o -name '__MACOSX' | head
# /var/www/site/assets/.DS_Store
curl -sI https://ornek.com/._index.html | head -2
# HTTP/2 200
# content-type: text/htmlSon komuttaki 200 ciddiye alınacak kısımdır, çünkü dosyanın sadece orada olmadığını, halka açık olduğunu söyler.
Sayıların oranı da bilgi verir. Sunucudaki dosya sayısı build'dekinin yaklaşık iki katıysa her dosyanın bir ikizi var demektir. Sadece birkaç dosya fazlaysa elinizdeki muhtemelen birkaç .DS_Store ya da eski sürümlerden kalan artıklardır. İkisi farklı sebeplerdir ve farklı temizlik ister, o yüzden sayıyı görmeden komut yazmayın.
Çözüm
Build çıktısındaki attribute'ları temizleyin, meta veri dosyalarını hariç tutun ve arşivleyiciye hiçbir şeyi bölmemesini söyleyin:
xattr -cr build
find build \( -name '.DS_Store' -o -name '._*' \) -delete
COPYFILE_DISABLE=1 tar --no-xattrs -czf site.tgz -C build .xattr -cr attribute'ları dizin ağacında temizler, COPYFILE_DISABLE=1 sistem arşivleyicisinin bölme davranışını kapatmak için okuduğu environment değişkenidir, --no-xattrs de aynı şeyi bayrak olarak söyler. İkisini birden kullanın, çünkü hangisinin geçerli olacağı yolda önce hangi tar'ın bulunduğuna bağlıdır. Temizleme adımını yalnızca build çıktısına uygulayın, depoya asla: karantina bayrağı taşıması gereken dosyaların bayrağını silmiş olursunuz.
Bunu bir kez yapıp unutmamak için temizleme adımını arşivi üreten komutun içine koyun, elle çalıştırılan ayrı bir adım olarak değil. Ayrı duran bir temizlik adımı, acelesi olan biri arşivi tek satırla kendi üretene kadar çalışır.
Deploy zip kullanıyorsa karşılığı, attribute'suz ve resource fork'suz bir kopya istemektir:
ditto -c -k --norsrc --noextattr --sequesterRsrc build site.zipDaha iyi çözüm arşiv göndermeyi tamamen bırakmaktır. Aynalayan bir sync kopyalama mekanizmasını hiç çağırmaz, çünkü siz istemedikçe extended attribute aktarmaz; üstelik sunucuda zaten duran artıkları temizleyecek tek şey onun silme aşamasıdır. Arşivle yapılan deploy dosyaların üzerine yazar ve geri kalan her şeyi olduğu yerde bırakır, bu yüzden artıklar sürüm sürüm birikir:
rsync -rlptD --delete \
--exclude '.DS_Store' --exclude '._*' --exclude '__MACOSX/' \
build/ deploy@host:/var/www/site/O silme aşaması, korumak istediğiniz şeyleri de silecek kadar güçlüdür, o yüzden yanına bir koruma listesi koyun. Sebebi aynalayan bir deploy'un sertifika yenilemeyi kırmasıyla aynı.
Sonra kapıyı web sunucusunda kapatın ki bir olay anında elle kopyalanan dosya hiçbir zaman sunulmasın:
location ~ /\._|/\.DS_Store$|/__MACOSX/ {
access_log off;
return 404;
}Bu kural, nokta ile başlayan yollara dair genel kuralın yanında durur; o yollar zaten internetten erişilemez olmalıdır. Tek bir dizüstü değil bir ekip düşünüldüğünde asıl yapısal çözüm, çıktıyı Linux runner'da üretmektir: extended attribute'u olmayan bir makine AppleDouble dosyasını en baştan üretemez.
Çalıştığını nasıl doğrularsınız
İki taraftaki dosyaları sayın ve sayılar tutmadığında deploy'u düşürün:
YEREL=$(find build -type f | wc -l | tr -d ' ')
UZAK=$(ssh deploy@host "find /var/www/site -type f | wc -l" | tr -d ' ')
[ "$YEREL" = "$UZAK" ] || { echo "dosya sayisi tutmuyor: yerel $YEREL, uzak $UZAK" >&2; exit 1; }
echo "$YEREL dosya yayina alindi"
# 412 dosya yayina alindiArdından o kalıbın gittiğini ve gitmiş kaldığını doğrulayın:
ssh deploy@host 'find /var/www/site \( -name "._*" -o -name ".DS_Store" \) | wc -l'
# 0
curl -s -o /dev/null -w '%{http_code}\n' https://ornek.com/._index.html
# 404Bu kontrolü deploy scriptinin son adımı yapın ve tutmadığında sıfırdan farklı bir kodla çıkın. Sayım karşılaştırması ancak deploy'u durdurduğunda işe yarar; log'a düşen bir uyarı satırı, hiç yazılmamış bir kontrolle aynı sonucu verir.
Baytların tam olarak önemli olduğu işlerde sayım zayıf, manifest güçlü bir kontroldür: build sırasında checksum üretin, manifest'i de gönderin ve sunucuda doğrulayın. Sayım size bir dosyanın eksik ya da fazla olduğunu söyler, manifest bir dosyanın yanlış olduğunu söyler.
Nelere dikkat etmeli
- Finder'ın Sıkıştır komutu meta veri dizinini her zaman üretir. Sürüm arşivi bir klasöre sağ tıklayarak değil, okuyabildiğiniz bir komutla hazırlanmalı.
- Kaynak ağacında
xattr -crçalıştırmak, hak eden dosyaların karantina bayrağını da siler ve uğradığı her dosyanın değişim zamanına dokunur. Onu build çıktısında çalıştırın, depoda değil. - Arşivi bir Mac'te açıp tekrar paketlemek sorunu büyütür, çünkü açma sırasında birleşen çift, yeni arşivde yeniden bölünür. Arşivi ürettiğiniz yerde bırakın, yolda elden geçirmeyin.
- Artıklar ancak biri onları sildiğinde kaybolur. Arşivle yapılan deploy, elle kopyalama veya silme aşaması olmayan bir sync, hepsini sonsuza kadar yerinde bırakır.
- Dosyayı yukarı çıkarırken paylaşılan bir temp yolundan geçirmek bunun üstüne ikinci bir sorun ekler; paylaşımlı sunucuda genel adlı bir dosyanın tuzağı budur.
._dosyaları isim sıralamasında gerçek dosyalardan önce gelir. Bir dizindeki ilk dosyayı seçen her script, siz fark etmeden meta veri kabını seçer ve verdiği hata içeriğin bozuk olduğunu söyler.- Dosya sayımı kontrolü, deploy meşru biçimde dosya sildiği ve karşılaştırma eski bir build'e yapıldığı gün yanlış alarm verir. Az önce ürettiğiniz build dizinini karşılaştırın, bir öncekini değil.
Buradan kalması gereken alışkanlık hatadan küçüktür. Deploy'dan sonra ne gittiğini sayın ve gönderdiğinizle karşılaştırın, çünkü arşiv kapalı bir kutudur ve onunla ilgili dürüst tek rapor karşı taraftan gelir. Zincirdeki her araç sizin yazmadığınız bir şey ekleme hakkına sahiptir: attribute'lar, meta veri dosyaları, dizin tarifleri, sıkıştırma artıkları. 412 dosya üreten bir build ile 412 dosya tutan bir sunucu, bir saniyede kontrol edebileceğiniz bir cümledir ve hiçbir test paketinin aramadığı koca bir sürpriz ailesini yakalar.
Sorular ve cevaplar
- Sunucumdaki ._ dosyaları ne?
- Bunlar AppleDouble dosyalarıdır: arşiv formatı satır içinde taşıyamadığı için ayrı bir dosyaya çıkarılmış extended attribute'lar. Mac bir tar veya zip yazarken bunları üretir, Mac olmayan her sistem ise onları tuhaf adlı sıradan dosyalar olarak görür. İçlerinde içeriğiniz değil Finder bilgisi ve karantina bayrağı gibi meta veriler bulunur, ama yine de istemeden yayına aldığınız dosyalardır.
- Neden sunucuda görünüyorlar da kendi makinemde görünmüyorlar?
- Çünkü macOS arşivi açarken çifti yeniden birleştirir ve Finder bu dosyaları gizler; aynı makinede gidip gelen bir arşiv temiz görünür. Linux'ta ise çift hiç birleştirilmez, iki dosya da diskte kalır. Bu asimetri, sorunun neden hep sunucuda keşfedilip test sırasında hiç görülmediğini açıklar.
- .DS_Store dosyası ._ dosyasından daha mı kötü?
- Bir yabancıya söylediği şey açısından evet. Bulunduğu dizinin içeriğini listeler: deploy etmediğiniz dosyaların adlarını ve görünüm ayarlarını. İkisi de web sunucusunda engellenmeli ve deploy'dan çıkarılmalı, ama önce düzeltilmeye değer olan dizin içeriğinin sızmasıdır.
- Linux runner'da build almak bunu kalıcı olarak çözer mi?
- Runner'ın ürettiği her şey için sorunun kaynağını ortadan kaldırır, çünkü ayrıştırılacak extended attribute yoktur. Sunucuda hâlihazırda duranları temizlemez ve bir olay anında birinin dizüstünden elle kopyaladığı dosyaya da yardımcı olmaz. Web sunucusundaki engelleme kuralını ve deploy'daki sayım kontrolünü yine de bırakın.