joe di castrohttp://joedicastro.comWed, 13 Jul 2011 20:21:00 +0200Fabric & Rsync para realizar Backupshttp://joedicastro.com/fabric-rsync-para-realizar-backups.html<p>En el <a href="http://joedicastro.com/sincronizar-dos-directorios-con-fabric-y-rsync.html">anterior articulo</a> empleaba <a href="http://fabfile.org/">fabric</a> y <a href="http://es.wikipedia.org/wiki/Rsync">rsync</a> para sincronizar un directorio local y uno remoto en ambas direcciones. Además le añadía las funcionalidades de <a href="http://joedicastro.com/logger-informes-legibles-para-tus-scripts-python.html">logger</a> y <a href="http://joedicastro.com/notificaciones-de-escritorio-en-ubuntu-desde-python.html">notify</a> para proporcionar información sobre el proceso durante y después de su ejecución. Y comenzaba el articulo recordando a <a href="http://joedicastro.com/sincronizar-una-carpeta-local-y-una-remota-a-traves-de-ftp-lftp-mirror.html">lftp-mirror</a>, el script que había creado para realizar la sincronización a través de FTP. Pero <strong>lftp-mirror</strong> realiza algo más que la sincronización, pues también permite realizar el archivado del directorio local en ficheros comprimidos y lanzar varias tareas en una sola ejecución.</p> <p>Ahora he añadido esta funcionalidad al fichero <strong>fabric</strong> creado anteriormente. Así empleando este fichero podemos realizar el Backup periódico de varios servidores en una sola operación y de forma completamente automática (basta con programar su ejecución). Se sincronizan los dos directorios y se crea un archivo comprimido del directorio local por cada día de la semana. De este modo siempre tenemos una copia del estado del directorio remoto de los últimos siete días. Y al final del proceso en nuestro correo, un email con el informe del resultado por cada una de las tareas ejecutadas.</p> <p>En este fichero, <strong>rsync_fabric.py</strong>, disponemos de tres posibles tareas:</p> <div class="codehilite"><pre><span class="gp">$</span> fab -l <span class="go"> A Fabric file for sync two directories (remote ⇄ local) with rsync.</span> <span class="go">Available commands:</span> <span class="go"> backup Sync from remote to local &amp; archive the local directory.</span> <span class="go"> down Sync from remote to local.</span> <span class="go"> up Sync from local to remote.</span> </pre></div> <p>Con la primera realizamos el backup (sincronización + archivado) y con las siguientes solo la sincronización desde o hacia el servidor. Una de las ventajas de fabric es que nos permite concatenar tareas fácilmente desde la línea de comandos, así podemos lanzar varias sincronizaciones de forma simultanea. Para poder realizar esto, creo una configuración de sincronización por defecto y después creo una función para cada una las tareas adicionales que simplemente redefinen los valores de estas variables globales. Por ejemplo:</p> <div class="codehilite"><pre><span class="c"># Variables globales de sincronización predefenidas</span> <span class="n">env</span><span class="o">.</span><span class="n">host_string</span> <span class="o">=</span> <span class="s">&quot;username@example.com&quot;</span> <span class="n">env</span><span class="o">.</span><span class="n">remote</span> <span class="o">=</span> <span class="s">&quot;/my_directory&quot;</span> <span class="n">env</span><span class="o">.</span><span class="n">local</span> <span class="o">=</span> <span class="s">&quot;/home/my_user/backups/my_directory&quot;</span> <span class="c"># Redefinimos estas variables para otra configuración de sincronización. Por </span> <span class="c"># supuesto, pueden tratarse de servidores distintos.</span> <span class="k">def</span> <span class="nf">_databases</span><span class="p">():</span> <span class="k">global</span> <span class="n">env</span> <span class="n">env</span><span class="o">.</span><span class="n">host_string</span> <span class="o">=</span> <span class="s">&quot;username@example.com&quot;</span> <span class="n">env</span><span class="o">.</span><span class="n">remote</span> <span class="o">=</span> <span class="s">&quot;/databases&quot;</span> <span class="n">env</span><span class="o">.</span><span class="n">local</span> <span class="o">=</span> <span class="s">&quot;/home/my_user/backups/databases&quot;</span> </pre></div> <p>Veamos ejemplos de como podemos utilizar estas tareas:</p> <div class="codehilite"><pre><span class="gp">#</span> <span class="s2">&quot;Si queremos sincronizar el contenido local hacia el remoto, por ejemplo </span> <span class="gp">#</span><span class="s2"> para subir los ficheros al servidor por primera vez. Empleando los valores </span> <span class="gp">#</span><span class="s2"> por defecto. El modificador -w lo empleo para que no se detenga en los </span> <span class="gp">#</span><span class="s2"> errores, que de ocurrir, los veremos luego en el informe final.&quot;</span> <span class="gp">$</span> fab -w up <span class="go">[localhost] local: rsync -pthrvz --delete /home/my_user/backups/my_directory/ </span> <span class="go"> username@example.com:my_directory</span> <span class="go">Done.</span> <span class="gp">#</span> <span class="s2">&quot;Pero también podemos especificar una tarea distinta a la por defecto de </span> <span class="gp">#</span><span class="s2"> este modo. Sincronizando desde el servidor a nuestro directorio local las </span> <span class="gp">#</span><span class="s2"> bases de datos.&quot;</span> <span class="gp">$</span> fab -w down:databases <span class="go">[localhost] local: rsync -pthrvz --delete username@example.com:databases/ </span> <span class="go">/home/my_user/backups/databases</span> <span class="go">Done.</span> <span class="gp">#</span> <span class="s2">&quot;Y por supuesto, podemos realizar varias tareas a la vez.&quot;</span> <span class="gp">$</span> fab -w down backup:databases <span class="go">[localhost] local: rsync -pthrvz --delete username@example.com:my_directory/ </span> <span class="go">/home/my_user/backups/my_directory</span> <span class="go">[localhost] local: rsync -pthrvz --delete username@example.com:databases/ </span> <span class="go">/home/my_user/backups/databases</span> <span class="go">Done.</span> </pre></div> <p>No empleo contraseña alguna, ni en el fichero ni en la línea de comandos, podría hacerse perfectamente, pero prefiero emplear una clave <a href="http://es.wikipedia.org/wiki/RSA">RSA</a> <a href="http://es.wikipedia.org/wiki/Criptograf%C3%ADa_asim%C3%A9trica">pública</a> autorizada para las sesiones SSH en el servidor. Es bastante más seguro y cómodo. En los ejemplos no se ve la salida de <em>rsync</em>, pues es capturada (así como los erores) para ser mostrada a posteriori en los informes. </p> <p>Un ejemplo de informe sería el siguiente:</p> <div class="codehilite"><pre>START TIME ===================================================================== miércoles 13/07/11, 19:48:55 ================================================================================ SCRIPT ========================================================================= fab (ver. Unknown) Fabric Rsync http://joedicastro.com Syncing username@example.com:databases to /home/my_user/backups/databases ================================================================================ RSYNC OUTPUT ___________________________________________________________________ receiving file list ... done sent 20 bytes received 825 bytes 153.64 bytes/sec total size is 827.76M speedup is 979595.42 ROTATE COMPRESSED COPIES _______________________________________________________ Created file: /home/my_user/backups/databases_13jul2011_19:49_mié.tar.gz Deleted old file: databases_13jul2011_19:37_mié.tar.gz DISK SPACE USED ================================================================ 1.60 GiB ================================================================================ END TIME ======================================================================= miércoles 13/07/11, 19:50:02 ================================================================================ </pre></div> <p>Que como podemos ver, ha tardado poco más de un minuto en sincronizar 827.56 Megabytes y el total de espacio ocupado por el directorio y los siete archivos comprimidos es de 1.60 Gibibytes (1,72 Gigabytes). </p> <h2 id="ventajas">Ventajas</h2> <p>Las ventajas de sincronizarlo con <strong>rsync + ssh</strong> vs <strong>ftp</strong>, como ya comenté en el anterior articulo son enormes. Se ahorra muchísimo tiempo y ancho de banda, lo que ayuda a no saturar la red y no tener que planificar con tanto cuidado las ventanas de backup. Por ejemplo he realizado unas pruebas y para las mismas condiciones: <strong>mismo servidor, mismo directorio, mismo horario y condiciones de red; la sincronización remoto → local a través de FTP emplea entre 35 y 45 minutos y cuando lo hacemos a través de rsync emplea entre 2 y 4 minutos</strong>. Ahí es nada, estamos hablando de un proceso ~13 veces más rápido. </p> <h2 id="c+digo">Código</h2> <p>El código del fichero fabric es el siguiente:</p> <div class="codehilite"><pre><span class="c">#!/usr/bin/env python</span> <span class="c"># -*- coding: utf8 -*-</span> <span class="kn">import</span> <span class="nn">os</span> <span class="kn">import</span> <span class="nn">glob</span> <span class="kn">import</span> <span class="nn">tarfile</span> <span class="kn">import</span> <span class="nn">time</span> <span class="kn">from</span> <span class="nn">get_size</span> <span class="kn">import</span> <span class="n">get_size</span> <span class="k">as</span> <span class="n">_get_size</span> <span class="kn">from</span> <span class="nn">get_size</span> <span class="kn">import</span> <span class="n">best_unit_size</span> <span class="k">as</span> <span class="n">_best_unit_size</span> <span class="kn">from</span> <span class="nn">logger</span> <span class="kn">import</span> <span class="n">Logger</span> <span class="k">as</span> <span class="n">_logger</span> <span class="kn">from</span> <span class="nn">notify</span> <span class="kn">import</span> <span class="n">notify</span> <span class="k">as</span> <span class="n">_notify</span> <span class="kn">from</span> <span class="nn">fabric.api</span> <span class="kn">import</span> <span class="n">env</span><span class="p">,</span> <span class="n">local</span> <span class="n">LOG</span> <span class="o">=</span> <span class="n">_logger</span><span class="p">()</span> <span class="c">#===============================================================================</span> <span class="c"># RSYNC HOSTS</span> <span class="c">#===============================================================================</span> <span class="c"># Your default host. No need any more if only wants a host.</span> <span class="n">env</span><span class="o">.</span><span class="n">host_string</span> <span class="o">=</span> <span class="s">&quot;username@host&quot;</span> <span class="n">env</span><span class="o">.</span><span class="n">remote</span> <span class="o">=</span> <span class="s">&quot;/your/remote/path&quot;</span> <span class="n">env</span><span class="o">.</span><span class="n">local</span> <span class="o">=</span> <span class="s">&quot;/your/local/path&quot;</span> <span class="c"># If wants to use various hosts, then define the previous variables like this, </span> <span class="c"># one function per host. </span> <span class="k">def</span> <span class="nf">_host_1</span><span class="p">():</span> <span class="sd">&quot;&quot;&quot;Host variables for host_1.&quot;&quot;&quot;</span> <span class="k">global</span> <span class="n">env</span> <span class="n">env</span><span class="o">.</span><span class="n">host_string</span> <span class="o">=</span> <span class="s">&quot;username@host_1&quot;</span> <span class="n">env</span><span class="o">.</span><span class="n">remote</span> <span class="o">=</span> <span class="s">&quot;/your/remote/path/in/host_1&quot;</span> <span class="n">env</span><span class="o">.</span><span class="n">local</span> <span class="o">=</span> <span class="s">&quot;/your/local/path/for/host_1&quot;</span> <span class="k">def</span> <span class="nf">_host_2</span><span class="p">():</span> <span class="sd">&quot;&quot;&quot;Host variables for host_2.&quot;&quot;&quot;</span> <span class="k">global</span> <span class="n">env</span> <span class="n">env</span><span class="o">.</span><span class="n">host_string</span> <span class="o">=</span> <span class="s">&quot;username@host_2&quot;</span> <span class="n">env</span><span class="o">.</span><span class="n">remote</span> <span class="o">=</span> <span class="s">&quot;/your/remote/path/in/host_2&quot;</span> <span class="n">env</span><span class="o">.</span><span class="n">local</span> <span class="o">=</span> <span class="s">&quot;/your/local/path/for/host_2&quot;</span> <span class="c"># ...</span> <span class="c">#</span> <span class="c"># def _host_n():</span> <span class="c"># &quot;&quot;&quot;Host variables for host_n.&quot;&quot;&quot;</span> <span class="c"># global env</span> <span class="c"># env.host_string = &quot;username@host_n&quot;</span> <span class="c"># env.remote = &quot;/your/remote/path/in/host_n&quot;</span> <span class="c"># env.local = &quot;/your/local/path/for/host_n&quot;</span> <span class="c">#===============================================================================</span> <span class="c"># END RSYNC HOSTS</span> <span class="c">#===============================================================================</span> <span class="k">def</span> <span class="nf">_log_start</span><span class="p">():</span> <span class="sd">&quot;&quot;&quot;Create the Start time info block for the log.&quot;&quot;&quot;</span> <span class="c"># Init the log for multiple hosts. Do not repeat the previous logs.</span> <span class="k">if</span> <span class="n">LOG</span><span class="o">.</span><span class="n">get</span><span class="p">():</span> <span class="n">LOG</span><span class="o">.</span><span class="n">__init__</span><span class="p">()</span> <span class="n">LOG</span><span class="o">.</span><span class="n">time</span><span class="p">(</span><span class="s">&quot;Start time&quot;</span><span class="p">)</span> <span class="k">def</span> <span class="nf">_log_end</span><span class="p">(</span><span class="n">task</span><span class="p">):</span> <span class="sd">&quot;&quot;&quot;Create the End time info block and send &amp; write the log.&quot;&quot;&quot;</span> <span class="n">_notify</span><span class="p">(</span><span class="s">&quot;Rsync&quot;</span><span class="p">,</span> <span class="s">&quot;Ended&quot;</span> <span class="p">,</span> <span class="s">&quot;ok&quot;</span><span class="p">)</span> <span class="n">LOG</span><span class="o">.</span><span class="n">time</span><span class="p">(</span><span class="s">&quot;End time&quot;</span><span class="p">)</span> <span class="n">LOG</span><span class="o">.</span><span class="n">free</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">linesep</span> <span class="o">*</span> <span class="mi">2</span><span class="p">)</span> <span class="n">LOG</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="bp">True</span><span class="p">)</span> <span class="n">LOG</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="s">&quot;Fabric Rsync ({0})&quot;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">task</span><span class="p">))</span> <span class="k">def</span> <span class="nf">_check_local</span><span class="p">():</span> <span class="sd">&quot;&quot;&quot;Create local directory if no exists.&quot;&quot;&quot;</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">(</span><span class="n">env</span><span class="o">.</span><span class="n">local</span><span class="p">):</span> <span class="n">os</span><span class="o">.</span><span class="n">mkdir</span><span class="p">(</span><span class="n">env</span><span class="o">.</span><span class="n">local</span><span class="p">)</span> <span class="k">def</span> <span class="nf">_rsync</span><span class="p">(</span><span class="n">source</span><span class="p">,</span> <span class="n">target</span><span class="p">,</span> <span class="n">delete</span><span class="p">):</span> <span class="sd">&quot;&quot;&quot;Process the _rsync command.&quot;&quot;&quot;</span> <span class="n">_log_start</span><span class="p">()</span> <span class="n">LOG</span><span class="o">.</span><span class="n">header</span><span class="p">(</span><span class="s">&quot;Fabric Rsync</span><span class="se">\n</span><span class="s">http://joedicastro.com&quot;</span><span class="p">,</span> <span class="s">&quot;Syncing {0} to {1}&quot;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">source</span><span class="p">,</span> <span class="n">target</span><span class="p">))</span> <span class="n">_notify</span><span class="p">(</span><span class="s">&quot;Rsync&quot;</span><span class="p">,</span> <span class="s">&quot;Start syncing {0} to {1}&quot;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">source</span><span class="p">,</span> <span class="n">target</span><span class="p">),</span> <span class="s">&quot;info&quot;</span><span class="p">)</span> <span class="n">out</span> <span class="o">=</span> <span class="n">local</span><span class="p">(</span><span class="s">&quot;rsync -pthrvz {2} {0}/ {1}&quot;</span><span class="o">.</span> <span class="n">format</span><span class="p">(</span><span class="n">source</span><span class="p">,</span> <span class="n">target</span><span class="p">,</span> <span class="s">&quot;--delete&quot;</span> <span class="k">if</span> <span class="n">delete</span> <span class="o">==</span> <span class="s">&quot;yes&quot;</span> <span class="k">else</span> <span class="s">&quot;&quot;</span><span class="p">),</span> <span class="n">capture</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span> <span class="n">_notify</span><span class="p">(</span><span class="s">&quot;Rsync&quot;</span><span class="p">,</span> <span class="s">&quot;Finished synchronization&quot;</span><span class="p">,</span> <span class="s">&quot;ok&quot;</span><span class="p">)</span> <span class="n">LOG</span><span class="o">.</span><span class="n">list</span><span class="p">(</span><span class="s">&quot;Rsync Output&quot;</span><span class="p">,</span> <span class="n">out</span><span class="p">)</span> <span class="k">if</span> <span class="n">out</span><span class="o">.</span><span class="n">failed</span><span class="p">:</span> <span class="n">LOG</span><span class="o">.</span><span class="n">list</span><span class="p">(</span><span class="s">&quot;Rsync Errors&quot;</span><span class="p">,</span> <span class="n">out</span><span class="o">.</span><span class="n">stderr</span><span class="p">)</span> <span class="k">def</span> <span class="nf">_compress</span><span class="p">(</span><span class="n">path</span><span class="p">):</span> <span class="sd">&quot;&quot;&quot;Compress a local directory into a gz file.</span> <span class="sd"> Creates a file for each weekday, an removes the old files if exists&quot;&quot;&quot;</span> <span class="n">os</span><span class="o">.</span><span class="n">chdir</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">os</span><span class="o">.</span><span class="n">pardir</span><span class="p">))</span> <span class="n">dir2gz</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">basename</span><span class="p">(</span><span class="n">path</span><span class="p">)</span> <span class="n">old_gzs</span> <span class="o">=</span> <span class="n">glob</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="s">&#39;{0}*{1}.tar.gz&#39;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">dir2gz</span><span class="p">,</span> <span class="n">time</span><span class="o">.</span><span class="n">strftime</span><span class="p">(</span><span class="s">&#39;%a&#39;</span><span class="p">)))</span> <span class="n">gz_name</span> <span class="o">=</span> <span class="s">&quot;{0}_{1}.tar.gz&quot;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">dir2gz</span><span class="p">,</span> <span class="n">time</span><span class="o">.</span><span class="n">strftime</span><span class="p">(</span><span class="s">&#39;</span><span class="si">%d</span><span class="s">%b%Y_%H:%M_%a&#39;</span><span class="p">))</span> <span class="n">gz_file</span> <span class="o">=</span> <span class="n">tarfile</span><span class="o">.</span><span class="n">open</span><span class="p">(</span><span class="n">gz_name</span><span class="p">,</span> <span class="s">&quot;w:gz&quot;</span><span class="p">)</span> <span class="n">gz_file</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">arcname</span><span class="o">=</span><span class="n">dir2gz</span><span class="p">)</span> <span class="n">gz_file</span><span class="o">.</span><span class="n">close</span><span class="p">()</span> <span class="n">output</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">linesep</span><span class="o">.</span><span class="n">join</span><span class="p">([</span><span class="s">&#39;Created file:&#39;</span><span class="p">,</span> <span class="s">&#39;&#39;</span><span class="p">,</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">getcwd</span><span class="p">(),</span> <span class="n">gz_name</span><span class="p">)])</span> <span class="k">for</span> <span class="n">old_gz</span> <span class="ow">in</span> <span class="n">old_gzs</span><span class="p">:</span> <span class="n">os</span><span class="o">.</span><span class="n">remove</span><span class="p">(</span><span class="n">old_gz</span><span class="p">)</span> <span class="n">output</span> <span class="o">+=</span> <span class="n">os</span><span class="o">.</span><span class="n">linesep</span><span class="o">.</span><span class="n">join</span><span class="p">([</span><span class="n">os</span><span class="o">.</span><span class="n">linesep</span><span class="p">,</span> <span class="s">&#39;Deleted old file:&#39;</span><span class="p">,</span> <span class="s">&#39;&#39;</span><span class="p">,</span> <span class="n">old_gz</span><span class="p">])</span> <span class="k">return</span> <span class="n">output</span> <span class="k">def</span> <span class="nf">_archive</span><span class="p">():</span> <span class="sd">&quot;&quot;&quot;Archive the local directory in a gz file for each weekday.&quot;&quot;&quot;</span> <span class="n">_notify</span><span class="p">(</span><span class="s">&#39;Rsync&#39;</span><span class="p">,</span> <span class="s">&#39;Compressing folder...&#39;</span><span class="p">,</span> <span class="s">&#39;info&#39;</span><span class="p">)</span> <span class="n">LOG</span><span class="o">.</span><span class="n">list</span><span class="p">(</span><span class="s">&#39;Rotate compressed copies&#39;</span><span class="p">,</span> <span class="n">_compress</span><span class="p">(</span><span class="n">env</span><span class="o">.</span><span class="n">local</span><span class="p">))</span> <span class="n">_notify</span><span class="p">(</span><span class="s">&quot;Rsync&quot;</span><span class="p">,</span> <span class="s">&quot;Finished compression&quot;</span><span class="p">,</span> <span class="s">&quot;ok&quot;</span><span class="p">)</span> <span class="k">def</span> <span class="nf">_get_diskspace</span><span class="p">():</span> <span class="sd">&quot;&quot;&quot;Get the disk space used by the local directory and archives.&quot;&quot;&quot;</span> <span class="n">gz_size</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">([</span><span class="n">_get_size</span><span class="p">(</span><span class="n">gz</span><span class="p">)</span> <span class="k">for</span> <span class="n">gz</span> <span class="ow">in</span> <span class="n">glob</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="s">&#39;{0}*.gz&#39;</span><span class="o">.</span> <span class="n">format</span><span class="p">(</span><span class="n">env</span><span class="o">.</span><span class="n">local</span><span class="p">))])</span> <span class="n">log_size</span> <span class="o">=</span> <span class="n">_get_size</span><span class="p">(</span><span class="n">LOG</span><span class="o">.</span><span class="n">filename</span><span class="p">)</span> <span class="k">if</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">(</span><span class="n">LOG</span><span class="o">.</span><span class="n">filename</span><span class="p">)</span> <span class="k">else</span> <span class="mi">0</span> <span class="n">local_size</span> <span class="o">=</span> <span class="n">_get_size</span><span class="p">(</span><span class="n">env</span><span class="o">.</span><span class="n">local</span><span class="p">)</span> <span class="n">size</span> <span class="o">=</span> <span class="n">_best_unit_size</span><span class="p">(</span><span class="n">local_size</span> <span class="o">+</span> <span class="n">gz_size</span> <span class="o">+</span> <span class="n">log_size</span><span class="p">)</span> <span class="n">LOG</span><span class="o">.</span><span class="n">block</span><span class="p">(</span><span class="s">&#39;Disk space used&#39;</span><span class="p">,</span> <span class="s">&#39;{0:&gt;76.2f} {1}&#39;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">size</span><span class="p">[</span><span class="s">&#39;s&#39;</span><span class="p">],</span> <span class="n">size</span><span class="p">[</span><span class="s">&#39;u&#39;</span><span class="p">]))</span> <span class="k">def</span> <span class="nf">up</span><span class="p">(</span><span class="n">server</span><span class="o">=</span><span class="bp">None</span><span class="p">,</span> <span class="n">dlt</span><span class="o">=</span><span class="s">&#39;yes&#39;</span><span class="p">):</span> <span class="sd">&quot;&quot;&quot;Sync from local to remote.&quot;&quot;&quot;</span> <span class="nb">globals</span><span class="p">()[</span><span class="s">&quot;_&quot;</span> <span class="o">+</span> <span class="n">server</span><span class="p">]()</span> <span class="k">if</span> <span class="n">server</span> <span class="k">else</span> <span class="bp">None</span> <span class="n">_rsync</span><span class="p">(</span><span class="n">env</span><span class="o">.</span><span class="n">local</span><span class="p">,</span> <span class="s">&quot;:&quot;</span><span class="o">.</span><span class="n">join</span><span class="p">([</span><span class="n">env</span><span class="o">.</span><span class="n">host_string</span><span class="p">,</span> <span class="n">env</span><span class="o">.</span><span class="n">remote</span><span class="p">]),</span> <span class="n">dlt</span><span class="p">)</span> <span class="n">_log_end</span><span class="p">(</span><span class="n">server</span><span class="p">)</span> <span class="k">def</span> <span class="nf">down</span><span class="p">(</span><span class="n">server</span><span class="o">=</span><span class="bp">None</span><span class="p">,</span> <span class="n">dlt</span><span class="o">=</span><span class="s">&#39;yes&#39;</span><span class="p">,</span> <span class="n">archive</span><span class="o">=</span><span class="bp">False</span><span class="p">):</span> <span class="sd">&quot;&quot;&quot;Sync from remote to local.&quot;&quot;&quot;</span> <span class="nb">globals</span><span class="p">()[</span><span class="s">&quot;_&quot;</span> <span class="o">+</span> <span class="n">server</span><span class="p">]()</span> <span class="k">if</span> <span class="n">server</span> <span class="k">else</span> <span class="bp">None</span> <span class="n">_check_local</span><span class="p">()</span> <span class="n">_rsync</span><span class="p">(</span><span class="s">&quot;:&quot;</span><span class="o">.</span><span class="n">join</span><span class="p">([</span><span class="n">env</span><span class="o">.</span><span class="n">host_string</span><span class="p">,</span> <span class="n">env</span><span class="o">.</span><span class="n">remote</span><span class="p">]),</span> <span class="n">env</span><span class="o">.</span><span class="n">local</span><span class="p">,</span> <span class="n">dlt</span><span class="p">)</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">archive</span><span class="p">:</span> <span class="n">_log_end</span><span class="p">(</span><span class="n">server</span><span class="p">)</span> <span class="k">def</span> <span class="nf">backup</span><span class="p">(</span><span class="n">server</span><span class="o">=</span><span class="bp">None</span><span class="p">):</span> <span class="sd">&quot;&quot;&quot;Sync from remote to local &amp; archive the local directory.&quot;&quot;&quot;</span> <span class="n">down</span><span class="p">(</span><span class="n">server</span><span class="p">,</span> <span class="n">archive</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span> <span class="n">_archive</span><span class="p">()</span> <span class="n">_get_diskspace</span><span class="p">()</span> <span class="n">_log_end</span><span class="p">(</span><span class="n">server</span><span class="p">)</span> </pre></div> <p>El fichero siempre actualizado puede ser encontrado en el repositorio <em>Python Recipes</em> que está alojado en <a href="http://github.com/joedicastro/python-recipes">github</a> con el nombre <code>rsync_fabfile.py</code> </p>joe di castroWed, 13 Jul 2011 20:21:00 +0200http://joedicastro.com/fabric-rsync-para-realizar-backups.htmlpythonfabricrsyncbackupsincronizarSincronizar dos directorios con Fabric y Rsynchttp://joedicastro.com/sincronizar-dos-directorios-con-fabric-y-rsync.html<p>Anteriormente habíamos visto como <a href="http://joedicastro.com/sincronizar-una-carpeta-local-y-una-remota-a-traves-de-ftp-lftp-mirror.html">sincronizar un directorio remoto y uno local empleando solamente FTP</a>. Ahora vamos a ver la forma de hacerlo empleando <a href="http://es.wikipedia.org/wiki/Ssh">ssh</a> y <a href="http://es.wikipedia.org/wiki/Rsync">rsync</a>. Para ello vamos a utilizar otra vez <strong>Python</strong> y una herramienta muy valiosa para cualquier <a href="http://es.wikipedia.org/wiki/Administrador_de_sistemas">sysadmin</a> que se precie como es <a href="http://fabfile.org/">fabric</a> (que descubrí gracias a Manuel Viera en <a href="http://python.majibu.org/preguntas/11/libreria-para-emplear-con-ssh">esta pregunta en majibu</a>). Evidentemente realizar la sincronización con rsync esta a años luz de hacerlo con FTP, la velocidad de sincronización, el tiempo empleado y la cantidad de datos a mover son mucho menores. FTP es algo que debería utilizarse únicamente cuando no disponemos de acceso via SSH.</p> <p>La gran ventaja de <strong>fabric</strong> es que nos permite ahorrarnos el tener que implementar el acceso SSH con <a href="http://www.lag.net/paramiko/">paramiko</a> y la entrada de opciones y argumentos con <em>argparse</em>. Gracias a esto los scripts necesarios son mucho más cortos y limpios y su utilización es bastante más sencilla. Fabric ya incorpora una función para emplear rsync, <code>rsync_project</code>, dentro de su modulo de proyectos contribuidos <code>fabric.contrib.project</code></p> <p>Una forma de implementar esta sincronización en ambas direcciones empleando esta función predefinida sería esta:</p> <div class="codehilite"><pre><span class="kn">from</span> <span class="nn">fabric.api</span> <span class="kn">import</span> <span class="n">env</span><span class="p">,</span> <span class="n">hosts</span><span class="p">,</span> <span class="n">local</span> <span class="kn">from</span> <span class="nn">fabric.contrib.project</span> <span class="kn">import</span> <span class="n">rsync_project</span> <span class="n">env</span><span class="o">.</span><span class="n">host_string</span> <span class="o">=</span> <span class="s">&quot;username@host&quot;</span> <span class="n">REMOTE_PATH</span> <span class="o">=</span> <span class="s">&quot;/your/remote/path&quot;</span> <span class="n">LOCAL_PATH</span> <span class="o">=</span> <span class="s">&quot;/your/local/path&quot;</span> <span class="nd">@hosts</span><span class="p">(</span><span class="n">env</span><span class="o">.</span><span class="n">host_string</span><span class="p">)</span> <span class="k">def</span> <span class="nf">rsync_up</span><span class="p">(</span><span class="n">dlt</span><span class="o">=</span><span class="s">&quot;yes&quot;</span><span class="p">):</span> <span class="n">rsync_project</span><span class="p">(</span><span class="n">REMOTE_PATH</span><span class="p">,</span> <span class="n">LOCAL_PATH</span> <span class="o">+</span> <span class="s">&quot;/&quot;</span><span class="p">,</span> <span class="n">delete</span><span class="o">=</span> <span class="bp">True</span> <span class="k">if</span> <span class="n">dlt</span> <span class="o">==</span> <span class="s">&quot;yes&quot;</span> <span class="k">else</span> <span class="bp">False</span><span class="p">)</span> <span class="k">def</span> <span class="nf">rsync_down</span><span class="p">(</span><span class="n">dlt</span><span class="o">=</span><span class="s">&quot;yes&quot;</span><span class="p">):</span> <span class="n">local</span><span class="p">(</span><span class="s">&quot;rsync -pthrvz {0}:{1}/ {2} {3}&quot;</span><span class="o">.</span> <span class="n">format</span><span class="p">(</span><span class="n">env</span><span class="o">.</span><span class="n">host_string</span><span class="p">,</span> <span class="n">REMOTE_PATH</span><span class="p">,</span> <span class="n">LOCAL_PATH</span><span class="p">,</span> <span class="s">&quot;--delete&quot;</span> <span class="k">if</span> <span class="n">dlt</span> <span class="o">==</span> <span class="s">&quot;yes&quot;</span> <span class="k">else</span> <span class="s">&quot;&quot;</span><span class="p">))</span> </pre></div> <p>Y luego solo tendríamos que llamar a la función deseada:</p> <div class="codehilite"><pre><span class="gp">#</span> <span class="s2">&quot;Para sincronizar de remoto a local&quot;</span> <span class="gp">$</span> fab rsync_down </pre></div> <blockquote> <p>Hay que tener en cuenta un detalle con fabric. Cuando se le pasa un parámetro, este es siempre convertido a una cadena. Luego al pasarle <code>True</code> o <code>False</code> no se convierte en un valor booleano, sino una cadena <code>"True"</code>o <code>"False"</code>. De ahí que compruebe si el parámetro coincide con <code>"yes"</code> en vez de un valor booleano.</p> </blockquote> <p>El problema con la función rsync predefinida de fabric es que esta pensada únicamente para subir archivos a un servidor remoto, es decir, es una sincronización en una sola dirección, por eso implemento la sincronización en sentido contrario sin emplearla y empleando <code>local</code>. La autentificación de la sesión SSH puede realizarse especificando la contraseña dentro del propio fichero, pero va en contra del sentido común emplear un método tan inseguro como este. Lo lógico es emplear autorizaciones de sesiones SSH sin contraseña por medio de una <a href="http://es.wikipedia.org/wiki/Criptograf%C3%ADa_asim%C3%A9trica">clave pública</a>.</p> <p>Podríamos prescindir de la librería incorporada dentro de fabric y tendríamos algo como esto:</p> <div class="codehilite"><pre><span class="kn">from</span> <span class="nn">fabric.api</span> <span class="kn">import</span> <span class="n">env</span><span class="p">,</span> <span class="n">local</span> <span class="n">env</span><span class="o">.</span><span class="n">host_string</span> <span class="o">=</span> <span class="s">&quot;username@host&quot;</span> <span class="n">REMOTE_PATH</span> <span class="o">=</span> <span class="s">&quot;/your/remote/path&quot;</span> <span class="n">LOCAL_PATH</span> <span class="o">=</span> <span class="s">&quot;/your/local/path&quot;</span> <span class="k">def</span> <span class="nf">_rsync</span><span class="p">(</span><span class="n">source</span><span class="p">,</span> <span class="n">target</span><span class="p">,</span> <span class="n">delete</span><span class="p">):</span> <span class="sd">&quot;&quot;&quot;Process the _rsync command.&quot;&quot;&quot;</span> <span class="n">output</span> <span class="o">=</span> <span class="n">local</span><span class="p">(</span><span class="s">&quot;rsync -pthrvz {0}/ {1} {2}&quot;</span><span class="o">.</span> <span class="n">format</span><span class="p">(</span><span class="n">source</span><span class="p">,</span> <span class="n">target</span><span class="p">,</span> <span class="s">&quot;--delete&quot;</span> <span class="k">if</span> <span class="n">delete</span> <span class="o">==</span> <span class="s">&quot;yes&quot;</span> <span class="k">else</span> <span class="s">&quot;&quot;</span><span class="p">))</span> <span class="k">def</span> <span class="nf">up</span><span class="p">(</span><span class="n">dlt</span><span class="o">=</span><span class="s">&#39;yes&#39;</span><span class="p">):</span> <span class="sd">&quot;&quot;&quot;Sync from local to remote.&quot;&quot;&quot;</span> <span class="n">_rsync</span><span class="p">(</span><span class="n">LOCAL_PATH</span><span class="p">,</span> <span class="s">&quot;:&quot;</span><span class="o">.</span><span class="n">join</span><span class="p">([</span><span class="n">env</span><span class="o">.</span><span class="n">host_string</span><span class="p">,</span> <span class="n">REMOTE_PATH</span><span class="p">]),</span> <span class="n">dlt</span><span class="p">)</span> <span class="k">def</span> <span class="nf">down</span><span class="p">(</span><span class="n">dlt</span><span class="o">=</span><span class="s">&#39;yes&#39;</span><span class="p">):</span> <span class="sd">&quot;&quot;&quot;Sync from remote to local.&quot;&quot;&quot;</span> <span class="n">_rsync</span><span class="p">(</span><span class="s">&quot;:&quot;</span><span class="o">.</span><span class="n">join</span><span class="p">([</span><span class="n">env</span><span class="o">.</span><span class="n">host_string</span><span class="p">,</span> <span class="n">REMOTE_PATH</span><span class="p">]),</span> <span class="n">LOCAL_PATH</span><span class="p">,</span> <span class="n">dlt</span><span class="p">)</span> </pre></div> <p>Pero... un momento, si estamos empleado un comando local, no empleamos <code>rsync_project</code> y empleamos una clave pública para el acceso SSH, entonces no estamos empleando <strong>paramiko</strong>, ¿de que nos sirve emplear fabric?. Bueno, en realidad <code>rsync_project</code> también emplea <code>local</code>, por lo que no emplea paramiko. Pero las ventajas vienen de que, por ejemplo, este mismo script se podría modificar fácilmente para ejecutar rsync en el servidor en vez de en nuestra maquina local, empleando <code>run</code> en vez de <code>local</code>. Además podemos emplear el mismo fichero para añadir varias tareas más a realizar en el servidor, aparte de la sincronización. Podríamos prescindir de fabric y hacer esto mismo con un script con un número similar de líneas, pero esto nos permite centralizar todas las tareas más comunes sobre ese servidor en un único fichero. Por ejemplo podríamos añadir una tarea para hacer un respaldo previo de una base de datos en el servidor, empleando un comando remoto en el servidor, luego hacer la sincronización separada de la BDD y el resto de ficheros y finalmente eliminar ese respaldo. Puede haber cientos de razones para preferir emplear fabric antes de un script independiente para la sincronización.</p> <h2 id="ejecuci+n_desatendida_de_la_sincronizaci+n">Ejecución desatendida de la sincronización</h2> <p>Si queremos programar esta tarea, no sería mala idea que nos avisara de cuando comienza a ejecutarse y del resultado de la misma. Para ello puedo emplear <a href="http://joedicastro.com/logger-informes-legibles-para-tus-scripts-python.html">Logger</a> y <a href="http://joedicastro.com/notificaciones-de-escritorio-en-ubuntu-desde-python.html">notify</a>, para implementar esta funcionalidad.</p> <div class="codehilite"><pre><span class="kn">from</span> <span class="nn">logger</span> <span class="kn">import</span> <span class="n">Logger</span> <span class="k">as</span> <span class="n">_logger</span> <span class="kn">from</span> <span class="nn">notify</span> <span class="kn">import</span> <span class="n">notify</span> <span class="k">as</span> <span class="n">_notify</span> <span class="kn">from</span> <span class="nn">fabric.api</span> <span class="kn">import</span> <span class="n">env</span><span class="p">,</span> <span class="n">local</span> <span class="n">env</span><span class="o">.</span><span class="n">host_string</span> <span class="o">=</span> <span class="s">&quot;username@host&quot;</span> <span class="n">REMOTE_PATH</span> <span class="o">=</span> <span class="s">&quot;/your/remote/path&quot;</span> <span class="n">LOCAL_PATH</span> <span class="o">=</span> <span class="s">&quot;/your/local/path&quot;</span> <span class="k">def</span> <span class="nf">_rsync</span><span class="p">(</span><span class="n">source</span><span class="p">,</span> <span class="n">target</span><span class="p">,</span> <span class="n">delete</span><span class="p">):</span> <span class="sd">&quot;&quot;&quot;Process the _rsync command.&quot;&quot;&quot;</span> <span class="n">log</span> <span class="o">=</span> <span class="n">_logger</span><span class="p">()</span> <span class="n">log</span><span class="o">.</span><span class="n">header</span><span class="p">(</span><span class="s">&quot;Fabric Rsync</span><span class="se">\n</span><span class="s">http://joedicastro.com&quot;</span><span class="p">,</span> <span class="s">&quot;Syncing {0} to {1}&quot;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">source</span><span class="p">,</span> <span class="n">target</span><span class="p">))</span> <span class="n">log</span><span class="o">.</span><span class="n">time</span><span class="p">(</span><span class="s">&quot;Start time&quot;</span><span class="p">)</span> <span class="n">_notify</span><span class="p">(</span><span class="s">&quot;Rsync&quot;</span><span class="p">,</span> <span class="s">&quot;Start syncing {0} to {1}&quot;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">source</span><span class="p">,</span> <span class="n">target</span><span class="p">),</span> <span class="s">&quot;info&quot;</span><span class="p">)</span> <span class="n">output</span> <span class="o">=</span> <span class="n">local</span><span class="p">(</span><span class="s">&quot;rsync -pthrvz {0}/ {1} {2}&quot;</span><span class="o">.</span> <span class="n">format</span><span class="p">(</span><span class="n">source</span><span class="p">,</span> <span class="n">target</span><span class="p">,</span> <span class="s">&quot;--delete&quot;</span> <span class="k">if</span> <span class="n">delete</span> <span class="o">==</span> <span class="s">&quot;yes&quot;</span> <span class="k">else</span> <span class="s">&quot;&quot;</span><span class="p">),</span> <span class="n">capture</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span> <span class="n">_notify</span><span class="p">(</span><span class="s">&quot;Rsync&quot;</span><span class="p">,</span> <span class="s">&quot;Finished&quot;</span><span class="p">,</span> <span class="s">&quot;ok&quot;</span><span class="p">)</span> <span class="n">log</span><span class="o">.</span><span class="n">list</span><span class="p">(</span><span class="s">&quot;Output&quot;</span><span class="p">,</span> <span class="n">output</span><span class="p">)</span> <span class="k">if</span> <span class="n">output</span><span class="o">.</span><span class="n">failed</span><span class="p">:</span> <span class="n">log</span><span class="o">.</span><span class="n">list</span><span class="p">(</span><span class="s">&quot;Error&quot;</span><span class="p">,</span> <span class="n">output</span><span class="o">.</span><span class="n">stderr</span><span class="p">)</span> <span class="n">log</span><span class="o">.</span><span class="n">time</span><span class="p">(</span><span class="s">&quot;End time&quot;</span><span class="p">)</span> <span class="n">log</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="s">&quot;Fabric Rsync&quot;</span><span class="p">)</span> <span class="k">def</span> <span class="nf">up</span><span class="p">(</span><span class="n">dlt</span><span class="o">=</span><span class="s">&#39;yes&#39;</span><span class="p">):</span> <span class="sd">&quot;&quot;&quot;Sync from local to remote.&quot;&quot;&quot;</span> <span class="n">_rsync</span><span class="p">(</span><span class="n">LOCAL_PATH</span><span class="p">,</span> <span class="s">&quot;:&quot;</span><span class="o">.</span><span class="n">join</span><span class="p">([</span><span class="n">env</span><span class="o">.</span><span class="n">host_string</span><span class="p">,</span> <span class="n">REMOTE_PATH</span><span class="p">]),</span> <span class="n">dlt</span><span class="p">)</span> <span class="k">def</span> <span class="nf">down</span><span class="p">(</span><span class="n">dlt</span><span class="o">=</span><span class="s">&#39;yes&#39;</span><span class="p">):</span> <span class="sd">&quot;&quot;&quot;Sync from remote to local.&quot;&quot;&quot;</span> <span class="n">_rsync</span><span class="p">(</span><span class="s">&quot;:&quot;</span><span class="o">.</span><span class="n">join</span><span class="p">([</span><span class="n">env</span><span class="o">.</span><span class="n">host_string</span><span class="p">,</span> <span class="n">REMOTE_PATH</span><span class="p">]),</span> <span class="n">LOCAL_PATH</span><span class="p">,</span> <span class="n">dlt</span><span class="p">)</span> </pre></div> <p>De esta forma, nos avisaría con una notificación en el escritorio de su inicio y fin, y al acabarse la sincronización, tendríamos un informe en nuestro correo parecido a este:</p> <div class="codehilite"><pre>SCRIPT ========================================================================= fab (ver. Unknown) Fabric Rsync Syncing username@host:/your/remote/path to /your/local/path ================================================================================ START TIME ===================================================================== miércoles 06/07/11, 21:50:48 ================================================================================ OUTPUT _________________________________________________________________________ receiving file list ... done ./ index.php sent 48 bytes received 200 bytes 45.09 bytes/sec total size is 99 speedup is 0.40 END TIME ======================================================================= miércoles 06/07/11, 21:50:54 ================================================================================ </pre></div> <p>Este fichero está disponible en el repositorio <em>Python Recipes</em> alojado en <a href="http://github.com/joedicastro/python-recipes">github</a>.</p>joe di castroWed, 06 Jul 2011 22:02:00 +0200http://joedicastro.com/sincronizar-dos-directorios-con-fabric-y-rsync.htmlpythonfabricrsyncsincronizarSincronizar una carpeta local y una remota a través de FTP: lftp-mirrorhttp://joedicastro.com/sincronizar-una-carpeta-local-y-una-remota-a-traves-de-ftp-lftp-mirror.html<p>A veces tenemos la necesidad de subir (o bajar) contenido a un servidor y posteriormente tener actualizados los cambios que se produzcan en uno (o ambos) de los lados. Es decir tener sincronizados el directorio remoto y el local. Esto es relativamente fácil cuanto tenemos acceso via <a href="http://es.wikipedia.org/wiki/L%C3%ADnea_de_comandos">consola</a> y <a href="http://es.wikipedia.org/wiki/Ssh">ssh</a> al servidor y podemos utilizar programas tan potentes como <a href="http://es.wikipedia.org/wiki/Rsync">rsync</a>. ¿Pero que ocurre cuando el único método del que disponemos para intercambiar ficheros con el servidor es a través del protocolo <a href="http://es.wikipedia.org/wiki/Ftp">FTP</a>, como ocurre con muchos servidores web?</p> <p>Bien, en ese caso, tenemos un pequeño problema. El protocolo <strong>FTP</strong> aunque perfectamente valido para las funciones para las que fue originalmente creado, la transferencia de archivos, no contempla este caso. La solución manual y menos efectiva es volver a transferir todos los archivos cada vez que se produce un cambio, solución nada recomendable a nada que el tamaño de estos empiece a ser superior a decenas de Megabytes. También podríamos ir comprobando manualmente que ficheros han cambiado y transferir únicamente estos, algo también muy poco recomendable si el número de archivos es elevado. Afortunadamente algunos clientes gráficos de <strong>FTP</strong> nos permiten comprobar que ficheros son distintos en uno y otro lado y luego transferir únicamente estos, lo cual ya es un método bastante más efectivo y adecuado. Aunque si se trata de directorios con muchos archivos y una estructura jerárquica compleja (muchos directorios y subdirectorios) el proceso es bastante lento pues ha de ir comprobando en un lado y en el otro las diferencias entre los archivos (fecha, tamaño y atributos únicamente) recorriendolos todos. ¿Pero que ocurre si queremos realizar esta operación de forma periódica y automática? entonces esta solución tampoco es valida, pues necesitaríamos un programa de línea de comandos o un script para realizarlo.</p> <p>Por suerte para nosotros, esta solución también está disponible a través de varios programas y scripts para consola, entre los cuales el mejor es <a href="http://lftp.yar.ru/"><strong>lftp</strong></a> de <strong>Alexander V. Lukyanov</strong>. Este fantástico programa es una navaja suiza para todo aquello que necesitemos hacer a través de <strong>FTP</strong>, siendo uno de los mejores clientes <strong>FTP</strong>, si no el mejor, que existen. Y una de las innumerables posibilidades que ofrece es precisamente la de <strong>sincronizar dos directorios con la opción mirror</strong> (espejar). De esta manera podemos mantener perfectamente sincronizados dos directorios de forma automática. <strong>Nos permite hacer la sincronización en ambas direcciones, remoto → local y local → remoto</strong>.</p> <p>Como ya he mencionado es muy potente y repleto de opciones y permite muchas más operaciones más allá de la sincronización entre directorios. Por este motivo <strong>he creado un <a href="http://es.wikipedia.org/wiki/Script">script</a> en <a href="http://es.wikipedia.org/wiki/Python">Python</a> que empleando lftp, se centra únicamente en la sincronización entre directorios a través de FTP y añade algunas nuevas funcionalidades, <code>lftp-mirror</code>.</strong></p> <h2 id="+que_ventajas_aporta_este_script">¿Que ventajas aporta este script?</h2> <ul> <li><strong>Proporciona un log detallado y legible</strong> que graba en un fichero en disco y <strong>que puede ser enviado por correo electrónico</strong> a una o varias direcciones empleando el servidor de correo local o uno externo.</li> <li><strong>Permite crear una copia comprimida por día de la semana del directorio local sincronizado</strong>. Esto nos permite tener el directorio actualizado y una copia de seguridad por cada uno de los últimos 7 días, para poder revertir algún cambio o borrado accidental.</li> <li><strong>Se centra únicamente en la sincronización (mirror)</strong> entre directorios, obviando las otras opciones que nos ofrece lftp</li> <li><strong>Nos proporciona</strong> (en el log) <strong>el tamaño del espacio ocupado en el disco duro por el directorio local y las copias de seguridad.</strong></li> <li><strong>Permite tres modos de ejecución distintos,</strong> lo que lo convierte en muy versátil:<ul> <li><strong>Como tarea programada</strong>. En este modo los parámetros de la sincronización se incluyen directamente dentro del script y solo es necesario programar su ejecución para automatizar el proceso. Es ideal para la sincronización periódica de un único directorio/servidor <strong>FTP</strong></li> <li><strong>Interactivo.</strong> En este modo los parámetros se introducen directamente como argumentos en la línea de comandos. Es ideal para ejecutar una sincronización puntual manual</li> <li><strong>Importando los parámetros desde un fichero de configuración.</strong> Este modo es similar al primero, con la diferencia de que en este caso los parámetros los tomamos de un fichero de configuración externo. Este fichero que podemos crear nosotros mismos (se sirve uno de ejemplo) nos permite establecer múltiples operaciones de sincronización que se ejecutaran de manera secuencial una detrás de otra.</li> </ul> </li> <li><strong>En sistemas operativos que lo soporten nos muestra notificaciones emergentes</strong> a través de la librería libnotify de la ejecución del script y su correcta finalización. Por ejemplo, a través de las notificaciones emergentes de <a href="http://es.wikipedia.org/wiki/Ubuntu"><strong>Ubuntu</strong></a>. Muy útil para conocer cuando se está ejecutando una tarea programada sin salida por consola.</li> <li>Si empleamos los modos de ejecución no interactivos, <strong>emplea <a href="http://es.wikipedia.org/wiki/Base64">base64</a> para una mínima protección de la contraseñas de acceso</strong> a los servidores <strong>FTP</strong> y evitar almacenarlas las mismas en texto claro. No es una fuerte medida de seguridad, pero es lo mínimo que deberíamos tener en cuenta.</li> </ul> <h2 id="+para_que_nos_puede_servir_este_script">¿Para que nos puede servir este script?</h2> <p>Vamos a ver un ejemplo de lo más común, las <strong>copias de seguridad de una página web</strong>. En muchos <a href="http://es.wikipedia.org/wiki/Hosting#Alojamiento_compartido_.28shared_hosting.29">hosting compartidos</a> la única posibilidad de transferir archivos con el servidor es a través de una cuenta <strong>FTP</strong>. Empleando este script, podemos crear un directorio en local donde haremos las copias de seguridad de los ficheros de la web y luego sincronizarlo automáticamente todos los días, descargando únicamente los ficheros que han cambiado. Con esto tendremos no solo el directorio actualizado diariamente, si no que además dispondremos de una copia de seguridad por cada uno de los siete días anteriores para poder corregir cualquier problema ocurrido entre esas fechas. Configurar algo así es realmente sencillo, únicamente tendríamos que cambiar los valores incorporados dentro del script por los que necesitamos y luego programar su ejecución diaria con cron.</p> <p>Para una introducción más detallada, instrucciones de ejecución, control de versiones y enlaces para la descarga, acudir al repositorio del script en <a href="http://github.com/joedicastro/lftp-mirror">github</a>.</p> <p>Un extracto del código de <strong>lftp-mirror.py</strong>:</p> <div class="codehilite"><pre><span class="k">def</span> <span class="nf">mirror</span><span class="p">(</span><span class="n">args</span><span class="p">,</span> <span class="n">log</span><span class="p">):</span> <span class="sd">&quot;&quot;&quot;Mirror the directories.&quot;&quot;&quot;</span> <span class="n">user</span> <span class="o">=</span> <span class="s">&#39;&#39;</span> <span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">anonymous</span> <span class="k">else</span> <span class="s">&#39; &#39;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">login</span><span class="p">)</span> <span class="n">local</span><span class="p">,</span> <span class="n">remote</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">normpath</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">local</span><span class="p">),</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">normpath</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">remote</span><span class="p">)</span> <span class="n">port</span> <span class="o">=</span> <span class="s">&#39;-p {0}&#39;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">port</span><span class="p">)</span> <span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">port</span> <span class="k">else</span> <span class="s">&#39;&#39;</span> <span class="n">include</span> <span class="o">=</span> <span class="s">&#39; --include-glob {0}&#39;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">inc_glob</span><span class="p">)</span> <span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">inc_glob</span> <span class="k">else</span> <span class="s">&#39;&#39;</span> <span class="n">exclude</span> <span class="o">=</span> <span class="s">&#39; --exclude-glob {0}&#39;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">exc_glob</span><span class="p">)</span> <span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">exc_glob</span> <span class="k">else</span> <span class="s">&#39;&#39;</span> <span class="n">url</span> <span class="o">=</span> <span class="s">&#39;http://joedicastro.com&#39;</span> <span class="n">msg</span> <span class="o">=</span> <span class="s">&#39;Connected to {1} as {2}{0}&#39;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">linesep</span><span class="p">,</span> <span class="n">args</span><span class="o">.</span><span class="n">site</span><span class="p">,</span> <span class="s">&#39;anonymous&#39;</span> <span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">anonymous</span> <span class="k">else</span> <span class="n">args</span><span class="o">.</span><span class="n">login</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span> <span class="n">msg</span> <span class="o">+=</span> <span class="s">&#39;Mirror {0} to {1}&#39;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">local</span> <span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">reverse</span> <span class="k">else</span> <span class="n">remote</span><span class="p">,</span> <span class="n">remote</span> <span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">reverse</span> <span class="k">else</span> <span class="n">local</span><span class="p">)</span> <span class="n">log</span><span class="o">.</span><span class="n">header</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">msg</span><span class="p">)</span> <span class="n">log</span><span class="o">.</span><span class="n">time</span><span class="p">(</span><span class="s">&#39;Start time&#39;</span><span class="p">)</span> <span class="n">notify</span><span class="p">(</span><span class="s">&#39;Mirroring with {0}...&#39;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">site</span><span class="p">),</span> <span class="s">&#39;sync&#39;</span><span class="p">)</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">(</span><span class="n">local</span><span class="p">):</span> <span class="n">os</span><span class="o">.</span><span class="n">mkdir</span><span class="p">(</span><span class="n">local</span><span class="p">)</span> <span class="n">log</span><span class="o">.</span><span class="n">list</span><span class="p">(</span><span class="s">&#39;Created new directory&#39;</span><span class="p">,</span> <span class="n">local</span><span class="p">)</span> <span class="n">os</span><span class="o">.</span><span class="n">chdir</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">local</span><span class="p">,</span> <span class="n">os</span><span class="o">.</span><span class="n">pardir</span><span class="p">))</span> <span class="c"># create the script file to import with lftp</span> <span class="n">scp_args</span> <span class="o">=</span> <span class="p">(</span><span class="s">&#39;-vvv&#39;</span> <span class="o">+</span> <span class="n">args</span><span class="o">.</span><span class="n">erase</span> <span class="o">+</span> <span class="n">args</span><span class="o">.</span><span class="n">newer</span> <span class="o">+</span> <span class="n">args</span><span class="o">.</span><span class="n">parallel</span> <span class="o">+</span> <span class="n">args</span><span class="o">.</span><span class="n">reverse</span> <span class="o">+</span> <span class="n">args</span><span class="o">.</span><span class="n">del_first</span> <span class="o">+</span> <span class="n">args</span><span class="o">.</span><span class="n">depth_first</span> <span class="o">+</span> <span class="n">args</span><span class="o">.</span><span class="n">no_empty_dir</span> <span class="o">+</span> <span class="n">args</span><span class="o">.</span><span class="n">no_recursion</span> <span class="o">+</span> <span class="n">args</span><span class="o">.</span><span class="n">dry_run</span> <span class="o">+</span> <span class="n">args</span><span class="o">.</span><span class="n">use_cache</span> <span class="o">+</span> <span class="n">args</span><span class="o">.</span><span class="n">del_source</span> <span class="o">+</span> <span class="n">args</span><span class="o">.</span><span class="n">missing</span> <span class="o">+</span> <span class="n">args</span><span class="o">.</span><span class="n">existing</span> <span class="o">+</span> <span class="n">args</span><span class="o">.</span><span class="n">loop</span> <span class="o">+</span> <span class="n">args</span><span class="o">.</span><span class="n">size</span> <span class="o">+</span> <span class="n">args</span><span class="o">.</span><span class="n">time</span> <span class="o">+</span> <span class="n">args</span><span class="o">.</span><span class="n">no_perms</span> <span class="o">+</span> <span class="n">args</span><span class="o">.</span><span class="n">no_umask</span> <span class="o">+</span> <span class="n">args</span><span class="o">.</span><span class="n">no_symlinks</span> <span class="o">+</span> <span class="n">args</span><span class="o">.</span><span class="n">suid</span> <span class="o">+</span> <span class="n">args</span><span class="o">.</span><span class="n">chown</span> <span class="o">+</span> <span class="n">args</span><span class="o">.</span><span class="n">dereference</span> <span class="o">+</span> <span class="n">exclude</span> <span class="o">+</span> <span class="n">include</span><span class="p">)</span> <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s">&#39;ftpscript&#39;</span><span class="p">,</span> <span class="s">&#39;w&#39;</span><span class="p">)</span> <span class="k">as</span> <span class="n">script</span><span class="p">:</span> <span class="n">lines</span> <span class="o">=</span> <span class="p">(</span><span class="s">&#39;open {0}ftp://{1} {2}&#39;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">secure</span><span class="p">,</span> <span class="n">args</span><span class="o">.</span><span class="n">site</span><span class="p">,</span> <span class="n">port</span><span class="p">),</span> <span class="s">&#39;user {0}&#39;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">user</span><span class="p">),</span> <span class="s">&#39;mirror {0} {1} {2}&#39;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">scp_args</span><span class="p">,</span> <span class="n">local</span> <span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">reverse</span> <span class="k">else</span> <span class="n">remote</span><span class="p">,</span> <span class="n">remote</span> <span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">reverse</span> <span class="k">else</span> <span class="n">local</span><span class="p">),</span> <span class="s">&#39;exit&#39;</span><span class="p">)</span> <span class="n">script</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">linesep</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">lines</span><span class="p">))</span> <span class="c"># mirror</span> <span class="n">cmd</span> <span class="o">=</span> <span class="p">[</span><span class="s">&#39;lftp&#39;</span><span class="p">,</span> <span class="s">&#39;-d&#39;</span><span class="p">,</span> <span class="s">&#39;-f&#39;</span><span class="p">,</span> <span class="n">script</span><span class="o">.</span><span class="n">name</span><span class="p">]</span> <span class="n">sync</span> <span class="o">=</span> <span class="n">Popen</span><span class="p">(</span><span class="n">cmd</span><span class="p">,</span> <span class="n">stdout</span><span class="o">=</span><span class="n">PIPE</span><span class="p">,</span> <span class="n">stderr</span><span class="o">=</span><span class="p">{</span><span class="bp">True</span><span class="p">:</span><span class="n">STDOUT</span><span class="p">,</span> <span class="bp">False</span><span class="p">:</span><span class="bp">None</span><span class="p">}[</span><span class="n">args</span><span class="o">.</span><span class="n">quiet</span><span class="p">])</span> <span class="c"># end mirroring</span> <span class="n">log</span><span class="o">.</span><span class="n">list</span><span class="p">(</span><span class="s">&#39;lftp output&#39;</span><span class="p">,</span> <span class="s">&#39;&#39;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">sync</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">readlines</span><span class="p">()))</span> <span class="c"># compress the dir and create a .gz file with date</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">args</span><span class="o">.</span><span class="n">reverse</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">args</span><span class="o">.</span><span class="n">no_compress</span><span class="p">:</span> <span class="n">notify</span><span class="p">(</span><span class="s">&#39;Compressing folder...&#39;</span><span class="p">,</span> <span class="s">&#39;info&#39;</span><span class="p">)</span> <span class="n">log</span><span class="o">.</span><span class="n">list</span><span class="p">(</span><span class="s">&#39;Rotate compressed copies&#39;</span><span class="p">,</span> <span class="n">compress</span><span class="p">(</span><span class="n">local</span><span class="p">))</span> <span class="c"># end compress</span> <span class="n">gz_size</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">([</span><span class="n">get_size</span><span class="p">(</span><span class="n">gz</span><span class="p">)</span> <span class="k">for</span> <span class="n">gz</span> <span class="ow">in</span> <span class="n">glob</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="s">&#39;{0}*.gz&#39;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">local</span><span class="p">))])</span> <span class="n">log_size</span> <span class="o">=</span> <span class="n">get_size</span><span class="p">(</span><span class="n">log</span><span class="o">.</span><span class="n">filename</span><span class="p">)</span> <span class="k">if</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">(</span><span class="n">log</span><span class="o">.</span><span class="n">filename</span><span class="p">)</span> <span class="k">else</span> <span class="mi">0</span> <span class="n">local_size</span> <span class="o">=</span> <span class="n">get_size</span><span class="p">(</span><span class="n">local</span><span class="p">)</span> <span class="n">size</span> <span class="o">=</span> <span class="n">best_unit_size</span><span class="p">(</span><span class="n">local_size</span> <span class="o">+</span> <span class="n">gz_size</span> <span class="o">+</span> <span class="n">log_size</span><span class="p">)</span> <span class="n">log</span><span class="o">.</span><span class="n">block</span><span class="p">(</span><span class="s">&#39;Disk space used&#39;</span><span class="p">,</span> <span class="s">&#39;{0:&gt;76.2f} {1}&#39;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">size</span><span class="p">[</span><span class="s">&#39;s&#39;</span><span class="p">],</span> <span class="n">size</span><span class="p">[</span><span class="s">&#39;u&#39;</span><span class="p">]))</span> <span class="n">log</span><span class="o">.</span><span class="n">time</span><span class="p">(</span><span class="s">&#39;End Time&#39;</span><span class="p">)</span> <span class="n">log</span><span class="o">.</span><span class="n">free</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">linesep</span> <span class="o">*</span> <span class="mi">2</span><span class="p">)</span> <span class="n">log</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="bp">True</span><span class="p">)</span> <span class="n">os</span><span class="o">.</span><span class="n">remove</span><span class="p">(</span><span class="n">script</span><span class="o">.</span><span class="n">name</span><span class="p">)</span> </pre></div> <p>Para obtener el código completo, ir al <a href="https://github.com/joedicastro/lftp-mirror/blob/master/src/lftp_mirror.py">fichero fuente</a>.</p>joe di castroSun, 19 Dec 2010 14:58:00 +0100http://joedicastro.com/sincronizar-una-carpeta-local-y-una-remota-a-traves-de-ftp-lftp-mirror.htmllinuxpythonscriptlftpftp mirrorsincronizarlftp-mirrorftp syncftp