ka.da

Aller au contenu | Aller au menu | Aller à la recherche

Mot-clé - ligne de commande

Fil des billets - Fil des commentaires

dimanche, novembre 21 2010

gestion et surveillance de processus système avec supervisor, pour gérer des projets Django, Ruby on Rails, WSGI...


Dans cet article, je vais vous présenter supervisor un système de supervision et gestion de processus. Je l'utilise pour gérer les différents processus utilisés pour servir des applications Django, Ruby on Rails ou WSGI. Je vais vous présenter l'outil en lui-même, ses avantages, comment l'installer et le configurer, comment définir les processus qu'on veut gérer avec supervisor en donnant quelques exemples, la commande de gestion supervisorctl et son utilisation possible pour des déploiements automatisés (avec Fabric par exemple).

Introduction

Dans deux précédents articles, j'ai parlé de ma migration de Apache vers Nginx. Ainsi que de l'utilisation de gunicorn et unicorn pour "servir" mes projets Django et Ruby on Rails respectivement. J'avais créé deux scripts init.d pour gérer le lancement automatique de ceux-ci.

Mais cette solution n'était pas entièrement satisfaisante et on m'a alors parlé de supervisor, runit, god... qui sont des logiciels permettant de gérer le lancement de processus personnalisés avec un énorme plus : la supervision des processus lancés avec ce type de programmes : si un processus crashe il est relancé automatiquement !

J'ai donc choisi de tester supervisor et, vu qu'il m'a convaincu, je l'ai gardé. Et en plus il est écrit en Python :)

Installation et configuration

Pour l'installation, vous avez le choix entre les outils fournis par votre distribution (# aptitude install supervisor pour moi) ou bien avec pip install supervisor. Je pars du principe que vous allez utiliser la version fournie par votre distribution (et que vous utilisez Debian/Ubuntu bien sûr !) : en soi l'installation avec pip n'aurait un intérêt que pour avoir une version plus récente du logiciel or supervisor est stable, et ne dois donc plus évoluer beaucoup. L'avantage d'utiliser la version Debian est que tout est déjà prêt : les fichiers init pour le lancement automatique du démon supervisord au démarrage du serveur, l'arborescence dans /etc/supervisord et même une configuration minimale suffisante pour démarrer.
Le seul hic c'est que Debian Lenny (l'actuelle stable) n'a pas supervisor dans ses dépôts, mais une version existe dans testing/Squeeze et est parfaitement installable sur Lenny.

La configuration de supervisor en lui-même se fait dans le fichier supervisord.conf dont la configuration par défaut suffit pour un fonctionnement standard, mais n'hésitez pas à regarder si vous voulez changer quelques options (comme lancer un mini serveur web de contrôle de supervisor).

Définition des processus et exemples

La configuration de chaque processus supervisé se fait par contre via un fichier distinct pour chaque processus et ceux-ci sont stockés sous Debian dans /etc/supervisor/conf.d avec un nom terminant par .conf.

Je ne vais pas entrer dans le détail du fonctionnement de supervisor et donc de la syntaxe et des possibilités des fichiers de configuration mais plutôt vous donnez quelques exemples en expliquant les points intéressants.

application WSGI sous Gunicorn (ici l'interface web de Mercurial)

[program:code_domain_tld]
command=/usr/bin/gunicorn code_domain_tld:application -c /var/www/code_domain_tld/gunicorn.conf.py
directory=/var/www/code_domain_tld
user=www-data
autostart=true
autorestart=true
startsecs=10
redirect_stderr=true
stdout_logfile=/var/log/supervisor/code_domain_tld.gunicorn.log

program est le nom du service pour supervisor, c'est celui qui est utilisé ensuite pour le contrôle (cf plus bas). J'ai tendance à mettre le nom de domaine qui est servi par ce service[1]
command est le nom de la commande qui va être lancée et contrôlée par supervisor, ici c'est simplement gunicorn en indiquant le chemin du fichier de configuration. Pour le code_domain_tld.application c'est l'application WSGI qui va être lancée par gunicorn. Pour ce cas précis, c'est un fichier code_domain_tld.py contenant

#!/usr/bin/env python

import os
import sys
from mercurial.hgweb.hgwebdir_mod import hgwebdir
from mercurial.hgweb.request import wsgiapplication

os.environ["HGENCODING"] = "UTF-8"

def make_web_app():
    return hgwebdir("/etc/mercurial/hgweb.code.domain.tld.config")

def application(environ, start_response):
    environ['wsgi.url_scheme'] = environ.get('HTTP_X_URL_SCHEME', 'http')
    app = wsgiapplication(make_web_app)
    return app(environ, start_response)

directory est le répertoire de travail de la commande précédente
user permet de spécifier l'utilisateur qui sera utilisé pour lancer le programme : veuillez à ce qu'il ait les droits sur les fichiers nécessaires et surtout évitez root !
autostart et autorestart me semblent assez clairs : indiquer si le programme (la commande) doit être lancé automatiquement et relancé automatiquement en cas de crash
startsecs est le nombre de secondes à partir desquelles supervisor va vérifier l'état du programme pour voir si il s'est lancé correctement. Si un programme met beaucoup de temps à se lancer on peut jouer sur cette valeur pour que supervisor attende avant de le considérer comme crashé
les options suivantes concernent la journalisation (logs) de ce programme par supervisor

application Django sous Gunicorn

[program:domain_tld]
command=/opt/django/domain_tld/_venv/bin/python /opt/django/domain_tld/_venv/bin/gunicorn_django -c /opt/django/domain_tld/gunicorn.conf.py
directory=/opt/django/domain_tld/projet
user=www-data
autostart=true
autorestart=true
startsecs=10
redirect_stderr=true
stdout_logfile=/var/log/supervisor/domain_tld.gunicorn.log

command la différence se situe ici avec l'utilisation d'un interpréteur Python dans un virtualenv. Le reste est similaire à l'exemple au-dessus.

application Ruby on Rails sous Thin

[program:dev_domain_tld
command=/var/lib/gems/1.8/bin/thin start -C /opt/redmine/config/thin.yml
directory=/opt/redmine
user=www-data
autostart=true
autorestart=true
startsecs=10
redirect_stderr=true
stdout_logfile=/var/log/supervisor/dev_domain_tld.thin.log

Ici c'est pareil, seule la commande change pour lancer un programme différent Thin[2] pour faire tourner une application Ruby on Rails.

Il est très important de noter que pour que supervisor puisse contrôler vos processus (leur état, les arrêter, les redémarrer) il faut que ceux-ci ne partent pas en tâche de fond ('daemonize'). Pour gunicorn par exemple, il faut spécifier daemon=False dans le fichier de configuration.

L'application de gestion supervisorctl

Supervisor apporte aussi un outil de grande utilité : sa commande supervisorctl qui permet de vérifier l'état des différents processus ainsi que de les contrôler. Voyons quelques exemples de son utilisation en mode interactif.

$ supervisorctl
code_domain_tld                RUNNING    pid 2143, uptime 18 days, 18:13:41
domain_tld                     RUNNING    pid 2140, uptime 18 days, 18:13:41
dev_domain_tld                 RUNNING    pid 2139, uptime 18 days, 18:13:41
supervisor>

On voit que quand on lance la commande, on a tout de suite l'information sur l'état des processus gérés par supervisor.

supervisor> status
code_domain_tld                RUNNING    pid 2143, uptime 18 days, 18:13:41
domain_tld                     RUNNING    pid 2140, uptime 18 days, 18:13:41
dev_domain_tld                 RUNNING    pid 2139, uptime 18 days, 18:13:41

La commande status renvoie cette même information, mais on peut utiliser status <nom d'un processus> pour avoir uniquement l'état d'un processus

supervisor> stop code_domain_tld
code_domain_tld: stopped
supervisor> start code_domain_tld
code_domain_tld: started
supervisor> restart code_domain_tld
code_domain_tld: stopped
code_domain_tld: started

Assez logiquement les commandes start, stop, restart suivies du nom d'un processus agissent sur le processus en question en l'arrêtant, le démarrant ou le redémarrant.

D'autres commandes peuvent être utiles, tapez help dans supervisorctl pour en savoir d'avantage.

L'énorme avantage de supervisorctl c'est qu'en plus d'être utilisable en mode interactif comme on l'a vu au dessus il est également utilisable en mode non-interactif. Vous prenez les commandes évoquées au-dessus et les indiquez directement en tant qu'arguments à la commande supervisorctl : la commande est lancée par supervisorctl qui vous rend la main aussitôt. Exemple :

$ supervisorctl status
code_domain_tld                RUNNING    pid 2143, uptime 18 days, 18:13:41
domain_tld                     RUNNING    pid 2140, uptime 18 days, 18:13:41
dev_domain_tld                 RUNNING    pid 2139, uptime 18 days, 18:13:41
$

J'utilise cette possibilité notamment dans mes scripts Fabric pour déployer rapidement des projets Django et les redémarrer. Un bout de code comme suit mettra à jour la configuration supervisor du projet si nécessaire, et relancera le processus en utilisant la nouvelle configuration :

def wsgiserver():
    filename = '%s.conf' % env.app_name
    dir = '/etc/supervisor/conf.d'
    if exists('%s/%s' % (dir, filename)):
        choice = prompt('supervisord conf file already exists, overwrite (Y/N)?', default='N', validate=r'[YyNn]')
        if choice.upper() == 'N':
            return
    put(filename, '%s/%s' % (dir, filename))
    run('sudo supervisorctl reread')
    run('sudo supervisorctl update')

Liens

Notes

[1] en substituant les . par des _ comme vous l'avez sûrement remarqué : je ne me souviens plus pourquoi mais je pense que à l'origine ça m'était nécessaire pour Python ou Gunicorn et j'ai donc gardé cette habitude

[2] oui avant j'utilisais Unicorn mais j'avais des problèmes qui se sont résolus en changeant de serveur

mardi, avril 6 2010

Héberger des projets Django avec Nginx et Gunicorn

Mise à jour : j'ai finalement décidé d'utiliser supervisor pour gérer/monitorer ce genre de services,voyez ce billet.

Je continue l'optimisation de mon serveur web (cf le premier épisode) avec cette fois-ci mes projets Django que j'ai migré vers Nginx et Gunicorn. (veuillez noter que Gunicorn permet également de faire tourner d'autres types d'applications Python que Django)

Virtualenv

Pour gérer mes projets Django, j'utilise virtualenv et toute cette doc part donc du principe que vous l'utilisez également. Si ce n'est pas le cas 1. je vous encourage à le faire ;) 2. vous devrez adapter cette doc en conséquence.

Gunicorn

On commence par installer Gunicorn dans chacun des environnements virtuels hébergeant vos projets Django (cf plus bas pour les détails)

$ pip install gunicorn

ou

$ easy_install gunicorn

Ensuite pour chaque projet Django qui sera propulsé par Gunicorn, le plus simple est de créer un fichier de configuration gunicorn.conf.py pour indiquer les options du serveur à son lancement. Voici un exemple de fichier :

backlog = 2048
bind = "unix:/var/run/gunicorn/monprojet.sock"
pidfile = "/var/run/gunicorn/monprojet.pid"
daemon = True
debug = False
workers = 2
logfile = "/opt/django/www_domain_tld/log/gunicorn.log"
loglevel = "info"

Pour bind vous pouvez soit spécifier un socket Unix soit une adresse et un port TCP/IP. Veuillez consulter la doc configuration pour les détails.

Vous pouvez alors lancer gunicorn avec la commande suivante :

$ gunicorn_django -c gunicorn.conf.py

Bien sûr, comme pour l'article précédent que se passe-t-il en cas de redémarrage du serveur ? Il faut relancer les processus gunicorn un par un. Pour éviter ce genre de désagréments j'ai donc créé un script destiné au système de démarrage sysvinit de Debian.

Ce script a comme prérequis :

  • d'avoir pour chaque projet Django
    • un virtualenv dédié dans un répertoire www_domaine_tld
    • dans ce virtualenv un répertoire nommé monprojet avec les sources de celui-ci et notamment les fichiers importants pour Django (settings.py, urls.py...)
  • d'avoir un répertoire /etc/gunicorn/sites
  • dans ce répertoire vous faites un lien vers le virtualenv de chaque projet en lui donnant le nom du répertoire du projet, exemple :
# cd /etc/gunicorn/sites
# ln -s /opt/django/www_domaine_tld monprojet
  • le fichier gunicorn.conf.py est situé dans le virtualenv (donc dans le répertoire parent de celui des sources du projet)
  • il faut que le pid spécifié dans ce fichier soit de la forme monprojet.pid puisque son nom est ainsi extrapolable par rapport au nom du lien dans /etc/gunicorn/sites

Ce qui donne (pour éclaircir tout ça) une arborescence comme suit :

/etc/gunicorn/sites/
                    monprojet1 -> /opt/django/www_domaine1_tld      # lien vers /opt/django/www_domaine1_tld
                    monprojet2 -> /opt/django/www_domaine2_tld      # lien vers /opt/django/www_domaine2_tld

/opt/django/
            www_domaine1_tld/
                             bin/                         #
                             include/                     # répertoires créés par virtualenv
                             lib/                         # 
                             monprojet1/                  # répertoire contenant les sources du projet
                             gunicorn.conf.py             # fichier de configuration de gunicorn
            www_domaine2_tld/
                             bin/                         #
                             include/                     # répertoires créés par virtualenv
                             lib/                         # 
                             monprojet2/                  # répertoire contenant les sources du projet
                             gunicorn.conf.py             # fichier de configuration de gunicorn

Et voici le script /etc/init.d/gunicorn

#!/bin/sh 

### BEGIN INIT INFO
# Provides:       gunicorn
# Required-Start: $local_fs $syslog
# Required-Stop:  $local_fs $syslog
# Default-Start:  2 3 4 5
# Default-Stop:   0 1 6
# Short-Description: Gunicorn processes
### END INIT INFO

USER=www-data
NAME="gunicorn"
DAEMON="gunicorn_django"
CONFDIR=/etc/gunicorn/sites
PIDDIR=/var/run/gunicorn
VENV_ACTIVATION="source ../bin/activate"
CONFFILE="../gunicorn.conf.py"
OPTIONS="-c $CONFFILE"
RETVAL=0

# source function library
. /lib/lsb/init-functions

# pull in default settings
[ -f /etc/default/gunicorn ] && . /etc/default/gunicorn

start()
{
    echo $"Starting $NAME."
    cd $CONFDIR;
    for d in *; do
        echo -n $d;
        PIDFILE=$PIDDIR/$d.pid
        if [ -f $PIDFILE ]; then
            echo ": already started!"
        else
            cd $d;
            cd $d;
            su -c "$VENV_ACTIVATION; $DAEMON $OPTIONS" $USER && echo ": OK";
        fi
    done
    echo "done"
}

stop()
{
    echo $"Stopping $NAME:"
    cd $CONFDIR
    for d in *; do
        echo -n $d;
        if [ -f $PIDDIR/$d.pid ]
        then kill -QUIT `cat $PIDDIR/$d.pid` && echo ": OK" || echo ": failed";
        fi
    done
    echo "done"
}

reload()
{
    echo $"Reloading $NAME:"
    cd $CONFDIR
    for d in *; do
        echo -n $d;
        if [ -f $PIDDIR/$d.pid ]
        then kill -HUP `cat $PIDDIR/$d.pid` && echo ": OK" || echo ": failed";
        fi
    done
    echo "done"
}

case "$1" in
    start)
        start
        ;;
    stop)
        stop
        ;;
    restart)
        reload
        ;;
    reload)
        reload
        ;;
    force-reload)
        stop && start
        ;;
    *)
        echo $"Usage: $0 {start|stop|restart}"
        RETVAL=1
