Aujourd'hui, on va se pencher sur plein de choses... On va couvrir d'une manière assez basique l'installation d'un environnement Ruby sur serveur, le développement d'un script d'un bot IRC grâce au framework Cinch puis la transformation de ce script en service, le tout sous un environnement Debian. Accrochez vous, c'est parti !

Concrètement on a besoin de quoi ?

D'un serveur ou d'un poste de travail sous GNU/Linux, on va y aller pas à pas pour installer tout ce qu'il nous faut ! Dans l'ordre on va procéder comme ceci:

  • Installer un environnement Ruby basique sur un serveur Debian (Ruby et Rubygems)
  • Développer un bot IRC très basique en Ruby avec le framework Cinch
  • Faire de ce script Ruby un service gérable comme tous les autres services (ssh, apache, etc...)

On va donc commencer par l'installation de Ruby sur un serveur Debian. Notez que la manière de faire est similaire sur une machine avec n'importe quelle distribution.

Ruby sur Debian

Actuellement sur la Debian 8, le wiki nous indique que la version de Ruby présente dans les dépôts est la 2.1. Sur Wheezy (Debian 7), c'est la 1.8. Tout ceci est quand même un peu vieux, donc nous allons installer par nous-même la version stable 2.3 (à l'heure où j'écris ces lignes).

Sachez qu'il y a plusieurs manières d'installer Ruby sur une distribution GNU/Linux, grace à des outils différents. Notamment via:

  • Les gestionnaires de paquets
  • L'installer ruby-build qui est un plugin pour rbenv
  • L'installer ruby-install qui permet de compiler et d'installer différentes versions de Ruby.
  • Les sources
  • Et bien d'autres...

Nous disposons de l'outil ruby-switch pour changer la version active de Ruby (Ruby, gem et irb) sur un serveur parmi les différentes versions de Ruby installées. Très pratique et disponible dans les dépôts (sur Debian du moins).

Nous allons opter pour une méthode simple mais efficace, l'installation d'un environnement Ruby et Gem depuis... les sources !

Installation de Ruby

D'abord on va télécharger des outils nécessaires à la future compilation et à l'utilisation de Ruby pour éviter de rencontrer des erreurs dans le futur:

sudo apt-get install build-essential git-core curl sqlite3 libsqlite3-dev libxml2-dev libxslt1-dev libreadline-dev libyaml-dev libcurl4-openssl-dev libncurses5-dev libgdbm-dev libffi-dev

Tout n'est pas nécessaire mais dans l'état c'est ce qu'il me faut pour le bot. Au moins avec ça vous n'aurez pas de soucis pour l'installation.

On récupère les sources qui nous intéressent depuis ruby-lang.org. Dans notre cas, ça sera la version 2.3.0 au format d'archive tar.gz.

Positionnez vous dans le répertoire de votre choix et téléchargez l'archive:

cd /root/
wget http://ftp.ruby-lang.org/pub/ruby/stable/ruby-2.3.0.tar.gz

On extrait le contenu de l'archive et on se place dans le dossier correspondant:

tar xvzf ruby-2.3.0.tar.gz
cd ruby-2.3.0

Puis on compile calmement tout ce petit monde avec les droits root grace aux trois commandes suivantes:

./configure
make
make install

A la fin de tout ceci, si il n'y a aucune erreur, un ruby -v dans un temrinal devrait vous renvoyer ceci (hormis l'architecture qui peut-être différente, 32bits dans mon cas):

ruby 2.3.0p0 (2015-12-25 revision 53290) [i686-linux]

Maintenant qu'on a Ruby, on va passer à l'installation de Rubygems qui est très similaire.

Installation de Rubygems

On file sur le site de Rubygems pour télécharger la dernière version, la 2.6.6 à l'heure où j'écris ces lignes. Prenez la version au format .tgz.

Idem, on se place dans un dossier puis on télécharge l'archive, et enfin, on l'extrait:

cd /root/
wget https://rubygems.org/rubygems/rubygems-2.6.6.tgz
tar xvzf rubygems-2.6.6.tgz

