1. # 3052

    How to enable Google Sync with Chromium 35 and later on Debian

     
    Since Chromium version 35, Debian does not provided API keys anymore. Thus, Chromium's features like Google Sync do not work anymore. To make it work, you need to get you own API keys. Let's see how.

    In order to get back Google Sync and other features, you need to get your own API keys. Details are available from Chromium developers documentation.

    chromium-dev Google group registration

    Registration to chromium-dev is quite obvious, even without receiving any mail. I won't get into details here.

    Project & API key creation

    Go to https://cloud.google.com/console and login with your Gmail credentials.

    Login to Google Cloud Console

    Create a project. I choosed to call it "MyChromiumProject".

    Create Project

    Then go to "API & Auth > API" tab.

    Enable needed Google APIs

    Look for "chrome" in "Browse API" input field. Enable every API there.
    I also disabled default APIs, but it's up to you to do the same or not.

    Search for Chrome Google APIs

    To have full Chromium features, you could also enable following API:

    - Google Maps Geolocation API
    - Safe Browsing API
    - Speech API
    - Time Zone API
    - Google+ API

    Here is the point I missed when I first created my API keys. The result was an error when signin to Chromium whith error message Service unavailable. Try again later..

    To avoid that error, go to API & Auth > Consent Screen and choose an email adress as well as a Product Name; then click Save.

    Choose Product Name for consent screen

    Go then to "API & Auth > Credentials" and click "Create new Client ID".
    Choose "Installed Application" for the Application type section and "Other" for the "Installed application type" section.

    Click then on "Create new Key", then choose "Browser key" and finally "Create" letting "Referers" textarea empty.

    Create credentials

    You're done for the Google part. Let's configure Chromium to use theses keys.

    Local browser configuration

    The easiest way to achieve this is to export Google API credentials as environment variables. This is suitable for a single-user computer, maybe not for a shared one. Anyway, let's create the file /etc/chromium.d/googleapikeys with content:

    export GOOGLE_API_KEY="your_api_key"
    export GOOGLE_DEFAULT_CLIENT_ID="your_client_id"
    export GOOGLE_DEFAULT_CLIENT_SECRET="your_client_secret"


    Once done, you can start chromium and login via the menu entry [pre]Sign in to Chromium...[/pre]

    Sign in to Chromium...

    Enjoy :)

    Update

    As Vincent Bernat said on Twitter:

    You can still use the Debian API keys. They will be shipped back "soon". We really have no clue while the maintainer withdrawn them


    To use Debian keys, you can replace /etc/chromium.d/googleapikeys content with:

    export GOOGLE_API_KEY="AIzaSyCkfPOPZXDKNn8hhgu3JrA62wIgC93d44k"
    export GOOGLE_DEFAULT_CLIENT_ID="811574891467.apps.googleusercontent.com"
    export GOOGLE_DEFAULT_CLIENT_SECRET="kdloedMFGdGla2P1zacGjAQh"
  2. # 3051

    This blog now supports Twitter cards

     
    I just enabled Twitter cards support for my blog. I'll use Summary cards so that blog post abstract will be displayed nicelly when posted on Twitter. Here's how I did to enable them (easy).

    Getting started

    First, and quite obviously, you'll need a Twitter account. This is mandatory for content attribution.

    Second, just go to dev.twitter.com/ (https) and log in.


    You can implement "Twitter cards" just adding some extra meta tag in HEAD section of your HTML page.
    I choose to support "Summary cards", which is quite obvious for a blog. Some meta tags are mandatory, some are not.

    At least, you'll need:

    * twitter:card with "summary" as content
    * twitter:creator with your Twitter account name as content ("@jbfavre" in my case)
    * twitter:title with your blog post title. (truncated at 70 characters)
    * twitter:description with a summary of the blog post (truncated at 200 characters)

    Some other meta tags are optional. You can find the list on Summary Card documentation on dev Twitter site

    Testing

    You can (and should) test your Summary cards: just use Twitter Card validator.

    Once you're happy, you can ask Twitter to whitelist your website.

    After some time, you'll receive an approval email. It was quickly done for me, maybe half an hour or so.

    And you're done ! (quite easy, isn't it ?)

    Of course, when you know that either title or description will be truncated if too long, you'll try to make it short enough so that they remains untouched. And basically, that always a good idea to summarize you posts.

    Oh, and this post is the first one which I'll tweet about with summary card :)
  3. # 3050

    Micro-caching & Nginx

     
    When you want to speed up you web server, you usually try to setup caching.
    That's great when your backend application is "cache aware". So are various apps like Wordpress or other blog engines.
    But, not every application are "cache aware", and you could still want to be able to cache pages and/or assets. Here comes micro-caching.
    The idea behind micro-caching is to be able to cache ressources for a short time. Time will depend on application behaviour, but think of it as it's still better to show a stale ressource than an error page.

    Let's see an example

    I wanted to speed up a smokeping frontend.

    That's basically a simple HTML page with some images. By default, every request will be sent to the FastCGI backend, which is a Perl CGI script, leading the page to be fully computed.

    But, since my smokeping only gets updates every minute, I found useless to call FastCGI backend for each request. How could I use some caching ?

    Sample Nginx configuration

    Basically, my smokeping vhost looks like:

    server {
    listen [::]:80;
    server_name smokeping.domain.tld;
    location /smokeping {
    alias /usr/lib/cgi-bin/smokeping.cgi;
    # Fastcgi socket
    fastcgi_pass unix:/var/run/smokeping/smokeping.socket;
    # Fastcgi parameters, include the standard ones
    include /etc/nginx/fastcgi_params;
    # Adjust non standard parameters
    fastcgi_param SCRIPT_FILENAME /usr/lib$fastcgi_script_name;
    }
    location /smokeping-static {
    alias /usr/share/smokeping/www/;
    }
    }


    A quite simple vhost file with 2 locations:

    * /smokeping which is an alias to smokeping.sgi script
    * /smokeping-static which is as alias to web directory from which all static assets (JS, images) will be loaded

    Static files caching

    First, and easiest, caching rules: static files

        location /smokeping-static {
    alias /usr/share/smokeping/www/;
    open_file_cache max=500 inactive=2m;
    open_file_cache_valid 1m;
    }


    Simply ask Nginx to keep track of every requested files for 2 minutes max, while expiring resources after 1 minute.
    My smokeping can generate up to 150 images, so 500 open file in memory is clearly enough.

    More details can be found on official nginx documentation

    Dynamic "files" caching

    Second, let's try to cache FastCGI script output.

        location /smokeping {
    alias /usr/lib/cgi-bin/smokeping.cgi;
    # Fastcgi socket
    fastcgi_pass unix:/var/run/smokeping/smokeping-web.socket;
    # Fastcgi parameters, include the standard ones
    include /etc/nginx/fastcgi_params;
    # Adjust non standard parameters (SCRIPT_FILENAME)
    fastcgi_param SCRIPT_FILENAME /usr/lib$fastcgi_script_name;

    # Set the cache up. Use "smokeping" cache for 1m
    fastcgi_cache smokeping;
    fastcgi_cache_valid any 2m;
    expires 1m;
    add_header X-Cache $upstream_cache_status;
    }


    At first, rules seems very simple:

    * use a cache named smokeping
    * cache FastCGI output for 2 minutes
    * Add HTTP header expires 1 min from now
    * Add custom HTTP Header to indicate HIT or MISS

    As it is, cache won't work, and nor nginx will start. That's because we ask nginx to use a cache zone named smokeping without defining it. Let's fix that, adding following options in /etc/nginx/conf.d/fastcgicache.conf:

    fastcgi_cache_path /var/cache/smokeping/fastcgi levels=1:2 keys_zone=smokeping:1m inactive=1m max_size=64m;
    fastcgi_cache_key $scheme$request_method$host$request_uri;
    fastcgi_cache_lock on;
    fastcgi_cache_use_stale error timeout invalid_header updating http_500;
    fastcgi_cache_valid 2m;
    fastcgi_ignore_headers Cache-Control Expires Set-Cookie;


    This will make nginx storing fastcgi ouput into /var/cache/smokeping/fastcgi directory, adding 2 levels based on the three last letters of the MD5 hash of fastcgi_cache_key. Cache will never use more than max_size on disk (and of course, you need to create missing directory and ensure nginx will have write permission on it).

    But, to improve performance, nginx will also store cached files metadata in shared memory zone. In our example, shared memory zone is named smokeping and will use at most 1m, while considering cache as valid for 2 minutes (option fastcgi_cache_valid). Finally, inactive entries will be invalidated after 1 minute.

    You might wonder why I want to invalidate entries older than 1 minute, AND keep them for 2 ? Simply because I use 'fastcgi_cache_use_stale' option which allow to send an expired entry while updating it. Remember we're tuning a smokeping. I'm fine with seeing some "outdated" HTML page :)

    Option 'fastcgi_cache_lock' allows to have at most one request updating resources while all tohers will be served with cached one (and staled if needed & available). Yet another protection for you backend :)

    Finally, 'fastcgi_ignore_headers' allows Nginx to disable processing of some HTTP headers send by FastCGI backend (in my case, there aren't any, as far I as saw).

    So far, so good. Got our cache o/

    But, what if you want to manually invalidate a ressource ?

    Cache invalidation

    We saw the basic rules for FastCGI cache. Now, let's add a way to invalidate cache, or at least force cache bypass.

    Usually, you'll want to:

    * never cache a POST request's response. Use fastcgi_no_cache
    * be able to bypass cache when needed. Use fastcgi_cache_bypass

    If thoses 2 options are not empty and not equal to 0, then they'll match, which means either cache won't be used, or it'll be bypassed.

    Our location block becomes:

        location /smokeping {
    alias /usr/lib/cgi-bin/smokeping.cgi;
    # Fastcgi socket
    fastcgi_pass unix:/var/run/smokeping/smokeping-web.socket;
    # Fastcgi parameters, include the standard ones
    include /etc/nginx/fastcgi_params;
    # Adjust non standard parameters (SCRIPT_FILENAME)
    fastcgi_param SCRIPT_FILENAME /usr/lib$fastcgi_script_name;

    # Set the cache up. Use "smokeping" cache for 1m
    fastcgi_cache_bypass $fastcgi_skipcache;
    fastcgi_no_cache $fastcgi_skipcache;
    fastcgi_cache smokeping;
    fastcgi_cache_valid any 2m;
    expires 1m;
    add_header X-Cache $upstream_cache_status;
    }


    Most of the example you'll find on the Internet will make you use an "if" statement to define fastcgi_skipcache. That's not a good idea. Lets' use map instead.

    Add following blocks in /etc/nginx/conf.d/fastcgicache.conf
    map $request_method $fastcgi_skipcache {
    default 0;
    POST 1;
    }
    map $arg_smrefresh $fastcgi_skipcache {
    default 0;
    force 1;
    }


    First one is quite simple to understand: set fastcgi_skipcache to 0 but for POST request.
    Second one is a bit trickier and means: set fastcgi_skipcache to 0, except when you have an argument named smrefresh and with value "force".

    Thats means you can always bypass cache adding "?smrefresh=force" at the end of your URL.

    Results

    Without any cache, the start page from my smokeping loads in around 800 milliseconds.
    With the cache, the same page loads in 5 miliseconds. No so bad, isn't it ?

    Cherry on the cake, caching time is only 1 minute, so I'll always have an up-to-date page while preserving server performances.

    And you, do you use micro-caching ?
  4. # 3049

    Smokeping custom Curl probe

     
    I just released a custom Curl probe for smokeping.

    Curl probe only fetch load time (that is, total time minus DNS resolution time). But I needed to be able to measure various response times like (extract from curl manpage):

    * time_namelookup
    * time_redirect
    * time_connect
    * time_appconnect
    * time_pretransfer
    * time_starttransfer
    * time_total

    If most of them are quite clear, some others are not that obvious. For example, time_appconnect indicate time spent in protocol negotiation before reaching the app.

    And that can be used to measure SSL negotiation time !

    To achieve that, I added a new configuration option "write_out", which can take on the above values. If empty, or not in the list, default Curl probe behaviour apply (total time minus DNS resolution time).

    As far as I know, smokeping can only receive one value at a time. That means you have to define as much Targets as wanted timers, and do as much requests as wanted timers.
  5. # 3048

    SelfOSS now supports FullTextRSS

     
    I'm very happy we finally found a solution to integrate it to main SelfOSS repository.

    It took some time, and two Pull Requests, but SelfOSS now integrates FullTextRSS.

    What is FullTextRSS ?

    FullTextRSS is a free software PHP application developped by FiveFilters. It alloows you to transform partial web feeds — often summary-only feeds which expect you to visit cluttered, ad-ridden site to read the full story — to deliver the full content stripped of clutter and ads.

    The very last version of FulltextRSS is not free (you need to pay to get it), while the penultimate one is.

    SelfOSS integration

    To be able to use FulltextRSS into SelfOSS, I created a "spout" (SelfOSS module's name) which extend legacy feed spout.
    When used, it will load truncated RSS feed and replace content with full content extracted from the web page.

    You can enable FultextRSS feed for any source, setting source's type to "RSS Feed (with FullTextRSS)".

    What next ?

    First, I'll try to improve FullTextRSS intergation, cleaning code and fix missing documentation.

    I also have others projects, like providing a source classification for sources, which could be used to display "hot" sources first, and "cold" sources last.
    Classification will be based on your actions on articles (share, display, star, ...). It's of course highly experimental for now :)
  6. # 3047

    PSES2014: ELK - Elasticsearch, Logstash & Kibana

     
    La dernière édition de Pas Sage En Seine s'est déroulée du 26 au 29 juin 2014.
    À cette occasion, j'ai eu l'opportunité de présenter une solution de gestion & analyse de logs basée sur la pile logicielle Elasticsearch, Logstash et Kibana.

    Il s'agissait également de montrer dans quelle mesure cette solution pouvait être utilisée pour traiter tout type d'évènements ou presque.

    À titre de démo, j'ai choisi de scruter Twitter à la recherche des mots-dièses "officiels" de l'évènement, PSES et PSES2014.

    S'en sont suivis quelques tweets de teasing, distillés tout au long de l'évènement. Le point d'orgue étant les stats finales, mais pas officielles, annoncées dimanche en fin de soirée:

    * 6548 tweets en 4 jours, dont 293 réponses et 3979 RT. On répète beaucoup à PSES ;)
    * 41 tweets géo-localisés, ce qui est bien mais pas top. On se cache assez bien à PSES
    * seulement 7 personnes avec plus de 100 tweets. On écoute beaucoup à PSES (ou alors on oublie le mot-dièse et du coup ça compte pas)
    * un tiercé gagnant: @bortzmeyer large vainqueur avec 592 tweets & RT, suivi de @alserweiss avec 247 tweets & RT et @laumarot avec 239 tweets & RT

    Pour ceux qui souhaitent voir ou revoir la causerie, voire même jouer avec ELK, j'ai mis en ligne:

    * Les slides
    * Les fichiers de configuration de la démo
    * La vidéo est également disponible

    Merci aux organisateurs et, peut-être, à l'année prochaine !
  7. # 3046

    Debian meetup du 4 juin 2014 chez Mozilla

     
    J'ai eu l'occasion d'y présenter l'utilisation de Debian par mon employeur: BlaBlaCar.
    Debian est déployé sur 100% de notre parc, soit une centaine de serveurs, dont 50% de machines virtuelles.

    Installation

    L'installation des serveurs physiques est naturellement automatique et silencieuse grâce à PXE et aux fichiers preseed.

    Lors de l'installation, seules les opération minimales sont effectuées:

    - partitionnement
    - installation de l'OS de base (plus un serveur OpenSSH)
    - paramétrage du mot de passe root
    - boostrap de Chef

    Une fois l'installation réalisée, c'est Chef qui prend la main pour configurer la machine.

    20 minutes après la mise sous tension de la machine, elle est prête à l'emploi.
    Ce temps est ramené à 10 minutes pour les machines virtuelles, la partie installation PXE n'ayant pas lieu d'être dans ce type d'environnement.

    Backport

    Nous utilisons, au maximum, les paquets fournis par Debian. Il arrive cependant que certains paquets soient absent du projet, ou que la version disponible ne réponde pas entièrement à notre
    besoin.

    Dans ce cas, et ils sont rares, nous pratiquons le rétro-portage.
    Cependant, même si les cas de rétro-portage sont rares, il n'est pas question de réaliser cette opération à la main: un seul paquet "raté" peut planter la prod.

    En conséquence, nous industrialisons ce processus à l'aide de Jenkins, git et reprepro.
    Ceci permet de gérer l'arborescence des sources upstream & Debian au sein d'un dépôt git et de gérer la construction des paquets sources & binaires de manière automatique.
    Le tout est propulsé par jenkins-debian-glue.

    La construction des paquets s'effectuant sur un système vierge, cela permet par exemple de vérifier que les dépendances sont satisfaites.

    Une fois construit, les paquets sont disponibles dans un dépôt Debian de test interne à l'entreprise.
    Lorsque la nouvelle version du paquet est jugée satisfaisante, le paquet est déplacé manuellement vers les dépôt de production et déployé.

    Améliorations

    Il reste possible d'améliorer ce système. Par exemple, activer les vérifications Lintian permettra d'obtenir des paquets de meilleure facture.
    De la même façon, l'utilisation de signature GPG pour les commit et les tag git ainsi que pour la construction des paquets permettra également de renforcer la sécurité de la chaîne.

    Les slides

    Les diapos sont disponibles en ligne:

    * sur Speakerdeck
    * sur Slideshare

    mais également :

    * depuis mon propre serveur

    Merci à Sylvestre Ledru qui m'a donné l'opportunité de prendre la parole !
  8. # 3045

    Paul d'Ivoi - Jud Allan, roi des Lads

     
    Pour les gens pressés ou ceux que la technique n'amuse pas, la version courte est visible sur le blog du bookscanner

    Prenez:

    * Un doux dingue qui construit une machine "infernale" dans un garage
    * Une oeuvre de 1909, dans le domaine public, qui n'est plus éditée
    * Un peu de temps et d'astuce
    * Beaucoup de logiciel libre et d'envie

    Vous obtiendrez deux epub, gratuits naturellement, et je l'espère parfaits !

    2 mois

    Cela aura été la durée, estimée, nécessaire pour reconstituer l'ouvrage après numérisation par le bookscanner.

    2 séances

    Il aura fallu deux séances de numérisation: la première avait mis en évidence un bug dans le logiciel embarqué dans les appareils photos qui corrompait certaines photos, les rendant inexploitables.

    2 moines copistes

    C'est ce travail de reconstitution qui a été le plus long bien entendu.

    L'objectif est simple: extraire et reformater le texte en Markdown de manière à faciliter la génération dans différents formats: epub, PDF, html, etc.
    Il s'agit également d'extraire les images d'illustration du livre
    original, tout en trouvant un compromis entre définition de l'image et poids final du livre en version numérique.

    Pour corser la difficulté, nous étions deux moines copistes. Il a fallu se synchroniser.

    Heureusement pour nous, et contrairement aux moines copistes,
    l'informatique et notamment le logiciel libre apporte son lot d'outils permettant cela.

    Nous avons donc utilisé:

    * Un bête éditeur de texte. Seule excentricité le concernant: la
    visualisation en temps réel de la conversion Markdown vers HTML
    * Git pour le suivi de version et l'intégration des
    corrections & images
    * Gimp pour le travail sur les images
    * pandoc pour la génération de l'epub final

    Le tout réalisé sur des machines équipées de GNU/Linux, avec une dose de ligne de commande pour "industrialiser" les corrections les plus courantes.

    2 epub

    Le livre original a été séparé en 2, pour conserver une taille de
    fichier raisonnable, et donc permettre la lecture sur un maximum
    d'appareils.

    Les epub sont disponibles sur le dépôt GitHub. Vous y trouverez:

    * la partie 1 Idylle en modern-sorcellerie
    * la partie 2 Lads’s king, le Roi des gamins

    Attention, les liens changeront à chaque nouvelle version de l'epub.

    2 doigts de technique

    Les fichiers de reconstitution de l'œuvre sont disponibles dans un dépôt GitHub.

    Préparation

    Le travail de numérisation vous donne une collection de photos prises par deux appareils photos, nommés right et left.

    Vous obtenez donc les pages paires dans le dossier right et les pages impaires dans le dossier left.
    Les photos sont de plus nommées automatiquement par les appareils
    suivant le format IMG_<num>.JPG. Naturellement, <num> ne correspond pas au numéro de page.

    Il faut donc commencer par réconcilier tout cela. Les pages de
    couverture sont renommées à la main, elle sont peu nombreuses.

    Il reste les pages paires

    PAGE=2
    for file in IMG_*.JPG;
    do
    mv right/${file} page-${PAGE}.JPG
    PAGE=$((PAGE+2))
    done


    et impaires

    PAGE=1
    for file in IMG_*.JPG;
    do
    mv left/${file} page-${PAGE}.JPG
    PAGE=$((PAGE+2))
    done


    Une fois toutes les pages rassemblées, il faut également leur appliquer une rotation de 90° vers la droite pour les pages impaires et 90° vers la gauche, ou 270° vers la droite, pour les pages paires.

    for file in page-*.JPG;
    do
    PAGE=${file#page-*}
    PAGE=${PAGE%.JPG}
    MODULO=$((PAGE % 2))
    ROTATE=$((270-MODULO*180))
    convert -rotate ${ROTATE} ${file} ${file%.*}.jpg
    mv ${file%.*}.jpg ${file}
    done


    Corrections du texte

    La partie reconnaissance de caractères ne sera pas détaillée ici (ce n'est pas moi qui l'ai faite), mais mes quelques essais avec Tesseract OCR ont été relativement satisfaisants.
    Benjamin, de son côté, a obtenu de meilleurs résultats avec ScanTailor et Tesseract.

    Néanmoins, on obtient dans tous les cas un fichier texte par page.
    On peut alors commencer les corrections.

    Dans mon cas, j'ai choisi le format Markdown. Couplé à pandoc, cela permettra d'avoir un format pivot à partir duquel il sera possible de générer le livre dans différents formats tels que PDF, epub, HTML, texte ou encore LaTeX par exemple.

    Il est également important de se préoccuper de la typographie. Cela améliore grandement le résultat final, mais pose parfois quelques soucis de compatibilité avec les liseuses.

    Au fur et à mesure, on devine que certaines corrections reviennent très souvent, trop pour ne pas être automatisées

    sed -i 's/fl/fl/g' 2-markdown_chapitres/part-*/*.md
    sed -i 's/fi/fi/g' 2-markdown_chapitres/part-*/*.md
    sed -i 's/ ?/ ?/g' 2-markdown_chapitres/part-*/*.md
    sed -i 's/ !/ !/g' 2-markdown_chapitres/part-*/*.md
    sed -i 's/ ;/ ;/g' 2-markdown_chapitres/part-*/*.md
    sed -i 's/ :/ :/g' 2-markdown_chapitres/part-*/*.md
    sed -i "s/'/’/g" 2-markdown_chapitres/part-*/*.md
    sed -i 's/‘/’/g' 2-markdown_chapitres/part-*/*.md
    sed -i 's/oe/œ/g' 2-markdown_chapitres/part-*/*.md
    sed -i 's/ae/æ/g' 2-markdown_chapitres/part-*/*.md
    sed -i 's/.../…/g' 2-markdown_chapitres/part-*/*.md
    sed -i 's/?../?…/g' 2-markdown_chapitres/part-*/*.md
    sed -i 's/!../!…/g' 2-markdown_chapitres/part-*/*.md
    sed -i 's/« /« /g' 2-markdown_chapitres/part-*/*.md
    sed -i 's/ »/ »/g' 2-markdown_chapitres/part-*/*.md
    sed -i 's/Mme /M^me^ /g' 2-markdown_chapitres/part-*/*.md
    sed -i 's/Mmes /M^mes^ /g' 2-markdown_chapitres/part-*/*.md
    sed -i 's/Mlle /M^lle^ /g' 2-markdown_chapitres/part-*/*.md
    sed -i 's/Mlles /M^lles^ /g' 2-markdown_chapitres/part-*/*.md
    sed -i 's/M. /M. /g' 2-markdown_chapitres/part-*/*.md
    sed -i 's/A /À /g' 2-markdown_chapitres/part-*/*.md


    Attention, n'exécutez ces commandes que sur une page corrigée pour limiter au maximum les effets de bord.

    Les images

    Le livre original étant illustré, il me paraissait naturel d'intégrer les illustrations au livre final.

    Il faut donc dégaîner GIMP pour:

    * Extraire l'illustration de l'image d'origine (et non de la version
    traitée par ScanTailor)
    * Convertir les images en "nuances de gris"
    * Exporter au format .jpg, qualité 90
    * Redimensionner l'image obtenue pour limiter le poids final de l'epub

    SIZE=480
    for file in *.JPG; do
    echo -n Converting ${file}...
    convert -resize ${SIZE}x${SIZE} -quality 60 "$file"
    "little/${file%.*}.jpg"
    echo done
    done


    Enfin, l'on peut générer l'epub lui-même. Je vous laisse étudier le fichier utilities/makefile du dépôt.

    Personnalisation de l'epub

    L'epub n'étant qu'un container
    zip
    , il est évidemment possible de la modifier avant de le reconstituer.

    unzip -d Jud_Allan-partie-1 Jud_Allan-partie-1.epub


    Dans mon cas, j'ai adapté le formatage des titres de chapitre et la feuille de style, puis reconstitué l'epub.

    cd Jud_Allan
    FILE="../output/Jud_Allan-part-1.epub"
    rm -f "$FILE"
    zip -X0 "$FILE" mimetype
    zip -X9Dr "$FILE" META-INF images *.*


    2 mercis

    Je ne peux évidemment terminer sans remercier Benjamin Sonntag pour sa "drôle de machine", mais également Agnès pour son aide pour la correction et sa patience dans l'apprentissage de Git !

    Voilà, si cela peut servir à d'autres, c'est tant mieux :)
  9. # 3044

    Adieu StatusNet, bonjour Gnu Social

     
    J'ai réalisé hier soir la migration de mon instance StatusNet vers GNU-Social. GNU-Social est le remplaçant officiel de StatusNet et, à ce titre, est 100% compatible.

    Pas de grosse surprise à attendre donc...

    La mise à jour s'est effectuée de manière très simple: cloner le
    dépôt GIT de Gnu-Social, vérifier que tous les correctifs que j'ai fait et utilise ont été intégrés, les ajouter le cas échéant, et pousser le code sur le serveur.

    Il ne manquaient que 2 correctifs, cosmétiques en plus, rien de grave.

    Une fois en place, lancer quelques scripts de mise à jour

    php scripts/updateurls.php
    php scripts/upgrade.php
    php scripts/checkschema.php


    Patienter (c'est un peu long de vérifier une base de 2Go sur un serveur n'ayant que 1Go de RAM)...

    Ré-ouvrir les accès web préalablement coupés, redémarrer les processus d'import Twitter...

    et pourtant...

    Et là, c'est le drame. Une trentaine de tweets ont été récupérés suite à l'interruption pour migration. Sauf qu'ils sont restés bloqués en file d'attente :-/

    15 minutes plus tard, 11 tweets seulement étaient importés, là où StatusNet faisait le boulot en quelques secondes. Un truc génial dans StatusNet, pour qui dispose d'un accès shell au serveur, c'est de pouvoir utiliser les processus de gestion de la file d'attente, afin de pouvoir effectuer les opérations d'envoi et d'import de messages en tâche de fond, plutôt que lors d'un appel HTTP.

    Sauf que, dans GNU-Social, le service de gestion de la file d'attente a (par défaut) purement et simplement disparu, au profit d'un gestionnaire "opportuniste".
    Celui-ci profite justement de chaque connexion HTTP pour traiter toute ou partie de la file d'attente...

    Sauf que sur un petit serveur un poil optimisé, par exemple en réduisant le temps d'exécution maximum de PHP, ben ça ne fonctionne plus. Le gestionnaire "opportuniste" n'a plus beaucoup d'opportunité, et il le dit en plus...

    Bon, je vous rassure, j'ai vite trouvé la solution...

    En l'occurrence, il faut d'abord réactiver l'ancien gestionnaire de file d'attente:

    --- a/scripts/getvaliddaemons.php
    +++ b/scripts/getvaliddaemons.php
    @@ -37,7 +37,7 @@ require_once INSTALLDIR.'/scripts/commandline.inc';

    $daemons = array();

    -#$daemons[] = INSTALLDIR.'/scripts/queuedaemon.php';
    +$daemons[] = INSTALLDIR.'/scripts/queuedaemon.php';

    if (Event::handle('GetValidDaemons', array(&$daemons))) {
    foreach ($daemons as $daemon) {


    Forcer son utilisation dans le fichier config.php

    $config['queue']['subsystem']     = 'db';


    Et enfin, éventuellement, supprimer le plugin en question qui risque de rentrer en conflit avec l'ancien gestionnaire. Là encore, dans le fichier config.php

    unset($config['plugins']['core']['OpportunisticQM']);
    unset($config['plugins']['core']['Cronish']);


    J'en ai profité pour désactiver également un autre plugin qui prétend faire "des choses" en profitant d'un appel HTTP.

    J'ai pas l'air comme ça, mais j'aime bien

    Bref, indéniablement du mieux.

    Beaucoup de correction de bug (parfois très vieux), incorporation de plusieurs correctifs qui attendaient chez StatusNet depuis plusieurs mois (années ?), mise à niveau de la compatibilité PHP & support de l'API witter 1.1 par exemple.

    Mais bon sang, pourquoi diable vouloir changer un truc qui marche comme le gestionnaire de file d'attente ? Parfois je ne comprends pas les développeurs...

    Merci à Postblue qui m'a fait gagner du temps pour la migration :)
  10. # 3043

    Tout est une question d'équilibre...

     

    Obi-Wan Kenobi à Dark Vador :
    « Tu devais amener l'équilibre dans la force, pas la condamner à la nuit ! »

    in « Star Wars, épisode III : La Revanche des Sith », source Wikipedia


    La surveillance de masse des citoyens par les États est un fait. Auparavant reléguée au rang de crainte émise par des paranoïaques, elle est aujourd’hui cruellement mise au jour par les révélations, entre autres, d’Edward Snowden.
    La nécessité de surveillance d’une société par l’État relève du besoin, simple à comprendre au demeurant, de protéger la-dite société. Il s’agit d’un moyen d’assurer la sécurité de la population qui la compose, des dangers et agresseurs intérieurs (ça c’est la police). Il s’agit également de pouvoir retrouver et punir ceux des citoyens qui décideraient de s’affranchir des règles en vigueur (ça c’est la justice). Les dangers extérieurs sont gérés par les forces armées.

    Elle relève donc d’un équilibre entre la protection de la vie privée et des libertés des citoyens et la nécessité de donner aux forces de l’ordre les capacités dont ils ont besoin pour assurer leurs missions. Cet équilibre a, toujours, été extrêmement délicat à trouver.

    Il faut se souvenir de la redoutable efficacité des services de Joseph Fouché durant le Directoire ou l’Empire pour comprendre que, même avec des moyens « restreints » comme ceux de l’époque, il est possible d’être particulièrement efficace.

    Ces moyens ont évolué bien sûr. Nous sommes passés des fiches cartonnées aux ordinateurs et c’est là une partie du problème. Il est non seulement possible de surveiller de manière beaucoup plus efficace, comprendre rentable, mais également de faire des choses jusqu’alors impensables, par exemple croiser les informations très rapidement.
    Si l’on considère que l’équilibre entre la vie privée et la nécessaire mission des forces de l’ordre était respecté il y a une trentaine d’années, il convient également de constater que cet équilibre a été rompu depuis, au bénéfice quasi exclusif des forces de l’ordre. Contre cela, pas grand-chose au profit des citoyens.

    Par exemple, si l’on savait il y a 30 ans que 2 personnes avaient échangé des informations puis qu’elles s’étaient rencontrées, il était en revanche potentiellement plus complexe d’affirmer l'existence d'un rapport de cause à effet entre ces 2 informations.
    Savoir que 2 personnes s’étaient rencontrées nécessitait au mieux d’avoir un indicateur sur place, au pire d’y envoyer quelques personnels de la préfecture de Police ou de la DST (à l’époque).

    Aujourd’hui, si l’on détecte un échange de mail et que la géolocalisation de leurs téléphones portables montre que 2 personnes étaient au même endroit en même temps, il est raisonnable de conclure que l’échange de mail pouvait bien concerner le rendez-vous. Ça paraît anodin, c’est au contraire tout:

    * la récupération de l’information est de plus en plus simple et coûte de moins en moins cher. Dit autrement, pour le même prix vous pouvez récupérer et stocker infiniment plus d’informations qu’il y a 30 ans.
    * l’analyse de cette masse d’information est également plus simple et efficace. La puissance des outils informatiques aujourd’hui permet d’obtenir une information en quasi temps réel, chose impensable il y a 30 ans, sauf à disposer d’une troupe nombreuse (la mémoire humaine est stupéfiante en termes de capacité de stockage et de traitement) mais évidemment chère et complexe à organiser.

    Bref, la surveillance généralisée est devenue la norme car elle est devenue bon marché.

    Suite aux révélations sur l’étendue de cette surveillance par les services secrets Américains, mais également Européens et notamment Français, une solution vite trouvée repose sur l’utilisation massive et généralisée du chiffrement. Le principe est simple: si la population a massivement recours au chiffrement, la surveillance généralisée va prendre du plomb dans l’aile.

    La généralisation du chiffrement empêchera les forces de l’ordre de travailler

    Aujourd’hui, selon les dires publics du directeur technique de la DGSE, la France joue en première division en ce qui concerne le recueil et l’analyse des métadonnées.
    Il n’est pas ici question du contenu des communications, simplement des metadonnées soit: qui communique avec qui, quand et pendant quelle durée.

    La généralisation du chiffrement ne devrait pas perturber tant que cela cette capacité. Il y aura néanmoins un impact comme l’explique Stéphane Bortzmeyer: par exemple pour le mail, l’utilisation de TLS masque les adresses mail source et destination. Seules subsisteront encore les adresses IP des machines qui acheminent le trafic.

    On sait également que des entreprises comme VUPEN Security font de la recherche de failles de sécurité un business. Elles sont très en pointe dans le domaine, gardent ces failles secrètes, pour les revendre aux services de sécurité, par exemple à la NSA.
    Il ne fait aucun doute que les services français ont un accès — contre espèces sonnantes et trébuchantes, business is business — à ces failles « 0-day ». Il est raisonnable de penser qu’ils les utilisent. Ils pourront continuer à les utiliser.

    Et, de toute façon, il y a de très grandes chances pour que les vrais méchants utilisent déjà massivement le chiffrement ou d’autres dispositifs de dissimulation.

    En passant, suggéreriez-vous de supprimer Tor ?
    Pourtant c’est du chiffrement, ça fonctionne et c’est utilisé par Silk-Road, que le FBI n’a réussi à stopper qu’après une boulette de son créateur.

    Le seul impact vraisemblable d’un chiffrement généralisé serait alors de noyer les communications des méchants dans la masse ? Voire, car les principaux services Internet aujourd’hui, de Google à Facebook en passant par Twitter ou Netflix, utilisent déjà le chiffrement.

    Ce que l’utilisation massive du chiffrement risque de changer en revanche, c’est que la surveillance généralisée redeviendra non rentable car beaucoup trop chère pour être réellement efficace.
    L’utilisation massive du chiffrement provoquera un ré-équilibre des pouvoirs entre État et citoyens.

    Jusqu'à ce qu'un jour, l’État obtienne un moyen simple de casser les clefs de chiffrement. On sera alors revenus à la case départ, l'équilibre sera à nouveau rompu jusqu'à ce que l'on trouve une autre solution.

    Une réponse technique à un problème politique

    Le chiffrement généralisé est en effet une solution non satisfaisante, plus exactement incomplète. Il est a priori nécessaire de compléter la réponse technique par une réponse politique. À mon grand regret cependant, je crains que cette réponse ne soit, au mieux, que partielle.

    Entendons-nous: même insuffisants, les garde-fous légaux existent déjà. Sauf que, comme souvent, la tentation est forte de tordre les grands principes pour arriver à ses fins.
    Une fois pris, bien entendu, il convient de s'excuser platement, de promettre la main sur le cœur et la larme à l'œil qu'on ne nous y reprendra plus… jusqu'à la prochaine fois…

    Ce n’est pas autrement que les différents services, US pour la NSA comme Européens pour la DGSE, parviennent à espionner leur propre population alors que la loi le leur interdit en théorie. Selon la rédaction de Reflets, il existe même de drôles de coïncidences entre les endroits où la France a vendu les systèmes d’Amesys ou de Qosmos et les points d’arrivée des câbles sous-marins d’Alcatel

    Au vu de tout cela, et connaissant nos politiques comme on les connait aujourd’hui, croyez-vous sincèrement qu’ils renonceront à pouvoir, un jour ou l’autre, refoutre la main dans le pot de confiture ?

    Le chiffrement devient de plus en plus une arme de protection massive du citoyen contre son propre État.

    L'État a eu en main tous les leviers de contrôle de la surveillance. Il a prouvé n'en être pas digne. Il est temps de lui en retirer une partie.

    Chiffrez… tout… en cas de besoin, vous devrez de toute façon donner la phrase de passe de votre clef aux forces de l'ordre lorsqu'elles sont mandatées par un juge et uniquement dans ce cas là.

    C'est aussi ça la séparation des pouvoirs. Reprenez le vôtre. La seule réponse à apporter consiste à utiliser les technologies de chiffrement à votre disposition et à expliquer au politique que, si on en est là, c’est de sa faute.
  11. # 3042

    L'« homme-masse »

     

    Partout l'homme-masse a surgi [...] un type d'homme hâtivement bâti, monté sur quelques pauvres abstractions et qui pour cela se retrouve identique d'un bout à l'autre de l'Europe. [...] Cet homme-masse, c'est l'homme vidé au préalable de sa propre histoire, sans entrailles de passé, et qui, par cela même, est docile à toutes les disciplines dites « internationales ». [...] Il lui manque un « dedans », une intimité inexorablement, inaliénablement sienne, un moi irrévocable. Il est donc toujours en disponibilité pour feindre qu'il est ceci ou cela. Il n'a que des appétits ; il ne se suppose que des droits ; il ne se croit pas d'obligations. C'est l'homme sans la noblesse qui oblige — sine nobilitate — le snob.

    Jose Ortega Y Gasset, La révolte des masses (1929), via Theatrum Belli
  12. # 3041

    Ivre, il décide de se mettre au C...

     
    en développant un module Nginx. Vous me direz qu'il y a plus simple, et vous n'aurez pas tort :)

    Ça faisait un bail que je voulais m'y mettre, sans trouver le courage de le faire, faute entre autre d'un objectif tangible et motivant.

    J'écris pas mal de doc. En markdown, parce que c'est plus rapide, et lisible, en l'état. Au boulot notamment, je prends des note sur l'architecture que nous utilisons, sur les recherches et travaux que je peux mener, des aides-mémoire des différents logiciels en production. Tout y passe... Tout en markdown.

    Utilisant Geany sous Debian Wheezy, j'ai naturellement installé le plugin markdown en backportant le paquet sid. Il me permet d'avoir le rendu HTML en direct, ce qui est bien pratique. Mais, histoire de partager mes notes avec mes collègues, l'idée a germé de pouvoir pousser les fichiers en markdown dans une arborescence web afin de pouvoir les consulter via un navigateur.

    Problème: il faut pouvoir convertir le markdown en HTML.

    Vous me direz que plein de scripts en python (voire même en Ruby pour les fous comme @Keltounet) font le job et le font bien, voire mieux. Là encore, vous n'aurez pas tord, mais il faut bien trouver un prétexte pour faire "des trucs" rigolos.

    Donc voilà. J'ai écris un module Nginx, en fait 2, qui transforment à la volée un document Markdown en HTML pour l'afficher dans le navigateur.

    Ils sont perfectibles bien sûr. Je n'ai pas la prétention de "savoir" programmer en C. Mais le défi m'a beaucoup plu, même s'il a fallu que @shtouff vienne à ma rescousse pour m'expliquer 2-3 bases quand même :) Merci à lui !

    En tout cas, ça m'a permit de démystifier, en partie, la programmation en C ainsi que le développement de module Nginx.

    Non, l'un comme l'autre ne sont pas si compliqués que cela... enfin... bon bref quoi...

    Le code est dispo, sous licence GPLv3, sur Github: https://github.com/jbfavre/ngx-markdown-module

    Happy Hacking !
  13. # 3040

    Le dilemme du prisonnier français

     
    Dans la théorie des jeux, le dilemme du prisonnier décrit une situation dans laquelle 2 joueurs ont intérêt à coopérer mais dans laquelle le choix rationnel pousse à trahir l’autre.

    En pratique, il s’agit de 2 prisonniers isolés, sans moyen de communication donc de concertation, qui se voient proposer un marché par l’administration pénitentiaire :

    Les options des prisonniers sont les suivantes:

     — si un des deux prisonniers dénonce l’autre, il est remis en liberté alors que le second obtient la peine maximale (10 ans) ;
     — si les deux se dénoncent entre eux, ils seront condamnés à une peine plus légère (5 ans) ;
     — si les deux refusent de dénoncer, la peine sera minimale (6 mois), faute d’éléments au dossier.


    D’une manière générale, le prisonnier a donc intérêt à dénoncer l’autre de manière à minimiser sa propre peine. Il s’agit d’un choix rationnel qui, pourtant, n’est pas optimal : si les deux choisissent de se taire, ils ne feront que 6 mois de prison contre 5 ans s’ils se dénoncent mutuellement.

    Oui mais voilà, comment s’assurer que l’autre fera le même choix ? Il n’existe aucun moyen de s’en assurer, tout repose sur la confiance mutuelle.

    Avec 2 prisonniers, c’est assez simple. Imaginez maintenant le même genre de problème avec... 60 millions de personnes.

    Et si la situation actuelle de la France pouvait être comparée à un dilemme du prisonnier géant ?

    La logique rationnelle voudrait que les Français acceptent le changement, porteur d’amélioration pour tout le monde.

    Mais chacun estime que l’autre va « gruger » et tirer la couverture à lui.

    Dans le principe, chacun est prêt aux efforts, sous réserve que ce soit l’autre qui les supporte.

    C’est bien connu, l’herbe est plus verte dans le pré d’à côté. Il est donc « normal » que l’effort soit porté par d’autres que soi.

    Pour certains, les politiques ne joueront pas le jeu (chat échaudé craint l’eau froide), pour d’autres, l’autre, l’étranger par exemple, est responsable de tous les maux. Pour d’autres enfin, une catégorie de la population, au hasard les fonctionnaires, ne fait aucun effort.

    Je n’oublie évidemment pas les patrons qui pensent que les salariés sont trop protégés et coûtent trop cher, pas plus que les salariés qui pensent que les patrons veulent juste les exploiter pour faire toujours plus de fric.

    Bref, tout le monde est à peu près d’accord pour constater le blocage et réclamer du changement, mais personne n’accepte de faire le premier pas, de peur d’être tout seul à en supporter les conséquences.

    Là encore, la confiance n’y est pas. Il est donc plus « confortable » de ne rien changer, plutôt que d’accepter un changement qui pourrait nous être défavorable.

    Avec ce type de raisonnement, on va tous prendre la peine maximum.

    Sauf... si « ça pète ».

    Dans ce cas là, les données du problème changent: la question ne sera plus de savoir quoi sacrifier ou quoi protéger. Si ça pète, nous seront au pied du mur.

    Dit autrement, nous risquons d’avoir le choix entre perdre beaucoup, ou perdre énormément, voire tout. En tout état de cause, si l’on va jusqu'au clash, il paraît illusoire d’espérer sauver les meubles. Ce sera la peine maximum pour tout le monde, plutôt que quelques efforts permettant de conserver un ensemble finalement pas si mal que ça.

    Ceci étant, le choc d’une révolte de grande ampleur pourrait également avoir pour effet de souder à nouveau la population, par delà les clivages traditionnels. Ce rapprochement pourrait alors déboucher sur un consensus pour faire évoluer le système (je sais, c'est très optimiste… mais c'est au conditionnel aussi hein…).

    En attendant, il paraît urgent de ne rien faire car, c'est bien connu:

    C'est au pied du mur qu'on voit le mieux le mur...

  14. # 3039

    Gestion des exceptions & Python

     
    Tout à la découverte de Python, j'ai besoin d'exécuter ceci:

    Essaie:
    mon bout de code
    Si ça merde:
    essaie ça plutôt
    Si ça merde toujours:
    laisse tomber


    Intuitivement, on voit ce dessiner un joli "if else else".
    Sauf que, en l'occurence, ça ne convient pas car "Si ça merde" & "Si ça merde toujours" correspondent en fait à des exceptions Python que je veux bien entendu gérer.

    En gros n00b à peine bourrin, je fais donc:

    try:
    mon code qui peut merder
    except:
    mon autre code qui peut merder aussi
    except:
    bon là ça va aller, je laisse tomber


    Évidemment, ça ne le fait pas:

    SyntaxError: default 'except:' must be last


    En fait, le mécanisme de gestion d'exception convient, mais il faut l'utiliser "convenablement", c'est-à-dire en imbriquant les try/except:

    try:
    try:
    mon code qui peut merder
    except:
    mon autre code qui peut merder aussi
    except:
    bon là ça va aller, je laisse tomber


    Merci à @pbeyssac, @guiguiabloc et @gordontesos pour le coup de main sur Twitter :)

    Le détail de la conversation se trouve ici

    Update
    L'ami @gordontesos s'est même fendu d'un billet pour (m')expliquer la gestion des exceptions en Python.
  15. # 3038

    Pour avoir préféré le crime de l’illégalité au crime de l’inhumanité

     

    Nous nous souvenions de quinze années de sacrifices inutiles, de quinze années d’abus de confiance et de reniement.
    Nous nous souvenions de l’évacuation de la Haute-Région, des villageois accrochés à nos camions, qui, à bout de forces, tombaient en pleurant dans la poussière de la route.
    Nous nous souvenions de Diên Biên Phû, de l’entrée du Vietminh à Hanoï.
    Nous nous souvenions de la stupeur et du mépris de nos camarades de combat vietnamiens en apprenant notre départ du Tonkin.
    Nous nous souvenions des villages abandonnés par nous et dont les habitants avaient été massacrés.
    Nous nous souvenions des milliers de Tonkinois se jetant à la mer pour rejoindre les bateaux français.
    Nous pensions à toutes ces promesses solennelles faites sur cette terre d’Afrique.
    Nous pensions à tous ces hommes, à toutes ces femmes, à tous ces jeunes qui avaient choisi la France à cause de nous et qui, à cause de nous, risquaient chaque jour, à chaque instant, une mort affreuse.
    Nous pensions à ces inscriptions qui recouvrent les murs de tous ces villages et mechtas d’Algérie : ‘L’Armée nous protégera, l’armée restera’.

    Nous pensions à notre honneur perdu


    Extrait de la plaidorie du commandant Hélie Denoix de Saint Marc devant le haut tribunal militaire, 5 juin 1961.