esac
exit $RETVAL

Nginx

Pour Nginx, on retrouve un fichier de configuration semblable à celui-ci :

upstream www_domaine_tld {
    server      unix:/var/run/gunicorn/monprojet.sock fail_timeout=0;
}

server {
    listen      80;
    server_name www.domaine.tld;

    access_log  /var/log/nginx/www.domaine.tld.access.log;
    error_log   /var/log/nginx/www.domaine.tld.error.log;

    location /media {
        root    /opt/django/www.domaine.tld/lib/python2.5/site-packages/django/contrib/admin/;
      expires       30d;
    }

    root            /opt/django/www.domaine.tld/monprojet/media;

    location / {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;       

        if (!-f $request_filename) {
            proxy_pass http://www_domaine_tld;
            break;
        }
    }
}

Et voili.

samedi, avril 3 2010

Faire tourner des applications Ruby on Rails avec Nginx et Unicorn

Mise à jour : j'ai finalement décidé d'utiliser supervisor pour gérer/monitorer ce genre de services, voyez ce billet.

Afin d'optimiser mon petit serveur virtuel chez Gandi qui, à force de lui ajouter des fonctions, des sites web, des utilisateurs avait de petits problèmes de charge au point de temps en temps de ne plus répondre, je migre du serveur web Apache vers Nginx. Étant donné que je fais tourner de tout et de rien sur Apache : du php, des cgi, du Ruby on Rails et du Django, l'opération s'avère un poil complexe, et je vais essayer de présenter petit à petit les solutions retenues pour servir chacune de ces technologies avec Nginx, en commençant par les applications Ruby on Rails.

Unicorn

Avec Apache, pour faire tourner mon application Ruby on Rails (Redmine) j'utilisais Passenger/mod_rails, parce qu'après moults recherches c'est la solution que j'avais trouvé. Le passage à Nginx m'a permis de me pencher à nouveau sur ce problème et j'ai décidé de me tourner vers Unicorn[1].

Pour installer unicorn le plus simple est de passer par l'outil de gestionnaire de packages Ruby gem :

# gem install unicorn

Afin de servir vos projets Rails, il faut lancer un processus Unicorn par projet, en spécifiant les options nécessaires. Le plus simple pour spécifier ces options est de créer un fichier de configuration pour Unicorn directement dans chaque projet Rails. Ce fichier doit être nommé unicorn.rb et situé dans le répertoire config du RAILS_ROOT c'est-à-dire du répertoire racine de votre projet.

Voici un exemple de fichier sur lequel vous pouvez vous baser pour créer votre propre configuration.

# configuration file for Unicorn (not Rack)
#
# See http://unicorn.bogomips.org/Unicorn/Configurator.html for complete
# documentation.

# Use at least one worker per core if you're on a dedicated server,
# more will usually help for _short_ waits on databases/caches.
worker_processes 2

# Help ensure your application will always spawn in the symlinked
# "current" directory that Capistrano sets up.
working_directory "/opt/redmine" # available in 0.94.0+

# listen on both a Unix domain socket and a TCP port,
# we use a shorter backlog for quicker failover when busy
listen "/var/run/unicorn/redmine.sock", :backlog => 64
#listen 8080, :tcp_nopush => true

