Dopo aver caricato un sito due volte, smetti di voler trascinare cartelle. Quello che vuoi è: fai in modo che il server assomigli a questa cartella locale, carica solo ciò che è cambiato, e dimmi cosa stai per fare prima di farlo.
Questa è la sincronizzazione delle cartelle, e vale la pena sapere come decide cosa significa “cambiato” — perché quando una sincronizzazione va male, va male in silenzio.
Come funziona davvero il confronto
Una sincronizzazione confronta due alberi di cartelle e classifica ogni file: solo locale, solo remoto, più recente da un lato, oppure identico. Per farlo le serve, per ogni file, un nome, una dimensione e una data di modifica.
La dimensione è affidabile. Le date non lo sono, ed è questa la parte che crea problemi.
Una classica risposta LIST di FTP è formattata per gli umani, con una risoluzione di un minuto — e per i file più vecchi di sei mesi mostra tipicamente un anno al posto dell’ora, quindi la risoluzione scende a un giorno. Il tuo filesystem locale registra le date di modifica con precisione inferiore al secondo. Confrontarle direttamente produce differenze fasulle a ogni singola esecuzione.
Un’implementazione corretta confronta con una tolleranza, e quando le date rientrano in quella tolleranza ripiega sulla dimensione. Upstream fa esattamente questo: le mtime vengono confrontate con una tolleranza che assorbe la granularità al minuto di LIST, e i file che pareggiano sulla data vengono poi confrontati sulla dimensione.
I server che supportano il comando MLSD (RFC 3659) restituiscono una data leggibile dalla macchina, il che toglie di mezzo ogni congettura — un altro motivo per preferire un server moderno, ed è lo stesso meccanismo dietro ai file nascosti che compaiono correttamente. SFTP ha metadati strutturati ovunque e non soffre di nulla di tutto questo.
I fusi orari, l’altra trappola delle date
I server FTP riportano gli orari nel proprio fuso, e l’RFC 959 non ha mai detto quale dovesse essere. Un server in UTC e un Mac in ora legale italiana non andranno d’accordo per due ore, che è più di qualsiasi tolleranza sensata — così una sincronizzazione ingenua decide che tutti i file sono diversi, e ricarica l’intero sito.
Se la tua prima sincronizzazione vuole trasferire tutto, di solito il motivo è questo. Le date MLSD sono definite in UTC, ed è la soluzione pulita.
La modalità mirror cancella
Le due modalità hanno conseguenze molto diverse:
Carica/aggiorna copia sul server i file nuovi e quelli cambiati, e lascia stare tutto il resto. È sicura ed è quello che ti serve la maggior parte delle volte. L’unico difetto è che i file cancellati in locale restano sul server per sempre.
Mirror rende la destinazione identica all’origine, il che significa cancellare tutto ciò che sul server non esiste in locale. È quello che vuoi per un deploy pulito — ed è anche il modo in cui si cancella una cartella uploads/ piena di contenuti degli utenti che era sempre esistita solo sul server.
Prima di ogni mirror, poniti una domanda: il server contiene qualcosa che in locale non c’è? Caricamenti degli utenti, cache generate, file di log, una cartella .well-known messa lì dagli strumenti dei certificati. Se sì, escludila oppure non fare il mirror.
È a questo che serve il dry run. La sincronizzazione di Upstream mostra un’anteprima completa di ogni azione prevista — carica, scarica, cancella, salta — prima che succeda qualsiasi cosa. Leggi l’elenco delle cancellazioni. Costa dieci secondi ed è la differenza tra un deploy e un incidente.
Escludere file con .syncignore
Le esclusioni stanno bene in un file accanto al progetto, non in una finestra di impostazioni che dimenticherai. Upstream legge un .syncignore dalla radice locale, con una sintassi in stile gitignore:
# cartelle nascoste (.git, .next, .cache, .idea …)
.*/
# spazzatura di sistema
.DS_Store
Thumbs.db
# dipendenze — non si pubblicano mai
node_modules/
__pycache__/
# file temporanei
*.swp
*~
La sintassi supportata è un sottoinsieme deliberato: * corrisponde all’interno di un singolo segmento di percorso, ** a qualsiasi profondità, ? a un carattere, una / finale limita la regola alle cartelle e una / iniziale la ancora alla radice. Un nome nudo corrisponde a qualsiasi profondità, esattamente come in gitignore. Non c’è la negazione con ! — è stata lasciata fuori di proposito, perché è con la negazione che i file di esclusione diventano difficili da prevedere.
Due comportamenti da conoscere: le regole si applicano a entrambi i lati, quindi un percorso escluso non viene né caricato né cancellato da un mirror. E se fornisci un .syncignore, questo sostituisce i valori predefiniti invece di aggiungersi — quindi se ne scrivi uno tuo, ricordati di rimettere .DS_Store e compagnia.
Nota anche che i valori predefiniti escludono le cartelle nascoste ma non i file nascosti: .htaccess e .env sono contenuto vero su un server web, e saltarli in silenzio sarebbe peggio dell’alternativa.
I conflitti
C’è un conflitto quando entrambi i lati sono cambiati dall’ultima volta che erano d’accordo. Non esiste una risposta automatica giusta in generale — il comportamento corretto è fermarsi e chiedere, file per file, rendendo evidente quale versione è quale. Qualsiasi strumento che risolva i conflitti in silenzio sta prendendo una decisione che probabilmente tu non avresti preso.
Passo per passo
- Connettiti e porta i due pannelli sulle cartelle che vuoi allineare — l’output della build da un lato, la web root dall’altro.
- Aggiungi un
.syncignorealla radice locale prima della prima esecuzione, coprendo.git/,node_modules/,.DS_Storee qualunque cosa contenga credenziali. - Scegli la direzione con cognizione — carica/aggiorna per un deploy normale, mirror solo quando il server deve essere una copia esatta.
- Esegui il dry run e leggilo, con particolare attenzione all’elenco delle cancellazioni.
- Applica, e lascia lavorare la coda — i trasferimenti procedono in parallelo e riprendono se la connessione cade.
- Controlla il risultato a campione nel pannello remoto, usando i colori del confronto per confermare che i due lati ora coincidono.
Domande frequenti
Perché la sincronizzazione vuole ricaricare tutti i file?
Quasi sempre è un problema di date: o il server riporta gli orari in un fuso diverso, oppure la risoluzione del suo LIST è troppo grossolana per essere confrontata con le tue date locali al di sotto del secondo. Un server che supporta MLSD, oppure SFTP, elimina l’ambiguità.
La sincronizzazione cancella file sul mio server?
Solo in modalità mirror, che esiste proprio per rendere la destinazione identica all’origine. La modalità carica/aggiorna non cancella mai. Leggi sempre l’anteprima dry run prima di applicare un mirror.
La sincronizzazione vede i file nascosti come .htaccess?
Dipende dal protocollo. Su SFTP sempre. Su FTP dipende dal fatto che il client li chieda al server nel modo giusto — vedi i file nascosti su FTP. Se non lo fa, i dotfile non vengono mai confrontati, in silenzio.
Posso sincronizzare solo una parte di una cartella?
Sì — sincronizza la sottocartella specifica, oppure usa un .syncignore per escludere ciò che non ti interessa. Le esclusioni valgono su entrambi i lati.
La sincronizzazione FTP è affidabile quanto rsync?
No, ed è giusto dirlo. rsync confronta il contenuto dei file con checksum a finestra scorrevole e trasferisce solo i blocchi cambiati. FTP non ha nulla di equivalente, quindi qualsiasi sincronizzazione basata su FTP confronta i metadati e trasferisce file interi. Se hai accesso SSH e puoi usare rsync, tecnicamente è superiore; una sincronizzazione grafica vince sulla comodità e sui server dove hai solo accesso al trasferimento file.
Cosa succede se la connessione cade a metà sincronizzazione?
I trasferimenti completati restano completati. La coda di Upstream riprova con attesa progressiva e riprende i file interrotti da dove si erano fermati, così rieseguire la sincronizzazione dopo si occupa solo di ciò che manca davvero.