L'installation est différente car on utilise Ruby pour installer Rubygems, très simple, on se déplace dans le dossier et on lance le script d'installation en Ruby:

cd rubygems-2.6.6
ruby setup.rb

Une fois l'installation temrinée, on vérifie que tout est correct avec un gem --version qui nous renvoie un gentil 2.6.6. On va quand même s'assurer d'avoir la dernière version possible avec un gem update --system.

Et voila, notre environnement Ruby basique est installé. Nous disposons de Ruby et de Rubygems. Avec Rubygems nous pouvons par exemple installer un environnement Ruby on Rails pour faire un chouette site via la commande: gem install rails, inutile dans le cas présent puisque ce qu'on veut, c'est développer un petit bot tout mignon pour IRC.

Un Bot comme dans "I, Robot" avec Will Smith ?

Clairement pas !

On parle d'un bot bête, un peu inutile. Je vous parle pas d'une véritable intelligence artificielle, mais juste d'un outil qui peut s'avérer pratique pour certaines choses pour animer un chan IRC. Un genre d'assistant un peu bêbête qu'on peut trouver dans certains films et qui, parfois, rend service.

On va le développer en Ruby, comme indiqué précédemment avec le framework Cinch, lui-même développé en Ruby et disponible sous forme de Gem. Extrêmement bien fait, orienté objet, threadé, bref il est absolument parfait par rapport à d'autres frameworks IRC. Chaque plugins dispose de son propre thread ce qui laisse la possibilité au thread principal de pouvoir continuer à bosser en toute sérénité, la sortie dans la console est colorée quand le bot parle, il y a un système de debug et le code est simple à faire et à comprendre, bref Cinch est vraiment super.

Sachez qu'il y a plein de frameworks pour IRC, en Python, en Perl et dans bien d'autres langages.

Attention, je ne suis pas développeur Ruby, j'ai appris sur le tas et spécialement pour cet article, j'ai bidouillé pour arriver à quelque chose de fonctionnel. Donc il est fort probable que tous les codes sources suivants soient honteux pour un vrai développeur Ruby, non optimisés et plein d'incohérences. Si vous êtes compétents en Ruby, n'hésitez pas à me faire parvenir vos modifications et améliorations ! Le code est volontairement simple pour que tout le monde puisse comprendre, l'avantage du Ruby c'est que ça peut se lire assez simplement. En bref, bienvenue au pays de la bidouille de code où les tests unitaires et la gestion d'erreur n'existe pas.

Installation de Cinch

Rien de bien compliqué, il suffit d'installer la gem Cinch via la commande suivante:

gem install cinch

Le code basique sur lequel on va s'appuyer et présenté par la documentation est le suivant:

require 'cinch'

bot = Cinch::Bot.new do
  configure do |c|
    c.server = "irc.freenode.org"
    c.channels = ["#cinch-bots"]
  end

  on :message, "hello" do |m|
    m.reply "Hello, #{m.user.nick}"
  end
end

bot.start

La ligne require 'cinch' nous permet d'importer ce qu'il faut pour utiliser le framework Cinch, ensuite on crée un objet nommé bot que l'on configure. Puis on applique la méthode start à notre objet bot pour démarrer le bot.

Dans la boucle de configuration, on peut indiquer beaucoup de choses comme le montre la documentation. On va donc un peu étoffer la configuration du bot via les lignes suivantes, volontairement commentées pour l'article:

bot = Cinch::Bot.new do
    configure do |c|
        c.nick = "Bot" # Le nom du bot
        c.realname = "DaBot" # Le nom réel du bot
        c.server = "irc.domain.tld" # Le serveur IRC
        c.channels = ["#chan"] # Le chan IRC
        c.delay_joins = 5 # Un temps d'attente avant de rejoindre un chan
        c.messages_per_second = 1.0 # messages par seconde,  pour éviter de se faire kick pour flood
        c.server_queue_size = 1 # Un message par cible pour du round robin, évite de se faire kick
    end
end