# nuke workers after 30 seconds instead of 60 seconds (the default)
timeout 30

# feel free to point this anywhere accessible on the filesystem
pid "/var/run/unicorn/redmine.pid"

# some applications/frameworks log to stderr or stdout, so prevent
# them from going to /dev/null when daemonized here:
stderr_path "/opt/redmine/log/unicorn.stderr.log"
stdout_path "/opt/redmine/log/unicorn.stdout.log"

# combine REE with "preload_app true" for memory savings
# http://rubyenterpriseedition.com/faq.html#adapt_apps_for_cow
preload_app true
GC.respond_to?(:copy_on_write_friendly=) and
  GC.copy_on_write_friendly = true

before_fork do |server, worker|
  # the following is highly recomended for Rails + "preload_app true"
  # as there's no need for the master process to hold a connection
  defined?(ActiveRecord::Base) and
    ActiveRecord::Base.connection.disconnect!

  # The following is only recommended for memory/DB-constrained
  # installations.  It is not needed if your system can house
  # twice as many worker_processes as you have configured.
  #
  # # This allows a new master process to incrementally
  # # phase out the old master process with SIGTTOU to avoid a
  # # thundering herd (especially in the "preload_app false" case)
  # # when doing a transparent upgrade.  The last worker spawned
  # # will then kill off the old master process with a SIGQUIT.
  # old_pid = "#{server.config[:pid]}.oldbin"
  # if old_pid != server.pid
  #   begin
  #     sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU
  #     Process.kill(sig, File.read(old_pid).to_i)
  #   rescue Errno::ENOENT, Errno::ESRCH
  #   end
  # end
  #
  # # *optionally* throttle the master from forking too quickly by sleeping
  # sleep 1
end

after_fork do |server, worker|
  # per-process listener ports for debugging/admin/migrations
  # addr = "127.0.0.1:#{9293 + worker.nr}"
  # server.listen(addr, :tries => -1, :delay => 5, :tcp_nopush => true)

  # the following is *required* for Rails + "preload_app true",
  defined?(ActiveRecord::Base) and
    ActiveRecord::Base.establish_connection

  # if preload_app is true, then you may also want to check and
  # restart any other shared sockets/descriptors such as Memcached,
  # and Redis.  TokyoCabinet file handles are safe to reuse
  # between any number of forked children (assuming your kernel
  # correctly implements pread()/pwrite() system calls)
end

Éditez les premières variables pour correspondre à votre configuration : nombre de processus au démarrage, répertoire de travail, écoute sur un socket ou un port TCP/IP, emplacement du fichier pid et des logs de capture de stdout et stderr.

Vous pouvez alors vous mettre dans le répertoire en question et lancer Unicorn comme suit :

/opt/redmine$ unicorn_rails  -c config/unicorn.rb -E production -D

Je n'ai malheureusement pas trouvé comment spécifier l'environnement à utiliser (le RAILS_ENV, ici production) autrement que dans la ligne de commande : l'indiquer dans le fichier de configuration ne marche pas... mais le premier qui trouve/sais merci de laisser un commentaire !

Tout ça c'est bien sympa mais ce n'est pas pratique de devoir lancer à la main chacun des processus unicorn nécessaire ni de devoir les relancer à la main si le serveur redémarre.

Pour pallier à ce problème, il faut créer un script de démarrage de Unicorn. Vu que ma Debian utilise un init à la Système V j'ai préféré créer un script pour ce système plutôt qu'utiliser un autre outil pour cela. Celui que j'utilise est adapté de ce site. Il suffit de vérifier que la partie des variables (notamment le RAILS_ENV, USER, DAEMON) correspond à votre configuration.

#!/bin/sh 

### BEGIN INIT INFO
# Provides:       unicorn
# Required-Start: $local_fs $syslog
# Required-Stop:  $local_fs $syslog
# Default-Start:  2 3 4 5
# Default-Stop:   0 1 6
# Short-Description: Unicorn processes
### END INIT INFO

RAILS_ENV="production"
USER=www-data
DAEMON="/var/lib/gems/1.8/bin/unicorn_rails"

NAME="unicorn"
CONFDIR=/etc/unicorn/sites
PIDDIR=/var/run/unicorn
CONFFILE=config/unicorn.rb
RETVAL=0

OPTIONS="-c $CONFFILE -D -E $RAILS_ENV"
# source function library
. /lib/lsb/init-functions

# pull in default settings
[ -f /etc/default/unicorn ] && . /etc/default/unicorn

test -x $DAEMON || exit 0

start()
{
    echo $"Starting $NAME."
    cd $CONFDIR;
    for d in *; do
        echo -n $d;
        cd $d;
        PIDFILE=$PIDDIR/$d.pid
        [ -f $PIDFILE ] && echo ": already started!"
        [ ! -f $PIDFILE ] && su -c "$DAEMON $OPTIONS" $USER && echo ": OK";
    done
    echo "done"
}

stop()
{
    echo $"Stopping $NAME:"
    cd $CONFDIR
    for d in *; do
        echo -n $d;
        if [ -f $PIDDIR/$d.pid ]
        then kill -QUIT `cat $PIDDIR/$d.pid` && echo ": OK" || echo ": failed";
        fi
    done
    echo "done"
}

reload()
{
    echo $"Reloading $NAME:"
    cd $CONFDIR
    for d in *; do
        echo -n $d;
        if [ -f $PIDDIR/$d.pid ]
        then kill -USR2 `cat $PIDDIR/$d.pid` && echo ": OK" || echo ": failed";
        fi
    done
    echo "done"
}

case "$1" in
    start)
        start
        ;;
    stop)
        stop
        ;;
    restart)
        reload
        ;;
    reload)
        reload
        ;;
    force-reload)
        stop && start
        ;;
    *)
        echo $"Usage: $0 {start|stop|restart}"
        RETVAL=1Unicorn se lance en utilisant des sockets mais peut également utiliser TCP/IP.
esac
exit $RETVAL

Ce script est orienté Debian, et devra probablement être adapté pour d'autres distributions.

Pour le faire tourner il vous faut :

  • un répertoire /etc/unicorn/sites dans lequel vous mettez des liens symboliques vers vos projets Rails (le RAILS_ROOT plus exactement de chacun)
  • dans chacun des RAILS_ROOT de vos projets un fichier config/unicorn.rb comme indiqué au-dessus

Nginx

Ensuite pour utiliser Unicorn depuis Nginx, il vous suffit d'avoir une déclaration de virtualhost du type :


upstream redmine_domain_tld {
    server      unix:/var/run/unicorn/redmine.sock fail_timeout=0;
}

server {
    listen      80;
    server_name redmine.domain.tld;

    access_log  /var/log/nginx/redmine.domain.tld.access.log;
    error_log   /var/log/nginx/redmine.domain.tld.error.log;

    location /images {
        root    /opt/redmine/public/images;
    }

    location / {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;       

        if (!-f $request_filename) {
            proxy_pass http://redmine_domain_tld;
            break;
        }
    }
}

Et voilà. La suite de ma migration de Apache vers Nginx dans un prochain épisode.

Notes

[1] merci à Olivier Meunier de m'avoir rappeler l'existence de ce soft... auquel j'aurais dû penser tout de suite en connaissant gunicorn

dimanche, février 14 2010

Django : gérer plusieurs domaines avec un seul projet et les mêmes données

Problématique

Toujours en plein dans Django, mon projet actuel est de refaire tous mes sites web hébergés sur divers domaines en Django. Jusque là tout va bien je dirais. Ça se complique un peu quand l'idée est d'utiliser le même projet pour gérer tous ces sites web.

Pourquoi utiliser le même projet ? Plusieurs (bonnes) raisons à cela :

  • simplifier le développement, la maintenance et le déploiement : un projet à gérer c'est mieux que plusieurs
  • partager les mêmes données : c'est à mon avis le point essentiel qui m'intéresse : pouvoir créer du contenu (billet de blog, photo,...) et pouvoir facilement l'affecter à tel ou tel site ou à plusieurs à la fois (cela dépend également des applications, cf plus bas)
  • tout administrer de façon centralisée : c'est la suite du point précédent : pouvoir, dans une interface d'administration unique, créer du contenu et l'affecter à tel ou tel site web sans devoir changer d'interface d'admin

Je vais donc vous proposer ici la solution que j'ai retenu pour servir 2 domaines différents avec Django. Ceux-ci seront pour l'exemple www.domaine1.tld et www.domaine2.tld (ouaouh ! que c'est original ! - ça marche bien évidemment pour un sous-domaine...).

L'historique de cette configuration est expliqué en bas de ce billet pour ceux que ça intéresse.

note importante : puisqu'on m'a signalé que la solution présentée ici n'est pas forcément "propre" au sens Django puisqu'elle implique de changer le nom de divers fichiers de base d'un projet Django, et puisqu'après réflexion je suis d'accord avec cette remarque... voici quelques éclaircissements :

Si vous faites les choses le plus possible dans le style Django, en pensant application réutilisable il est en effet préférable de penser chaque domaine comme un projet séparé et donc d'isoler les fichiers plutôt de cette façon

www_domaine1_tld/
                 __init__.py
                 manage.py
                 settings.py
                 urls.py

www_domaine2_tld/
                 __init__.py
                 manage.py
                 settings.py
                 urls.py

ce qui permet de ne pas modifier les fichiers manage, settings et urls. Cela est encore facilité si votre projet est composé d'applications réutilisables, installables à la manière de modules Python (ce qui doit d'ailleurs être au maximum le cas).

