Un bot IRC en Ruby
Table des matières
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.
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
Bref, que du classique.
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é, 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’existent pas, bref, bienvenue dans un système similaire au monde professionnel.
Installation de Cinch
Il suffit d’installer la gem Cinch via la commande suivante, comme on le ferait avec une autre gem:
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 serveurpassword
: Le mot de passe associé au comptetype
: 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. Outre le fait d’utiliser un outil, il est quand même bon de savoir comment il fonctionne, c’est amusant d’une part et d’autre part ça fait apprendre des choses.
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, dans le cas présent on a juste un peu étoffé la phase de configuration.
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
Le bot scrute la conversation et si il 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 il 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 (oui, c’est l’expérience qui parle).
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. Idem, si il y a un problème avec le fichier, il n’est pas géré.
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, 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 nommainform
- Dans cette
form
, va sur leselect
nomméd
et indique la valeur-2
(correspond à l’expiration du paste) - Toujours dans cette
form
, coche lacheckbox
avec l’idwrap
- 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 validé
- 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, d’une manière plus propre que présentée ici.
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 pour ce genre d’utilisation.
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.
Et hop, un service pour notre bot !