Pourquoi mettre un delay_joins ? Tout simplement pour avoir le temps de s'identifier sur le serveur avant de rejoindre un chan, 5 secondes est un temps d'attente largement suffisant pour ça. Notre bot va donc disposer d'un compte sur le serveur IRC et on va faire en sorte qu'il s'identifie, qu'il s'applique un mode particulier puis qu'il rejoingne un chan quelconque.

Login du bot

Pour qu'il puisse s'identifier, on va utiliser un plugin déjà tout fait, ça va nous permettre de voir comment intégrer un plugin au code actuel.

On va donc installer le plugin cinch-identify qui est disponible sous forme de gem Ruby. On l'installe avec la commande suivante: gem install cinch-identify.

On ajoute notre require en haut du code:

require "cinch/plugins/identify"

Et on va intégrer à la configuration du bot l'utilisation du plugin identify de la manière suivante:

c.plugins.plugins = [Cinch::Plugins::Identify]

Oui, c'est un simple tableau de plugins que nous passons, plus précisément, Identify est une classe. Donc par la suite nous pourrons créer nos propres classes et les intégrer dans ce tableau en tant que plugin.

On va ajouter quelques options au plugin en question via le code suivant:

c.plugins.options[Cinch::Plugins::Identify] = {
    :username => "Bot",
    :password => "password",
    :type     => :secure_quakenet,
    }

Un simple hash avec nos configurations. Bien sûr, cela dépend du plugin utilisé. Dans notre cas on a:

  • username: Le nom d'utilisateur du compte IRC sur le serveur
  • password: Le mot de passe associé au compte
  • type: Le type d'authentification

Les différents types d'authentification sont les suivants:

  • :nickserv pour NickServ
  • :userserv pour UserServ
  • :dalnet pour DALnet
  • :quakenet pour la commande auth sur QuakeNet (pas sécurisée)
  • :secure_quakenet ou :challengeauth pour un peu plus de sécurité chez Quakenet
  • :keynet pour KreyNet

Attention toutefois, quand vous utilisez ces types (hormis :secure_quakenet et :challengeauth), le mot de passe apparait en clair dans les logs !

Notez que vous pouvez reprendre le code de cinch-identify pour y ajouter votre propre type. Quand on regarde le code, il est relativement simple, par exemple pour le :type => quakenet :

when :quakenet
  debug "Identifying with Q"
  identify_quakenet

La fonction identify_quakenet:

def identify_quakenet
    User("Q@CServe.quakenet.org").send("auth %s %s" % [config[:username], config[:password]])
end

Ca envoie simplement un message privé à l'utilisateur Q@CServe.quakenet.org avec le contenu auth username password. La valeur de l'utilisateur et le mot de passe étant récupérés de la configuration. Si vous regardez l'aide de Quakenet pour savoir comment s'authentifier, c'est complétement cohérent:

AUTH

Usage:
/msg Q@CServe.quakenet.org AUTH <user> <password>

On ne fait qu'automatiser cette action via le code. Simplissime !

Un code complet, qui permet au bot de s'identifier, de s'appliquer un mode permettant de cacher son hostname sur les serveurs Quakenet puis de rejoindre un chan nommé "Coucou":

require 'cinch'
require "cinch/plugins/identify"

bot = Cinch::Bot.new do
    configure do |c|
        c.nick = "Bot"
        c.user = "Bot"
        c.realname = "DaBot"
        c.server = "irc.quakenet.org"
        c.channels = ["#coucou"]
        c.modes = ["+x"]
        c.delay_joins = 5 #Permet d'avoir le temps de s'auth avant de /join
        c.messages_per_second = 1.0
        c.server_queue_size = 1
        c.plugins.plugins = [Cinch::Plugins::Identify]
        c.plugins.options[Cinch::Plugins::Identify] = {
          :username => "Dabot",
          :password => "dapassword",
          :type     => :secure_quakenet,
        }
    end
end
bot.start

Certes, le bot ne fait rien au niveau des interactions avec son environnement.