La documentation ci-dessous est donc toujours valable mais il est probablement préférable d'utiliser cette manière de faire... à vous de voir maintenant...

Arborescence

Le premier point à voir est l'organisation des fichiers dans le répertoire du project. Afin de pas trop encombrer celui-ci, les fichiers ayant la même fonction (les fichiers settings.py, urls.py, les templates) sont déplacés dans des sous-répertoires, les fichiers manage.py restant eux à la racine, comme suit :

monprojet/
          www_domaine1_tld_manage.py
          www_domaine2_tld_manage.py
          settings/
                   __init__.py
                   www_domaine1_tld.py
                   www_domaine2_tld.py
                   global_settings.py
          urls/
               __init__.py
               www_domaine1_tld.py
               www_domaine2_tld.py
          templates/
                    www_domaine1_tld
                    www_domaine2_tld

il ne faut pas oublier les fichiers __init__.py aux endroits indiqués.

Fichiers manage.py

Pour exécuter des commandes Django, on peut utiliser $ django-admin.py <commande> <options> avec certaines contraintes (spécifier le fichier settings...) ou le "raccourci" $ ./manage.py <commande> <options>.

Dans le cas où l'on veut gérer plusieurs domaines, et où le fichier "settings" ne s'appelle pas settings.py, il faut créer un fichier manage.py différent par domaine.

Exemple avec www_domaine1_tld_manage.py.

#!/usr/bin/env python
from django.core.management import execute_manager
try:
    import settings.www_domaine1_tld
except ImportError:
    import sys
    sys.stderr.write("Error: Can't find the file 'settings.www_domaine1_tld.py' in the directory containing %r...." % __file__)
    sys.exit(1)

if __name__ == "__main__":
    execute_manager(settings.www_domaine1_tld)

Il suffira alors de lancer une commande comme la suivante pour exécuter des commandes Django sur un certain domaine

$ ./www_domaine1_tld_manage.py <commande> <options>

mise à jour : vu qu'on me l'a signalé, cette manipulation concernant les fichiers manage.py n'est pas nécessaire puisqu'il suffit d'ajouter l'option --settings=settings.www_domaine1_tld pour pouvoir utiliser ./manage.py de manière classique

Fichiers urls.py

Pour les fichiers urls, il suffit de créer les fichiers suivant l'arborescence indiquée au-dessus en utilisant des fichiers urls.py "classiques", avec la différence que si vous devez importer les "settings" du domaine il faut indiquer le module sous la forme settings.www_domaine1_tld

Framework "sites" de Django

Si vous voulez utiliser les mêmes applications sur plusieurs domaines sans pour autant devoir publier le même contenu partout, il faut alors utiliser des applications qui utilisent le framework "sites".

Vous devez dans ce cas :

  • créer des sites dans la base de données
  • et spécifier le SITE_ID qui va bien dans le fichier "settings" (voir plus bas)

Fichiers settings.py

Pour gérer les paramètres de chaque domaine/site on aura un fichier pour chacun ainsi qu'un fichier pour les paramètres globaux (pour les bases de données, les applications installées...).

Voici les paramètres spécifiques à chaque domaine, avec des commentaires.

# paramètres Django pour le site www.domaine1.tld

import os

# on charge ici les paramètres spécifiés dans global_settings.py
from global_settings import *

# on initialise BASE_DIR avec le répertoire de base du projet,
#  pour l'utiliser pour d'autres paramètres
BASE_DIR = os.path.join(os.path.dirname(__file__), '..')

# il faut indiquer ici l'identifiant du site (au sens Django), ansi que dans la base de données
# cf http://docs.djangoproject.com/en/dev/ref/contrib/sites/
SITE_ID = 1

# chemin absolu vers le répertoire des médias du domaine
MEDIA_ROOT = os.path.join(BASE_DIR, 'static', 'www_domaine1_tld')

# URL pour gérer les médias venant de MEDIA_ROOT
MEDIA_URL = 'http://www.domaine1.tld/static/'

# il faut ici initialiser une clé secrète distincte pour chaque domaine
SECRET_KEY = 'xxx'

# on spécifie ici le fichier urls.py
ROOT_URLCONF = 'monprojet.urls.www_domaine1_tld'

# on spécifie ici le répertoires des templates
TEMPLATE_DIRS = (
    os.path.join(BASE_DIR, 'templates', 'www_domaine1_tld')
)

# ce qui suit n'est pas obligatoire mais permet d'avoir un fichier spécifique
# en local afin par exemple de surcharger quelques paramètres pour le dev
try:
     from local_settings import *
except ImportError:
     pass

Et voici un exemple de fichier global_settings.py avec les paramètres qui peuvent/doivent être partagés.

DATABASE_ENGINE = ''
DATABASE_NAME = ''
DATABASE_USER = ''
DATABASE_PASSWORD = ''
DATABASE_HOST = ''
DATABASE_PORT = ''

TEMPLATE_LOADERS = (
    'django.template.loaders.filesystem.load_template_source',
    'django.template.loaders.app_directories.load_template_source',
)

MIDDLEWARE_CLASSES = (
    'django.middleware.common.CommonMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
)

INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.admin',
)

Serveur web/Python/WSGI

Pour cette partie, j'utilise pour ma part (pour l'instant) Apache+mod_wsgi mais je ne vais pas rentrer dans les détails puisque ça n'est pas spécifique au cas particulier qui nous intéresse ici.

J'ai un fichier pour Apache qui indique un VirtualHost pour chaque domaine et pointe vers un fichier WSGI pour chacun également.

Dans le fichier WSGI il faut faire attention à étendre le PYTHONPATH si nécessaire et indiquer le fichier settings correctement, un peu comme ce qui suit :

import sys

sys.path.append('/opt/django/monprojet/')
os.environ['DJANGO_SETTINGS_MODULE'] = 'monprojet.settings.www_domaine1_tld'

Historique

Au début de ma recherche sur cette problématique je me suis tout de suite dit que c'était le boulot de Django de faire ça, et quand j'ai compris que ce n'était pas le cas naturellement, je me suis orienté vers des applications qui, étendant Django, lui permettait de gérer cela. J'ai trouvé deux applications dont le but semble de combler ce manque :

Je ne me souviens plus pourquoi j'ai mis de côté django-multisite mais mes tests ont porté sur django-threaded-multihost. L'application faisait ce pourquoi elle était prévu mais avait un problème majeur : il n'y avait qu'un settings.py, un urls.py... pour tous les domaines servis et là je me suis rendu compte que ce n'était pas vraiment ce dont j'avais besoin puisque le but était de pouvoir servir le même contenu mais bien évidemment de façon différente... Retour à la case départ !

Heureusement je suis tombé sur une discussion sur IRC #django-fr entre david`bgk et cyberdelia concernant ce thème et cyberdelia m'a alors expliqué comment ses divers domaines (pour http://www.croisedanslemetro.com il me semble (j'aime le concept de ce site :) soit dit en passant) pointant vers un même projet étaient gérés... c'était ce qu'il me fallait ! Le temps que ça mûrisse, celui de m'y replonger, d'implémenter la solution et de la tester, je me suis dit qu'il serait intéressant de poster cette solution et me voilà donc... (en ajoutant le temps de rédaction de ce billet ;-)... j'espère que ça pourra être utile à d'autres.

mercredi, juin 3 2009

Mercurial : éditer l'historique d'un dépôt (les changesets) avec les MQ

J’ai toujours[1] cru qu’on ne pouvait éditer l’historique des ‘changesets’ de Mercurial, mais en fait si ! Et c’est grâce à ce billet sur le blog de Jesper Noehr (un des gars derrière bitbucket) que j’ai découvert qu’en fait c’est tout à fait possible.

En utilisant les MQ (mercurial queues) dont je vous ai déjà parlé, il est donc tout à fait possible de rééditer des modifications déjà enregistrées (committées) dans votre dépôt Mercurial.

Bien évidemment, ce genre de manipulations n’est possible que si vous contrôlez votre dépôt et ses éventuels clones. Dès l’instant où celui-ci a pu être cloné, vous avez perdu la maîtrise de votre code et les modifications sur lesquelles vous voulez revenir sont déjà parties.

Nous sommes dans un dépôt test avec 3 changesets

$ hg log
changeset:   2:6a2d12a15cda
tag:         tip
summary:     modifications de a et b

changeset:   1:ca5faf3b4493
summary:     ajout de b

changeset:   0:66545c7be018
summary:     ajout de a

et nous souhaitons revenir sur les changesets 1 et 2.

Nous initialisons d’abord un dépôt de MQ si ce n’est pas déjà fait

$ hg qinit -c

[2]

Puis nous importons les changesets que nous voulons modifier

$ hg qimport -r 2:1

Si on regarde maintenant le log

changeset:   2:6a2d12a15cda
tag:         qtip
tag:         2.diff
tag:         tip
summary:     modification de a et b

changeset:   1:ca5faf3b4493
tag:         1.diff
tag:         qbase
summary:     ajout de b

changeset:   0:66545c7be018
tag:         qparent
summary:     ajout de a

on retrouve bien nos 3 différents changesets sauf que les 2 derniers sont différents : ce sont maintenant des patchs sous forme MQ que nous pouvons alors manipuler de façon classique[3]

On peut donc dépiler tous les patchs pour revenir dans l’état qu’on voulait avant les changesets 1 et 2

$ hg qpop -a
Patch queue now empty
$ hg log
changeset:   0:66545c7be018
tag:         tip
summary:     ajout de a

