Add: New posts from the legacy blog
|
@ -0,0 +1,24 @@
|
||||||
|
---
|
||||||
|
title: "Apache: Alta carga de CPU"
|
||||||
|
date: 2011-08-09T10:27:52Z
|
||||||
|
draft: false
|
||||||
|
tags: [ "sysadmin" ]
|
||||||
|
image:
|
||||||
|
---
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Llevo desde el fin de semana con la mosca detrás de la oreja. Uno de los servidores que administro ha visto incrementada de forma inexperada su carga media de CPU sin motivo aparente. Donde el <em>load average</em> normal de 1 minuto variaba entre <em>0.40</em> y <em>0.80</em> de repente suponía cargas tan elevadas como <em>60</em> o <em>100</em> unidades.</p>
|
||||||
|
<p>
|
||||||
|
En esos momentos puntuales que llegaban a dejar la máquina <em>zombie</em> el proceso que abarcaba un consumo de entre el <em>60%</em> y el <em>90%</em> de CPU era <em>apache2</em>. Intrigante que ni <em>error.log</em> ni <em>slow-queries.log</em> de MySQL (lo que normalmente suele ser cuello de botella) dieran ninguna pista.<!--more--></p>
|
||||||
|
<p>
|
||||||
|
<em>Analytics</em> tampoco decía nada de un aumento considerable de visitas -más bien al contrario- y el resto de herramientas de monitorización parecían cómplices del problema (¡tener <em>tools</em> para ésto!).</p>
|
||||||
|
<p>
|
||||||
|
En un alarde de desesperación y viendo más o menos por dónde podían venir los tiros -a través del método de <em>prueba y error</em>- localicé el <em>VirtualHost</em> que estaba dando problemas, un código no auditado y afamado por su ausente optimización. Vamos que ya tenía precedentes, aunque ninguno de esta índole.</p>
|
||||||
|
<p>
|
||||||
|
Después de desactivar distintas partes del dominio en más pruebas esperando focalizar el error en algún script concreto, la conclusión es que tenía que ser algo que se incluía en todos los archivos, algo común a toda la web. Así que miramos los <em>includes</em> comunes (si, PHP) y llegamos a la conclusión de que era <strong>problema de sesiones</strong>, sin exculpar al programador.</p>
|
||||||
|
<p>
|
||||||
|
Una de las primeras acciones de todos los scripts es definir el tiempo de sesión a <strong>3 días</strong>, definir el directorio donde se guardarán los archivos de sesión y arrancar la sesión. Probablemente el programador no esperaba tener <em>60k</em> visitas y más de <em>200k</em> páginas vistas en los 3 días que dura cada sesión, pero está claro que para <em>apache2</em> suponía un problema el acceso de lectura/escritura a un mismo directorio con más de <em>90k</em> archivos.</p>
|
||||||
|
<p>
|
||||||
|
La solución inicial -a falta de más tiempo para cambiar el sistema de sesiones a <em>memcache</em>, <em>Redis</em> o cualquier otra solución basada en RAM- era sencilla, reducir el tiempo de sesión, vaciar el directorio donde se guardan las sesiones, comprobar de nuevo el <em>load average</em> durante un intervalo representativo y una tarea programada que monitorice el contenido de ese directorio. Después de todo la carga se ha vuelto a estabilizar entre <em>0.30</em> y <em>0.40</em>.</p>
|
||||||
|
<p>
|
||||||
|
No sé si estoy para moralejas porque la solución es temporal, pero como <a href="http://twitter.com/#!/r0sk/status/100688297642311681">lo prometido es deuda</a> me gustaría terminar esta anotación advirtiendo a todo el mundo sobre la fiabilidad del código no auditado, el uso moderado de las sesiones y las endorfinas que libera uno cuando consigue resolver algo <em>"así"</em>.</p>
|
|
@ -0,0 +1,54 @@
|
||||||
|
---
|
||||||
|
title: "Apache + Squid + Nginx"
|
||||||
|
date: 2011-10-28T23:50:34Z
|
||||||
|
draft: false
|
||||||
|
tags: [ ]
|
||||||
|
image:
|
||||||
|
---
|
||||||
|
|
||||||
|
<p>
|
||||||
|
¡Menuda combinación!. A decir verdad empecé jugando un poco con el maldito <em>slowloris</em> y al final acabé montando este batiburrillo de servidores, primero para <em>paliar</em> el efecto del dichoso gusano y segundo para preparar el servidor para la inminente nueva versión del blog - <em>que me gustaría estrenar con el décimo aniversario de este humilde rinconcito</em> -.</p>
|
||||||
|
<p>
|
||||||
|
En un esquema inicial analógico de esos que tantos nos gustan podemos ver la pirula (pido perdón de antemano por la calidad de la foto):<!--more--></p>
|
||||||
|
<p style="text-align: center;">
|
||||||
|
<img alt="" src="gallery/apache-squid-nginx.jpg" style="width: 300px; height: 305px;" /></p>
|
||||||
|
<p>
|
||||||
|
Probablemente no sea necesario tener 3 servidores para este entorno, seguro que puedo poner a escuchar Nginx en el puerto 80 y redirigir todo el tráfico dinámico al 81 a la vez que responde al tráfico estático de ciertos subdominios (static*) de forma automática y en la misma instancia. Pero tenía la tarde libre y me apetecía probar una configuración <em>rara</em> con Squid.</p>
|
||||||
|
<p>
|
||||||
|
<strong>Apache</strong></p>
|
||||||
|
<p>
|
||||||
|
La configuración de Apache es de lo más sencilla, lo único que he hecho ha sido ponerlo a escuchar en el puerto 81 por defecto, y a todos sus <em>Virtualhosts</em> también. No había mucho más que tocar puesto que ya tenía el <em>mod_php</em> y todas las dependencias instaladas.</p>
|
||||||
|
<p>
|
||||||
|
<strong>Nginx</strong></p>
|
||||||
|
<p>
|
||||||
|
Nunca había jugado con él y a primera vista me gustó la sencillez de sus archivos de configuración. Tampoco tenía que hacer gran cosa, ponerlo a escuchar en el puerto 82 y poco más puesto que sólo serviría contenido estático. Creé los <em>Virtualhosts</em> que atenderían las peticiones estáticas, los activé vía enlace simbólico a <em>sites-enabled</em> y poco más. La configuración de un <em>Virtualhost</em> cualquiera:</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
server {
|
||||||
|
listen 82;
|
||||||
|
server_name static.dominio.com;
|
||||||
|
root /home/www/dominio/sd/static/;
|
||||||
|
autoindex on;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<strong>Squid</strong></p>
|
||||||
|
<p>
|
||||||
|
Aquí vino la diversión, ¿cómo decirle a Squid que balanceara el tráfico dinámico al puerto 81 y el estático al puerto 82?. Después de leer la documentación y hacer varias pruebas con <em>cache_peer</em>, y <em>cache_peer_domain</em> he llegado a la conclusión que la configuración "<em>buena</em>" es la siguiente:</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
cache_peer ip parent 81 0 no-query originserver name=server_1
|
||||||
|
cache_peer_domain server_1 dominio.com www.dominio.com
|
||||||
|
cache_peer ip parent 82 0 no-query originserver name=server_2
|
||||||
|
cache_peer_domain server_2 static.dominio.com
|
||||||
|
```
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Como se puede ver, habría que cambiar <em>ip</em> por la ip pública correspondiente y los <em>fqdn</em> por los reales.</p>
|
||||||
|
<p>
|
||||||
|
<strong>Conclusión</strong></p>
|
||||||
|
<p>
|
||||||
|
He pasado una tarde agradable en compañía de mis amigos los servidores web. No, en serio, al final he conseguido reproducir el escenario que me había propuesto, (aún sabiendo que se podría mejorar), he frenado los sockets incompletos de Apache y he preparado el servidor para servir estáticos de forma independiente del contenido dinámico (que en breve cambiará de PHP a Python + Django).</p>
|
||||||
|
<p>
|
||||||
|
No ha estado mal :).</p>
|
After Width: | Height: | Size: 112 KiB |
|
@ -0,0 +1,33 @@
|
||||||
|
---
|
||||||
|
title: "Bash shellshock bug update: loving Fabric"
|
||||||
|
date: 2014-09-26T08:36:17Z
|
||||||
|
draft: false
|
||||||
|
tags: [ ]
|
||||||
|
image: shellshock.jpg
|
||||||
|
---
|
||||||
|
|
||||||
|
<p>When you wake up in the morning with a new like the <a href="http://www.troyhunt.com/2014/09/everything-you-need-to-know-about.html">bash shellshock bug</a> you should get away your lazy part and update all your servers to new patched version. This is ssh every single server, check the vulnerability, update the package, check again that all is ok and move on to next host.</p>
|
||||||
|
<p>If you have tons of servers the task becomes tedious. To get a bit of fun you can choose to develop a <a href="http://www.fabfile.org/">Fabric</a> module that makes the job instead of you. The fun:</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
def make_me_a_bashandwich():
|
||||||
|
puts(red('Checking bash vulnerability'))
|
||||||
|
out = sudo("env x='() { :;}; echo vulnerable' bash -c 'echo this is a test'")
|
||||||
|
if "vulnerable" in out:
|
||||||
|
puts(red('Vulnerable'))
|
||||||
|
puts(red('Updating bash'))
|
||||||
|
sudo('apt-get update')
|
||||||
|
sudo('apt-get install --only-upgrade %s' % package)
|
||||||
|
else:
|
||||||
|
puts(green('OK'))
|
||||||
|
```
|
||||||
|
|
||||||
|
<p>Your only job is to run that piece of code in all your servers and take a deep look to the screen:</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
$ fab -H server1,server2,server3 -u root make_me_a_bashandwich
|
||||||
|
```
|
||||||
|
|
||||||
|
<p><img style="display: block; margin-left: auto; margin-right: auto; width: 100%; height: auto;" src="gallery/bash-shellshock.png" alt="" /></p>
|
||||||
|
<p>In my humble opinion, it's more fun than the old way, isn't it?</p>
|
||||||
|
<p><span>Image credit: <em>Robert Graham</em>, </span><a href="https://twitter.com/ErrataRob/status/514839781881032705" target="_blank">Twitter</a>.</p>
|
After Width: | Height: | Size: 70 KiB |
|
@ -0,0 +1,51 @@
|
||||||
|
---
|
||||||
|
title: "Cambiar kernel en ovh con grub (nfs)"
|
||||||
|
date: 2013-03-11T07:59:44Z
|
||||||
|
draft: false
|
||||||
|
tags: [ ]
|
||||||
|
image:
|
||||||
|
---
|
||||||
|
|
||||||
|
<p>El principal problema que existe para montar un servidor NFS en <span class="search_hit">OVH</span> es que los kernels que habilitan por defecto (<span class="search_hit">OVH</span> + grsec), además de venir con grsec como su propia descripción indica, no tienen soporte para instalar módulos de <span class="search_hit">kernel</span>. De forma que al intentar instalar el módulo para NFS nos dará un fantástico y maravilloso error. Por defecto tenemos el siguiente escenario:</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
# uname -a
|
||||||
|
Linux ns302399.ovh.net 3.2.13-grsec-xxxx-grs-ipv6-64 #1 SMP Thu Mar 29 09:48:59 UTC 2012 x86_64 GNU/Linux
|
||||||
|
```
|
||||||
|
|
||||||
|
<p>Así que no queda otra que reemplazar el <span class="search_hit">kernel</span> por uno que ya lleve el módulo de nfs <em>builtin. </em>Llegados a este punto tenemos varias opciones: instalar un <span class="search_hit">kernel</span> desde fuentes, modificar el ya instalado o bajarnos uno de los kernels ”<em>-filer</em>” de <span class="search_hit">OVH</span>. Los kernels "<em>-filer</em>" están pensados para este tipo de servidores de ficheros. Por sencillez optaremos por esta tercera opción.</p>
|
||||||
|
<p>Primeramente descargamos las imágenes del nuevo kernel que vamos a instalar desde el ftp oficial de <span class="search_hit">OVH</span> (<a class="urlextern" title="ftp://ftp.ovh.net/made-in-ovh/bzImage/" rel="nofollow" href="ftp://ftp.ovh.net/made-in-ovh/bzImage/">ftp://ftp.<span class="search_hit">ovh</span>.net/made-in-<span class="search_hit">ovh</span>/bzImage/</a>):</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
# cd /boot/
|
||||||
|
# wget ftp://ftp.ovh.net/made-in-ovh/bzImage/3.2.13/bzImage-3.2.13-xxxx-grs-ipv6-64-filer
|
||||||
|
# wget ftp://ftp.ovh.net/made-in-ovh/bzImage/3.2.13/System.map-3.2.13-xxxx-grs-ipv6-64-filer
|
||||||
|
# ls
|
||||||
|
bzImage-3.2.13-xxxx-grs-ipv6-64 System.map-3.2.13-xxxx-grs-ipv6-64
|
||||||
|
bzImage-3.2.13-xxxx-grs-ipv6-64-filer System.map-3.2.13-xxxx-grs-ipv6-64-filer
|
||||||
|
grub
|
||||||
|
```
|
||||||
|
|
||||||
|
<p>Ahora tendremos que modificar <em>grub</em> para que al arrancar la máquina lo haga con el nuevo <span class="search_hit">kernel</span>. Lo hacemos editando el fichero <em>/etc/grub.d/06_OVHkernel</em>, en la línea que antes hacía referencia al <span class="search_hit">kernel </span><em>bzImage*64</em> ahora le agregamos un <em>-filer</em> y guardamos los cambios:</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
OS="Debian GNU/Linux"
|
||||||
|
LINUX_ROOT_DEVICE=${GRUB_DEVICE}
|
||||||
|
linux=`ls -1 -t /boot/bzImage*64-filer 2>/dev/null | head -n1`
|
||||||
|
```
|
||||||
|
|
||||||
|
<p>Una vez tenemos el nuevo <span class="search_hit">kernel</span> referenciado en el archivo de plantilla <em>06_OVHkernel</em>, ejecutamos el <em>grub-mkconfig</em> para hacer efectiva la configuración, es recomendable hacer antes una copia de seguridad de la configuración anterior (sabe más el diablo por viejo que por diablo):</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
# cp /boot/grub/grub.cfg /boot/grub/grub.cfg.old
|
||||||
|
# grub-mkconfig > /boot/grub/grub.cfg
|
||||||
|
```
|
||||||
|
|
||||||
|
<p>Finalmente toca reiniciar, cruzar los dedos y, si todo ha ido correctamente, comprobar que ya tenemos el nuevo <span class="search_hit">kernel</span> listo para hacer funcionar nuestro servidor NFS:</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
# uname -a
|
||||||
|
Linux ns302399.ovh.net 3.2.13-grsec-xxxx-grs-ipv6-64-filer #1 SMP Thu Mar 29 11:26:19 UTC 2012 x86_64 GNU/Linux
|
||||||
|
```
|
||||||
|
|
||||||
|
<p>No es complicado, pero es engañoso pensar en montar un NFS sobre OVH como una trivial tarea de poco más de 5 minutos. </p>
|
|
@ -0,0 +1,30 @@
|
||||||
|
---
|
||||||
|
title: "¿Codeigniter-Reactor + esteroides?"
|
||||||
|
date: 2011-02-04T20:09:38Z
|
||||||
|
draft: false
|
||||||
|
tags: [ "php", "code" ]
|
||||||
|
image:
|
||||||
|
---
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Es algo que todavía no llego a entender ni asimilar. He pasado la mayor parte de la tarde para configurar la nueva versión 2.0 de <a href="https://github.com/philsturgeon/codeigniter-reactor/">CodeIgniter-Reactor</a> con varias librerías que, para mi forma de desarrollar, son indispensables en cualquier framework de programación orientado a web:</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href="https://bitbucket.org/wiredesignz/codeigniter-modular-extensions-hmvc">HMVC</a>: Gracias a la librería de <em>Wiredesignz</em> podemos ordenar nuestro código en módulos y simplificar la lógica de la aplicación.</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://github.com/pyrocms/pyrocms/tree/master/system/pyrocms/modules/modules">ModulesModule</a>: Ahora que todo será un módulo, no vendría mal un módulo encargado de ejecutar las tareas más comunes de los módulos (instalar, desinstalar, actualizar...). Un módulo de módulos.</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://github.com/pyrocms/pyrocms/tree/master/system/pyrocms/modules/settings">SettingsModule</a>: No me gusta cargar la configuración desde ficheros <em>config/*</em>, por comodidad y para que el usuario pueda cambiar cualquier parámetro ajustable de una aplicación, prefiero hacerlo en base de datos y cachearlo a disco si es necesario. Me quedo con la librería de <a href="http://pyrocms.com">PyroCMS</a>.</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://github.com/pyrocms/pyrocms/tree/master/system/pyrocms/modules/themes">ThemesModule</a>: Otra característica imprescindible sería tener una aplicación <em>themeable</em> y que desde un interfaz de administración se pueda cambiar tranquilamente el aspecto de la misma. Para ello podemos hacer uso de este módulo capaz de instalar y desinstalar themes.</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://github.com/pyrocms/pyrocms/blob/master/system/pyrocms/libraries/Template.php">TemplateLibrary</a> y <a href="https://github.com/pyrocms/pyrocms/blob/master/system/pyrocms/libraries/Tags.php">TagsLibrary</a>: Una vez hemos decidido hacer la aplicación <em>modular</em> y <em>themeable</em> siguiendo el patrón <em>MVC</em>, un buen lenguaje de <em>template</em> para que los diseñadores no se vuelvan locos con el lenguaje dinámico sería un punto más.</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://github.com/philsturgeon/codeigniter-migrations/">MigrationsLibrary</a>: Una vez lo pruebas se convierte en <em>musthave</em>. Se trata de una librería para hacer migraciones de versiones en base de datos. Gestiona los cambios entre versiones de forma sencilla.</li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
<!--more-->Como podéis ver siguiendo los enlaces, casi todo lo necesario para montar el esquema inicial de un framework PHP decente está en los repositorios de <a href="https://www.philsturgeon.co.uk/">Phil Sturgeon</a> (<a href="https://bitbucket.org/philsturgeon/">bitbucket</a> y <a href="https://github.com/philsturgeon/">github</a>). Su <a href="http://www.pyrocms.org">PyroCMS</a> cuenta con todas estas características pero tiene muchas otras librerías que no son necesarias -a mi modo de ver- para tomarlo como base para cualquier otro tipo de proyecto.</p>
|
||||||
|
<p>
|
||||||
|
Demasiado complejo de ensamblar, una vez he conseguido hacerlo funcionar más o menos todo ya no tengo ganas de empezar a programar la aplicación que tenía en mente. ¿Por qué no hay *nada* con todas esas funciones de serie en PHP?. Si, ya sé que con <a href="http://www.rubyonrails.org/">Ruby on Rails</a> o <a href="http://djangoproject.org">Django</a> hubiera tenido todas estas características -y más- en un par de comandos, pero tenía que desengañarme.</p>
|
||||||
|
<p>
|
||||||
|
En este sentido PHP sigue estando tan verde como hace años, me hace perder demasiado tiempo aún conociendo la estructura del lenguaje. Todos sabemos como es la curva de aprendizaje al principio, pero -aunque duela- ha llegado el momento de invertir en otra cosa. <em>Shame on me</em>?</p>
|
|
@ -0,0 +1,255 @@
|
||||||
|
---
|
||||||
|
title: "Desarrollo web con Python: Flask"
|
||||||
|
date: 2012-02-15T18:47:34Z
|
||||||
|
draft: false
|
||||||
|
tags: [ ]
|
||||||
|
image:
|
||||||
|
---
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Hace algún tiempo empecé a utilizar <a href="http://flask.pocoo.org/">Flask</a> para un proyecto personal. Flask es un mini framework de desarrollo en python que me ha convencido desde el principio por su sencillez. Intentaré exponer un pequeño ejemplo para que os hagáis una idea de cómo funciona.</p>
|
||||||
|
<p style="text-align: center; ">
|
||||||
|
<img alt="" src="gallery/flask.png" style="width: 200px; height: 78px; " /></p>
|
||||||
|
<p>
|
||||||
|
Y como <a href="http://toporojo.es/blog/">Álex</a> ha escrito un <a href="http://toporojo.es/blog/2012/02/11/desarrollo-web-con-python-pylons/">completo tutorial sobre Pylons/Pyramid</a> con un ejemplo de aplicación, no quería ser menos y explicar mis aventuras y desventuras con esta otra pequeña joya de Python así que allá vamos.</p>
|
||||||
|
<p>
|
||||||
|
<!--more--></p>
|
||||||
|
<p>
|
||||||
|
<strong>Instalación </strong></p>
|
||||||
|
<p>
|
||||||
|
Flask está basado en <a href="http://werkzeug.pocoo.org/">Werkzeug</a>, una librería WSGI para Python. Si usamos <a href="http://pypi.python.org/pypi/pip">pip</a> y <a href="http://pypi.python.org/pypi/virtualenv">virtualenv</a> para crear el entorno virtual y comenzar a trabajar (situación que <a href="http://www.userlinux.net/django-virtualenv-pip.html">ya he explicado en su día</a>) vemos que las dependencias de Flask son mínimas:</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
$ sudo easy_install pip
|
||||||
|
$ pip install virtualenv
|
||||||
|
$ mkdir -p flaskblog/{src,env}
|
||||||
|
$ cd flaskblog/
|
||||||
|
$ virtualenv --distribute --no-site-packages env/
|
||||||
|
$ pip -E env/ install flask
|
||||||
|
Downloading Flask-0.8.tar.gz
|
||||||
|
Downloading/unpacking Werkzeug>=0.6.1 (from flask)
|
||||||
|
Downloading/unpacking Jinja2>=2.4 (from flask)
|
||||||
|
Installing collected packages: flask, Jinja2, Werkzeug
|
||||||
|
$ pip -E env/ install flask-wtf
|
||||||
|
Downloading Flask-WTF-0.5.2.tar.gz
|
||||||
|
Downloading/unpacking WTForms (from flask-wtf)
|
||||||
|
Installing collected packages: flask-wtf, WTForms
|
||||||
|
$ pip -E env/ install flask-sqlalchemy
|
||||||
|
```
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Una vez creado el entorno virtual instalamos <em>Flask</em>, que depende de <em>Werkzeug</em> y de <em>Jinja2</em> como sistema de templates. También instalamos <a href="http://packages.python.org/Flask-WTF/">Flask-WTF</a> para tener integración con <em>WTForms</em> y hacer más sencillo el uso de formularios con funciones avanzadas (csrf, etc...). Y por último <em>Flask-SQLAlchemy </em>como complemento ORM para la base de datos.</p>
|
||||||
|
<p>
|
||||||
|
<strong>Requirements y configuración</strong></p>
|
||||||
|
<p>
|
||||||
|
A continuación vamos a entrar por primera vez en el entorno y crear el fichero de requisitos - yo normalmente le llamo <em>requirements.txt</em> - para poder regenerar el entorno virtual tantas veces y en tantas máquinas como queramos:</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
$ source env/bin/activate
|
||||||
|
(env)$ pip freeze > src/requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Si luego queremos regenerar el entorno a partir del fichero lo haremos ejecutando el siguiente comando:</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
$ pip install -E nuevo_env/ -r src/requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Empezamos a montar el esqueleto de la aplicación dentro del directorio <em>src/ </em>creando un directorio para los templates, como queremos que nuestra aplicación pueda tener varios templates a elegir, creamos también el "por defecto":</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
(env)$ mkdir -p templates/default/
|
||||||
|
```
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Ya que estamos empezando una aplicación desde cero nos gustaría tener un mínimo de configuración para la misma así que vamos a usar un fichero para que guarde ciertos valores variables:</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
(env)$ cat config.py
|
||||||
|
import os
|
||||||
|
DEBUG = True
|
||||||
|
_basedir = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
DATA_PATH = os.path.join(_basedir, 'data')
|
||||||
|
DEFAULT_TPL = 'default'
|
||||||
|
USERNAME = 'admin'
|
||||||
|
PASSWORD = 'default'
|
||||||
|
SECRET_KEY = 'devel secret key'
|
||||||
|
URL = 'http://localhost:5000/'
|
||||||
|
TITLE = 'OurApplication'
|
||||||
|
VERSION = '0.1'
|
||||||
|
LANG = 'es'
|
||||||
|
LANG_DIRECTION = 'ltr'
|
||||||
|
YEAR = '2012'
|
||||||
|
del os
|
||||||
|
```
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Con la configuración y el entorno listos, lo siguiente será empezar la aplicación propiamente dicha, así que manos a la obra.</p>
|
||||||
|
<p>
|
||||||
|
<strong>Primeros pasos con Flask</strong></p>
|
||||||
|
<p>
|
||||||
|
Vamos a reducir el grueso de la aplicación a un solo archivo, le llamaremos <em>blog.py </em>- ya sé que no suena muy original pero imagino que será entendible -. Nuestro <em>blog.py</em> será el controlador principal, en él incluiremos de forma excepcional el modelo, los formularios y los métodos que se encargarán de la lógica y de llamar a las plantillas. Para empezar por el principio definimos el archivo como una aplicación Flask y cargamos la configuración que antes hemos creado:</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
OurApplication
|
||||||
|
~~~~~~~~~~~~~~
|
||||||
|
:copyright: (c) 2011 by Oscar M. Lage.
|
||||||
|
:license: BSD, see LICENSE for more details.
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
from flask import Flask, render_template, request, redirect
|
||||||
|
from werkzeug.routing import Rule
|
||||||
|
# Flask application and config
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.config.from_object('config')
|
||||||
|
# Middleware to serve the static files
|
||||||
|
from werkzeug import SharedDataMiddleware
|
||||||
|
import os
|
||||||
|
app.wsgi_app = SharedDataMiddleware(app.wsgi_app, {
|
||||||
|
'/': os.path.join(os.path.dirname(__file__), 'templates', app.config['DEFAULT_TPL'])
|
||||||
|
})
|
||||||
|
# Index
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
return render_template(app.config['DEFAULT_TPL']+'/index.html',
|
||||||
|
conf = app.config)
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app.run()
|
||||||
|
```
|
||||||
|
|
||||||
|
<p>
|
||||||
|
* Lo más raro son esas 3 lineas que hemos incluido en el archivo, una especie de <em>Middleware</em> para poder servir archivos estáticos directamente desde el template seleccionado.</p>
|
||||||
|
<p>
|
||||||
|
Una vez tenemos la aplicación "preparada" hemos de crear la plantilla para que se pueda ejecutar sin errores, para ello dentro del directorio templates/default/ creamos el archivo index.html al que referenciamos en el controlador, el contenido del archivo podría ser algo así:</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
||||||
|
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml"
|
||||||
|
xml:lang="{{ conf['LANG'] }}" lang="{{ conf['LANG'] }}"
|
||||||
|
dir="{{ conf['LANG_DIRECTION'] }}">
|
||||||
|
<head>
|
||||||
|
<title>{% block title %}{% endblock %} [{{ conf['TITLE'] }}]</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="header">
|
||||||
|
<h1>{{ conf['TITLE'] }}</h1>
|
||||||
|
</div>
|
||||||
|
<div id="content">
|
||||||
|
Contenido
|
||||||
|
</div>
|
||||||
|
<div id="footer">
|
||||||
|
<p>{{ conf['TITLE'] }} - {{ conf['YEAR'] }}</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
```
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Y ya podríamos ejecutar por primera vez nuestra aplicación desde consola para luego comprobar que todo está correcto en navegador, así que vamos a ello:</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
$ python blog.py
|
||||||
|
* Running on http://127.0.0.1:5000/
|
||||||
|
* Restarting with reloader
|
||||||
|
```
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Si abrimos el navegador en la url proporcionada (http://127.0.0.1:5000/) obtendremos una pantalla similar a la de la siguiente figura:</p>
|
||||||
|
<p style="text-align: center; ">
|
||||||
|
<img alt="" src="gallery/flask001.png" style="width: 300px; height: 219px; " /></p>
|
||||||
|
<p>
|
||||||
|
<strong>SQLAlchemy</strong></p>
|
||||||
|
<p>
|
||||||
|
Una vez tenemos la base y el esqueleto de la aplicación vamos a incorporar una base de datos para hacerla dinámica. Para ello usaremos <a href="http://packages.python.org/Flask-SQLAlchemy/">Flask-SQLAlchemy</a>. Para trabajar con SQLAlchemy agregamos una nueva variable de configuración para la base de datos:</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
SQLALCHEMY_DATABASE_URI = 'sqlite:///'+ os.path.join(os.path.dirname(__file__), 'database.db')
|
||||||
|
```
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Y le decimos a <em>blog.py</em> el esquema que vamos a utilizar:</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
from flaskext.sqlalchemy import SQLAlchemy
|
||||||
|
...
|
||||||
|
db = SQLAlchemy(app)
|
||||||
|
...
|
||||||
|
# Model
|
||||||
|
class Blog(db.Model):
|
||||||
|
__tablename__ = 'Blog'
|
||||||
|
__mapper_args__ = dict(order_by="date desc")
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
subject = db.Column(db.Unicode(255))
|
||||||
|
author = db.Column(db.Unicode(255))
|
||||||
|
date = db.Column(db.DateTime())
|
||||||
|
content = db.Column(db.Text())
|
||||||
|
```
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Por último creamos la base de datos desde una consola python para poder usarla desde cualquier parte del controlador, tan simple como entrar a python desde el directorio src/ donde tenemos blog.py y ejecutar lo siguiente:</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
>>> from blog import db
|
||||||
|
>>> db.create_all()
|
||||||
|
```
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Ya tenemos el archivo <em>database.db</em> preparado para cargarlo de datos mediante los métodos correspondientes de <em>blog.py</em>, algunos ejemplos:</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
@app.route('/add', methods=['GET','POST'])
|
||||||
|
def add():
|
||||||
|
if request.method == 'POST':
|
||||||
|
post = Blog(request.form['subject'], request.form['content'])
|
||||||
|
db.session.add(post)
|
||||||
|
db.session.commit()
|
||||||
|
return redirect(url_for('index'))
|
||||||
|
return render_template(app.config['DEFAULT_TPL']+'/add.html',
|
||||||
|
conf = app.config)
|
||||||
|
```
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<strong>Flask-WTForms</strong></p>
|
||||||
|
<p>
|
||||||
|
Una vez tenemos preparada la base de datos y el método que agregará nuevos posts tan solo falta crear el formulario que nos permitirá tal funcionalidad y crear el template, poco más de un par de lineas:</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
# Create Form
|
||||||
|
class CreateForm(Form):
|
||||||
|
subject = TextField('Subject', [validators.required()])
|
||||||
|
content = TextAreaField('Content', [validators.required(), validators.Length(min=1)])
|
||||||
|
```
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Y el template con el formulario correspondiente sería el siguiente:</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
<form method="post" action="">
|
||||||
|
<dl>
|
||||||
|
{{ form.csrf }}
|
||||||
|
{{ form.subject.label }} {{ form.subject(style="width:100%") }}
|
||||||
|
{% for error in form.subject.errors %} {{ error }} {% endfor %}
|
||||||
|
<br />
|
||||||
|
{{ form.content.label }} {{form.content(style="height:100px;width:100%") }}
|
||||||
|
{% for error in form.content.errors %} {{ error }} {% endfor %}
|
||||||
|
</dl>
|
||||||
|
<p><input type="submit" value="submit">
|
||||||
|
</form>
|
||||||
|
```
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Y ya tendríamos un formulario para agregar posts totalmente funcional. Entre esta vista, la de un listado de posts y la del detalle de los mismos (ver repositorio más abajo), tendríamos una pequeña aplicación con <em>Flask</em> + <em>SQLAlchemy</em> + <em>WTForms</em>.</p>
|
||||||
|
<p>
|
||||||
|
<strong>Resumiendo</strong></p>
|
||||||
|
<p>
|
||||||
|
Para finalizar, además de las vistas que hemos dicho que faltaban, tendríamos que dar un poco más de colorido a los templates (css, imágenes...), crear un <em>layout.html</em> que se pueda extender desde el resto de plantillas y poner un poco de orden en todo lo explicado, pero como no quiero extenderme mucho más en el artículo, nada mejor que un pequeño repositorio en el que se pueden ir viendo los avances:</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href="https://bitbucket.org/r0sk/flaskblog">Flaskblog (bitbucket)</a></li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
Espero que con este pequeño y humilde ejemplo haya quedado más o menos claro el uso de este framework. Siempre es divertido probar cosas nuevas y cuando se trata de piezas tan sencillas y bien documentadas como las tratadas el placer es doble.</p>
|
After Width: | Height: | Size: 171 KiB |
After Width: | Height: | Size: 160 KiB |
After Width: | Height: | Size: 171 KiB |
|
@ -0,0 +1,10 @@
|
||||||
|
---
|
||||||
|
title: "Disconnecting a bit"
|
||||||
|
date: 2013-06-13T06:52:10Z
|
||||||
|
draft: false
|
||||||
|
tags: [ ]
|
||||||
|
image:
|
||||||
|
---
|
||||||
|
|
||||||
|
<p>Yesterday we had a great afternoon. From time to time it's recommended to turn off the computer world and enjoy with family and friends. Yesterday we did. I was really tired and needing fresh air on my mind.</p>
|
||||||
|
<p>We must repeat, almost, once a week!</p>
|
|
@ -0,0 +1,143 @@
|
||||||
|
---
|
||||||
|
title: "Django and memcache: clear cache keys"
|
||||||
|
date: 2014-02-05T13:35:54Z
|
||||||
|
draft: false
|
||||||
|
tags: [ "django", "code" ]
|
||||||
|
image: web-accelerators-memcached.jpg
|
||||||
|
---
|
||||||
|
|
||||||
|
<p>Let's play <a href="https://www.djangoproject.com/">Django</a> with <a href="http://memcached.org/">Memcached</a>. As the great framework Django is, it's so easy to activate <a href="https://docs.djangoproject.com/en/dev/topics/cache/">any kind of cache</a> in your project. <em>Memcached</em> is one of the options, but you can also work with <em>DatabaseCache</em>, <em>FileBasedCache</em>, <em>LocMemCache</em>, <em>MemcachedCache,</em> <em>DummyCache</em> (a kind of non-cache very useful for devel/test enviroments) or - of course - your own <em>CustomCache</em> if you want.</p>
|
||||||
|
<p><strong>Activating cache</strong></p>
|
||||||
|
<p>It's too easy to activate the cache feature, it's enough to set the preferences in settings, install <a href="https://pypi.python.org/pypi/python-memcached/">python-memcached</a> in your enviroment (in case you will use <em>MemcachedCache</em>), and not much more to do. A couple of examples:</p>
|
||||||
|
<p>1. Basic <em>FileBasedCache</em> settings:</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
# settings.py
|
||||||
|
CACHES = {
|
||||||
|
'default': {
|
||||||
|
'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
|
||||||
|
'LOCATION': '/var/tmp/django_cache',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<p>2. <em>MemcachedCache</em> settings:</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
# settings.py
|
||||||
|
CACHES = {
|
||||||
|
'default': {
|
||||||
|
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
|
||||||
|
'LOCATION': '127.0.0.1:11211',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<p>3. Depending on the enviroment you can use <em>MemcachedCache</em> and <em>DummyCache</em>:</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
# settings.devel.py
|
||||||
|
CACHES = {
|
||||||
|
'default': {
|
||||||
|
'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
# settings.production.py
|
||||||
|
CACHES = {
|
||||||
|
'default': {
|
||||||
|
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
|
||||||
|
'LOCATION': '127.0.0.1:11211',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<p><strong>Setting the places where cache will act</strong></p>
|
||||||
|
<p>Now that we have our project with a configured kind of cache, we must to say where and when to activate it. There are multiple ways to do it (on the <a href="https://docs.djangoproject.com/en/dev/topics/cache/#the-per-site-cache">whole site</a>, <a href="https://docs.djangoproject.com/en/dev/topics/cache/#the-per-view-cache">views</a>, <a href="https://docs.djangoproject.com/en/dev/topics/cache/#template-fragment-caching">templates</a>, <a href="https://docs.djangoproject.com/en/dev/topics/cache/#specifying-per-view-cache-in-the-urlconf">urls</a>...). I'm going to use... let's say urls. So, in our <em>urls.py</em> we have to set a time and activate the cache:</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
# urls.py
|
||||||
|
|
||||||
|
from django.views.decorators.cache import cache_page
|
||||||
|
|
||||||
|
ctime = (60 * 24) # A day
|
||||||
|
urlpatterns = patterns('',
|
||||||
|
url(r'^$',
|
||||||
|
cache_page(ctime)(BlogIndexView.as_view()),
|
||||||
|
{},
|
||||||
|
'blog-index'
|
||||||
|
),
|
||||||
|
...
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
<p>A simple server reload will be enough to have cache running. We can see it on action with <a href="https://github.com/django-debug-toolbar/django-debug-toolbar">django-debug-toolbar</a>, <a href="https://github.com/bartTC/django-memcache-status">django-memcache-status</a> or something like that.</p>
|
||||||
|
<p><strong>Clean a specific key cache</strong></p>
|
||||||
|
<p>And now the funniest part. For example, talking about a blog tool, when you write a new post (or editing older one) the software should be able to remove some cache keys, i.e. the <em>blog-index</em> one (because you have a new post) and the <em>post-detail</em> other (because you must be able to inmediately see the changes in the post you're editting).</p>
|
||||||
|
<p>Following <a href="http://stackoverflow.com/questions/2268417/expire-a-view-cache-in-django">this link</a> I've created a cache.py with this content:</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
# cache.py
|
||||||
|
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
def expire_view_cache(
|
||||||
|
view_name,
|
||||||
|
args=[],
|
||||||
|
namespace=None,
|
||||||
|
key_prefix=None,
|
||||||
|
method="GET"):
|
||||||
|
|
||||||
|
"""
|
||||||
|
This function allows you to invalidate any view-level cache.
|
||||||
|
|
||||||
|
view_name: view function you wish to invalidate or it's named url pattern
|
||||||
|
args: any arguments passed to the view function
|
||||||
|
namepace: optioal, if an application namespace is needed
|
||||||
|
key prefix: for the @cache_page decorator for the function (if any)
|
||||||
|
|
||||||
|
http://stackoverflow.com/questions/2268417/expire-a-view-cache-in-django
|
||||||
|
added: method to request to get the key generating properly
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
from django.http import HttpRequest
|
||||||
|
from django.utils.cache import get_cache_key
|
||||||
|
from django.core.cache import cache
|
||||||
|
from django.conf import settings
|
||||||
|
# create a fake request object
|
||||||
|
request = HttpRequest()
|
||||||
|
request.method = method
|
||||||
|
if settings.USE_I18N:
|
||||||
|
request.LANGUAGE_CODE = settings.LANGUAGE_CODE
|
||||||
|
# Loookup the request path:
|
||||||
|
if namespace:
|
||||||
|
view_name = namespace + ":" + view_name
|
||||||
|
request.path = reverse(view_name, args=args)
|
||||||
|
# get cache key, expire if the cached item exists:
|
||||||
|
key = get_cache_key(request, key_prefix=key_prefix)
|
||||||
|
if key:
|
||||||
|
if cache.get(key):
|
||||||
|
cache.set(key, None, 0)
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
```
|
||||||
|
|
||||||
|
<p>And last step is to call the expire_view_cache function on model form save hook (<em>admin.py</em> in this case):</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
# admin.py
|
||||||
|
|
||||||
|
from cache import expire_view_cache
|
||||||
|
|
||||||
|
class PostAdminForm(admin.ModelAdmin):
|
||||||
|
...
|
||||||
|
def save_model(self, request, obj, form, change):
|
||||||
|
expire_view_cache("blog-index")
|
||||||
|
expire_view_cache("post-detail", [obj.slug])
|
||||||
|
```
|
||||||
|
|
||||||
|
<p>And that's all, we are able to <em>clean/purge/remove</em> the cache when a new post is added or edited. As you can see in the code, cache is fun but you have to be careful to set it on the right way.</p>
|
After Width: | Height: | Size: 16 KiB |
|
@ -0,0 +1,25 @@
|
||||||
|
---
|
||||||
|
title: "Django: Cambiando de DB Engine"
|
||||||
|
date: 2012-04-28T18:42:31Z
|
||||||
|
draft: false
|
||||||
|
tags: [ "code", "django" ]
|
||||||
|
image:
|
||||||
|
---
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Normalmente las especificaciones de un entorno de desarrollo y las de un entorno en producción suelen ser bastante diferentes. Quizás en el primero buscas la comodidad mientras que cuando se hace el <em>deploy</em> a producción priman otras cosas. Este suele ser el caso del motor de base de datos cuando trabajas con <a href="http://djangoproject.org">Django</a>.</p>
|
||||||
|
<p>
|
||||||
|
Durante el proceso de desarrollo es muy cómodo utilizar <em>sqlite </em>porque no requiere de ningún servidor adicional y está soportado de base en Django (<em>builtin</em>). Es posible que una vez acabado el desarrollo queramos cambiar a otro <em>SGBD</em> "de verdad" como pueden ser <em>MySQL</em> o <em>PostgreSQL</em>.</p>
|
||||||
|
<p>
|
||||||
|
Lo que podría suponer un quebradero de cabeza en otras arquitecturas, en nuestro caso utilizando <em>dumpdata</em> y <em>loaddata</em>, se reduce a las siguientes tres lineas:</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
$ python manage.py dumpdata --indent=4 --format=json > fixtures.json
|
||||||
|
$ scp fixtures.json user@remote:/path/project/
|
||||||
|
(remote)$ python manage.py loaddata fixtures.json
|
||||||
|
```
|
||||||
|
|
||||||
|
<p>
|
||||||
|
En la primera usamos <a href="https://docs.djangoproject.com/en/dev/ref/django-admin/#dumpdata-appname-appname-appname-model">dumpdata</a> para hacer un dumpeado de todos los datos de la aplicación (sqlite) en formato json, posteriormente pasamos ese archivo al entorno de producción (en este caso pongamos de ejemplo a otro servidor) y allí finalmente (con el conector apuntando al nuevo gestor) ejecutamos el <a href="https://docs.djangoproject.com/en/dev/ref/django-admin/#loaddata-fixture-fixture">loaddata</a> para insertar los datos.</p>
|
||||||
|
<p>
|
||||||
|
Et voilà!</p>
|
After Width: | Height: | Size: 3.0 KiB |
|
@ -0,0 +1,97 @@
|
||||||
|
---
|
||||||
|
title: "Django deploy: problems with limited hosting"
|
||||||
|
date: 2014-07-21T21:51:18Z
|
||||||
|
draft: false
|
||||||
|
tags: [ "django", "code" ]
|
||||||
|
image: django.png
|
||||||
|
---
|
||||||
|
|
||||||
|
<p>Some months ago I had to deal with a <a href="../../../../blog/symfony2-en-un-hosting-compartido.html">Symfony2 project in a shared hosting</a> (Spanish article) and now the big next deal is a similar task with <a href="https://www.djangoproject.com/">Django</a>.</p>
|
||||||
|
<p>The project is almost done and I have the hosting credentials, once I'm in I noticed that there is no chance to configure anything (Apache, WSGI or whatever) so I was a bit lost. Thanks to my <a href="http://ailalelo.com/">Ailalelo's mates</a> (they had lot of experience with this kind of situations) I found the proper configuration.</p>
|
||||||
|
<p>Hosting is <em>django-ready</em>, but the version they're running (<em>1.4.2</em>) is not the best choice, I want to install <em>1.6.x</em>, the one I have used to develop the project. The other big requirement is <em>virtualenv+pip</em> to retrieve the packages I'm using.</p>
|
||||||
|
<p>Mainly I've solved it with two files, <code>project.cgi</code> and <code>.htaccess</code>.</p>
|
||||||
|
<h2>project.cgi</h2>
|
||||||
|
<p>The hosting structure is like many others, I have access to a <em>homedir</em> with the following directories:</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
myhome/
|
||||||
|
logs/
|
||||||
|
tmp/
|
||||||
|
www/
|
||||||
|
cgi-bin/
|
||||||
|
index.html
|
||||||
|
```
|
||||||
|
|
||||||
|
<p>Before to say Apache what to do with our project, let's install <code>virtualenv</code> and all the requirements, my choice is to put the environment out of the <code>www/</code> directory:</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
myhome/
|
||||||
|
env/
|
||||||
|
logs/
|
||||||
|
tmp/
|
||||||
|
www/
|
||||||
|
cgi-bin/
|
||||||
|
project/
|
||||||
|
index.html
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
$ virtualenv env
|
||||||
|
$ . env/bin/activate
|
||||||
|
$ pip install -r www/project/requirements/production.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
<p>Seems to be that apache's <em>mod_cgi</em> will process all the files you put in the <code>cgi-bin</code> directory, so we already know where to save our <code>project.cgi</code>. I have to tell apache to use my own <em>virtualenv</em> <code>python</code>, where the environment and the project are. And finally set some environment variables:</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
#!/home/myhome/env/bin/python
|
||||||
|
|
||||||
|
import os
|
||||||
|
from os.path import abspath, dirname
|
||||||
|
from sys import path
|
||||||
|
|
||||||
|
actual = os.path.dirname(__file__)
|
||||||
|
path.insert(0, os.path.join(actual, "..", "project/project"))
|
||||||
|
path.insert(0, os.path.join(actual, "..", "env"))
|
||||||
|
|
||||||
|
# Set the DJANGO_SETTINGS_MODULE environment variable.
|
||||||
|
os.environ['PATH'] = os.environ['PATH'] + ':/home/myhome/www/project/node_modules/less/bin'
|
||||||
|
os.environ['SECRET_KEY'] = 'SECRETKEY'
|
||||||
|
os.environ['DJANGO_SETTINGS_MODULE'] = "project.settings.production"
|
||||||
|
|
||||||
|
from django.core.servers.fastcgi import runfastcgi
|
||||||
|
runfastcgi(method="threaded", daemonize="false")
|
||||||
|
```
|
||||||
|
|
||||||
|
<p>Note that I modified the PATH because I have to be able to use <code>less</code> binary, required for <a href="http://django-compressor.readthedocs.org/en/latest/">django-compressor</a> package. In this particular case my user was not allowed to install <em>node/less</em> in the system, so I had to install it locally, referencing the particular <code>node_modules</code> folder.</p>
|
||||||
|
<h2>.htaccess</h2>
|
||||||
|
<p>Now that Apache knows what to do, we should redirect almost all the incoming traffic to the <em>cgi</em>, so let's write some<code>.htaccess</code> rules:</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
AddHandler fcgid-script .fcgi
|
||||||
|
RewriteEngine On
|
||||||
|
|
||||||
|
RewriteRule ^static/(.*)$ project/project/static/$1 [L]
|
||||||
|
RewriteRule ^media/(.*)$ project/project/media/$1 [L]
|
||||||
|
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-f
|
||||||
|
RewriteRule ^(.*)$ cgi-bin/project.cgi/$1 [QSA,L]
|
||||||
|
|
||||||
|
AddDefaultCharset UTF-8
|
||||||
|
```
|
||||||
|
|
||||||
|
<p>The <code>media/</code> and <code>static/</code> dirs are redirected to the proper location, because they didn't work as is. Not much more to say with this file, it's easy to understand I think.</p>
|
||||||
|
<h2>Remember</h2>
|
||||||
|
<ul>
|
||||||
|
<li>To properly install the environment.</li>
|
||||||
|
<li>Set up this two files (cgi and .htaccess).</li>
|
||||||
|
<li>Take care with node tools (bower, npm, less, node_modules...).</li>
|
||||||
|
<li>Most of times django-compressor is a PITA in shared hostings.</li>
|
||||||
|
<li>Take double care with static/media settings.</li>
|
||||||
|
<li>Set the proper permissions in the .cgi file.</li>
|
||||||
|
<li>Having a bit of patience and access to <a href="http://stackoverflow.com/">stackoverflow</a> is to have it a half fixed ;).</li>
|
||||||
|
</ul>
|
||||||
|
<p> </p>
|
||||||
|
<ul>
|
||||||
|
</ul>
|
|
@ -0,0 +1,20 @@
|
||||||
|
---
|
||||||
|
title: "Django: limpiando usuarios desde shell"
|
||||||
|
date: 2013-09-12T23:03:29Z
|
||||||
|
draft: false
|
||||||
|
tags: [ "django", "code" ]
|
||||||
|
image:
|
||||||
|
---
|
||||||
|
|
||||||
|
<p>Es tal el interés por el <a href="http://www.vamosavidas.com/foros/">foro de VamosaVidas</a> (si, ese que rompí en su día y <a href="http://www.vamosavidas.com/los-duendes-del-foro-nos-lo-han-devuelto.html">he vuelto a arreglar</a>) que todos los días tenemos cientos de registros. Realmente se trata de robots que se dedican a hacer spam, pero eso es lo de menos :P.</p>
|
||||||
|
<p>Mientras no actualizo y arreglo <a href="https://github.com/pennersr/django-allauth">django-authall</a> es un poco coñazo andar mirando en el admin el último usuario bueno y borrar manualmente uno a uno cotejando que no me equivoque, así que he pensado en un par de lineas que no deberían hacer demasiado daño si se usan con precaución. Desde la shell de nuestro proyecto podemos hacer algo así:</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
$ python manage.py shell
|
||||||
|
>>> from django.contrib.auth.models import User
|
||||||
|
>>> list(User.objects.order_by('date_joined'))
|
||||||
|
>>> [user.delete() for user in list(User.objects.order_by('date_joined'))[100:]]
|
||||||
|
```
|
||||||
|
|
||||||
|
<p>Primero estamos listando todos los usuarios por orden de fecha de registro, los convertimos en lista para que no salgan los datos "<em>truncados</em>". Y en segundo lugar estamos eliminando todos excepto los 100 primeros.</p>
|
||||||
|
<p>Esta es la sencillez y potencia de Python.</p>
|
|
@ -0,0 +1,151 @@
|
||||||
|
---
|
||||||
|
title: "Django + virtualenv + pip"
|
||||||
|
date: 2011-02-23T21:37:56Z
|
||||||
|
draft: false
|
||||||
|
tags: [ "django", "code" ]
|
||||||
|
image:
|
||||||
|
---
|
||||||
|
|
||||||
|
<p>No lo tenía claro, pero cuando entendí lo que suponía y cómo se trabajaba con <em>virtualenv</em> + <em>pip</em> me decidí a probarlo. Voy a <em>intentar</em> explicar como se utilizan estas herramientas de una forma genérica, para hacernos una idea de lo que significa y los casos en los que se pueden aplicar. A grandes rasgos:</p>
|
||||||
|
<ul>
|
||||||
|
<li class="level1">
|
||||||
|
<div class="li"><a href="http://djangoproject.org">Django</a>: Framework en <em>python</em>, creo que no necesita mucha más explicación.</div>
|
||||||
|
</li>
|
||||||
|
<li class="level1">
|
||||||
|
<div class="li"><a href="http://pypi.python.org/pypi/virtualenv">Virtualenv</a>: Herramienta necesaria para crear un entorno virtual de <em>python</em>, con las versiones específicas de los paquetes y/o dependencias que hagan falta para el proyecto.</div>
|
||||||
|
</li>
|
||||||
|
<li class="level1">
|
||||||
|
<div class="li"><a href="http://pypi.python.org/pypi/pip">Pip</a>: Gestor/Instalador de esos paquetes (similar a <em>easy_install</em>).</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p>Con estas herramientas intentaremos instalar un entorno virtual independiente para gestionar todas las dependencias de nuestro proyecto.<!--more--></p>
|
||||||
|
<p><strong>Instalación del entorno virtualenv + pip</strong></p>
|
||||||
|
<p>Primero instalamos <em>pip</em>, que como hemos mencionado anteriormente, es el gestor de <em>paquetes</em> con el que vamos a instalar el resto de componentes. Tiramos de <em>easy_install</em> para instalarlo. Una vez instalado <em>pip</em>, instalamos también <em>virtualenv</em> y tendremos la base necesaria para seguir trabajando:</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
$ sudo easy_install pip
|
||||||
|
$ pip install virtualenv
|
||||||
|
```
|
||||||
|
|
||||||
|
<p>Vamos a crear el entorno. Supongamos 2 directorios de trabajo, <em>src/</em> y <em>env/</em>. El directorio <em>src/</em> lo destinaremos al código fuente (podemos tirar de repositorio o iniciar nuestro proyecto directamente ahí), en <em>env/</em> meteremos todos los paquetes y dependencias del proyecto, osea el entorno:</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
$ mkdir -p foo/{env,src}
|
||||||
|
$ cd foo/
|
||||||
|
$ virtualenv --distribute --no-site-packages env/
|
||||||
|
New python executable in env/bin/python
|
||||||
|
Installing distribute........................................done.
|
||||||
|
```
|
||||||
|
|
||||||
|
<p>Ahora ya estamos preparados para instalar las aplicaciones/dependencias dentro del entorno de trabajo, lo haremos "desde fuera" con la opción <em>-E</em>:</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
$ pip -E env/ install django
|
||||||
|
Downloading/unpacking django
|
||||||
|
Downloading Django-1.2.4.tar.gz (6.4Mb): 6.4Mb downloaded
|
||||||
|
Running setup.py egg_info for package django
|
||||||
|
[...]
|
||||||
|
Successfully installed django
|
||||||
|
Cleaning up...
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
$ sudo pip -E env/ install -e hg+https://bitbucket.org/ubernostrum/django-registration#egg=django-registration
|
||||||
|
$ sudo pip -E env/ install -e git+https://github.com/flashingpumpkin/django-socialregistration.git#egg=django-socialregistration
|
||||||
|
[instalamos el resto de dependencias]
|
||||||
|
```
|
||||||
|
|
||||||
|
<p>Vamos a comprobar que todo está correcto, para ello utilizamos una herramienta que se llama <em>yolk</em>, capaz de listar todos los paquetes que tenemos instalados en el entorno, primero instalamos esa herramienta, luego entramos en el entorno y la ejecutamos (aunque podríamos hacer lo mismo con la orden <em>pip freeze </em>como se muestra a continuación):</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
$ pip -E env/ install yolk
|
||||||
|
$ source env/bin/activate
|
||||||
|
(env)$ yolk -l
|
||||||
|
Django - 1.2.4 - active
|
||||||
|
Python - 2.6.6 - active development (/usr/lib/python2.6/lib-dynload)
|
||||||
|
distribute - 0.6.14 - active
|
||||||
|
pip - 0.8.1 - active
|
||||||
|
wsgiref - 0.1.2 - active development (/usr/lib/python2.6)
|
||||||
|
yolk - 0.4.1 - active
|
||||||
|
```
|
||||||
|
|
||||||
|
<p><strong>Nota</strong>: Como se puede ver en el ejemplo de arriba, para entrar en el entorno que hemos creado usamos el comando <em>source env/bin/activate</em>, para salir del mismo usamos el comando <em>deactivate</em>.</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
(env)$ pip freeze
|
||||||
|
Django==1.2.4
|
||||||
|
distribute==0.6.14
|
||||||
|
wsgiref==0.1.2
|
||||||
|
yolk==0.4.1
|
||||||
|
```
|
||||||
|
|
||||||
|
<p>Ahora que tenemos más o menos clara la forma de actuar, vamos a hacer un <em>freeze</em> y guardar el archivo de requisitos dentro del repositorio para poder volver a reproducirlo en cualquier otra máquina. Insisto, todos estos comandos pueden ejecutarse dentro del entorno (env) o fuera del mismo con la opción <em>-E entorno</em> de <em>pip</em>:</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
(env)$ cd foo/src/
|
||||||
|
(env)$ pip freeze > requirements.txt
|
||||||
|
(env)$ cat requirements.txt
|
||||||
|
Django==1.2.4
|
||||||
|
South==0.7.3
|
||||||
|
distribute==0.6.14
|
||||||
|
wsgiref==0.1.2
|
||||||
|
yolk==0.4.1
|
||||||
|
(env)$ deactivate
|
||||||
|
$
|
||||||
|
```
|
||||||
|
|
||||||
|
<p>Y ya podemos empezar a programar el proyecto en dentro de <em>src/</em>.</p>
|
||||||
|
<p><strong>Replicando el entorno</strong></p>
|
||||||
|
<p>Suponemos ahora que estamos en otra máquina -aunque en el ejemplo seguimos en la misma- donde queremos montar de nuevo todo el entorno. Primero creamos de nuevo la estructura anterior y luego hacemos un <em>pull</em> del repositorio la carpeta <em>src/</em> (como estamos en la misma máquina lo simulando haciendo un <em>cp</em> :P):</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
$ mkdir -p bar/{env,src}
|
||||||
|
$ cp -r foo/src/* bar/src
|
||||||
|
```
|
||||||
|
|
||||||
|
<p>Y ahora procuramos recrear el entorno, primero hacemos un entorno nuevo y vacío en <em>bar/env/</em> y luego instalamos todo lo que hemos <em>logueado</em> en el <em>requirements.txt</em>:</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
$ virtualenv --distribute --no-site-packages bar/env/
|
||||||
|
$ pip install -E bar/env/ -r bar/src/requirements.txt
|
||||||
|
...
|
||||||
|
Successfully installed Django South ... yolk
|
||||||
|
Cleaning up...
|
||||||
|
```
|
||||||
|
|
||||||
|
<p>Comprobamos que todo esté correcto:</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
$ source bar/env/bin/activate
|
||||||
|
(env)$ pip freeze
|
||||||
|
Django==1.2.4
|
||||||
|
distribute==0.6.14
|
||||||
|
wsgiref==0.1.2
|
||||||
|
yolk==0.4.1
|
||||||
|
(env)$ deactivate
|
||||||
|
$
|
||||||
|
```
|
||||||
|
|
||||||
|
<p><strong>Conclusión</strong></p>
|
||||||
|
<p><em>Virtualenv</em> y <em>pip</em> forman una combinación excelente de elementos para hacer de un proyecto un <em>ente</em> sin dependencias frustradas y con todos los requisitos y versiones específicas para garantizar el correcto funcionamiento del mismo. Ahora, por lo que me han contado, debería interesarme en <a href="http://fabfile.org/">Fabric</a> para automatizar los <em>deploys</em> y seguir haciendo magia.</p>
|
||||||
|
<p><strong>Referencias</strong></p>
|
||||||
|
<div class="level2 section_highlight">
|
||||||
|
<ul>
|
||||||
|
<li class="level1">
|
||||||
|
<div class="li"><a class="urlextern" title="http://www.saltycrane.com/blog/2009/05/notes-using-pip-and-virtualenv-django/" rel="nofollow" href="http://www.saltycrane.com/blog/2009/05/notes-using-pip-and-virtualenv-django/">Saltycrane</a></div>
|
||||||
|
</li>
|
||||||
|
<li class="level1">
|
||||||
|
<div class="li"><a class="urlextern" title="http://ryanwilliams.org/2009/Jun/09/deploying-django-sites-fabric-pip-and-virtualenv" rel="nofollow" href="http://ryanwilliams.org/2009/Jun/09/deploying-django-sites-fabric-pip-and-virtualenv">Ryanwilliams</a></div>
|
||||||
|
</li>
|
||||||
|
<li class="level1">
|
||||||
|
<div class="li"><a class="urlextern" title="http://techylinguist.com/how-to/python-and-django-dev-environment-virtualenv-and-pip" rel="nofollow" href="http://techylinguist.com/how-to/python-and-django-dev-environment-virtualenv-and-pip">Techylinguist.com</a></div>
|
||||||
|
</li>
|
||||||
|
<li class="level1">
|
||||||
|
<div class="li"><a class="urlextern" title="http://www.sebasmagri.com/2010/aug/12/pip-fabric-y-virtualenv-herramientas-para-el-desar/" rel="nofollow" href="http://www.sebasmagri.com/2010/aug/12/pip-fabric-y-virtualenv-herramientas-para-el-desar/">Sebasmagri 1</a></div>
|
||||||
|
</li>
|
||||||
|
<li class="level1">
|
||||||
|
<div class="li"><a class="urlextern" title="http://www.sebasmagri.com/2010/aug/14/esqueleto-para-proyectos-django-porque-la-forma-im/" rel="nofollow" href="http://www.sebasmagri.com/2010/aug/14/esqueleto-para-proyectos-django-porque-la-forma-im/">Sebasmagri 2</a></div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
|
@ -0,0 +1,48 @@
|
||||||
|
---
|
||||||
|
title: "Dovecot, pequeñas peculiaridades"
|
||||||
|
date: 2010-09-07T09:20:13Z
|
||||||
|
draft: false
|
||||||
|
tags: [ "dovecot" ]
|
||||||
|
image:
|
||||||
|
---
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Desde hace algún tiempo -y después de haber lidiado con <a href="http://www.cyrusimap.org/">Cyrus</a> y <a href="http://www.courier-mta.org/">Courier</a>- he optado por <a href="http://dovecot.org/">Dovecot</a> como servidor <em>POP3</em> e <em>IMAP</em> para máquinas en producción. Por varios motivos: la sencillez de configuración, sigue los estándares, soporta <em>mbox</em> y <em>Maildir</em> y algo muy importante, tiene un backend de autentificación <em>SMTP</em> compatible con <a href="http://www.postfix.org/">Postfix</a> (entre otros).</p>
|
||||||
|
<p>
|
||||||
|
Sin duda el servicio de correo electrónico es el menos agradecido y probablemente el más doloroso para el <em>sysadmin</em> pero el haber dado con esta combinación de elementos me ha ahorrado un montón de problemas.</p>
|
||||||
|
<p>
|
||||||
|
De todos modos en la última instalación que me ha tocado he encontrado un par de peculiaridades que me gustaría documentar por si alguien se encuentra en la misma situación.<!--more--></p>
|
||||||
|
<p>
|
||||||
|
Habiendo instalado el mismo O.S., las mismas versiones de software y exactamente los mismos ficheros de configuración a la hora de despachar correos me encuentro con un error inexperado en <em>mail.log</em>:</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
... status=bounced (local configuration error)
|
||||||
|
```
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Así sin más descripción no puedo adivinar mucho así que decido activar errores en <em>dovecot.conf</em> con la directiva de configuración <em>log_path = /var/log/<span class="search_hit">dovecot</span>.log.</em> Ahora sí podemos sacar más información del <em>dovecot.log</em>:</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
Fatal: postmaster_address setting not given
|
||||||
|
```
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Esto ya es otra cosa, después de un poco de <em>googling</em> corrijo el fallo agregando al fichero de configuración una dirección de postmaster, sigue pareciéndome raro porque <em>/etc/aliases</em> es el mismo que otras máquinas y nunca había notificado este problema antes pero bueno, es cuestión de agregar a <em>dovecot.conf</em> lo siguiente:</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
protocol lda {
|
||||||
|
postmaster_address = tu-postmaster@tu-dominio.com
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Reinicio el servicio y a funcionar... pero no por mucho tiempo puesto que al día siguiente me encuentro con el servicio parado, vuelvo a reiniciar y el proceso se vuelve a parar cada día, repitiendo la jugada. Volviendo a los logs -ese gran invento- veo que todos los días a eso de las <em>6:00am</em> suelta el siguiente mensaje:</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
dovecot: 2010-09-05 05:59:53 Fatal: Time just moved backwards by 9 seconds. This might cause a lot of problems, so I'll just kill myself now. http://wiki.dovecot.org/TimeMovedBackwards
|
||||||
|
```
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Justo a esa hora tengo una tarea programada que sincroniza la hora del servidor con <em>rdate</em>. Al parecer Dovecot detecta que la hora ha cambiado y para no entrar en conflictos se hace el <em>harakiri.</em> Interesante, es algo que tienen documentado en <a href="http://wiki.dovecot.org/TimeMovedBackwards">su wiki</a> y te animan a que cambies <em>rdate/ntpdate</em> por <em>ntpd</em>, <em>clockspeed</em> o <em>chrony.</em></p>
|
||||||
|
<p>
|
||||||
|
Nada del otro mundo pero sí me ha supuesto algo de tiempo saber el origen de los errores para poder subsanarlos así que bueno, si al menos esta entrada ayuda a alguien o le ahorra algún dolor de cabeza me daré por contento.</p>
|
|
@ -0,0 +1,20 @@
|
||||||
|
---
|
||||||
|
title: "El mejor firewall"
|
||||||
|
date: 2010-02-11T16:16:22Z
|
||||||
|
draft: false
|
||||||
|
tags: [ ]
|
||||||
|
image:
|
||||||
|
---
|
||||||
|
|
||||||
|
Lo he visto <a href="http://www.puntogeek.com/2010/02/10/humor-el-mejor-firewall-p/">en puntogeek</a> a través del <a href="http://twitter.com/demogar/status/8960593725">twitter de demogar</a> y me ha parecido tan gracioso que he caido en el <em>burdo</em> copy-paste:
|
||||||
|
<br /><br />
|
||||||
|
<ol>
|
||||||
|
<li>Una célula humana contiene 75MB de información génetica.</li>
|
||||||
|
<li>Un espermatozoide contiene la mitad; eso significa 37.5MB.</li>
|
||||||
|
<li>Un ml de semen contiene 100 millones de espermatozoides.</li>
|
||||||
|
<li>En promedio la eyaculación dura 5 segundos y contiene 2.24 ml de semen</li>
|
||||||
|
<li>Esto significa que la producción del miembro de un hombre equivale 37.5MB x 100,000,000 x 2.25)/5 = 1,687,500,000,000,000 bytes/segundo = 1,6875 Terabytes/seg.</li>
|
||||||
|
<br />
|
||||||
|
Esto quiere decir que el óvulo femenino soporta este ataque DDoS a 1,5 terabytes por segundo, y solo permite que pase un solo paquete de información lo que la hace el mejor hardware firewall del mundo.
|
||||||
|
<br /><br />
|
||||||
|
La mala noticia de esto, es que ese paquete de información que deja pasar, cuelga el sistema por aproximadamente nueve meses.
|
|
@ -0,0 +1,85 @@
|
||||||
|
---
|
||||||
|
title: "El papeleo de ser padre"
|
||||||
|
date: 2013-01-08T18:41:27Z
|
||||||
|
draft: false
|
||||||
|
tags: [ ]
|
||||||
|
image:
|
||||||
|
---
|
||||||
|
|
||||||
|
<p>Pasados esos primeros momentos de tensión y felicidad máximos, vuelves a casa para que la realidad trate de despertarte a golpe de pequeñas bofetadas. O eso es lo que me está pasando a mi. Pasas de preocuparte porque todo salga bien en la eco, porque no haya ninguna complicación en el momento del parto, porque todas las pruebas estén correctas, a otro tipo de minucias que, si bien no son tan importantes, acaban desgastando igualmente a ese individuo comúnmente llamado <em>neopadre</em>.</p>
|
||||||
|
<p>Por un lado tenemos la repetitiva monotonía de las tomas cada 3 horas (creo que cualquiera que haya sido padre y vuelva a escuchar esas palabras - "<em>cada 3 horas</em>" - debe sentir escalofríos occipitales), que además, si tienes la suerte de contar con gemelos (<em>double win!</em>), se multiplican por doble trabajo y mitad de descanso, a menos que cuentes con la ayuda del encomiable <em>Dr. Octopus</em>.</p>
|
||||||
|
<p>Y por otro lado viene el jaleo de los papeleos, la oficialidad de los renacuajos pasa por gastar más gasolina (porque <em>Lugo</em> en estas fechas, diga lo que diga <a href="http://blog.e-shell.org">Borja</a>, no está para mucha bicicleta) y tiempo que en cualquier otra cosa que se te ocurra. Y aquí es donde me quiero parar a detallar el proceso, porque el resto es algo que se aprende con el tiempo y los ríos de consejos que te darán tus más cercanos. Pero los papeles, ¡Ay los papeles!...</p>
|
||||||
|
<p><strong>Hospital</strong></p>
|
||||||
|
<p>En el hospital lo principal es (que todo salga bien, obviamente) estar tranquilo y disfrutar del mayor tiempo posible con tu(s) hijo(s), esas primeras horas son muy importantes y (por lo que dicen) marcan el lazo de unión entre padres e hijos. Sin embargo tienes que estar pendiente también de pedir en admisión del propio hospital (o en la propia planta, depende del funcionamiento) ciertos documentos:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Justificante de ingreso de la madre.</li>
|
||||||
|
<li>Certificado de nacimiento.</li>
|
||||||
|
<li>Informe de alta de la madre.</li>
|
||||||
|
</ul>
|
||||||
|
<p>Además tendrás que rellenar (imagino que dependiendo de autonomías y funcionamientos internos) otros papeles correspondientes con la salud del nacido:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Solicitud de vacunación hepatitis b.</li>
|
||||||
|
<li>Solicitud de pruebas sordera.</li>
|
||||||
|
<li>Pruebas metabólicas para detección prematura de enfermedades.</li>
|
||||||
|
<li>Montón de información sobre lactancia y otras fases venideras.</li>
|
||||||
|
</ul>
|
||||||
|
<p><strong>Registro civil</strong></p>
|
||||||
|
<p>Una de las primeras cosas que se te pasan por la cabeza es registrar al/los renacuajos. Toda esa discusión por los nombres con tu pareja, suegra y demás familia, tienen que acabar en un papel legal firmado sellado y timbrado. En nuestro caso, en el propio hospital te indican cómo hacer, te ofrecen El Papel Amarillo (tm) con información relativa al parto donde tendrás que cubrir poco más que los datos de los progenitores y los nombres de los chavales. Todo esto se ha de entregar en el Registro Civil junto con el libro de familia, por lo que:</p>
|
||||||
|
<ul>
|
||||||
|
<li>El Papel Amarillo.</li>
|
||||||
|
<li>Encuesta del INE.</li>
|
||||||
|
<li>Libro de familia (en caso de no estar casados la chicha es mayor, pero la desconozco).</li>
|
||||||
|
<li>Fotocopia del DNI de ambos progenitores.</li>
|
||||||
|
<li>Firmar un par de documentos.</li>
|
||||||
|
</ul>
|
||||||
|
<p>En un par de días tendrás que volver a recoger el libro de familia actualizado. Oficialmente a partir de ese momento ya les puedes llamar por su nombre, antes recomiendo términos como <em>cacahuete</em>, <em>cosito</em>, <em>renacuajo</em>, etc...</p>
|
||||||
|
<p><strong>Certificado de empresa</strong></p>
|
||||||
|
<p>Si estás trabajando por cuenta ajena deberás pedirle a tu empresa un certificado de que estás trabajando allí (¡curioso eh!). En ese papel van detalladas también las bases de cotización, necesarias para tramitar tu baja por maternidad/paternidad. Ojo, aseguraos de que el certificado de empresa va firmado y sellado por la empresa o no valdrá de mucho. Depende de la empresa podrán pediros cierta acreditación de que realmente habéis tenido un hijo, aseguraos de llevar encima:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Justificante del ingreso de la madre.</li>
|
||||||
|
<li>Informe del alta de la madre.</li>
|
||||||
|
<li>Libro de familia debidmente actualizado.</li>
|
||||||
|
</ul>
|
||||||
|
<p><strong>Padrón</strong></p>
|
||||||
|
<p>Aunque el "derecho a una vivienda digna" no sea tal en este país y, desgraciadamente, haya muchas personas sin hogar propio; hay que empadronar a tus hijos en algún sitio. Así que toca ir al Ayuntamiento de turno y rellenar los correspondientes formularios, para ello debemos llevar de casa:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Libro de familia debidamente actualizado (y fotocopias por si acaso).</li>
|
||||||
|
<li>DNI de los progenitores.</li>
|
||||||
|
</ul>
|
||||||
|
<p><strong>Alta del bebé en la Seguridad Social</strong></p>
|
||||||
|
<p>Se tramita en la Seguridad Social y, como todo lo que se tramita en el <em>INSS</em>, dependiendo del funcionario de turno se puede contar como una experiencia maravillosa o maravillosamente traumática, antes de nada ¡mucha suerte!. Para dar de alta en la Seguridad Social y meterlo en la cartilla de alguno de los padres es interesante saber qué pediatra toca a cada uno, porque podréis escoger la cartilla del padre o de la madre. Documentos imprescindibles a presentar con el correspondiente impreso:</p>
|
||||||
|
<ul>
|
||||||
|
<li>DNI de los progenitores (llevando el del que va a acoger al niño en su cartilla llegaría, pero por si las moscas llevad los 2).</li>
|
||||||
|
<li>Libro de familia debidamente actualizado.</li>
|
||||||
|
<li>Tarjeta Sanitaria del progenitor que va a acoger al niño (recomendable llevar los 2).</li>
|
||||||
|
</ul>
|
||||||
|
<p><strong>Informe de Maternidad (El Papelito Azul)</strong></p>
|
||||||
|
<p>El médico de cabecera debe expedir este informe que hará falta posteriormente para presentar la baja por maternidad. Para ello debemos llevar:</p>
|
||||||
|
<ul>
|
||||||
|
<li>El informa de alta de la madre del hospital.</li>
|
||||||
|
<li>Libro de familia debidamente actualizado.</li>
|
||||||
|
<li>DNI de la madre.</li>
|
||||||
|
<li>Tarjeta Sanitaria de la madre.</li>
|
||||||
|
</ul>
|
||||||
|
<p><strong>Baja por paternidad/maternidad</strong></p>
|
||||||
|
<p>También se tramita en la Seguridad Social (Instituto Nacional de la Seguridad Social, INSS, etc...). Y normalmente tiene tela porque depende de cada caso, no es lo mismo cuenta ajena que autónomo, etc... pero por lo general te pedirán:</p>
|
||||||
|
<ul>
|
||||||
|
<li>DNI de los progenitores (y fotocopia del mismo).</li>
|
||||||
|
<li>Libro de familia debidamente actualizado (y fotocopia del mismo).</li>
|
||||||
|
<li>Certificado de empresa en caso de estar trabajando por cuenta ajena (y fotocopia del mismo).</li>
|
||||||
|
<li>Los 2 últimos cupones de autónomo en caso de estar dado de alta en el R.E.T.A.</li>
|
||||||
|
<li>Número de cuenta para la parte correspondiente de la nómina.</li>
|
||||||
|
<li>Si la baja es por maternidad, además hace falta el informe de maternidad aka El Papelito Azul (y fotocopia del mismo).</li>
|
||||||
|
</ul>
|
||||||
|
<p><strong>Solicitud de prestaciones</strong></p>
|
||||||
|
<p>Dependiendo del caso, del embarazo, del parto y de la situación personal, es posible que tengas derecho a ciertas ayudas que papá Estado concede (de momento) por traer un hijo a este mundo. Este tipo de ayudas se suelen tramitar también desde el INSS (Seguridad Social, Instituto Nacional de la Seguridad Social, etc...) y suelen tener requisitos variopintos, así que por norma general diremos que hace falta:</p>
|
||||||
|
<ul>
|
||||||
|
<li>DNI del interesado (y fotocopia del mismo).</li>
|
||||||
|
<li>Tarjeta Sanitaria del interesado.</li>
|
||||||
|
<li>Copia de la la última declaración de la Renta.</li>
|
||||||
|
<li>Número de cuenta por si eres un suertudo/a y te conceden la prestación.</li>
|
||||||
|
</ul>
|
||||||
|
<p>A tales momentos (<em>enero 2013</em>) creo que sigue vigente una prestación de deducción por maternidad (100€/mes durante un par de años) y alguna que otra <a href="http://www.seg-social.es/Internet_1/Trabajadores/PrestacionesPension10935/Prestacionesfamilia10967/Prestacioneconomica33761/index.htm">subvención por partos múltiples</a> (<a href="http://www.seg-social.es/prdi00/groups/public/documents/binario/097846.pdf">impreso</a>) o algo así. Más información en la propia <a href="http://www.seg-social.es/">web de la Seguridad Social</a>.</p>
|
||||||
|
<p><strong>En definitiva...</strong></p>
|
||||||
|
<p>Decir antes de nada que todo este rollo que acabo de soltar está sujeto al funcionamiento de cada Comunidad Autónoma, de papá Estado e, imagino, dependerá también del año en el que se tramite puesto que estas cosas cambian tanto como el devenir del líquido elemento. Ah, ¡y las firmas!. No os olvidéis de fecha y firma en todos y cada uno de los papeles que se presenten, pero bueno, eso ya os lo recordará también el funcionario de turno.</p>
|
||||||
|
<p>Con todo esto que os estoy contando no quiero decir que no esté encantado con este nuevo <em>status</em> parental, al contrario (ríos de babas salen diariamente de mi cansada boca), pero si os estáis planteando tener descendencia, contad también con estas situaciones, con toneladas de paciencia, gasolina en el coche, disponibilidad para hacer varios viajes a la misma ventanilla y un par de bolígrafos a mano cargados de tinta.</p>
|
|
@ -0,0 +1,23 @@
|
||||||
|
---
|
||||||
|
title: "Emacs go to line y reload sin salir de Emacs"
|
||||||
|
date: 2012-08-13T19:39:29Z
|
||||||
|
draft: false
|
||||||
|
tags: [ ]
|
||||||
|
image:
|
||||||
|
---
|
||||||
|
|
||||||
|
<p>Se me hace muy cómodo tener un atajo de teclado para la típica orden de "<em>ir a la linea X</em>", así que en vez de escribir todo el tochaco de comando que hace falta en Emacs para ello (<em>M-x goto-line<RET>#linea<RET></em>) voy a hacer un binding. Abro el archivo .emacs y agrego lo siguiente:</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
;; Goto-line
|
||||||
|
short-cut key(global-set-key "C-l" 'goto-line)
|
||||||
|
```
|
||||||
|
|
||||||
|
<p>Una vez guardado y para hacer efectiva la configuración sin tener que salir de Emacs ejecuto lo siguiente: </p>
|
||||||
|
|
||||||
|
```
|
||||||
|
M-x load-file
|
||||||
|
~/.emacs
|
||||||
|
```
|
||||||
|
|
||||||
|
<p>Y ya tengo lo que quería, mi shortcut de "<em>ir a la linea X</em>" en <em>Ctrl + L</em>. Tip básico de Emacs patrocinado por chocolate blanco Mercadona y agua de mineralización muy débil Bezoya.</p>
|
|
@ -0,0 +1,11 @@
|
||||||
|
---
|
||||||
|
title: "Feeling an old techie"
|
||||||
|
date: 2015-03-25T19:00:19Z
|
||||||
|
draft: false
|
||||||
|
tags: [ ]
|
||||||
|
image: old-techie.jpg
|
||||||
|
---
|
||||||
|
|
||||||
|
<p>I was thinking about that feeling lately. It seems that I can not follow the wave out there, there are many things escaping from my comprehension due lack of time. Starting to talk about anything not related to my <em>day-by-day</em> is starting to feeling myself out of date.</p>
|
||||||
|
<p>Last days I could get some short time to read about a couple of topics - <em>frontend</em> related this time - and I felt like I was using <em>cobol</em> (nothing against <em>cobol</em> here). It's a fact you can not focus in every new brilliant and recently released piece of technology, but it's like there was an interestellar space between what I thought it was the right way and recent developments or best practices.</p>
|
||||||
|
<p>Probably have to go back to the old habit of taking Friday afternoons (or any other shift) as <em>research & development</em>. Would be great.</p>
|
After Width: | Height: | Size: 1.1 MiB |
|
@ -0,0 +1,22 @@
|
||||||
|
---
|
||||||
|
title: "Hacer scroll en GNU Screen"
|
||||||
|
date: 2011-04-11T12:31:43Z
|
||||||
|
draft: false
|
||||||
|
tags: [ ]
|
||||||
|
image:
|
||||||
|
---
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Me ha pasado un par de veces y siempre he acabado <a href="http://www.saltycrane.com/blog/2008/01/how-to-scroll-in-gnu-screen/">en el mismo sitio</a> así que he decidido documentarlo con un mini-post, porque seguro que no será la última vez que me pase. Ejecutando ciertos comandos en remoto a través de <a href="http://www.gnu.org/software/screen/">screen</a> hay veces que el <em>scroll</em> se desborda y necesitaría leer parte de la salida, tan sencillo como:</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<em>C-a [</em> - Para entrar en modo <em>scrollback.</em></li>
|
||||||
|
<li>
|
||||||
|
<em>C-a ESC</em> - Para entrar en modo <em>scrollback</em> (mismo efecto que el punto anterior).</li>
|
||||||
|
<li>
|
||||||
|
Usa el scroll del ratón por ejemplo para ver/seleccionar lo que quieras.</li>
|
||||||
|
<li>
|
||||||
|
<em>ESC</em> - Para salir del modo <em>scrollback</em>.</li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
Otra sencillo <em>hack</em> para afinar una de las mejores herramientas que he usado (<a href="http://www.userlinux.net/tag-screen/">y ya van unos cuantos</a>). Así que nada más, a disfrutarlo.</p>
|
After Width: | Height: | Size: 249 KiB |
After Width: | Height: | Size: 209 KiB |
After Width: | Height: | Size: 242 KiB |
After Width: | Height: | Size: 272 KiB |
After Width: | Height: | Size: 143 KiB |
After Width: | Height: | Size: 190 KiB |
After Width: | Height: | Size: 228 KiB |
After Width: | Height: | Size: 195 KiB |
After Width: | Height: | Size: 260 KiB |
After Width: | Height: | Size: 187 KiB |
After Width: | Height: | Size: 228 KiB |
After Width: | Height: | Size: 160 KiB |
After Width: | Height: | Size: 159 KiB |
After Width: | Height: | Size: 334 KiB |
After Width: | Height: | Size: 145 KiB |
After Width: | Height: | Size: 177 KiB |
After Width: | Height: | Size: 135 KiB |
After Width: | Height: | Size: 192 KiB |
After Width: | Height: | Size: 380 KiB |
After Width: | Height: | Size: 340 KiB |
After Width: | Height: | Size: 365 KiB |
|
@ -0,0 +1,13 @@
|
||||||
|
---
|
||||||
|
title: "Lazy summer"
|
||||||
|
date: 2015-09-14T10:48:38Z
|
||||||
|
draft: false
|
||||||
|
tags: [ ]
|
||||||
|
image: summer2015.jpg
|
||||||
|
---
|
||||||
|
|
||||||
|
<p>There were almost no workouts during July + August, and September is starting the same way. We're experiencing too much changes with the kids school and the inestable weather here, so my body is feeling dumb, as my mind.</p>
|
||||||
|
<p>On the other hand we had a nice summer, traveling from here to there and enjoying weather, beach, friends and family. Enough to charge batteries and start thinking in next adventure.</p>
|
||||||
|
|
||||||
|
{{< gallery match="gallery/*" sortOrder="asc" rowHeight="150" margins="5" thumbnailResizeOptions="600x600 q90 Lanczos" previewType="blur" embedPreview="true" >}}
|
||||||
|
|
After Width: | Height: | Size: 228 KiB |
|
@ -0,0 +1,32 @@
|
||||||
|
---
|
||||||
|
title: "Limitando usuarios ssh en Mercurial"
|
||||||
|
date: 2010-07-21T01:24:05Z
|
||||||
|
draft: false
|
||||||
|
tags: [ ]
|
||||||
|
image:
|
||||||
|
---
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Si algo bueno tiene <a href="http://mercurial.selenic.com/">Mercurial</a> es que permite la autentificación de usuarios a través de SSH. Es muy sencillo agregar un nuevo usuario a un desarrollo/repositorio: <em>adduser</em> y con meterlo dentro del grupo correspondiente al desarrollo llegaría. Pero ¿qué ocurre si no queremos que ese usuario haga otra cosa que no sean comandos <em>hg</em>?.</p>
|
||||||
|
<p>
|
||||||
|
Conociendo la existencia de <a href="http://www.selenic.com/repo/hg-stable/raw-file/tip/contrib/hg-ssh">hg-ssh</a> no ocurre demasiado, se trata de un script que hemos de referenciar en el <em>authorized_keys </em>del usuario que acabamos de crear de forma que todos los comandos entrantes pasen por este script. El script se encarga de parsear el comando que se pide en ejecución: si es de la familia de <em>Mercurial</em> lo ejecuta, en cualquier otro caso mostrará un error.</p>
|
||||||
|
<p>
|
||||||
|
Ejemplo de <em>authorized_keys</em>:</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
command="~/hg-ssh /home/repo1 /home/repo2",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-dss AAAA...
|
||||||
|
```
|
||||||
|
|
||||||
|
<p>
|
||||||
|
He optado por copiar el archivo hg-ssh en el directorio <em>home</em> del usuario, pero se podría referenciar directamente el que trae de ejemplo la instalación de <em>Mercurial</em>.<!--more--></p>
|
||||||
|
<p>
|
||||||
|
Nos encontramos con otro pequeño inconveniente, si el script se tiene que referenciar en el archivo <em>authorized_keys</em> como he dicho antes, el usuario debe tener su clave pública DSA/RSA configurada en el servidor, así que para estar seguros de que no se salta las restricciones impuestas por <em>hg-ssh</em>, deberíamos desactivar cualquier intento de acceso por contraseña:</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
# nano /etc/ssh/sshd_config
|
||||||
|
Match Group desarrolladores
|
||||||
|
PasswordAuthentication no
|
||||||
|
```
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Reiniciamos <em>sshd</em> y todo debería funcionar de forma adecuada, el usuario podrá hacer <em>commits</em>, <em>push</em>, <em>pull</em>, <em>update</em>... desde la máquina cuya clave <em>id_dsa.pub</em> hemos agregado en el servidor pero no podrá acceder a través de ssh a ningún otro comando, ni a shell; por lo tanto creo que se ha conseguido el principal objetivo.</p>
|
After Width: | Height: | Size: 21 KiB |
|
@ -0,0 +1,87 @@
|
||||||
|
---
|
||||||
|
title: "Mercurial: automatizando al máximo"
|
||||||
|
date: 2010-03-18T13:28:20Z
|
||||||
|
draft: false
|
||||||
|
tags: [ ]
|
||||||
|
image:
|
||||||
|
---
|
||||||
|
|
||||||
|
<p>Cuando trabajamos con servidores de versiones seguro que hay muchas razones de peso de por medio, una de ellas -la que veremos- puede ser la replicación de código en diversas máquinas. <br /><br /> Supongamos un montón de máquinas que comparten el mismo código de repositorio, el orden de propagación de un cambio en todas esas máquinas es sencillo: <br /><br /></p>
|
||||||
|
<ol>
|
||||||
|
<li>Programamos dicho cambio en nuestro servidor de desarrollo (<em>devel</em>).</li>
|
||||||
|
<li>Hacemos un <em>commit</em> local (en sistemas de versionado distribuido -como Mercurial- cada repositorio también es servidor).</li>
|
||||||
|
<li>Lo siguiente es un <em>push</em> al servidor donde almacenamos el código (<em>repo código</em>).</li>
|
||||||
|
<li>Ahora tocaría entrar en cada una de esas máquinas en las que queremos propagar el código y ejecutar un <em>hg pull ; hg update</em>.</li>
|
||||||
|
</ol>
|
||||||
|
<p><!--more--> Tedioso cuando el número de máquinas que se manejan tiene dos cifras. Intentemos automatizar todo el proceso a través de las opciones que nos ofrece <a href="http:/mercurial.selenic.com/">Mercurial</a>. <br /><br /> Combinando la <a href="/342_ssh_sin_password.html">confianza entre equipos</a> por ssh y los <a href="/mercurial-hook-on-push.html">hooks de mercurial</a> podemos crear un monstruo sencillo de manejar.</p>
|
||||||
|
<div class=""center"><img title=""Esquema" src="gallery/esquema-mercurial.jpg" alt=""Esquema" /></div>
|
||||||
|
<h2>Local/Devel</h2>
|
||||||
|
<p>En la copia local lo primero que tenemos que hacer es un <em>clone</em> del repositorio <em>padre</em> y definir los archivos que vamos a ignorar. Por ejemplo los archivos de configuración de base de datos o de peculiaridades locales de cada proyecto en producción (idioma, usuarios, themes, datos de usuario, imágenes...):</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
[devel] # cat repo/.hgignore
|
||||||
|
syntax: glob
|
||||||
|
.hgignore
|
||||||
|
config.php
|
||||||
|
bd.php
|
||||||
|
.project
|
||||||
|
uploads*
|
||||||
|
```
|
||||||
|
|
||||||
|
<p><strong>Nota</strong>: una buena práctica sería no agregar esos ficheros que estamos ignorando al repositorio, podemos agregar unos <em>config-example.php</em> o algo así para tener una referencia pero no las configuraciones en sí -<em>imho</em>-. <br /><br /> A la hora de hacer un <em>pull</em> al repositorio del código todos los archivos que concuerden con los patrones escritos en el fichero <em>.hgignore</em> no se agregarán al repositorio. <strong>Ojo</strong>: el fichero <em>.hgignore</em> debe estar en el raíz del repositorio y debe pertenecer al mismo usuario que vaya a hacer el pull.</p>
|
||||||
|
<h2>Repo código</h2>
|
||||||
|
<p>Aquí es donde viene la <em>chicha</em>. Teóricamente en el repositorio del código no habría demasiado que tocar, sin embargo es aquí donde ejerceremos la mayor automatización del proceso. Para propagar los cambios a las máquinas en producción podemos hacerlo de dos formas: <br /><br /></p>
|
||||||
|
<ol>
|
||||||
|
<li>Como hacíamos antes, entrando en cada máquina en producción y hacer pull del repositorio del código</li>
|
||||||
|
<li>Desde el repositorio del código hacer push a todas las máquinas en producción</li>
|
||||||
|
</ol>
|
||||||
|
<p><br /> Este punto ha sido el que más me ha costado entender, digamos que dependiendo de la dirección en la que se ejecute el <em>pull/push</em> los resultados son los mismos. Con lo que, si desde el repositorio del código ejecutamos lo siguiente, los cambios se propagarán a las máquinas 1, 2, 3 y 4:</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
[repo] # hg push ssh://usuario@maquina1/repo
|
||||||
|
[repo] # hg push ssh://usuario@maquina2/repo
|
||||||
|
[repo] # hg push ssh://usuario@maquina3/repo
|
||||||
|
[repo] # hg push ssh://usuario@maquina4/repo
|
||||||
|
```
|
||||||
|
|
||||||
|
<p>Tuneando un poco el <em>repo/.hg/hgrc</em> del repositorio podemos <em>acortar</em> el comando e intentar automatizarlo un poco más, fijaos:</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
[repo] # cat repo/.hg/hgrc
|
||||||
|
[paths]
|
||||||
|
maquina1 = ssh://user@maquina1/repo
|
||||||
|
maquina2 = ssh://user@maquina2/repo
|
||||||
|
maquina3 = ssh://user@maquina3/repo
|
||||||
|
maquina4 = ssh://user@maquina4/repo
|
||||||
|
```
|
||||||
|
|
||||||
|
<p><strong>Ojo</strong>: el fichero <em>hgrc</em> debe estar dentro del directorio <em>.hg/</em> del repositorio y debe pertenecer al mismo usuario que vaya a hacer el pull. <br /><br /> De forma que para ejecutar el comando de propagación a todas las máquinas tendremos que hacer un:</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
[repo] # hg push maquina1 ; hg push maquina2 ; hg push maquina3 ; hg push maquina4
|
||||||
|
```
|
||||||
|
|
||||||
|
<p>Ya tenemos los <em>push</em> automatizados, ¿y los updates del lado de <em>producción</em>?. Vamos a ello.</p>
|
||||||
|
<h2>Máquina producción</h2>
|
||||||
|
<p>Cada vez que una de estas máquinas recibe un <em>push</em> del repositorio de código, mercurial debe actualizar los datos con un <em>hg update</em>. Para hacerlo automáticamente en cada <em>push</em> -ya lo hemos visto <a href="../../../../mercurial-hook-on-push.html">en otro post</a>- en el repositorio de cada una de las máquinas en producción configuramos el <em>repo/.hg/hgrc</em> de forma que:</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
[hooks]
|
||||||
|
changegroup = hg update
|
||||||
|
```
|
||||||
|
|
||||||
|
<p><strong>Ojo</strong>: el fichero <em>hgrc</em> debe estar dentro del directorio <em>.hg/</em> del repositorio y debe pertenecer al mismo usuario que vaya a hacer el pull.</p>
|
||||||
|
<h2>Proceso</h2>
|
||||||
|
|
||||||
|
```
|
||||||
|
- Programamos en "devel" y guardamos cambios.
|
||||||
|
- [devel] # hg commit
|
||||||
|
- [devel] # hg push
|
||||||
|
- [repo] # hg push maquina1 ; hg push maquina2...
|
||||||
|
pushing to ssh://user@maquina1/repo
|
||||||
|
searching for changes
|
||||||
|
pushing to ssh://user@maquina2/repo
|
||||||
|
searching for changes
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
---
|
||||||
|
title: "Mercurial en Fedora Core 4 y CentOS 5"
|
||||||
|
date: 2010-03-08T17:22:14Z
|
||||||
|
draft: false
|
||||||
|
tags: [ ]
|
||||||
|
image:
|
||||||
|
---
|
||||||
|
|
||||||
|
Necesitaba instalar <a href="http://mercurial.selenic.com/">Mercurial</a> en varias máquinas totalmente desactualizadas, concretamente una <em>Fedora Core release 4 (Stentz)</em> y una <em>CentOS release 5 (final)</em>, pensé que iba a ser un lío de dependencias pero al final ha resultado inexplicablemente más sencillo de lo esperado:
|
||||||
|
<!--more-->
|
||||||
|
<h2>Fedora Core 4</h2>
|
||||||
|
|
||||||
|
```
|
||||||
|
# cat /etc/fedora-release
|
||||||
|
Fedora Core release 4 (Stentz)
|
||||||
|
# wget http://packages.sw.be/mercurial/mercurial-1.0.2-1.fc4.rf.i386.rpm
|
||||||
|
# rpm -Uvh mercurial-1.0.2-1.fc4.rf.i386.rpm
|
||||||
|
# hg --version
|
||||||
|
Mercurial Distributed SCM (version 1.0.2)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
<h2>CentOS 5</h2>
|
||||||
|
|
||||||
|
```
|
||||||
|
# cat /etc/redhat-release
|
||||||
|
CentOS release 5 (Final)
|
||||||
|
# wget http://packages.sw.be/mercurial/mercurial-1.3.1-1.el5.rf.i386.rpm
|
||||||
|
# rpm -Uvh mercurial-1.3.1-1.el5.rf.i386.rpm
|
||||||
|
# hg --version
|
||||||
|
Mercurial Distributed SCM (version 1.3.1)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
<h2>Binarios</h2>
|
||||||
|
Vale que no sean la última versión (1.5 <abbr title="en este momento">atm</abbr>) pero estos binarios me han ahorrado bastante tiempo y evitado un problema, mola :).
|
|
@ -0,0 +1,17 @@
|
||||||
|
---
|
||||||
|
title: "Mercurial: Hook on push"
|
||||||
|
date: 2010-02-21T13:21:26Z
|
||||||
|
draft: false
|
||||||
|
tags: [ ]
|
||||||
|
image:
|
||||||
|
---
|
||||||
|
|
||||||
|
Lo tenía pendiente desde que cambié de Subversion a Mercurial, sabía que se podía y que era algo trivial pero lo vas dejando y bueno, <em>just happens</em>. El caso es que cuando haces un <em>push</em> al servidor lo normal es hacer un <em>update</em> de su lado, así que el <em>hook</em> que lo automatiza es el correspondiente:
|
||||||
|
|
||||||
|
```
|
||||||
|
[server]$ cat /path/del/repo/.hg/hgrc
|
||||||
|
[hooks]
|
||||||
|
changegroup = hg update
|
||||||
|
```
|
||||||
|
|
||||||
|
Y fuera preocupaciones. La obligada referencia a <a href="http://the.taoofmac.com/space/blog/2007/09/14/1130">Tao of Mac</a>, desde donde me vino la inspiración mientras intentaba poner el <em>Macbook</em> a punto.
|
|
@ -0,0 +1,25 @@
|
||||||
|
---
|
||||||
|
title: "Mercurial: Merge branches"
|
||||||
|
date: 2013-07-17T10:38:15Z
|
||||||
|
draft: false
|
||||||
|
tags: [ ]
|
||||||
|
image:
|
||||||
|
---
|
||||||
|
|
||||||
|
<p>Tarde o temprano llegaría la hora de trabajar con <em>branches</em>. Y ello lleva inequívocamente a tener que mezclarlas algún día. Suponiendo un escenario de repositorio con dos ramas (<em>default</em>, <em>new</em>), una vez el código es estable y está probado, haremos lo siguiente:</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
$ hg branch
|
||||||
|
new
|
||||||
|
$ hg pull -u
|
||||||
|
$ hg commit -m "[ADD] New features"
|
||||||
|
$ hg push
|
||||||
|
$ hg update default
|
||||||
|
$ hg branch
|
||||||
|
default
|
||||||
|
$ hg merge new
|
||||||
|
$ hg commit -m "[MER] Merging default and new branches"
|
||||||
|
$ hg push
|
||||||
|
```
|
||||||
|
|
||||||
|
<p>El proceso es lógico, estamos trabajando en la rama <em>new</em>, hacemos commit de todos los cambios, nos pasamos a la rama <em>default</em>, hacemos un <em>merge</em> de una con otra y <em>commiteamos</em> de nuevo la mezcla. Una vez hecho todo ésto ya volvemos a tener una sola rama <em>default</em> en la que seguir la linea de desarrollo.</p>
|
After Width: | Height: | Size: 107 KiB |
|
@ -0,0 +1,36 @@
|
||||||
|
---
|
||||||
|
title: "Mercurial: pager extension"
|
||||||
|
date: 2014-01-23T15:19:07Z
|
||||||
|
draft: false
|
||||||
|
tags: [ ]
|
||||||
|
image:
|
||||||
|
---
|
||||||
|
|
||||||
|
<p>Sometimes when you are using <a href="http://mercurial.selenic.com/">Mercurial</a> you want to paginate the output results. With <em>paginate</em> I mean use someting like bash <em>more</em> or <em>less</em> commands. Trying to focus yourself in a long list display is nearly imposible without a pager, so you can do it <em>old-school-style</em>:</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
$ hg st | more
|
||||||
|
$ hg st | less
|
||||||
|
```
|
||||||
|
|
||||||
|
<p>Or you can activate the Mercurial <a href="http://mercurial.selenic.com/wiki/PagerExtension">PagerExtension</a>, easy peasy:</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
[extensions]
|
||||||
|
pager=
|
||||||
|
|
||||||
|
[pager]
|
||||||
|
pager = LESS='FRX' less
|
||||||
|
attend =
|
||||||
|
ignore = help
|
||||||
|
```
|
||||||
|
|
||||||
|
<p>Once you have saved theese lines on your <em>~/.hgrc</em>, all the Mercurial commands will be paged. Remember that, by default, <em>PagerExtension</em> only works with <em>annotate</em>, <em>cat</em>, <em>diff</em>, <em>export</em>, <em>glog</em>, <em>log</em> and <em>qdiff</em> commands. If you want it to be run with all the commands you have to add the "<strong><em>attend=</em></strong>" line. On the other hand, if you want to ignore some commands you can specify that with the "<em>ignore</em>" option as you can see in the above sample.</p>
|
||||||
|
<p><img style="display: block; margin-left: auto; margin-right: auto; width: 100%; meight: 100%;" src="gallery/mercurial-pager.png" alt="Mercurial Pager" /></p>
|
||||||
|
<p>More information about the PagerExtension and Mercurial in general:</p>
|
||||||
|
<ul>
|
||||||
|
<li><a href="http://www.userlinux.net/mercurial-colores-y-pager.html">Mercurial, colores y pager (Spanish) on Userlinux</a></li>
|
||||||
|
<li><a href="http://mercurial.selenic.com/wiki/PagerExtension">PagerExtension oficial site</a></li>
|
||||||
|
<li><a href="http://stackoverflow.com/questions/7598558/mercurial-pager-extension">Mercurial PagerExtension on StackOverflow</a></li>
|
||||||
|
<li><a href="http://stackoverflow.com/questions/1869040/what-are-the-best-and-must-have-hg-mercurial-extensions">Must have Mercurial extensions on StackOverflow</a></li>
|
||||||
|
</ul>
|
|
@ -0,0 +1,59 @@
|
||||||
|
---
|
||||||
|
title: "Mercurial sobre Apache"
|
||||||
|
date: 2010-05-07T14:52:47Z
|
||||||
|
draft: false
|
||||||
|
tags: [ ]
|
||||||
|
image:
|
||||||
|
---
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Mi predilección por <a href="http://mercurial.selenic.com/">Mercurial</a> ha quedado patente en <a href="http://www.userlinux.net/mercurial-automatizando-al-maximo.html">algún</a> que <a href="http://www.userlinux.net/primeros-pasos-con-mercurial.html">otro</a> <a href="http://www.userlinux.net/mercurial-hook-on-push.html">post</a>, así que una vez estamos conforme con nuestro servidor de versiones llega el momento de dar un paso más. Intentaremos configurar un interfaz web para mostrar el código a todo el mundo (una especie de <a href="http://trac.edgewall.org/">Trac</a> solo para código y adaptado a <em>Mercurial</em>).</p>
|
||||||
|
<p>
|
||||||
|
El proceso es tan sencillo como crear otro <em>VirtualHost</em> en tu Apache con unas características un poco especiales porque en vez de tirar de archivos dinámicos (.php, .asp…) vamos a tirar de un cgi en Python, así que la configuración sería algo así:</p>
|
||||||
|
<p>
|
||||||
|
<!--more--></p>
|
||||||
|
|
||||||
|
```
|
||||||
|
<VirtualHost *:80>
|
||||||
|
ServerAdmin webmaster@localhost
|
||||||
|
DocumentRoot /home/www/userlinux/sd/mercurial/cgi-bin/
|
||||||
|
ServerName hg.userlinux.net
|
||||||
|
ScriptAlias / /home/www/userlinux/sd/mercurial/cgi-bin/hgwebdir.cgi/
|
||||||
|
<Directory "/home/www/userlinux/sd/mercurial/cgi-bin/">
|
||||||
|
SetHandler cgi-script
|
||||||
|
AllowOverride None
|
||||||
|
Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch
|
||||||
|
Order allow,deny
|
||||||
|
Allow from all
|
||||||
|
</Directory>
|
||||||
|
ErrorLog /home/www/userlinux/sd/mercurial/logs/error.log
|
||||||
|
CustomLog /home/www/userlinux/sd/mercurial/logs/access.log combined
|
||||||
|
</VirtualHost>
|
||||||
|
```
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Fijaos que el archivo <em>hgwebdir.cgi</em> es el que lleva todo el trabajo, y ¿de dónde lo sacamos?. Si tenemos instalado Mercurial (cosa que se da por supuesta), ya lo tenemos en el sistema, tan solo hemos de copiarlo a su nueva ubicación y tan contentos:</p>
|
||||||
|
<p>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
# cp /usr/share/doc/mercurial/examples/hgwebdir.cgi /home/www/userlinux/sd/mercurial/cgi-bin/ # chmod a+x /srv/hg/cgi-bin/hgwebdir.cgi
|
||||||
|
```
|
||||||
|
|
||||||
|
<p>Ahora vamos a configurar alguna que otra preferencia del interfaz web, para ello creamos al mismo nivel del <em>cgi</em> un archivo de configuración llamado <em>hgweb.config</em>:</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
[paths]
|
||||||
|
rcms-plugin-prueba = /home/mercurial/rcms-plugin-prueba/
|
||||||
|
[web]
|
||||||
|
style = gitweb
|
||||||
|
allow_archive = bz2 gz zip
|
||||||
|
maxchanges = 200
|
||||||
|
```
|
||||||
|
|
||||||
|
<p>
|
||||||
|
En él -como veis- definimos los repositorios que vamos a mostrar (nombre = ruta) y varias opciones de la web como su theme, si permitimos o no descargar el contenido del repositorio…</p>
|
||||||
|
<p>
|
||||||
|
Ojo con los permisos, tanto del archivo de configuración como de los repositorios, a los que ha de acceder el usuario propietario de Apache para leer. Un reinicio de Apache bastaría para tener el repositorio listo:</p>
|
||||||
|
<p style="text-align: center;">
|
||||||
|
<img alt="" src="gallery/mercurial-apache.png" style="width: 400px; height: 385px;" /></p>
|
After Width: | Height: | Size: 224 KiB |
After Width: | Height: | Size: 210 KiB |
After Width: | Height: | Size: 176 KiB |
After Width: | Height: | Size: 224 KiB |
After Width: | Height: | Size: 250 KiB |
After Width: | Height: | Size: 193 KiB |
After Width: | Height: | Size: 191 KiB |
|
@ -0,0 +1,14 @@
|
||||||
|
---
|
||||||
|
title: "New monitor: Acer G276HLA"
|
||||||
|
date: 2015-07-20T11:48:53Z
|
||||||
|
draft: false
|
||||||
|
tags: [ ]
|
||||||
|
image: acerg276hl2.jpg
|
||||||
|
---
|
||||||
|
|
||||||
|
<p>A <a href="http://www.twitter.com/marcosbl">great friend of mine</a> adviced me about a bid during the Amazon Premium Day. We have been talking about monitors some time ago and he remembered I was searching a replacement for my 22" Benq.</p>
|
||||||
|
<p>It is a led monitor <a href="http://www.amazon.es/gp/product/B007Z1AWT6?psc=1&redirect=true&ref_=oh_aui_detailpage_o01_s01">Acer G276HLA</a> with 27 inches and DVI + HDMI ports, you can read the other specifications in the <a href="http://www.acer.co.uk/ac/en/GB/content/model/UM.HG6EE.A01">Acer web</a>.</p>
|
||||||
|
|
||||||
|
{{< gallery match="gallery/*" sortOrder="asc" rowHeight="150" margins="5" thumbnailResizeOptions="600x600 q90 Lanczos" previewType="blur" embedPreview="true" >}}
|
||||||
|
|
||||||
|
<p>One of the "cons" is the max. allowed resolution (<em>1920x1080</em>), but as my Macbook Air (not the retina one) has that same value, it's good for me. After a couple of days using it, I'm totally adapted and I think I've done a good buy.</p>
|
|
@ -0,0 +1,10 @@
|
||||||
|
---
|
||||||
|
title: "Nginx"
|
||||||
|
date: 2011-11-10T21:16:03Z
|
||||||
|
draft: false
|
||||||
|
tags: [ ]
|
||||||
|
image:
|
||||||
|
---
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Cuando algo tan sencillo de instalar y de configurar se convierte en un servicio <em>mejor</em> que sus antecesores en prácticamente todas sus características podemos decir que hemos dado un salto de calidad importante. Y eso que todavía no he probado la última rama. Gracias <a href="http://sysoev.ru/en/">Igor</a>.</p>
|
|
@ -0,0 +1,17 @@
|
||||||
|
---
|
||||||
|
title: "Pair programming con tmux"
|
||||||
|
date: 2013-02-25T08:56:09Z
|
||||||
|
draft: false
|
||||||
|
tags: [ ]
|
||||||
|
image:
|
||||||
|
---
|
||||||
|
|
||||||
|
<p>Con screen <a href="../../../../screen-el-nuevo-kibitz.html">era sencillo</a> crear un entorno para el pair programming, pero con <a href="http://tmux.sourceforge.net/">tmux</a> también es trivial. Me refiero a compartir una sesión de terminal para poder interactuar desde varias localizaciones distintas:</p>
|
||||||
|
|
||||||
|
```
|
||||||
|
tmux -S /tmp/pair
|
||||||
|
chmod 777 /tmp/pair
|
||||||
|
tmux -S /tmp/pair attach
|
||||||
|
```
|
||||||
|
|
||||||
|
<p>Se trata de crear una sesión especificando el destino, asignarle permisos (en este caso a todo el mundo) y conectarse a esa sesión con el otro usuario deseado. A partir de ahí ambos usuarios (el que ha creado la sesión y el que se ha conectado posteriormente) pueden interactuar simultáneamente.</p>
|
|
@ -0,0 +1,16 @@
|
||||||
|
---
|
||||||
|
title: "Papá, ¡somos campeones del mundo!"
|
||||||
|
date: 2010-07-12T01:40:30Z
|
||||||
|
draft: false
|
||||||
|
tags: [ ]
|
||||||
|
image:
|
||||||
|
---
|
||||||
|
|
||||||
|
<p style="text-align: center;">
|
||||||
|
<img alt="" src="gallery/campeones-del-mundo.jpg" style="width: 400px; height: 253px;" /></p>
|
||||||
|
<p>
|
||||||
|
Me compraste mi primer balón, mi primera equipación, me enseñaste a jugar limpio, a vivir los partidos con entusiasmo, con emoción pero con serenidad, a creer siempre en el buen fútbol, en la humildad, en los buenos jugadores. Sabías que algún día llegaría, tenía que llegar después de tantos partidos, de tanto sufrimiento, de tantos palos. Nunca perdiste la esperanza.</p>
|
||||||
|
<p>
|
||||||
|
Y ha llegado, ha sido hoy, gracias a la mejor generación de futbolistas de la historia del país puedo decir que hemos visto juntos a España ganar una Eurocopa y un Mundial. Y es muy posible que ni mis hijos ni mis nietos puedan decir lo mismo a sus padres.</p>
|
||||||
|
<p>
|
||||||
|
<strong>Papá... ¡¡somos campeones del mundo!!.</strong></p>
|
|
@ -0,0 +1,11 @@
|
||||||
|
---
|
||||||
|
title: "PyConES 2014"
|
||||||
|
date: 2014-11-20T13:53:25Z
|
||||||
|
draft: false
|
||||||
|
tags: [ ]
|
||||||
|
image: pycones2014.jpg
|
||||||
|
---
|
||||||
|
|
||||||
|
<p>El pasado fin de semana del 8-9 de Noviembre (2014) se celebró en Zaragoza la segunda edición de la <a href="http://2014.es.pycon.org/">PyConES</a>, la conferencia nacional de Python con mayor relevancia de España. <a href="http://2013.es.pycon.org/">El año pasado</a> también asistimos en su primera edición celebrada en Madrid y este año no iba a ser menos así que allá me fui con <a href="https://twitter.com/wushell">Borja</a>, en tren, desde Lugo.</p>
|
||||||
|
<p>Las <a href="http://2014.es.pycon.org/talks">charlas</a> a las que asistí fueron muy interesantes, normalmente casi siempre lo son si te pones en <em>modo esponja</em> e intentas sacar provecho a cada frase o idea del ponente. Al igual de interesante que coincidir con toda esa gente que vemos de <em>sarao</em> en<em>sarao</em> y hacer efectivas algunas desvirtualizaciones.</p>
|
||||||
|
<p>Y como el año pasado me olvidé de publicar <a href="../../../../gallery/photos/tag/pycones2013/">las fotos</a>, este año no quería repetir la desfachatez: <a href="http://oscarmlage.com/gallery/photos/tag/pycones2014/">FOTOS</a>, así que sin tiempo para una reseña más profunda... ¡nos vemos en la PyConES2015!.</p>
|
After Width: | Height: | Size: 112 KiB |
After Width: | Height: | Size: 203 KiB |
After Width: | Height: | Size: 180 KiB |
After Width: | Height: | Size: 208 KiB |
After Width: | Height: | Size: 231 KiB |
After Width: | Height: | Size: 164 KiB |
After Width: | Height: | Size: 223 KiB |
After Width: | Height: | Size: 162 KiB |
After Width: | Height: | Size: 164 KiB |
After Width: | Height: | Size: 162 KiB |
After Width: | Height: | Size: 213 KiB |
After Width: | Height: | Size: 282 KiB |
After Width: | Height: | Size: 253 KiB |
After Width: | Height: | Size: 214 KiB |
After Width: | Height: | Size: 151 KiB |
After Width: | Height: | Size: 188 KiB |
After Width: | Height: | Size: 185 KiB |
After Width: | Height: | Size: 185 KiB |
After Width: | Height: | Size: 198 KiB |
After Width: | Height: | Size: 203 KiB |
After Width: | Height: | Size: 223 KiB |
After Width: | Height: | Size: 245 KiB |
After Width: | Height: | Size: 252 KiB |
After Width: | Height: | Size: 202 KiB |
After Width: | Height: | Size: 207 KiB |
After Width: | Height: | Size: 160 KiB |
After Width: | Height: | Size: 174 KiB |
After Width: | Height: | Size: 154 KiB |
After Width: | Height: | Size: 177 KiB |
After Width: | Height: | Size: 233 KiB |