En quelques lignes déjà, on a quelque chose de fonctionnel sans trop forcer avec deux boucles et un hash, pratique Cinch !

Dis bonjour à la dame

Un bot un peu plus causant serait le bienvenu non ? On va essayer de le faire parler, un simple bonjour déjà.

On va faire en sorte qu'il réponde à l'utilisateur qui dit un simple "bonjour" sur le chan.

Le code est suivant, à intégrer entre la configuration du bot et le bot.start:

on :message, "bonjour" do |m|
  m.reply "Salut, #{m.user.nick}"
end

Si le bot trouve le message "bonjour" et UNIQUEMENT "bonjour" (donc pas "Bonjour" ni "BONJOUR"), il va immédiatement répondre "Salut, nick de l'auteur du message". m.user.nick Correspond à Cinch::User#nick qui renvoie le nick de l'utilisateur. On peut récupérer plein de choses, notamment quelques informations utiles via les attributs suivants (liste non complète):

  • User#away : Le message d'asbence indiqué dans la commande /away ou rien (String ou nil)
  • User#idle : Le nombre de seconde d'idle d'un utilisateur (Integer)
  • User#realname : Le nom réel de l'utilisateur (String)
  • User#last_nick : Le nick précédent (String)

Et dispose aussi de quelques méthodes. Pour avoir tous les attributs et méthodes, le meilleur moyen reste bien évidemment de lire la documentation de Cinch.

En bref, une boucle qui va répondre à chaque utilisateur disant le mot "bonjour", boucle threadée pour laisser le reste de l'execution se faire et traiter plusieurs événements en parallèle.

Des regex pour l'interaction

Afin que le bot réagisse un peu plus finement, nous pouvons utiliser les expressions régulières en lieu et place d'un simple mot entre guillemet.

Sur IRC, il est possible que quelques personnes parlent au bot pour essayer d'en cerner les fonctionnalités et les éventuels easter-eggs, donc les gens vont essayer de lui dire bonjour de toutes les manières possibles et de lui poser des questions à tout va pour espérer avoir une réaction du bot.

On va faire une regex toute simple, pour que le bot réponde "Salutations" à différents types de bonjour le concernant. Pour l'exemple, on part du principe que la personne parle au bot, et que par conséquent elle commence sa phrase par le nom du bot, ce qui donnerait une regex comme ceci:

/Dabot: (hi|(s|S)alut|o\/|cc toa|(c|C)oucou)/

Intégrons cette regex au code:

    on :message, /Dabot: (hi|(s|S)alut|o\/|cc toa|(c|C)oucou)/ do |m|
      m.reply "#{m.user.nick}: Salutations !"
    end

Basique certes, mais efficace, ce principe de regex peut être assez intéressant, mais reste tout de même très limité. Ca reste un exemple bidon, mais ça montre bien l'étendue de la chose.

Un système de commande

Le framework Cinch dispose d'une fonctionnalité extrêmement chouette, un système de préfixe. Dans l'état actuel du bot, il va simplement réagir aux messages et voir si ça colle avec la regex ou le message indiqué puis lancer (ou non) sa phrase.

Si nous faisons nous-même nos propres classes, le framework Cinch intègre par défaut un préfixe pour matcher les commandes. Pour faire simple, on va coder une classe qui va faire en sorte d'afficher l'aide du bot, via la commande !help. Comme il y a préfixe, il y a classe. Le préfixe par défaut est !. Vous disposez de plusieurs moyens pour le changer.

De manière globale dans la configuration du bot:

c.plugins.prefix = /^\%/

Le préfixe devient % pour toutes les commandes de toutes les classes.

Vous pouvez le changer uniquement pour un plugin particulier:

class MonPlugin
    include Cinch::Plugin

    set :prefix, /^%/
end

Le préfixe d'une commande devient % uniquement pour les méthodes de cette classe.

En ce qui nous concerne, on va laisser le préfixe par défaut, à savoir !, il nous suffit largement.

Help !

Notre classe va ressembler à ceci:

class Help
    include Cinch::Plugin

    match(/help$/, method: :help)

    def help(m)
        User(m.user.nick).send("Bonjour, je suis Dabot, un bot stupide avec une seule commande d'aide inutile.")
    end
end

On déclare le match avec sa regex et sa méthode associée, puis on écrit la méthode à exécuter quand ça match.

Ainsi, si un utilisateur lance un !help sur le chan IRC, le bot va lui /query le texte indiqué.

Pour que ce plugin personnel fonctionne, nous devons l'intégrer dans la configuration du bot, dans le tableau de plugin bien sûr:

c.plugins.plugins = [Help, Cinch::Plugins::Identify]

Si vous souhaitez mettre en place des "sous-commandes" pour un système d'aide plus complexe, vous pouvez opérer ainsi:

class Help
    include Cinch::Plugin

    match(/help$/, method: :help)
    match(/help date$/, method: :helpDate)
    match(/help bidule$/, method: :helpBidule)

    def help(m)
        User(m.user.nick).send("Bonjour, je suis Dabeaut, un bot stupide avec une seule commande.")
    end

    def helpDate(m)
        User(m.user.nick).send("!date - Retourne la date et l'heure courante.")
    end

    def helpBidule(m)
        # code ici ...
    end
end

Bref, jouez avec les regex pour arriver à ce que vous souhaitez. Au pire, allez tester vos regex sur regexr mais n'oubliez pas d'enlever le ! en début de commande !

Une simple commande de date

Prenons une classe Autre dans laquelle on va implémenter une commande !date qui nous retournera la date et l'heure, correctement formatées si possible:

class Autre
    include Cinch::Plugin

    match(/date$/, method: :date)

    def date(m)
        time = Time.new
        date = time.strftime("%Y-%m-%d %H:%M:%S")
        m.reply "#{m.user.nick}: #{date.to_s}"
    end
end

Simplissime ! N'oubliez pas de l'ajouter au tableau de plugin dans la configuration du bot.

Exemple concret: Un système de quote

En guise d'exemple concret, on va créer un système de gestion des citations. Les fonctions du bot sur IRC sont les suivantes:

  • !quote add <citation> -- Ajoute une citation
  • !quote stat -- Information sur le nombre de citations actuellement enregistrées par le bot
  • !quote rand -- Tire au sort une citation et l'affiche sur le chan
  • !quote list -- Crée un paste contenant toutes les citations puis envoie en /query l'URL à l'utilisateur

Code de base

On commence par la création de la classe, les matchs et le squelette des méthodes:

class Quote
    include Cinch::Plugin

    match(/quote add (.*)/, method: :ajouterCitation)
    match(/quote list/, method: :listerCitation)
    match(/quote rand/, method: :choisirCitation)
    match(/quote stat/, method: :stat)

    def ajouterCitation(m, param)

    end

    def listerCitation(m)

    end

    def choisirCitation(m)

    end

    def stat(m)

    end
end

On va intégrer notre nouveau plugin à la liste des plugins dans la configuration du bot (en plus du plugin Help, Identify et celui de Date ):

c.plugins.plugins = [Help, Quote, Date, Cinch::Plugins::Identify]

Ajout de citation

On s'occupe de l'ajout d'une citation. On voit que le match de la commande !quote add <citation> comporte deux parties: La commande et "tout et n'importe quoi" ensuite (le (.*)). Ce tout et n'importe quoi est récupéré dans le paramètre param de la méthode correspondante et deviendra la citation qui sera écrite dans le fichier.

Le but du jeu:

  • Ouvrir un fichier
  • Ecrire la citation dedans
  • Informer de l'ajout de la citation
  • Fermer le fichier

De manière très basique et sans gérer d'erreur, ça peut ressembler à ça:

    def ajouterCitation(m, param)
        file = File.open("/votre/chemin/fichierQuotes", "a+:UTF-8")  # On précise l'encodage en UTF-8
        file.puts(param)
        m.reply "#{m.user.nick}: Citation ajoutée !"
        file.close
    end