On peut alors à coup de hg qpush/hg qpop empiler/dépiler nos patchs afin de les modifier, les réorganiser ou ajouter des changesets, et donc revenir sur l’historique de notre dépôt.

J’ai découvert qu’en fait cette information est également disponible sur le site de Mercurial, voyez http://www.selenic.com/mercurial/wi…

Notes

[1] “toujours” est peut-être un trop grand mot, ça ne fait pas non plus si longtemps que ça que je connais Mercurial ;-)…

[2] tant qu’à faire nous ajoutons -c pour avoir un dépôt MQ ‘versionnable’

[3] je vous renvoie vers les chapitre 12 et 13 du hgbook pour plus d’infos

vendredi, décembre 12 2008

Mercurial : partager votre dépôt de patches MQ en même temps que votre dépôt principal

Travaillant actuellement sur un projet de développement en mode collaboratif, nous utilisons Mercurial pour gérer nos sources, et je me suis mis à utiliser intensément les MQ (Mercurial Queues) pour gérer mes propres modifications.

Je ne vais pas rentrer dans les détails de ce que sont les MQ, juste vous dire que c’est un système permettant de gérer une série de patchs “flottants”, un peu comme des commits mais que vous pouvez dépiler et empiler pour les modifier suivant vos besoins, tout en suivant le développement principal.
Je vous renvoie vers la documentation officielle et le chapitre qui y est consacré dans le livre (à lire et relire pour comprendre le principe).

Jusque là en travaillant de mon côté avec des MQ, je pouvais tranquillement poursuivre plusieurs développements en parallèle en local, tout en suivant le développement sur la branche principale. Quand un patch était ok, je l’appliquais sur le dépôt principal et tout allait bien.
Le problème se pose maintenant parce que je veux, tout en maintenant une série de patches, pouvoir les partager afin de montrer l’avancée de mon travail. (tout en continuant à pouvoir les empiler/dépiler bien évidemment)
C’est possible mais pas très pratique : ça demande de réinitialiser le dépôt côté serveur et les numéros de changesets changent, sans parler de l’obligation de passer par un dépôt local tiers.

Je me suis donc mis à chercher de la doc sur le partage de patches MQ et suis tombé sur ce très intéressant article intitulé Using Mercurial Queues and bitbucket.org[1] et, ne voulant pas utiliser bitbucket pour stocker mon dépôt[2], me suis mis en tête de faire quelque chose d’équivalent sur mon propre hébergement.

Peu de doc existe donc voici la mienne…

Sur le serveur

on commence par initialiser un dépôt qu’on va appeler test

$ hg init /chemin/vers/test

puis on y initialise un dépôt de MQ (qui l’on retrouvera dans .hg/patches)

$ cd /chemin/vers/test
$ hg qinit -c

on doit ensuite créer un fichier hgrc afin de spécifier les autorisations de chacun des deux dépôts : /chemin/vers/test/.hg/hgrc et /chemin/vers/test/.hg/patches/.hg/hgrc : je ne rentre pas dans les détails et vous renvoie vers mon article dédié au partage de dépôts Mercurial si vous en avez besoin.

Il faut également ajouter ces deux dépôts dans la configuration hgweb(dir).cgi

[paths]
test  =  /chemin/vers/test/
test-mq = /chemin/vers/test/.hg/patches

Si vous allez sur l’url de votre site vous devriez alors voir quelque chose comme ça Capture-Mercurial_repositories_index_-_Iceweasel.png

Sur le client

Clone

on clone le dépôt en précisant qu’on veut également le dépôt de MQ grâce à la commande dédiée qclone

$ hg qclone http://hg.domain.tld/test

Push

quand on veut pusher nos modifications sur le dépôt en y incluant notre dépôt de patches, il faut

  • d’abord dépiler tous les patches
$ hg qpop -a
  • ensuite pusher le dépôt principal
$ hg push https://hguser@hg.domain.tld/test
  • puis le dépôt MQ
$ hg push https://hguser@hg.domain.tld/test-mq

Pull

pour mettre à jour notre dépôt local avec les modifications disponibles sur le serveur, il faut procéder de la même façon que pour le push avec la commande pull : dépiler les patchs, mettre à jour chacun des deux dépôts.

Voilà, je crois que je n’ai rien oublié, n’hésitez pas à me le dire si c’est le cas.

Notes

[1] je reparlerais peut-être d’ailleurs à une autre occasion de bitbucket mais pour faire simple c’est une sorte de github à la sauce Mercurial

[2] principalement parce que pour l’instant le développement est fermé, quand il s’ouvrira bitbucket pourra être une solution intéressante

mardi, avril 22 2008

Mercurial : faire des modifications distantes et avoir les fichiers mis à jour sur le serveur

Aujourd'hui, j'ai découvert quelque chose d'étrange en utilisant Mercurial.

J'ai donc un dépôt privé accessible via https (cf ce post), je le clone, je fais des modifications, je committe, puis je push sur le serveur

$ hg clone https://code.veiras.info/private_repo
$ vi...
$ hg commit -m "nouvelles modifs"
$ hg push
http authorization required
realm: code.veiras.info Mercurial repository
user: X
password: X
searching for changes
adding changesets
adding manifests
adding file changes
added 1 changesets with 3 changes to 3 files

Jusque là tout va bien, c'est la façon normale de travailler et j'ai toujours fait comme ça. Sauf qu'aujourd'hui, j'étais également connecté sur mon serveur en ssh, je vais donc dans mon dépôt, un hg tip me dit bien que je viens de faire un commit, mais par contre quand je veux regarder mes fichiers... mes modifications n'y sont pas et là, je tombe des nues : qu'ai-je donc fait mal ? Encore une histoire de permissions sur les fichiers ?

<mode panique>ÇA VEUT DIRE QUE TOUT CE QUE J'AI COMMITTÉ DEPUIS TOUT CE TEMPS EST PERDU ???!!!</mode panique>

vite fait, je saute sur mon client IRC préféré[1] et rebondit sur #mercurial (sur freenode) où l'on me rassure (?) en me disant que tout est normal.

Pour résumer :

  • si je veux avoir les modifs également sur les fichiers en local sur le serveur, il faut que je fasse un hg update sur le serveur;
  • si je veux automatiser cet update, il faut utiliser un 'hook' : plus d'informations sur le site officiel ou sur le guide non officiel;
  • si on utilise pas sur le serveur le dépôt, on peut même effacer tout les fichiers à l'exception du répertoire .hg/, celui-ci contenant justement tout l'historique du dépôt.
  • si on veut effacer ces fichiers, un rm * pouvant poser des problèmes plus tard, il est préférable d'utiliser hg update null qui permet de revenir à la révision 0
  • si jamais on a à nouveau besoin de ces fichiers sur le serveur, un hg update fera l'affaire.

Notes

[1] j'en profite pour signaler l'existence de mibbit.com très pratique quand on est coincé derrière un proxy et qu'on a besoin d'aide...

lundi, mars 24 2008

migration Sarge -> Etch (OCSInventory-NG)

Comme c'est le calme plat en ce moment sur ce blog[1], je vais suivre l'exemple de Grégory Colpart et retranscrire ici une migration Sarge -> Etch[2].

Le serveur en question est un serveur dédié au logiciel libre d'inventaire OCSInventory-NG, c'est donc tout simplement une combinaison de serveur web Apache, de serveur de base de données MySQL, de PHP et de Perl. La vraie particularité (et seule ?) de ce serveur est celle d'être un serveur virtuel Xen.

Une fois le fichier /etc/apt/sources.list édité pour y ajouter les sources de etch, un

# aptitude update
# aptitude dist-upgrade

a suffi pour lancer la migration.

Quelques petits problèmes se sont néanmoins présentés :

  • Apache ne voulait pas se lancer et j'avais une erreur
[Tue Mar 18 11:40:40 2008] [error] Can't locate Apache/compat.pm in @INC.....
[Tue Mar 18 11:40:40 2008] [error] Can't load Perl module Apache::Ocsinventory for server...

dans mes logs Apache. Il faut alors éditer le fichier de configuration OCS pour Apache (/etc/apache2/conf.d/ocsinventory.conf chez moi) et modifier la ligne

PerlSetEnv OCS_MODPERL_VERSION 2

Explication : la migration m'a fait passé de apache à apache2 et de libapache-mod-perl (version 1.x) à libapache2-mod-perl2 (version 2.x)

  • j'ai eu quelques problèmes avec MySQL qui ne voulait pas redémarrer, j'en ai donc profité pour migrer de mysql-server-4.1 à mysql-server-5.0 sans problème (à part que le serveur phpmyadmin permettant de gérer les divers serveurs mysql n'a pas le paquet mysql-client-5.0... pour l'instant)
  • je suis également passé de php4 à php5 sans problème non plus.

Rien de particulier donc. J'ai fait rebooter mon serveur virtuel au cas où, et j'avais, par sécurité, fait une copie de mon serveur virtuel (stocké sur LVM) avant la migration pour le cas où celle-ci se passerait mal. Au final, moins de 2h de maintenance, et moins d'une 1/2 heure - 1 heure je pense, pour ce serveur.

Notes

[1] en partie à cause d'un déménagement qui a entrainé une coupure internet mais pas seulement...

[2] migration non initialement prévue mais, afin de pouvoir utiliser un petit script de ma composition (ocs-ipinterface), j'avais besoin du paquet python-ipy non disponible sur sarge

mercredi, janvier 30 2008

Mercurial : partage de dépôts différents en http et https + push par https

