{"id":4,"date":"2008-02-24T17:50:18","date_gmt":"2008-02-24T16:50:18","guid":{"rendered":"http:\/\/www.zarrelli.org\/blog\/index.php\/2008\/02\/24\/backup-in-rete-pratico-e-veloce-con-ftp-2\/"},"modified":"2008-02-24T17:51:53","modified_gmt":"2008-02-24T16:51:53","slug":"backup-in-rete-pratico-e-veloce-con-ftp-2","status":"publish","type":"post","link":"https:\/\/www.zarrelli.org\/blog\/backup-in-rete-pratico-e-veloce-con-ftp-2\/","title":{"rendered":"Backup in rete pratico e veloce con FTP &#8211; 2"},"content":{"rendered":"<p>Dopo quasi, ahem, un anno di stasi, vediamo di chiudere l&#8217;agomento inziato con il primo post <a href=\"\/blog\/index.php\/2007\/04\/21\/backup-in-rete-pratico-e-veloce-con-ftp-1\/\" target=\"_blank\">Backup in rete pratico e veloce con FTP -1<\/a>, passando alla parte pi\u00f9 &#8220;operativa&#8221;.<\/p>\n<p><img src='\/blog\/wp-content\/uploads\/2008\/02\/backup_tape.jpg' alt='backup_tape.jpg' align=\"right\"\/><\/p>\n<p>Vediamo, qui di seguito, un semplice shell script che si occupa di creare un backup all&#8217;interno di un file tgz, che avr\u00e0 come prefisso la data del giorno di archiviazione. Creato il file, provveder\u00e0 a trasferirlo via ftp sulla macchina d&#8217;appoggio, e infine canceller\u00e0 l&#8217;archivio il cui suffisso sar\u00e0 costituito dalla data odierna &#8211; 15 giorni.<\/p>\n<p>Prima di procedere, per\u00f2, un avvertimento: questo post \u00e8 decisamente lungo e, per qualche verso, anche noioso. <\/p>\n<p>Non dite che non vi avevo avvisati \ud83d\ude42<\/p>\n<p><!--more--><\/p>\n<p><strong>Rendere uno script portabile e flessibile, l&#8217;intestazione<\/strong><\/p>\n<p>A cosa serve uno script? Beh, semplicemente, banalizzando, a rendere semplici e veloci operazioni ripetitive, a sollevare l&#8217;utente da una routine che non richiede una particolare attenzione.<\/p>\n<p>Ci\u00f2 vale anche per lo script stesso. Ovvero, non deve solo rendere semplici le altre operazioni, ma deve semplificare al tempo stesso la sua gestione. Uno programma, per quanto piccolo, che dia luogo a malfunzionamenti appena lo si tocchi o sposti, oppure che sia difficile da modificare a seconda delle esigenze contingenti, non assolve pienamente agli scopi cui canonicamente \u00e8 deputato.<\/p>\n<p>Allora, come rendere semplice la gestione di uno script?<\/p>\n<p>In prima battuta, possiamo pensare a due potenziali scenari problematici:<\/p>\n<ol>\n<li>\n Lo script, che \u00e8 sempre andato bene, deve essere modificato, magari, nel nostro caso, per includere nuove directory o per escluderne altre, per modificare la denominazione dei file di archivio, oppure per modificare l&#8217;arco di ritenzione dei backup sul server FTP remoto su cui appoggeremo i file compressi;<\/p>\n<\/li>\n<li>\nLo script, che \u00e8 sempre andato bene, deve essere spostato su una differente distribuzione Linux o su una variante Unix, nella quale le utility di sistema sono poste in posizioni differenti nell&#8217;albero delle directory.\n<\/li>\n<\/ol>\n<p>Nel primo caso, saremo costretti a scorrere tutto il file alla ricerca di ogni evenienza della porzione da modificare, mentre i pi\u00f9 esperti dovranno evocare arcane stringhe composte da sed, awk e cut.<\/p>\n<p>Nel secondo caso, bisogner\u00e0 scorrere tutto il programma, cercare il riferimento al percorso delle utility impiegate, confrontarlo con il percorso delle stesse nel nuovo sistema e apportare le modifiche del caso.<\/p>\n<p>Operazioni scomode entrambe, non vi pare? Scomode e portano via parecchio tempo, prone come sono agli errori.<\/p>\n<p>Meglio ricorrere a un espediente del tutto lecito e semplice da utilizzare, ovvero creare<br \/>\nun&#8217;intestazione allo script, ovvero una parte iniziale completamente parametrizzata, grazie alla quale ogni elemento modificabile sar\u00e0 racchiuso in una variabile, che verr\u00e0 poi richiamata al momento opportuno, nella parte elaborativa del programma. Per cambiare quindi un valore, baster\u00e0 modificare il contenuto della variabile cui \u00e8 associato e quindi la modifica verr\u00e0 riflessa in ogni punto dello script in cui la variabile stessa verr\u00e0 richiamata. Semplice, efficace, pulito.<\/p>\n<p>Il secondo espediente cui ricorreremo \u00e8 definito comunemente &#8220;sostituzione di comando&#8221;, ovvero si istanzia una variabile non tanto con un valore predefinito, quanto con il risultato di un comando da eseguire. Vedremo in seguito pi\u00f9 dettagliatamente di cosa si tratta, baster\u00e0 ora un esempio schematico.<\/p>\n<p>Volendo richiamare il comando <code>tar<\/code>, lo si pu\u00f2 indicare per esteso, per esempio <\/p>\n<p><code><br \/>\n\/bin\/tar<br \/>\n<\/code><\/p>\n<p>In questo caso, non ci sono particolari note da rilevare. Lo script richiamer\u00e0 il programma semplicemente seguendo il percorso indicato.<\/p>\n<p>Piccolo, grande problema: se lo script viene spostato su un sistema in cui l&#8217;utility tar si trova in un percorso differente, il programma non eseguir\u00e0 l&#8217;utility indicata, dato che il percorso non porter\u00e0 a nulla di utilizzabile. Se, per\u00f2, invece di utilizzare una indicazione statica si chiede a un&#8217;utility di sistema di trovare tutte le altre, il punto di fallimento viene ridotto all&#8217;unico primo programma richiamato &#8220;alla cieca&#8221;. Facciamo un esempio<\/p>\n<p><code><br \/>\nTAR=$(which tar)<br \/>\n<\/code><code><\/p>\n<p>In questo caso, abbiamo chiesto all'utility <\/code><code>which<\/code> di trovare il percorso dell&#8217;utility <code>tar<\/code> e abbiamo assegnato il percorso stesso alla variabile <code>TAR<\/code>. In questo modo, dovremo fare attenzione unicamente al fatto che which sia richiamabile direttamente dall&#8217;ambiente legato all&#8217;account utilizzato per eseguire lo script (ovvero che il suo percorso sia compreso nella variabile d&#8217;ambiente <code>PATH<\/code>). Accertato ci\u00f2, baster\u00f2 richiamare il contenuto della variabile <code>TAR<\/code> per eseguire il comando rintracciabile al percorso assegnato.<\/p>\n<p>Torneremo sull&#8217;argomento pi\u00f9 avanti, non vi preoccupate.<\/p>\n<p><strong>Lo script di backup<\/strong><\/p>\n<p>Un po&#8217; di attenzione, non \u00e8 difficile. Prima vediamo lo script al completo, poi esamineremo le varie parti interessanti.<\/p>\n<p><code><br \/>\n#!\/bin\/sh<\/p>\n<p>RM=$(which rm)<br \/>\nTAR=$(which tar)<br \/>\nTAR_OPTS=\"cvzf\"<br \/>\nTAR_REMOVE=\"--remove-files -cvzf\"<br \/>\nFTP=$(which ftp)<br \/>\nFIND=$(which find)<br \/>\nECHO=$(which echo)<br \/>\nDATA=$(which date)<br \/>\nDATE=$($DATA +%F)<br \/>\nRM_DATE=$(date --date='15 days ago' +'%Y-%m-%d')<\/p>\n<p>ARCHIVE=\"$DATE-nome-del-server.backup.tgz\"<br \/>\nARCHIVE_MYSQL=\"$DATE-mysql.nome-del-server.com.backup.tgz\"<br \/>\nBACKUP_SPOOL=\"\/var\/spool\/backup\"<br \/>\nBACKUP_DIR=\"\/usr\/local\/apache \/usr\/local\/mysql\/ \/var\/mysql \/var\/lib\/awstats \/etc\/ $BACKUP_SPOOL\/$MYSQL_BACKUP_FILE\"<br \/>\nBACKUP_EXCLUDE_DIR=\"\/usr\/local\/apache\/tmp\/ \/usr\/local\/apache\/htdocs\/data\/videos\/ \/var\/spool\/backup \/etc\/passwd \/etc\/shadow\"<\/p>\n<p>BACKUP_MACRO=\"backup\"<\/p>\n<p>MYSQL_DUMP=$(which mysqldump)<\/p>\n<p>MYSQL_OPTS=\"--socket=\/usr\/local\/apache\/mysql.sock\/mysql.sock --all-databases --user=nome_utente --password=password_di_accesso\"<br \/>\nMYSQL_BACKUP_FILE=\"$DATE-mysql.nome-del-server.com.backup.sql\"<br \/>\nSERVER=\"indirizzo-del-server-di-appoggio\"<\/p>\n<p>$MYSQL_DUMP $MYSQL_OPTS > $BACKUP_SPOOL\/$MYSQL_BACKUP_FILE<\/p>\n<p>$TAR $TAR_REMOVE $BACKUP_SPOOL\/$ARCHIVE_MYSQL $BACKUP_SPOOL\/$MYSQL_BACKUP_FILE<\/p>\n<p>$FIND $BACKUP_EXCLUDE_DIR > $BACKUP_SPOOL\/IGNORE.LST<\/p>\n<p>$TAR $TAR_OPTS $BACKUP_SPOOL\/$ARCHIVE -X  $BACKUP_SPOOL\/IGNORE.LST $BACKUP_DIR<\/p>\n<p>$ECHO \"\\$ $BACKUP_MACRO $RM_DATE\" | $FTP $SERVER<\/p>\n<p>$RM $BACKUP_SPOOL\/$ARCHIVE $BACKUP_SPOOL\/$ARCHIVE_MYSQL<br \/>\n<\/code><code><\/p>\n<p>Complicato? No, sembra esserlo ma \u00e8 pi\u00f9 semplice di quanto non si immagini.<\/p>\n<p>Diamo un'occhiata pi\u00f9 da vicino ad alcune delle prime righe, esclusa la sha-bang, ovvero a quelle che ci consentiranno di creare i nomi dei file di backup, in modo che racchiudano la data in cui gli archivi sono stati creati:<\/p>\n<p><\/code><code><br \/>\nDATA=$(which date)<br \/>\nDATE=$($DATA +%F)<br \/>\nRM_DATE=$(date --date='15 days ago' +'%Y-%m-%d')<br \/>\nARCHIVE=\"$DATE-nome-del-server.backup.tgz\"<br \/>\nARCHIVE_MYSQL=\"$DATE-mysql.nome-del-server.com.backup.tgz\"<br \/>\n<\/code><\/p>\n<p>Come primo accorgimento, eseguiamo una <strong>sostituzione di comando<\/strong>. In pratica, la variabile <code>DATA<\/code> viene istanziata con l&#8217;output del comando <code>which date<\/code>.<\/p>\n<p>Cosa significa tutto ci\u00f2? Facciamo ricorso alla shell e vediamo cosa succede. Iniziamo con l&#8217;eseguire il comando <code>which date<\/code>:<\/p>\n<p><code><br \/>\nzarrelli@moveaway:~$ which date<br \/>\n\/bin\/date<br \/>\nzarrelli@moveaway:~$<br \/>\n<\/code><code><\/p>\n<p>Semplicemente, which restituisce il percorso al comando che gli si fornisce come argomento, sempre che il comando sia all'interno di una delle directory indicate nella variabile d'ambiente <\/code><code>PATH<\/code> correlata all&#8217;utente che ha lanciato il comando <code>which<\/code>.<\/p>\n<p>Controprova:<\/p>\n<p><code><br \/>\nzarrelli@moveaway:~$ echo $PATH<br \/>\n\/usr\/local\/sbin:\/usr\/local\/bin:\/usr\/sbin:\/usr\/bin:\/sbin:\/bin:\/usr\/games<br \/>\nzarrelli@moveaway:~$<br \/>\n<\/code><\/p>\n<p>oppure:<\/p>\n<p><code><br \/>\nzarrelli@moveaway:~$ env | grep PATH<br \/>\nPATH=\/usr\/local\/sbin:\/usr\/local\/bin:\/usr\/sbin:\/usr\/bin:\/sbin:\/bin:\/usr\/games<br \/>\nzarrelli@moveaway:~$<br \/>\n<\/code><\/p>\n<p>Perch\u00e9 utilizzare una sostituzione di comando? Perch\u00e9 in questo modo avremo degli script portabili. Ovvero, posto che si utilizzino delle utility di sistema, anche se queste, a seconda delle distribuzioni utilizzate, o delle varianti Unix adottate, si trovano in un percorsi differenti da quelli in cui risiedono all&#8217;interno del sistema utilizzato per creare lo script, la sostituzione di variabile rende sempre possibile invocarle, senza preoccuparsi di dove siano localizzate nel file system.<\/p>\n<p>Fatto salvo che, ovviamente, l&#8217;utility puntata dalla sostituzione di variabile sia in un percorso compreso all&#8217;interno della variabile <code>PATH<\/code>, ma ci\u00f2 \u00e8 verificato nel momento in cui si utilizzino utility di sistema e ambienti variabili di sistema predefinite.<\/p>\n<p>Quindi, la variabile <code>DATA<\/code> conterr\u00e0 il percorso all&#8217;utility date, ovunque essa si trovi, all&#8217;interno delle directory comprese nella variabile <code>PATH<\/code>.<\/p>\n<p>Ora che abbiamo la possibilit\u00e0 di richiamare facilmente il comando <code>date<\/code>, istanziamo una variabile <code>DATE<\/code> con il valore restituito ($) dall&#8217;utility di sistema <code>date<\/code>, la quale a sua volta mostrer\u00e0 la data di sistema, formattandone l&#8217;output nel formato numerico anno-mese-giorno, grazie all&#8217;utilizzo del modificatore  +%F. Tanto per intenderci<\/p>\n<p><code><br \/>\nzarrelli@moveaway:~$ date +%F<br \/>\n2008-02-23<br \/>\nzarrelli@moveaway:~$#<br \/>\n<\/code><\/p>\n<p>potrebbe essere interessante, come potreste trovare pi\u00f9 piacevole un<\/p>\n<p><code><br \/>\nzarrelli@moveaway:~$ date +%A.%d.%B.%Y<br \/>\nsabato.23.febbraio.2008<br \/>\nzarrelli@moveaway:~$<br \/>\n<\/code><\/p>\n<p>Rimane a voi giocare con i modificatori del comando date (man date per consultare il manuale in linea) e trovare la soluzione con la quale vi sentite pi\u00f9 a vostro agio.<\/p>\n<p>Ovviamente, al posto di richiamare direttamente l&#8217;utility <code>date<\/code>, baster\u00e0 recuperare il valore della variabile <code>DATA<\/code> (ovvero il percorso all&#8217;utility <code>date<\/code>) anteponendole il segno di dollaro, e passandole il modificatore. <\/p>\n<p><code><br \/>\nDATE=$($DATA +%F)<br \/>\n<\/code><\/p>\n<p>Quindi, <code>DATE<\/code> avr\u00e0 come valore l&#8217;output del comando \/bin\/date +%F.<\/p>\n<p>La seconda riga ha un significato molto simile. Solamente, in questo caso date \u00e8 forzato a mostrare la data di 15 giorni precedenti alla data corrente (&#8211;date=&#8217;15 days ago&#8217;), formattandone l&#8217;output nel formato numerico anno-mese-giorno. Da notare che i due modificatori +%F e +&#8217;%Y-%m-%d&#8217;, sono equivalenti.<\/p>\n<p>Nelle due righe seguenti, semplicemente diamo forma ai nomi di due file:<\/p>\n<ol>\n<li>\nuno, assegnato alla variabile <code>ARCHIVE<\/code>, verr\u00e0 utilizzato per contenere il backup del file system\n<\/li>\n<li>\nl&#8217;altro, assegnato alla variabile <code>ARCHIVE_MYSQL<\/code>, racchiuder\u00e0 il dump del database MySQL.\n<\/li>\n<\/ol>\n<p>Da notate che il nome di ognuno \u00e8 creato preponendo a una parte fissa il contenuto della variabile <code>DATE<\/code>: in questo modo, ogni giorno verranno creati due file i cui nomi rimanderanno direttamente alla data di creazione, rendendoli facilmente riconoscibili.<\/p>\n<p>Continuiamo con il seguente blocco di variabili<\/p>\n<p><code><br \/>\nBACKUP_SPOOL=\"\/var\/spool\/backup\"<br \/>\nBACKUP_DIR=\"\/usr\/local\/apache \/usr\/local\/mysql\/ \/var\/mysql \/var\/lib\/awstats \/etc\/ $BACKUP_SPOOL\/$MYSQL_BACKUP_FILE\"<br \/>\nBACKUP_EXCLUDE_DIR=\"\/usr\/local\/apache\/tmp\/ \/usr\/local\/apache\/htdocs\/data\/videos\/ \/var\/spool\/backup \/etc\/passwd \/etc\/shadow\"<br \/>\n<\/code><\/p>\n<p>La prima riga indica una variabile nella quale viene conservato il valore del percorso a una directory di spool, ovvero la directory nella quale verranno conservati gli archivi contenenti i backup.<\/p>\n<p>La seconda riga contiene l&#8217;elenco delle directory da sottoporre all&#8217;utility di backup. Da notare che uno dei percorsi non \u00e8 indicato direttamente, ma richiamando i valori delle variabili <code>BACKUP_SPOOL<\/code> e <code>MYSQL_BACKUP_FILE<\/code>, concatenandoli e frapponendo una slash (<code>\/<\/code>). Espandendo le variabili concatenate <\/p>\n<p><code><br \/>\n$BACKUP_SPOOL\/$MYSQL_BACKUP_FILE<br \/>\n<\/code><\/p>\n<p>si avr\u00e0 quindi l&#8217;indirizzo:<\/p>\n<p><code><br \/>\n\/var\/spool\/backup\/$DATE-mysql.nome-del-server.com.backup.sql<br \/>\n<\/code><\/p>\n<p>In pratica, interpretando la riga, si pu\u00f2 facilmente comprendere che nel backup totale di tutti i file si vuole includere espressamente anche il dump giornaliero del database MySQL. Quello e solo quello, anche se ve ne fossero di precedenti nella stessa directory <code>\/var\/spool\/backup<\/code>.<\/p>\n<p>La terza indica di quali directory e file non si vuole avere una copia.<\/p>\n<p>In effetti, \u00e8 inutile avere una copia delle varie tmp, spool il cui contenuto \u00e8 per s\u00e9 temporaneo, di directory &#8220;pesanti&#8221;, magari contenenti video di famiglia che si possono facilmente recuperare a casa, ma che incrementerebbero notevolmente la dimensione file di backup.<\/p>\n<p>Ragionamento un po&#8217; piu&#8217; complesso per passwd e shadow: in un file di backup in chiaro che passa per un protocollo non criptato, meglio non infilare dati sensibili, a meno di crittare direttamente l&#8217;archivio creato, prima di trasferirlo.<\/p>\n<p>Ora, \u00e8 il momento del terzultimo blocco di variabili:<\/p>\n<p><code><br \/>\nBACKUP_MACRO=\"backup\"<br \/>\nRM=$(which rm)<br \/>\nTAR=$(which tar)<br \/>\nTAR_OPTS=\"cvzf\"<br \/>\nTAR_REMOVE=\"--remove-files -cvzf\"<br \/>\nFTP=$(which ftp)<br \/>\nFIND=$(which find)<br \/>\nECHO=$(which echo)<br \/>\n<\/code><\/p>\n<p><code>BACKUP_MACRO<\/code> viene utilizzata per contenere il nome della macro che andremo a definire a livello di client FTP, il che ci consentir\u00e0 di automatizzare il trasferimento dei dati. Non andiamo oltre, per ora, approfondiremo l&#8217;argomento fra poco.<\/p>\n<p>Il resto \u00e8 composto da sostituzioni di comando, cui si affiancano variabili che contengono le opzioni da passare ad alcuni di questi comandi. Attenzione. A volte conviene inglobare le opzioni nella stessa variabile che contiene il comando stesso, come in <code>DATE<\/code>, altre volte, specialmente quando la stessa utility debba essere utilizzata in modi differenti, come <code>TAR<\/code>, \u00e8 pi\u00f9 utile separare le opzioni dall&#8217;eseguibile, per poter comporre pi\u00f9 facilmente differenti righe di comando.<\/p>\n<p>Penultimo blocco di intestazione:<\/p>\n<p><code><br \/>\nMYSQL_DUMP=$(which mysqldump)<br \/>\nMYSQL_OPTS=\"--socket=\/usr\/local\/apache\/mysql.sock\/mysql.sock --all-databases --user=nome_utente --password=password_di_accesso\"<br \/>\nMYSQL_BACKUP_FILE=\"$DATE-mysql.nome-del-server.com.backup.sql\"<br \/>\n<\/code><\/p>\n<p>Beh, a questo punto dovrebbe esservi chiaro cosa si intende fare. Si recupera il percorso all&#8217;utility necessaria a creare il dump dei database contenuti in MySQL, si predispongono le opzioni necessarie per accedere al server db (da notare che in questo caso, essendo il server MySQL in locale, si indica una bella socket a file, tanto per andare pi\u00f9 veloci, altrimenti si pu\u00f2 indicare l&#8217;indirizzo IP corrispondente). Infine si predispone un file in cui riversare il dump del database, intestandolo con la data in cui questo verr\u00e0 creato.<\/p>\n<p>Ultima informazione contenuta nell&#8217;intestazione:<\/p>\n<p><code><br \/>\nSERVER=\"indirizzo-del-server-di-appoggio\"<br \/>\n<\/code><\/p>\n<p>Nulla di particolare: si tratta dell&#8217;hostname, o dell&#8217;indirizzo IP (consiglio quest&#8217;ultimo), del server FTP sul quale appoggeremo i file di backup.<\/p>\n<p><strong>La parte elaborativa dello script<\/strong><\/p>\n<p>Passiamo ora all&#8217;ultima parte dello script, quella elaborativa, quella, insomma, che compie il lavoro sporco.<\/p>\n<p>Avendo creato un&#8217;intestazione che raccoglie tutti i dati essenziali, &#8220;importante&#8221; quindi anche come peso, la parte di processazione sar\u00e0, al contrario, molto leggera, anche se meno espressiva. Leggiamola e commentiamola riga per riga:<\/p>\n<p><code><br \/>\n$MYSQL_DUMP $MYSQL_OPTS > $BACKUP_SPOOL\/$MYSQL_BACKUP_FILE<br \/>\n<\/code><\/p>\n<p>La prima operazione compiuta dal nostro script consister\u00e0 in un backup del database. Richiamando i contenuti delle variabili sulla riga, verr\u00e0 richiamata l&#8217;utility <code>mysqldump<\/code>, cui saranno passate varie opzioni. L&#8217;output del programma, ovvero il dump dei database, verr\u00e0 rediretto (<code>><\/code>) sul file il cui percorso \u00e8 indicato dalla seconda parte della riga di comando, creato dinamicamente accedendo ai valori delle variabili di riferimento, in parte generati dall&#8217;output del comando <code>date<\/code>. In pratica, in <code>\/var\/spool\/backup<\/code> avrete un file che inizia con la data del giorno in cui avete creato il dump e termina con <code>-mysql.nome-del-server.com.backup.sql<\/code>.<\/p>\n<p><code><br \/>\n$TAR $TAR_REMOVE $BACKUP_SPOOL\/$ARCHIVE_MYSQL $BACKUP_SPOOL\/$MYSQL_BACKUP_FILE<br \/>\n<\/code><\/p>\n<p>Nulla di particolare: viene creato un archivio compresso del file contenente il dump dei database. Una volta terminata l&#8217;archiviazione, il file .sql non compresso viene cancellato.<\/p>\n<p><code><br \/>\n$FIND $BACKUP_EXCLUDE_DIR > $BACKUP_SPOOL\/IGNORE.LST<br \/>\n<\/code><\/p>\n<p>Quando si esegue l&#8217;archiviazione di una directory, normalmente l&#8217;utility <code>tar<\/code> seleziona tutti i file in essa contenuti, tutte le sotto directory e tutti i file nelle sotto directory.<\/p>\n<p>Non sempre questo pu\u00f2 essere ci\u00f2 che si vuole.<\/p>\n<p>Per esempio, si vuole eseguire il backup della directory <code>\/etc<\/code>, ma non di <code>\/etc\/passwd<\/code> e <code>\/etc\/shadow<\/code>, oppure di <code>\/home\/utente<\/code> ma non di <code>\/home\/utente\/.wine<\/code>. Magari si vuole avere un backup dell proprio sito <code>\/usr\/local\/apache\/htdocs\/<\/code> ma non della directory <code>\/usr\/local\/apache\/htdocs\/data\/videos\/<\/code> nella quale sono contenuti i videocast ospitati dal vostro sito, che renderebbero inutilmente pesante il file di backup.<\/p>\n<p>Che fare?<\/p>\n<p>Semplice, basta chiedere all&#8217;utility <code>find<\/code> di elencare tutti i percorsi ai file e alle directory che non si vogliono archiviare, per poi salvare il suo output in una black list che verr\u00e0 passata a <code>tar<\/code>, in modo che li ignori. <\/p>\n<p><code><br \/>\n$TAR $TAR_OPTS $BACKUP_SPOOL\/$ARCHIVE -X  $BACKUP_SPOOL\/IGNORE.LST $BACKUP_DIR<br \/>\n<\/code><\/p>\n<p>Ed ecco <code>tar<\/code> all&#8217;opera: archivier\u00e0 tutti i file contenuti nelle directory indicate (e da notare che le sotto directory, cos\u00ec come le directory, sono anch&#8217;esse dei file), ignorando (<code>-X<\/code>) quelli contenuti in <code>IGNORE.LST<\/code>.<\/p>\n<p><code><br \/>\n$ECHO \"\\$ $BACKUP_MACRO $RM_DATE\" | $FTP $SERVER<br \/>\n<\/code><\/p>\n<p>Non rimane che spostare via FTP gli archivi creati. La riga qui sopra vi sembra incomprensibile? Gi\u00e0. Per ora diciamo che l&#8217;utility <code>ftp<\/code> viene richiamata indicandole il server il cui indirizzo \u00e8 contenuto in <code>SERVER<\/code> e passandole una macro che si occuper\u00e0 di caricare i nuovi archivi e, per sicurezza, di ricavare dello spazio, cancellando sul server remoto gli archivi pi\u00f9 vecchi della data indicata in <code>RM_DATE<\/code>.<\/p>\n<p>Vedremo tra poco pi\u00f9 in dettaglio come si costruisce una macro FTP. Per ora, basti sapere che entra in gioco il file <code>.netrc<\/code>, contenuto nella directory home dell&#8217;utente con il quale viene lanciato lo script, il quale consente di passare all&#8217;utility non solo le credenziali di login al server FTP ma anche una serie di comandi necessari ad automatizzare le procedure di trasferimento. <\/p>\n<p><code><br \/>\n$RM $BACKUP_SPOOL\/$ARCHIVE $BACKUP_SPOOL\/$ARCHIVE_MYSQL<br \/>\n<\/code><code><\/p>\n<p>Siamo arrivati alla fine dello script. Facciamo un po' di pulizie, cancellando dalla spool gli archivi che abbiamo appena spostato sul server remoto.<\/p>\n<p>Terminato lo script principale, non rimane che scrivere qualche riga ancora, per automatizzare le operazioni di trasferimento: \u00e8 tempo di affrontare le macro FTP.  <\/p>\n<p><strong>La macro FTP<\/strong><\/p>\n<p>Il \"gioco\" dell'FTP ruota tutto attorno a un file, <\/code><code>.netrc<\/code>, posto nella directory dell&#8217;utente utilizzato per eseguire lo script.<\/p>\n<p>Come al solito, vediamone prima l&#8217;intera sequenza di comandi, per poi commentarla passo passo:<\/p>\n<p><code><br \/>\nmachine localhost<br \/>\n        login caricamento<br \/>\n        password caricapassword<\/p>\n<p>macdef  backup<br \/>\n        binary<br \/>\n        lcd \/var\/spool\/backup<br \/>\n        cd archivio_backup<br \/>\n        mput *.tgz<br \/>\n        delete $1-nome-del-server.backup.tgz<br \/>\n        delete $1-mysql.nome-del-server.backup.tgz<br \/>\n        quit<\/p>\n<p><\/code><\/p>\n<p>Iniziamo con la prima riga:<\/p>\n<p><code><br \/>\nmachine localhost<br \/>\n<\/code><\/p>\n<p>Possiamo definire questo come il marcatore d&#8217;inizio di un contenitore di istruzioni. Quando viene lanciato, FTP cerca nel file <code>.netrc<\/code>, l&#8217;istruzione <code>machine<\/code> e compara il valore che la segue con l&#8217;argomento che le \u00e8 stato fornito, sulla riga di comando, come indirizzo del server remoto cui collegarsi. Se l&#8217;uguaglianza \u00e8 riscontrata, FTP processer\u00e0 tutte le rimanenti istruzioni, fino a fine file o alla seguente istruzione <code>machine<\/code>. <\/p>\n<p>Detto in soldoni, <code>machine<\/code> deve riportare lo stesso indirizzo contenuto nella variabile <code>SERVER <\/code>. Volendo, si potrebbero avere pi\u00f9 blocchi <code>machine<\/code> per eseguire login differenti su macchine diverse.<\/p>\n<p><code><br \/>\n        login caricamento<br \/>\n        password caricapassword<br \/>\n<\/code><\/p>\n<p>Qui inserite la login e la password del vostro account FTP sul server remoto. Niente di speciale.<\/p>\n<p><code><br \/>\nmacdef  backup<br \/>\n<\/code><\/p>\n<p>Ecco iniziare la parte pi\u00f9 interessante del file. Questo marcatore definisce una macro, il cui nome deve essere indicato sulla stessa riga. In questo caso, si chiamer\u00e0 <code>backup<\/code>. Le righe che seguiranno questa dichiarazione iniziali saranno considerati come facenti parte della macro, fino a che non verr\u00e0 incontrato un carriage return, o due caratteri di newline consecutivi. In pratica, per terminare una macro, dovete semplicemente lasciare una riga vuota alla fine.<\/p>\n<p>Vi \u00e8 un limite di 16 macro e 4096 caratteri in totale, per tutto il file <code>.netrc<\/code>, quindi non sprecateli. Da ricordare che esistono due caratteri speciali:<\/p>\n<ol>\n<li>\n<code>$<\/code> Seguito da uno o pi\u00f9 numeri viene interpretato come il corrispondente argomento sulla riga di comando che ha richiamato la macro. Se <code>$<\/code> viene fatto seguire da una <code>i<\/code>, la macro viene ciclata e al primo &#8220;giro&#8221;, <code>i<\/code> viene sostituito con il primo argomento della riga di comando che ha richiamato la macro, al secondo, con il secondo argomento e cos\u00ec di seguito.\n<\/li>\n<li>\n<code>\\<\/code> Ha la classica funzione di escape, e quindi qualsiasi carattere che venga preceduto da <code>\\<\/code> non verr\u00e0 interpretato.\n<\/li>\n<\/ol>\n<p><code><br \/>\n        binary<br \/>\n<\/code><\/p>\n<p>Ci assicuriamo che il trasferimento dei file avvenga in modalit\u00e0 binaria.<\/p>\n<p><code><br \/>\n        lcd \/var\/spool\/backup<br \/>\n<\/code><\/p>\n<p>Ci spostiamo, sul server remoto, nella directory nella quale vogliamo caricare gli archivi.<\/p>\n<p><code><br \/>\n        cd archivio_backup<br \/>\n<\/code><\/p>\n<p>Ci spostiamo, sul server locale (quello su cui abbiamo creato gli archivi), nella directory di spool nella quale abbiamo creato i file <code>.tgz<\/code>.<\/p>\n<p><code><br \/>\n        mput *.tgz<br \/>\n<\/code><\/p>\n<p>Magia del globbing. In pratica, usiamo <code>*<\/code> per istruire il client FTP affinch\u00e9 carichi sul server remoto tutti i file <code>.tgz<\/code> presenti in <code>\/var\/spool<\/code>.<\/p>\n<p><code><br \/>\n        delete $1-nome-del-server.backup.tgz<br \/>\n<\/code><\/p>\n<p>Ora, ricordate la penultima istruzione dello script? Diamoci nuovamente un&#8217;occhiata:<\/p>\n<p><code><br \/>\n$ECHO \"\\$ $BACKUP_MACRO $RM_DATE\" | $FTP $SERVER<br \/>\n<\/code><\/p>\n<p>Cosa significa questa stringa? Semplicemente, si richiama il client FTP, passandogli come argomento l&#8217;indirizzo del server cui collegarsi e, in pipe (<code>|<\/code>) gli si fornisce il nome della macro da richiamare e la data con la quale comporre il nome dei file da cancellare.<\/p>\n<p>Fermiamoci un breve inciso.<\/p>\n<p>In realt\u00e0, il nome della macro viene accettato da FTP nel formato:<\/p>\n<p><code><br \/>\n$ nome_macro<br \/>\n<\/code><\/p>\n<p>Quindi, nel nostro caso, il nome della macro accettato da FTP \u00e8:<\/p>\n<p><code><br \/>\n$ backup<br \/>\n<\/code><\/p>\n<p>che \u00e8 ben diverso da:<br \/>\n<code><br \/>\n$BACKUP_MACRO<br \/>\n<\/code><\/p>\n<p>Quest&#8217;ultimo, infatti non \u00e8 altro che l&#8217;istruzione per richiamare il contenuto della variabile <code>BACKUP_MACRO<\/code>, quindi, la precedente riga, si deve leggere:<\/p>\n<p><code><br \/>\nbackup<br \/>\n<\/code><\/p>\n<p>Il client FTP, per\u00f2, accetta il nome della macro, solo se preceduta da <code>$<\/code>$ deve essere passato in modo che non ci siano equivoci, che non possa essere interpretato all&#8217;interno della macro, quindi va preceduto dal carattere speciale <code>\\<\/code> e inviato in questa forma come argomento da passare al client.<\/p>\n<p>La precedente riga, quindi, interpretata correttamente, operando la giusta sostituzione di variabili, nel sistema utilizzato per questi esempi si risolve in:<\/p>\n<p><code><br \/>\n\/bin\/echo \"\\$ backup 2008-02-09\" | \/usr\/bin\/ftp indirizzo-del-server-di-appoggio<br \/>\n<\/code><\/p>\n<p>e quindi la precedente riga di macro:<\/p>\n<p><code><br \/>\n        delete $1-nome-del-server.backup.tgz<br \/>\n<\/code><\/p>\n<p>interpretata diventa:<\/p>\n<p><code><br \/>\n        delete 2008-02-09-nome-del-server.backup.tgz<br \/>\n<\/code><\/p>\n<p>dato che 2008-02-09 \u00e8 il primo argomento (<code>$<\/code> privo di escape), passato alla riga di comando che ha invocato la macro. <\/p>\n<p>Da notare che si \u00e8 deciso di tenere una quindicina di giorni di archivi, ma l&#8217;effettivo arco di ritenzione \u00e8 condizionato solo dallo spazio a disposizione, quindi sta a voi decidere quanto, in base ai vostri personali criteri.<\/p>\n<p><code><br \/>\n        delete $1-mysql.nome-del-server.backup.tgz<br \/>\n<\/code><\/p>\n<p>Qui vale il discorso fatto per la precedente istruzione. Cambia solo la parte finale del nome del file da cancellare, in questo caso l&#8217;archivio contenente il dump del database.<\/p>\n<p><code><br \/>\n        quit<br \/>\n<\/code><\/p>\n<p>Finito. Trasferiti i file, si chiude la connessione al server.<\/p>\n<p>Copiate il blocco di istruzioni appena viste in un file <code>.netrc<\/code> e ponetelo nella home dell&#8217;utente che intendete utilizzare per eseguire lo script.<\/p>\n<p>Modificate le propriet\u00e0 del file con il comando:<\/p>\n<p><code><br \/>\nchmod 600 .netrc<br \/>\n<\/code><\/p>\n<p>altrimenti vi ritroverete con un FTP ricalcitrante. Occhio anche ai permessi e proprietario del file contenente lo script, mai lasciarlo eseguibile o modificabile da altri che non sia l&#8217;utente da voi scelto per eseguire questo compito.<\/p>\n<p>Ricordatevi, infine, di terminare il blocco di macro, in <code>.netrc<\/code> con una riga vuota.<\/p>\n<p><strong>Conclusioni<\/strong><\/p>\n<p>Ci\u00f2 che abbiamo visto \u00e8 un esempio di come, sfruttando gli automatismi messi a disposizione dalle utility e dai comandi presenti in un sistema Unix sia possibile automatizzare compiti anche complessi, ma ripetitivi.<\/p>\n<p>Ora, se prendiamo il file contenente lo script appena creato, lo rendiamo eseguibile e lo spostiamo nella cartella <code>cron.daily<\/code>, avremo un sistema automatizzato che tutte le notti archivier\u00e0 i dati e li sposter\u00e0 su un server remoto, in modo che avremo sempre a disposizione un backup completo dei file che ci interessa preservare.<\/p>\n<p>Certo, non si tratta di un sistema sofisticato, che consente di creare backup totali e incrementali, o meglio, ci vorrebbe ancora un po&#8217; di lavoro perch\u00e9 possa farlo, ma il suo lavoro lo esegue velocemente e senza troppi fronzoli.<\/p>\n<p>Si pu\u00f2 migliorare?<\/p>\n<p>Certo.<\/p>\n<p>Quello che vi ho proposto \u00e8 solo uno scheletro di base. Intendiamoci, gi\u00e0 cos\u00ec funziona senza problemi, ma si potrebbe implementare un&#8217;ulteriore struttura di controllo, che verifichi l&#8217;effettiva creazione dei file di archivio prima di trasferirli, che ci avvisi se la connessone FTP non \u00e8 disponibile, insomma, che ci dia un feedback sul buon esito di tutta l&#8217;operazione.<\/p>\n<p>In realt\u00e0, non \u00e8 molto difficile implementare dei controlli. Potrete farlo tranquillamente voi, oppure potrebbe essere l&#8217;argomento di un prossimo post.<\/p>\n<p>Buon lavoro!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Dopo quasi, ahem, un anno di stasi, vediamo di chiudere l&#8217;agomento inziato con il primo post Backup in rete pratico e veloce con FTP -1, passando alla parte pi\u00f9 &#8220;operativa&#8221;. Vediamo, qui di seguito, un semplice shell script che si occupa di creare un backup all&#8217;interno di un file tgz, che avr\u00e0 come prefisso la &hellip;<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[13,62],"tags":[102,86,472,87,97,118,139,104,120,484],"class_list":["post-4","post","type-post","status-publish","format-standard","hentry","category-computer","category-sysadmin","tag-102","tag-backup","tag-fon","tag-ftp","tag-giornali","tag-home","tag-linux","tag-mac","tag-shell","tag-sicurezza","without-featured-image"],"_links":{"self":[{"href":"https:\/\/www.zarrelli.org\/blog\/wp-json\/wp\/v2\/posts\/4","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.zarrelli.org\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.zarrelli.org\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.zarrelli.org\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.zarrelli.org\/blog\/wp-json\/wp\/v2\/comments?post=4"}],"version-history":[{"count":0,"href":"https:\/\/www.zarrelli.org\/blog\/wp-json\/wp\/v2\/posts\/4\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.zarrelli.org\/blog\/wp-json\/wp\/v2\/media?parent=4"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.zarrelli.org\/blog\/wp-json\/wp\/v2\/categories?post=4"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.zarrelli.org\/blog\/wp-json\/wp\/v2\/tags?post=4"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}