L'utilisation est simple, dans le chan IRC écrire la commande suivie de la citation:

!quote add <Zilkos> arch sapu

Comme la regex intègre un espace, le premier caractère écrit dans le fichier sera bien <. Le bot nous répondra un gentil Zilkos: Citation ajoutée !

Statistiques sur les citations

On enchaine avec la fonction de statistique qui nous retourne le nombre de citation dans le fichier:

Le but:

  • Ouvrir le fichier
  • Lire le nombre de ligne
  • Retourner une réponse cohérente selon le nombre de ligne
  • Fermer le fichier

Le code possible:

    def stat(m)
        file = File.open("/votre/chemin/fichierQuotes", "r:UTF-8")
        nbLigne = file.readlines

        if (nbLigne.size == 0)
            m.reply "Il n'y a pas de citation."
        elsif (nbLigne.size == 1)
            m.reply "Il y a #{nbLigne.size} citation en mémoire."
        else
            m.reply "Il y a #{nbLigne.size} citations en mémoire."
        end
        file.close
    end

Il y a sans doute un moyen pour gérer le pluriel plutôt qu'un elsif/else clairement sale j'en conviens, je ne me suis pas penché dessus.

Tirage au sort d'une citation

Là encore c'est très simple:

  • On ouvre le fichier
  • On récupère le nombre de ligne
  • On affiche une ligne au hasard
  • On ferme le fichier

Le code possible:

    def choisirCitation(m)
        file = File.open("/votre/chemin/fichierQuotes", "a+:UTF-8")
        lignes = file.readlines
        if (lignes == 0)
            m.reply "Il n'y a pas de citation !"
        else
            m.reply "-> #{lignes[rand(lignes.size)]}"
        end
        file.close
    end

Lister les citations

Là, il faut ruser. En effet si votre fichier contient 350 citations, il est bien évidemment hors de question de lancer tout ça sur le chan, pour éviter de spammer et donc prendre le risque que votre bot se fasse kicker.

Personnellement, je voulais que la personne puisse consulter toutes les citations, de manière simple et sans spam.