J’ai commencé à regarder du côté des SCM[1] (marre de la gestion CPOLD ;-) il y a quelque temps déjà, et m’était tout d’abord arrêté sur Subversion [2] étant novice en la matière. Il s’est vite révélé peu adapté à ma pratique : “développement” sur plusieurs machines pas toujours online, et pas de serveur dédié toujours en ligne pour stocker mes dépôts. Depuis peu, ce dernier problème s’est réglé (merci Gandi Hébergement[3]) mais pour autant, j’ai abandonné Subversion pour Mercurial, un gestionnaire de code source distribué.

Je ne vais pas entrer ici dans les détails sur les caractéristiques d’un gestionnaire de code source distribué, ses avantages, ou même sur une présentation détaillée sur Mercurial et son mode de fonctionnement (je vous laisse visiter le site officiel pour cela). Je vais uniquement présenter des détails sur la configuration que j’ai mise en place afin de parvenir à partager aisément des dépôts différents via http et https, ainsi que comment faire pour autoriser la mise à jour de ceux-ci via https.


présentation des dépôts Mercurial sur le web


Imaginons que vos dépôts soient stockés dans /mercurial et que vous vouliez pouvoir donner un accès en lecture sur le web, à l’adresse http://hg.domain.tld. Nous allons configurer Apache et Mercurial pour ce faire.

Tout d’abord, nous allons créer un emplacement pour les fichiers web et le script CGI en Python pour Mercurial. Prenons /var/www/domain.tld/hg. Créons à cet endroit un répertoire hgweb dans lequel nous allons placer le script hgwebdir.cgi que vous pouvez trouver sur votre système (sous Debian /usr/share/doc/mercurial/examples/hgwebdir.cgi) ou sur le site de Mercurial. Éditez ce fichier pour avoir à la fin :

def make_web_app():
    return hgwebdir("/etc/mercurial/hgweb.config")

Nous allons ensuite créer le fichier /etc/mercurial/hgweb.config avec un contenu comme suit :

[paths]
depot1  =  /mercurial/depot1
depot2  =  /mercurial/depot2

On peut également utiliser la directive [collections] pour partager une hiérarchie de dépôts mais nous partons du principe que l’on veut contrôler précisément ce que l’on partage.

À partir de là, il nous reste à configurer Apache. Créez un fichier /etc/apache2/sites-available/hg.domain.tld afin de déclarez votre VirtualHost. Voici en exemple mon fichier

NameVirtualHost *:80

<VirtualHost *:80>
    ServerAdmin webmaster@hg.domain.tld

    ServerName hg.domain.tld  

    DocumentRoot /var/www/domain.tld/hg

    <Directory /var/www/domain.tld/hg/>
        Options FollowSymLinks +ExecCGI
        AddHandler cgi-script .cgi
        DirectoryIndex hgweb/hgwebdir.cgi
        AllowOverride None

        RewriteEngine on
        RewriteBase /hgweb
        RewriteRule ^$ hgwebdir.cgi  [L]
        RewriteCond %{REQUEST_FILENAME} !-f
        RewriteRule (.*) hgwebdir.cgi/$1  [QSA,L]
    </Directory>

    LogLevel warn
    ErrorLog /var/log/apache2/hg.domain.tld-error.log
.   CustomLog /var/log/apache2/hg.domain.tld-access.log combined
    
    ServerSignature Off
</VirtualHost>

Notez ici les règles de réécriture qui permettent de faire disparaître hgwebdir.cgi de vos URL. C’est ce qui m’a pris le plus de temps à trouver avant de tomber sur cette doc.

Activez votre nouveau “site” ainsi que les modules Apache dont vous aurez besoin

# a2ensite hg.domain.tld
# a2enmod mod_python
# a2enmod rewrite
# /etc/init.d/apache2 restart

Vous pouvez maintenant aller voir http://hg.domain.tld et parcourir vos dépôts Mercurial depot1 et depot2


partager des dépôts différents par https


Tout ça, c’est très bien, mais on aimerait également pouvoir avoir accès à certains dépôts que l’on ne veut pas donner en lecture à tout internet : des dépôts privés en somme. Nous allons donc partager ceux-ci sur un site sécurisé avec authentification.

Nous allons donc retourner dans notre répertoire /var/www/domain.tld/hg/hgweb, copier hgwebdir.cgi en hgwebdirssl.cgi et éditez ce fichier pour avoir :

def make_web_app():
    return hgwebdir("/etc/mercurial/hgwebssl.config")

Créons le fichier /etc/mercurial/hgwebssl.config avec :

[paths]
depot1  =  /mercurial/depot1
depot2  =  /mercurial/depot2
depot_prive3 = /mercurial/depot3
depot_prive4 = /mercurial/depot4

Et retournons éditer /etc/apache2/sites-available/hg.domain.tld pour y ajouter :

# en haut du fichier
NameVirtualHost *:443

# à la fin du fichier
<VirtualHost *:443>
    ServerAdmin webmaster@hg.domain.tld

    ServerName hg.domain.tld

    DocumentRoot /var/www/domain.tld/hg

    SSLEngine on
    SSLCertificateFile /etc/apache2/ssl/hg.domain.tld.pem
    SSLCertificateKeyFile /etc/apache2/ssl/hg.domain.tld.key

    <Directory /var/www/domain.tld/hg/>
        Options FollowSymLinks +ExecCGI
        AddHandler cgi-script .cgi
        DirectoryIndex hgweb/hgwebdirssl.cgi
        AllowOverride None

        RewriteEngine on
        RewriteBase /hgweb
        RewriteRule ^$ hgwebdirssl.cgi  [L]
        RewriteCond %{REQUEST_FILENAME} !-f
        RewriteRule (.*) hgwebdirssl.cgi/$1  [QSA,L]

        AuthUserFile /etc/mercurial/hgweb.htpasswd
        AuthGroupFile /dev/null
        AuthName "hg.domain.tld Mercurial repository"
        AuthType Basic
#       <LimitExcept GET OPTIONS>
                Require valid-user
#       </LimitExcept>
    </Directory>

    ErrorLog /var/log/apache2/hg.domain.tld-error.log

    LogLevel warn

    CustomLog /var/log/apache2/hg.domain.tld-access.log combined
    ServerSignature Off

</VirtualHost>

On retrouve ici les directives rewrite que nous avons utilisé dans la première partie, à la différence qu’elles pointent vers le CGI spécifique à notre configuration SSL, et quelques directives spécifiques justement à https/SSL. Bien évidemment, il vous faudra créer un fichier de mots de passe (nommé ici hgweb.htpasswd) avec :

# htpasswd -c /etc/mercurial/hgweb.htpasswd hguser

pour autoriser l’utilisateur hguser.

Notez les directives <LimitExcept GET OPTIONS> qui, si elles sont décommentées, permettent à tout le monde de visualiser les dépôts tout en demandant une authentification pour interagir via les commandes de Mercurial (clone, pull, push…).

Rechargez la configuration d’Apache et regardez la différence entre http://hg.domain.tld et https://hg.domain.tld


autoriser la mise à jour des dépôts via https


Pour autoriser le push via https, une fois le travail ci-dessus effectué, il suffit d’éditer la configuration des dépôts en modifiant le fichier .hg/hgrc de chaque projet. Éditez-le ou créez-le et insérez les lignes suivantes :

[web]
allow_push = hguser


utilisation


Voilà c’est fait, vous pouvez maintenant interagir très simplement avec vos dépôts Mercurial. Pour cloner un dépôt public

$ hg clone http://hg.domain.tld/depot1

Pour cloner un dépôt privé

$ hg clone https://hguser@hg.domain.tld/depot_prive3

Pour mettre à jour (push) ce même dépôt

$ hg push https://hguser@hg.domain.tld/depot_prive3

J’ajouterais que le plus dur est de gérer finement les droits d’accès aux divers fichiers nécessaires au bon fonctionnement de cette configuration. Pour faire simple, comme d’habitude, laissez le minimum de droits sur les fichiers de configuration (appartenance à l’utilisateur www-data et lecture pour lui), et pour les dépôts j’ai choisi la solution un groupe devel pour mon utilisateur principal ainsi que www-data et les droits pour ce groupe.

mise à jour : il semble nécessaire (depuis les dernières mises à jour de Mercurial ?) d’ajouter une ligne baseurl = dans la section [web] des fichiers .hg/hgrc afin de cacher les hgweb(dir).cgi dans les url.

Notes

[1] définition en français et en anglais

[2] cf cet article

[3] je sais que je n’ai pas parlé de cette nouvelle offre très intéressante de Gandi le registrar bien connu (pour sa qualité de service, son support et son engagement) alors allez voir le bar de gandi

mercredi, janvier 9 2008

utiliser apt-cacher pour les mises à jour de vos machines Debian

Que ce soit à la maison ou au travail, quand on commence à avoir beaucoup de machines Debian, se pose le problème de leurs mises à jour et surtout de la bande passante utilisée pour celles-ci.

Au niveau Debian, plusieurs solutions existent, comme utiliser un proxy web classique (Squid par exemple), partager le répertoire contenant les fichiers téléchargés (ce qui peut être un peu risqué), répliquer complètement l'arborescence Debian (mais ça demande à télécharger des paquets dont probablement on ne se servira jamais) ou bien encore utiliser un outil dédié à ce problème.
Plusieurs existent : apt-proxy, approx, apt-cacher.

Il semble que de ces différentes solutions aucune ne ressorte vraiment, et que des problèmes existent sur chacune d'elle. En tout cas, ici, je vais vous présenter apt-cacher.

côté serveur

L'installation est, comme d'habitude, facile sous Debian, il vous suffit de faire un

# aptitude install apt-cacher

Ensuite, un petit tour du côté de /etc/apt-cacher/apt-cacher.conf nous permet de configurer l'application, notamment le port d'écoute, le répertoire cache, les hôtes autorisés. En général, les paramètres par défaut sont corrects, je n'ai eu besoin de modifier que les paramètres pour utiliser un proxy :

http_proxy=votreproxy.lan.domain.tld:8080
use_proxy=1

Ensuite, pour permettre le lancement automatique de l'application, il faut modifier /etc/default/apt-cacher

AUTOSTART=1

Il suffit ensuite de vérifier que vos paramètres dans /etc/apt/sources.list sont bons. Il faut par exemple que toutes les distributions pouvant être utilisées par les clients soient listées.

côté client

Il suffit, sur chaque client amené à utiliser le proxy apt, de modifier /etc/apt/sources.list pour avoir au lieu de

deb http://ftp2.fr.debian.org/debian/ etch main
deb http://security.debian.org/ etch/updates main

quelque chose comme ceci pour pointer vers votre serveur apt-cacher et son port associé (3142 par défaut)

deb http://apt-cacher:3142/ftp2.fr.debian.org/debian/ etch main
deb http://apt-cacher:3142/security.debian.org/ etch/updates main

Remarquez que l'on indique à apt-cacher le miroir Debian que l'on veut utiliser, donc, afin de profiter vraiment de apt-cacher, il faut veiller à ce que tous les clients utilisent le même.[1]

Pour faire la modification facilement, je vous propose une petite ligne de commande sed :

# sed -i -e 's!http://!http://apt-proxy:3142/!g' /etc/apt/sources.list

Pensez à vérifier que les paramètres de proxy apt sont bons, c'est-à-dire en général qu'il n'y a pas de proxy indiqué dans /etc/apt/apt.conf ou dans le répertoire /etc/apt/apt.conf.d/[2].

Notes

[1] Si vraiment, vous avez beaucoup de clients, et que faire la modification sur chacun devient très compliqué, je vous encourage alors à aller voir du côté de Puppet ou Cfengine. Vous pouvez trouver des informations complémentaires sur debian-administration.

[2] par exemple, xen-tools écrit un fichier proxy-guess dans ce répertoire avec les paramètres proxy de l'hôte Xen dom0

vendredi, septembre 28 2007

réinstallation de GRUB sur le MBR avec un système sur LVM chiffré

Après réinstallation de Windows XP sur mon portable professionnel, je me retrouve comme d'habitude à chercher les commandes dont j'ai besoin pour réinstaller GRUB sur le MBR qu'a écrasé Windows et qui m'empêche d'avoir accès à Linux. Voici donc un petit mémo.

J'utilise le live-cd System Rescue CD pour ce faire. Je démarre dessus.

Une fois que j'ai accès au prompt, je déchiffre ma partition chiffrée (ici /dev/hda3) contenant mon LVM.

root@sysresccd /root % /sbin/cryptsetup luksOpen /dev/hda3 hda3_decrypt

Je rescanne les LVM et active les nouveaux volumes découverts

root@sysresccd /root % vgscan
root@sysresccd /root % vgchange -a y

Je crée ensuite un répertoire temporaire où je monte mon / et mon /boot et tous les autres partitions pouvant être nécessaires (/usr est le minimum pour avoir accès à l'exécutable grub)

root@sysresccd /root % cd /tmp
root@sysresccd /tmp % mkdir root
root@sysresccd /tmp % mount /dev/data-root root
root@sysresccd /tmp % mount /dev/hda2 root/boot
root@sysresccd /tmp % mount /dev/data-usr root/usr
root@sysresccd /tmp % mount /dev/data-var root/var

Il suffit ensuite de faire un chroot sur le système Debian et de lancer la réinstallation de GRUB

root@sysresccd /tmp % chroot /tmp/root /bin/bash
sysresccd:/# /usr/sbin/grub
grub> root (hd0,1)
grub> setup (hd0)
...
grub> quit
sysresccd:/# exit

Et voilà, je peux redémarrer et retrouver le menu me permettant d'avoir accès à Debian.

mercredi, septembre 26 2007

faire tourner Windows sur Xen

Pour une raison ou pour une autre[1] vous pouvez avoir envie (?) ou besoin de faire tourner un Windows sous votre Linux. Heureusement, depuis peu Xen couplé à un processeur récent le permet.

Installation de Windows 2000 sur Xen (vue dans un bureau Gnome)

prérequis

  • un processeur HVM : vous pouvez vérifier ici si le vôtre l'est
  • un poste/serveur Linux Debian Etch minimum[2] avec Xen fonctionnel
  • un CDROM d'installation de Windows (je n'ai testé qu'avec Windows 2000 mais XP et 2003 Server semblent fonctionner également)
  • des petits doigts :) et ce billet

procédure
Pour utiliser les nouvelles fonctionnalités des processeurs virtualisants et donc pouvoir installer Windows[3] il vous faut installer un package spécial qui fournit les outils utilisateurs

# aptitude install xen-ioemu-3.0.3-1

On copie ensuite le cdrom sur le disque dur

# dd if=/dev/cdrom of=/tmp/win2000pro.iso

On crée un fichier image vide appelé à contenir le système invité (6Go ici)

# dd if=/dev/zero of=Win2k.img bs=1M count=6144

On crée le fichier de configuration Xen /etc/xen/win2000.cfg pour Windows. Voici le mien, adaptez-le à votre configuration

# more /etc/xen/win2k.cfg
kernel = "/usr/lib/xen-default/boot/hvmloader"
builder='hvm'
memory = '256'
name = 'Win2000'
disk = [ 'file:/mnt/media/data/xen/domains/win2k/win2k.img,ioemu:hda,w','file:/tmp/win2000pro.iso,ioemu:hdc:cdrom,r' ]
vif = [ 'type=ioemu, bridge=xenbr0' ]
device_model = '/usr/lib/xen-default/bin/qemu-dm'
memmap = '/usr/lib/xen/boot/mem-map.sxp'
boot='d'
sdl=1
vnc=0

Les directives spécifiques à un hôte HVM sont kernel, builder, device_model et memmap.
Dans la partie disk il vous faut spécifier l'image ISO pour l'installation, vous pouvez ensuite connecter votre lecteur de cd-rom en remplaçant la ligne par disk = 'file:/mnt/media/data/system/xen/do....
La directive boot indique 'd' pour booter sur le cd-rom et 'c' pour booter sur le disque dur virtuel.
sdl=1 permet, si vous êtes sous X, d'avoir une fenêtre qui s'ouvre automatiquement quand vous lancez la machine virtuelle ce qui est au moins nécessaire lors de l'installation. Vous pouvez ensuite utiliser le mode de prise à distance que vous préférez. (mise à jour : si vous voulez utilisez VNC, changez sdl=0 vnc=1 et connectez-vous avec vncviewer sur localhost:5900)

Et voilà, un petit

# xm create /etc/xen/win2000.cfg

et c'est parti !

divers
Pour pouvoir monter le fichier image Windows que vous avez un bête mount -o loop ne suffit pas, voici la commande qui va bien

# mount -o loop,offset=$((63*512)),rw /mnt/media/data/system/xen/domains/win2k/win2k.img temp/

J'ai vu également que Russell Coker a écrit un billet intitulé A support guide [for|to] Xen avec un résumé des principales commandes utiles pour Xen.

Au fait, je fais tourner un Windows 32 bits sur ma Debian en 64 bits.

copies d'écrans
Quelques screenshots pour le plaisir

Installation de Windows 2000 sur Xen

Installation de Windows 2000 sur Xen (premier login)

Installation de Windows 2000 sur Xen (le fameux "Démarrer avec Windows 2000")

PS : je suis depuis peu syndiqué sur le planète des utilisateurs francophones de Debian, planète libre et peut-être même planète APRIL alors :

PS2 : dès vendredi 1er octobre, une nouvelle taxe[4] "copie privée" arrive sur les disques durs externes et les clés USB, alors si vous aviez un achat en tête, précipitez-vous !

Notes

[1] la mienne, c'est d'espérer pouvoir enfin rejouer à SimCity 4 Rush Hour que malheureusement je n'arrive pas à faire tourner avec Wine

[2] à vrai dire n'importe quelle distribution est valable bien évidemment mais je me base sur celle-ci

[3] en fait n'importe quel OS non modifié comme par exemple OpenBSD ou NetBSD (cf)

[4] qui a dit encore une ? qu'il se dénonce !

mardi, septembre 11 2007

Flash sous Debian 64 bits

Depuis que j'ai un nouveau PC qui tourne sous 64 bits, mon gros problème était d'arriver à utiliser Flash sous Mozilla et Firefox. En effet, Flash est une technologie propriétaire[1] et Adobe ne propose pas de version 64 bits. Plusieurs solutions existent néanmoins pour réussir à le faire mais elle sont toutes assez compliquées comme avoir un chroot 32 bits pour faire tourner les applications 32 bits[2]. Heureusement, depuis peu existe un utilitaire appelé nspluginwrapper permettant d'utiliser des plugins sur une plateforme pour laquelle ils ne sont pas conçus, comme par exemple des plugins 32 bits sur une plateforme 64 bits qui est ce qui nous intéresse.

J'ai commencé par suivre une doc Ubuntu qui m'a permis d'avoir Flash sous Mozilla Seamonkey/Iceape mais malheureusement ça ne marchait pas pour Firefox/Iceweasel.