Comme vous le savez (ou pas), je dispose d'un Pasthis sur Unixmail. J'ai donc eu l'idée de faire intéragir le bot avec le Pasthis pour qu'il génère un paste avec l'intégralité des citations dedans. J'ai légèrement modifié le code de Pasthis pour me simplifier la vie, j'ai seulement indiqué des ID sur quelques balises HTML pour me simplifier la vie par la suite. On va donc interagir avec le site pour qu'il fasse le nécessaire, à savoir:

  • Ouvre le fichier de citation
  • Va sur le site p.unixmail.fr
  • Positionne toi sur la balise form qui a le nom mainform
  • Dans cette form, va sur le select nommé d et indique la valeur -2 (correspond à l'expiration du paste)
  • Toujours dans cette form, coche la checkbox avec l'id wrap
  • Ensuite, pour chaque ligne présente dans le fichier de citation, tu l'écrit dans le textarea nommé p, précédé du numéro de ligne pour gagner en visibilité.
  • Valide le paste
  • Récupère l'URL du paste
  • Envoie l'URL du paste en /query à la personne qui l'a demandé

Pfiou, ça en fait du boulot ! Avec tout ça, on peut générer un paste à lecture unique (pour des raisons de surcharge serveur) qui se détruit après lecture, parfait !

J'ai longuement réfléchi pour savoir comment exploiter un site web avec Ruby et j'ai trouvé l'excellentissime Mechanize qui existe aussi pour Python et Perl et qui nous permet d'intéragir avec un site web, via du code. Du moins, on agit sur le code HTML de la page.

Installez donc la gem via un gem install mechanize et n'oubliez pas de rajouter un require 'mechanize' en haut de votre script. Vous devriez avoir besoin d'un require rubygems également.

Le code est le suivant, volontairement commenté:

    def listerCitation(m)
        file = File.open("/votre/chemin/fichierQuotes")
        i = 0 #compteur de ligne
        agent = Mechanize.new()
        page = agent.get('http://p.unixmail.fr') # On récupère la page
        form = page.form('mainform') # On indique le form sur lequel on va travailler
        form.d = '-2' # l'expiration du paste
        form.checkbox_with(:name => 'wrap').check # on coche l'adaptation des lignes
        file.each_line do |line| # pour chaque ligne du fichier on écrit dans le p
            i = i + 1
            form.p = form.p + i.to_s + " " + line
        end
        if(i >= 1) # Si une ligne ou plus, on valide le paste
            page = agent.submit(form)
            url = agent.page.uri # on récupère l'URL du paste
            User(m.user.nick).send("--> #{url}")
        else
            User(m.user.nick).send("Désolé, aucune citation n'a encore été enregistrée :(")
        end
    end

Là encore, c'est sans doute pas optimisé ni très propre, mais ça fonctionne. En une commande (!quote list), le bot nous renvoie une URL qui contient toutes les citations et dont la lecture est unique.

Fin

En bref, pour les vrais développeurs Ruby, il y a moyen de beaucoup s'amuser avec ce type de bot ! Le bot que j'ai construit actuellement comporte quelques fonctions (~400 lignes), tout n'est pas très stable encore, mais j'y travaille.

Je ne connaissais pas Ruby avant, je trouve qu'il est plutôt souple, il se rapproche de Python avec un côté orienté objet beaucoup plus clair et moins lourd syntaxiquement parlant, une armée de gem disponibles. Avec du temps, je pense que je vais m'y pencher plus sérieusement qu'actuellement car ce langage est vraiment très pratique et on peut faire beaucoup en peu de lignes, sans compter que Cinch, c'est vraiment le pied !

Bonus: Un service pour le bot !

On va maintenant transformer ce script en un service pour Debian, au même titre qu'un service Apache, proftpd, samba, etc.

Debian nous offre un squelette d'un fichier de service déjà tout prêt, donc on va s'en servir et le modifier.

Pour commencer, on copie le squelette dans un fichier de service avec le nom de notre choix (qui sera le nom du service):

cp /etc/init.d/skeleton /etc/init.d/dabot

On édite notre fichier dabot, notamment le début du fichier avec les instructions suivantes:

PATH=/sbin:/usr/sbin:/bin:/usr/bin
DESC="Dabot - Bot IRC"
NAME=ruby
DAEMON=/usr/bin/$NAME
DAEMON_ARGS="/chemin/vers/dabot.rb"
PIDFILE=/var/run/$NAME.pid
SCRIPTNAME=/etc/init.d/$NAME

On modifie donc la description, qui sera la description du service. Le name est le nom de l'executable, on utilise Ruby, auquel on va passer en argument le chemin du script du bot dans la ligne DAEMON_ARGS.

Dans la suite du fichier, on va effectuer quelques modifications dans le do_start():


do_start()
{
        # Return
        #   0 if daemon has been started
        #   1 if daemon was already running
        #   2 if daemon could not be started
        start-stop-daemon --start --quiet --background --make-pidfile --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \
                || return 1
        start-stop-daemon --start --quiet --background --make-pidfile --pidfile $PIDFILE --exec $DAEMON -- \
                $DAEMON_ARGS \
                || return 2
        # Add code here, if necessary, that waits for the process to be ready
        # to handle requests from services started subsequently which depend
        # on this one.  As a last resort, sleep for some time.
}

On y ajoute --background. On ajoute aussi le --make-pidfile pour le bon fonctionnement de la chose car les autres fonctions du service se basent sur le PID, donc c'est quand même mieux d'en créer un !

On rend le script executable via un chmod +x /etc/init.d/dabot puis on fait un petit coup d'update-rc avec la commande update-rc.d dabot defaults. Le service démarre en même temps que les autres au démarrage et vous pouvez le gérer via les commandes habituelles du genre service dabot stop, du moins sous Debian et avec ce système de gestion des services.

Si toutefois vous voulez réagir sur l'article, n'hésitez pas à rejoindre les listes de diffusion !

Article suivant Article précédent