Heureusement cet utilitaire est arrivé dans Debian testing, et, comme depuis peu j'ai un pied dans testing/lenny[3] j'ai alors pu l'installer tout simplement avec

# aptitude install nspluginwrapper

et là, magie ! Iceape et Iceweasel supportent le Flash... :)

update : j'ai oublié de vous dire pourquoi Flash ça pue

Notes

[1] vive les logiciels libres !

[2] cf http://debian-administration.org pour des articles traitant du problème

[3] principalement pour avoir une version de Digikam supérieure à 0.9.2 qui gère les fichiers RAW

jeudi, septembre 6 2007

script OpenBSD pour mise à jour DNS dynamique chez l'APINC

Je me suis enfin intéressé à la possibilité de créer des zones DNS dynamiques sur mes domaines DNS hébergés sur l'APINC, et ai créé un script shell qui marche sous OpenBSD. Le seul prérequis : avoir curl[1]. Vous copiez ce script, remplissez les variables et le mettez dans votre cron pour qu'il s'exécute régulièrement.

#!/bin/sh

## variables
# inserer la liste des id APINC de vos zones DNS dynamiques
ids="xxxx_xxxxx xxxx_xxxxx"
# le nom de votre interface connectée à internet
interface=fxp0

export old_ip=`cat /tmp/my_ip`
export ip=`ifconfig |grep -A1 $interface|grep "inet "|cut -d " " -f 2`

if [ "$old_ip" != "$ip" ]; then
echo "changement d'ip"
echo "$ip" > /tmp/my_ip

for id in $ids
do
curl http://www.apinc.org/board/dns/dyn.php?id=$id\&ip=$ip
done
fi

[inspiré de]

update j'ai mis à jour le script pour gérer plusieurs zones dynamiques

jeudi, août 30 2007

ssh : supprimer une entrée du fichier known_hosts

pour ne pas rechercher sans fin cette astuce quand j'en ai besoin...

Sous Debian, quand on se connecte à un serveur ssh, avec les options par défaut, il y a une vérification de la signature de la clé du serveur. Ces signatures sont enregistrés dans le fichier ~/.ssh/known_hosts et lorsqu'on se connecte à un serveur auquel on s'est déjà connecté, le client vérifie si la clé du serveur n'a pas changé, cela afin d'éviter des problèmes de sécurité (connexion à un serveur usurpateur).
Le problème se pose lorsqu'on sait que la clé a changé, par exemple si c'est un serveur qu'on gère et qu'on vient de le réinstaller, et que le client ssh n'arrête pas de se plaindre que la clé est mauvaise et ne veut à aucun prix s'y connecter. Dans ce cas, il faut éditer le fichier et enlever l'entrée correspondante qui pose problème... hum... malheureusement, maintenant c'est difficile à faire le fichier étant hashé. La commande magique c'est donc

ssh-keygen -R <nom_du serveur_SSH>

[vu sur linuxfr]

samedi, juillet 28 2007

le son sur la M2N

Je continue ma bataille pour tout faire marcher sur ma carte mère ASUStek M2N, et j'aborde maintenant le son. Sur Debian Etch par défaut, avec le noyau 2.6.18-adm64 (qu'il soit Xen ou pas) celui-ci ne marche pas, le driver ne semblant pas se charger correctement. Si vous faites un

# alsaconf

il ne vous détecte aucune carte son.

Il nous faut récupérer les drivers ALSA plus récents que ceux disponibles sur Etch.

Tout d'abord on désinstalle/purge[1] tous les paquets alsa déjà installés[2] Ensuite on installe les paquets qui nous seront nécessaires

# aptitude install alsa-utils alsa-base build-essential linux-headers-`uname -r`

On récupère les derniers drivers

# wget ftp://ftp.alsa-project.org/pub/driver/alsa-driver-1.0.14.tar.bz2

Puis on les compile et installe

# tar xvjf alsa-driver-1.0.14.tar.bz2
# ./configure --with-oss=yes --with-cards=hda-intel 
# make
# make install

On met à jour la liste des modules

# update-modules

Et enfin on détecte la carte son

# alsaconf 

[vu sur]

On peut ensuite vérifier que tout fonctionne bien en utilisant par exemple les commandes indiquées sur le forum Trucs & Astuces de debian-fr.org ici.

oups, j'ai oublié d'indiquer l'essentiel : les identifiants de ma carte son

# lspci |grep -i audio
00:05.0 Audio device: nVidia Corporation MCP61 High Definition Audio (rev a2) 
# for i in `lspci | grep [aA]udio | sed "s/ .*$//"`; do echo `lspci -n | grep $i` ; done
00:05.0 0403: 10de:03f0 (rev a2)

Notes

[1] # aptitude purge <paquet>

[2] on les trouve avec dpkg -l | grep alsa

Xen sur une ASUStek M2N

Je suis actuellement en phase "installation système" sur mon nouveau pc construit autour d'une carte mère ASUStek M2N basée sur un chipset nVidia nForce 430. Malheureusement, Xen n'a pas marché direct, et, m'étant un peu arraché les cheveux pour le faire fonctionner, je poste ici les informations importantes contenues dans le fichier /boot/grub/menu.lst[1] afin de faire fonctionner le kernel 2.6.18-4-xen-amd64 :

title Xen 3.0.3-1-amd64 / Debian GNU/Linux, kernel 2.6.18-4-xen-amd64
root (hd0,0)
kernel /xen-3.0.3-1-amd64.gz noapic
module /vmlinuz-2.6.18-4-xen-amd64 root=/dev/mapper/vg--root-lv--root ro acpi=off noapic nolapic console=tty0
module /initrd.img-2.6.18-4-xen-amd64
savedefault

Notes

[1] les parties importantes sont les mentions 'noapic', 'acpi=off', 'nolapic'

jeudi, juillet 12 2007

faire du NAT avec Xen

Si vous ne disposez que d'une adresse publique et que vous voulez héberger plusieurs serveurs virtuels sous Xen, vous êtes obligé de passer par du NAT et dans ce cas voici comment configurer votre hôte dom0 Xen pour que ça marche.

Tout d'abord il faut modifier le fichier /etc/xen/xend-config.sxp pour y indiquer

(network-script network-nat)
(vif-script vif-nat)

Ensuite, pour Debian, un simple script ajouté au répertoire /etc/network/if-up.d[1] et nommé iptables[2] par exemple, fournit les règles pour le pare-feu iptables pour effectuer la translation d'adresse :

#!/bin/sh

echo "1" > /proc/sys/net/ipv4/ip_forward
iptables -t nat -A POSTROUTING -s 192.168.0.0/16 -j MASQUERADE

Si ensuite vous voulez faire de la redirection de ports (ou port forwarding) il faut alors rajouter à ce fichier une règle du genre

iptables -A PREROUTING -t nat -p tcp -i eth0 --dport 80 -j DNAT --to 192.168.3.2:80

en spécifiant correctement les ports et adresses qui correspondent à votre configuration.

Bien évidemment, ce morceau de script pour configurer le pare-feu ne suffit pas pour assurer la sécurité de vos machines Xen (dom0 et domU)

Notes

[1] le fait de placer ce fichier dans ce répertoire permet qu'il soit lancé à chaque fois que le réseau est lancé

[2] n'oubliez pas de le rendre exécutable avec un chmod u+x iptables

mercredi, avril 11 2007

enregistrer vos sessions console avec script et scriptreplay

Il y a quelques semaines, Uwe Hermann nous expliquait comment enregistrer (et rejouer) une session console avec script et scriptreplay, et je pensais en parler ici... et puis j'ai oublié.

Heureusement, je viens de tomber sur la traduction en français de ce billet et cette fois-ci, je n'attends pas et j'en parle aussitôt ! ;-)

En effet, ce truc est assez énorme, puisqu'il permet non seulement d'enregistrer sa session console mais également de la rejouer, et ça marche [1] ! Vraiment impressionnant de revoir par exemple l'édition de fichiers avec vi se rejouer devant tes yeux...

Notes

[1] assez bien... il me semble que j'avais testé aussitôt et qu'à un moment, ça ne ressemblait plus à rien, mais c'est à vérifier !

dimanche, novembre 5 2006

patcher OpenBSD

À la suite de ce billet, voyons maintenant comment patcher notre serveur OpenBSD de façon plus simple, en procédant patch par patch, une fois qu'on a récupéré les sources complètes.

Sur la page d'errata de notre version (3.9 en l'occurence), on trouve les patchs par alerte de sécurité.

Prenons comme l'exemple, la dernière concernant OpenSSH, datée du 12 octobre[1].

On récupère le patch concerné par exemple dans /tmp

# cd /tmp;
# ftp ftp://ftp.openbsd.org/pub/OpenBSD/patches/3.9/common/015_ssh.patch

on lit l'entête du fichier pour connaître les instructions de déploiement du patch, et on les suit

# more /tmp/015_ssh.patch

on applique le patch

# cd /usr/src
# patch -p0 < /tmp/015_ssh.patch

on recompile et on installe

# cd usr.bin/ssh
# make obj
# make cleandir
# make
# make install

et, même si ce n'est pas indiqué, on arrête et redémarre le service ssh

# ps ax | grep sshd
 <pid> ??  Is      0:00.07 /usr/sbin/sshd
# kill <pid>; /usr/sbin/sshd

Notes

[1] tiens... anniversaire de la découverte de l'Amérique par Christophe Colomb en 1492 (cf Wikipedia)

- page 1 de 2