Autoblog de gordon.re

Ce site n'est pas le site officiel de gordon.re
C'est un blog automatisé qui réplique les articles de gordon.re

Installation de Proxmox avec chiffrement de disque

2016-02-21T00:00:00+01:00 - (source)

Suite à l’installation d’une nouvelle machine en datacenter, j’ai souhaité dépoussiérer mon utilisation de Proxmox, et réfléchir à ce qui pouvait être fait pour améliorer mes précédentes utilisations, du point de vue de la sécurité.

Première installation test

Dès ma machine reçue (un Dell R210), j’ai téléchargé une image Proxmox pour l’installer. Étant frileux sur l’adoption de SystemD, j’ai opté pour une version 3.4 de Proxmox, basée sur Debian-oldstable, et donc sous SysVinit. Je précise au passage que je dispose de deux disques durs dans cette machine, et que je souhaite utiliser du RAID1 soft afin de sécuriser mes données (dans le sens « ne pas les perdre suite à une panne »). À savoir que l’installeur Proxmox ne supporte pas le RAID soft(en) lors d’une installation bare-metal.

Bref, lors de l’installation, je remarque l’ajout d’options « ZFS » en tant que système de fichier supporté pour le formatage. Excellente nouvelle, car ZFS gère la création de systèmes en miroir, en alternative à dm-raid. Encore mieux, Proxmox propose, si on utilise ZFS, de créer une grappe en miroir, et donc de tout gérer de lui-même.

J’opte donc pour cette approche. J’installe Proxmox, je le configure, puis je crée un conteneur pour tester mes repères (et les performances de la machine). Ça fonctionne, très bien. Étant donné que c’est à ce moment que FAImaison s’installe en datacenter, j’en profite pour racker ma machine et jouer le rôle de beta-testeur pour l’association. Ce qui se traduit par un succès, grâce aux adminsys qui ont fait un super boulot. Bref, ça marche. Sauf qu’il y a un problème important : je n’ai pas chiffré le disque, lors de l’installation. Étant donné que cette machine sera vouée à héberger mes données, ce n’est pas un état de fait acceptable.

Approche naïve

Je planifie donc une intervention au datacenter, avec cet objectif : copier intégralement le contenu des disques sur un disque externe, puis reformater les partitions ZFS pour créer une partition LUKS, puis restaurer les partitions ZFS mirrorées sur les conteneurs LUKS ouverts. Piece of cheese, comme disent les auvergnats. Sauf que c’était la théorie.

La pratique a donc été constituée d’une heure et demi de galère pour réussir à simplement démarrer une clé USB live (Ubuntu, en l’occurrence), sur la machine. Je m’en suis sorti en activant l’UEFI dans le BIOS du serveur. Ensuite est venue la consternation en constatant le schéma de partitionnement d’un des disques :

root@fromage:~/# parted -l
Model: ATA WDC WD5003ABYX-1 (scsi)
Disk /dev/sda: 500GB
Sector size (logical/physical): 512B/512B
Partition Table: gpt

Number  Start   End     Size    File system  Name  Flags
 1      17.4kB  1049kB  1031kB                     bios_grub
 2      1049kB  500GB   500GB   zfs          zfs
 9      500GB   500GB   8389kB


Model: ATA WDC WD5003ABYX-1 (scsi)
Disk /dev/sdb: 500GB
Sector size (logical/physical): 512B/512B
Partition Table: gpt

Number  Start   End     Size    File system  Name  Flags
 1      17.4kB  1049kB  1031kB                     bios_grub
 2      1049kB  500GB   500GB   zfs          zfs
 9      500GB   500GB   8389kB


Model: Unknown (unknown)
Disk /dev/zd0: 16.1GB
Sector size (logical/physical): 512B/4096B
Partition Table: loop

Number  Start  End     Size    File system     Flags
 1      0.00B  16.1GB  16.1GB  linux-swap(v1)

L’information utile est la suivante : contrairement aux architectures que j’ai l’habitude de traiter en termes de chiffrement de disque, il n’y a pas de partition /boot, mais une seule (par disque) partition ZFS, qui contient toute la racine du système. J’apprends donc que Grub sait démarrer nativement du ZFS.

Le problème, c’est que pour chiffrer la racine du disque, il faut que je puisse sortir le noyau et son image initramfs de là, d’où la partition /boot séparée. Du haut de ma connaissance moyenne de ZFS, je n’étais pas suffisamment à l’aise pour tenter une modification à la volée du schéma de partitionnement. Donc, impasse pour cette approche.

Approche idéale : utilisation de chiffrement intégré à ZFS

Il faut savoir que ZFS était un projet de Sun, initialement. Sun ayant été racheté par le mal absoluOracle, leur branche de ZFS a très vite perdu sa licence libre. Aujourd’hui, seule cette branche supporte le chiffrement intégré. Les versions intégrées à FreeBSD et GNU/Linux n’en disposent pas, et ce n’est visiblement pas prêt d’arriver. Seconde impasse.

Approche de compromis

À ce stade, je commençais à me rendre compte qu’une réinstallation de la machine était indispensable, et qu’il faudrait abandonner ZFS. Je me suis alors renseigné sur l’état du chiffrement intégré à Ext4(en), existant depuis la version 4.1 du noyau Linux. Cet article(en) m’a fait comprendre que l’implémentation n’était pas tout à fait sèche, et qu’elle partageait notamment la faiblesse de conception d’eCryptFS(en), c’est à dire que seul le contenu et le nom des fichiers est chiffré, pas les méta-données (nombre de fichiers, dates d’accès et de modification…). Ne souhaitant pas baser le chiffrement du serveur gérant mes données privées sur un système instable, et étant donné que la version de Proxmox que je souhaitais utiliser avait un kernel trop vieux, je suis passé à autre chose.

Approche standard

Par « standard », j’entends « comme à mon habitude », c’est à dire le formatage à la main, avec un /boot séparé. Donc, plutôt que d’installer Proxmox en bare-metal, il a fallu installer une Debian standard, avec chiffrement, puis, une fois celle-ci opérationnelle, suivre les instructions pour installer Proxmox dessus. Le tout en acceptant finalement de me baser sur la dernière version de Debian, comprenant SystemD.

J’enchaîne donc sur la suite de l’article avec les instructions détaillées sur cette installation.

Installation définitive

D’abord, il nous faut booter sur un installeur Debian. Téléchargez donc celle-ci sur le site officiel, prenez soin de vérifier l’empreinte du fichier (et vérifiez l’intégrité des empreintes.

gordon@fromage:~$ wget http://cdimage.debian.org/debian-cd/8.3.0/amd64/iso-cd/debian-8.3.0-amd64-netinst.iso
[…]
gordon@fromage:~$ wget http://cdimage.debian.org/debian-cd/8.3.0/amd64/iso-cd/SHA512SUMS
[…]
gordon@fromage:~$ wget http://cdimage.debian.org/debian-cd/8.3.0/amd64/iso-cd/SHA512SUMS.sign
[…]
gordon@fromage:~$ gpg --recv-key 6294BE9B
gpg: clef 6294BE9B : clef publique « Debian CD signing key <debian-cd@lists.debian.org> » importée
gordon@fromage:~$ gpg --verify SHA512SUMS.sign
gpg: les données signées sont supposées être dans « SHA512SUMS »
gpg: Signature faite le dim. 24 janv. 2016 19:08:33 CET avec la clef RSA d'identifiant 6294BE9B
gpg: Bonne signature de « Debian CD signing key <debian-cd@lists.debian.org> » [inconnu]
gpg: Attention : cette clef n'est pas certifiée avec une signature de confiance.
gpg:             Rien n'indique que la signature appartient à son propriétaire.
Empreinte de clef principale : DF9B 9C49 EAA9 2984 3258  9D76 DA87 E80D 6294 BE9B
gordon@fromage:~$ sha512sum -c SHA512SUMS --ignore-missing
debian-8.3.0-amd64-netinst.iso: Réussi

Une fois ces vérifications passées avec succès, copiez l’image sur une clé USB avec dd, en prenant soin de ne pas vous planter le nom de périphérique.

# après branchement de la clé USB
root@fromage:~$ dmesg | tail -n 1
[16916.267063] sd 6:0:0:0: [sdb] Attached SCSI removable disk
# ma clé est donc "sdb"
root@fromage:~$ dd if=debian-8.3.0-amd64-netinst.iso of=/dev/sdb bs=1M

Branchez la clé sur le serveur, démarrez dessus et sélectionnez « Expert install ».

Démarrage de
l’installation Debian

Je ne m’étends pas sur l’intégralité de l’installation, si vous n’avez jamais installé de Debian, peut-être que l’installation d’une Proxmox chiffrée est inadaptée pour vous.

Lors de l’étape « Load installer components from CD », prenez soin de choisir crypto-dm-modules et parted-udeb dans la liste.

Sélection des composants d’installation

Continuez jusqu’à l’étape de partitionnement. Choisissez « Manual ». Voici, pour information, ce que nous allons mettre en place.

Schéma de
partitionnement final

Partitionnement manuel

Vous devriez avoir quelque chose de similaire. Au besoin, supprimez les partitions existantes (ou créez une table de partition vierge), pour partir d’un état similaire au mien.

Disques vierges

La toute première étape est de créer deux paires de volumes RAID : un pour le /boot, l’autre pour le volume chiffré qui contiendra nos autres volumes. Commencez donc par créer, dans chaque disque, une partition destinée à être une grappe RAID.

Création des partitions pour /boot

Création des partitions pour /boot

Création des partitions pour /boot

Nous avons maintenant deux partitions destinées au RAID, un sur chaque disque. Nous allons créer le volume pour /boot, puis nous occuper de la suite. Ça se passe dans « Configure software RAID ». Suivez les instructions, sélectionnez bien /dev/sda1 et /dev/sdb1, qui sont les partitions que nous venons de créer.

Création du RAID pour /boot

Prêt à créer /boot

Utilisons ce « RAID1 device #0 » pour créer une partition au format ext2 (pas besoin de journalisation pour un /boot), et assignez-lui le bon point de montage.

Création de /boot

/boot créé

Étape suivante, utiliser l’espace libre restant sur les disques (notez qu’il y a un espace libre d’1Mo avant mon RAID, ce n’est pas lui qui nous intéresse) pour créer une seconde grappe RAID. On peut passer directement par le menu « Configure software RAID ». Cette fois, sélectionnez bien les deux « FREE SPACE », qui devraient de toutes façons être les seuls disponibles.

Création de la grappe RAID principale

RAID créé

C’est maintenant sur la ligne en dessous de « RAID1 device #1 » que nous allons créer le reste. Dans l’ordre : un conteneur LUKS (pour le chiffrement), puis un groupe de volume LVM, puis nos partitions finales.

Allons donc dans « Configure encrypted volumes ». Sélectionnez /dev/md1 comme volume à chiffrer, étant donné que c’est le volume RAID principal (les autres ne sont que les miettes restantes sur les disques, et notre /boot).

Sélection du volume à chiffrer

À la fin de la configuration du volume chiffré, il faudra vous armer d’un peu de patience, car l’installateur écrira des données aléatoires sur l’espace en question, pour nettoyer d’éventuelles anciennes traces de fichiers.

Vient l’épineuse question du choix de la passphrase. Comme d’habitude, choisissez avec soin et ne notez pas cette passphrase. Rappelez-vous pour autant que si vous la perdez, votre serveur est mort.

Volume LUKS créé

Étape suivante, si vous avez suivi : création du groupe de volumes LVM. Ça se fait dans « Configure the Logical Volume Manager ». Créez un « volume group » (je le nomme « vg0 », pas besoin d’être beaucoup plus créatif), puis choisissez votre volume chiffré précédemment créé (/dev/mapper/md1_crypt ici).

Création du groupe de volumes

Une fois le groupe de volume créé, il reste à créer les volumes logiques, soit les partitions finales. Créez-en 3 : 'root', 'data' et 'swap'. Leur signification est claire. Le volume data contiendra /var/lib/vz, soit l’intégralité des VMs de votre serveur. C’est donc lui qui sera logiquement le plus gros. Vous devriez pouvoir vous en sortir avec 20Go pour root si vous disposez de suffisamment de place. Pour swap, l’équivalent de la moitié de votre RAM est correct.

Détail des volumes logiques

Détail des volumes logiques

L’affichage de l’outil de partitionnement commence à sérieusement se complexifier. Rassurez-vous, nous en avons quasiment fini. Il reste à créer les systèmes de fichiers sur les volumes logiques nouvellement créés. J’opte pour XFS. N’oubliez pas de définir le LV swap comme… du swap, ni de définir /var/lib/vz comme point de montage du volume data.

Fin du partitionnement

Nous en avons enfin fini. Enregistrez, puis continuez l’installation. Lors de l’installation du boot loader (Grub), l’installeur m’a renvoyé une erreur, sans trop d’explications. Il semble que l’installeur Debian soit cassé à ce niveau. Pour corriger cela, choisissez « Execute a shell », puis entrez les commandes suivantes :

~ # parted /dev/sda
GNU Parted 3.2
Using /dev/sda
Welcome to GNU Parted! Type 'help' to view a list of commands.
(parted) set 1 bios_grub on
set 1 bios_grub on
(parted) quit
~ # parted /dev/sdb
GNU Parted 3.2
Using /dev/sdb
Welcome to GNU Parted! Type 'help' to view a list of commands.
(parted) set 1 bios_grub on
set 1 bios_grub on
(parted) quit
~ # exit

Le menu principal réapparaît alors, et vous pouvez donc installer Grub sur /dev/sda. Redémarrez ensuite.

Si tout se passe bien, Grub se lance, puis le système. Vous aurez alors à entrer votre passphrase LUKS pour déverrouiller le volume.

prompt LUKS

Entrez donc la passphrase, le système boote correctement.

Installation complète

C’est bien beau d’avoir un disque entièrement (ou presque) chiffré, mais comment le déverrouiller en cas de reboot, étant donné que la machine est bien au chaud dans un datacenter ? Il est possible de faire démarrer un démon SSH au sein de l’initramfs, qui permet d’entrer la passphrase de façon sécurisée à distance.

Authentifiez-vous donc sur votre système nouvellement installé, il va falloir toucher un peu à la génération de l’initramfs. Je vous conseille la lecture de ces deux pages (1, 2) sur le wiki de FAImaison qui détaillent la procédure. Je vous suggère donc de suivre le déroulé du premier lien, ce que j’ai fait moi-même.

Avant de redémarrer la machine pour tester le déchiffrement à distance, n’oubliez pas d’installer grub sur sdb, pour assurer la redondance des disques (sdb doit pouvoir remplacer sda à la volée) :

root@fromage:~# grub-install /dev/sdb

Dans le fichier /etc/initramfs-update/root/.ssh/authorized_keys que vous avez rempli selon le guide précédent, insérez command="cat - >/lib/cryptsetup/passfifo" (sans oublier l’espace finale) juste avant votre clé (ou avant chacune d’entre elles). Si vous aviez déjà tapé update-initramfs -u, refaites-le pour prendre en compte cette modification.

Si, comme moi, votre système local utilise ZSH comme shell, la commande à base de read -p… ne fonctionnera pas. À la place, utilisez celle-ci (qui fonctionnera même sous bash, et qui prend en compte l’astuce précédente) :

gordon@local:~$ echo -n 'Password: '; read -s pw; echo -n $pw | ssh  -o UserKnownHostsFile=~/.ssh/known_hosts.initramf root@fromage "cat - >/lib/cryptsetup/passfifo"; unset pw

On colle Proxmox par dessus

Maintenant, il reste à suivre bêtement (ou presque) le guide d’installation Proxmox. J’attire votre attention sur un détail : lorsqu’il faut ajouter le dépôt Proxmox sur APT, on vous demande d’ajouter la clé GPG utilisée pour signer les paquets. Et la commande pour le faire est…

wget -O- "http://download.proxmox.com/debian/key.asc" | apt-key add -

Comme vous pouvez le voir, on vous recommande d’importer une clé GPG depuis un serveur web non sécurisé. Pour rappel, vérifier l’intégrité des paquets grâce à cette clé vous permet d’éviter d’installer des programmes réécrits par des tiers, un peu comme peut l’être cette clé. En résumé, c’est une très mauvaise pratique d’importer cette clé de cette façon-là, car rien ne vous garantit que c’est la bonne.

Alors, que se passe-t-il si on essaie tout simplement de rajouter un « s » sur « http » ? Et bien, le certificat TLS est invalide, car il appartient à enterprise.proxmox.com et proxmox.com. Par ailleurs, le serveur web ne renvoie pas vers la même page. Impossible donc de récupérer la clé GPG de façon fiable par ce biais.

Pour l’instant, le seul moyen (largement détourné) pour m’assurer de la fiabilité de cette clé est le suivant :

1) Je télécharge l’ISO bare-metal de Proxmox sur https://www.proxmox.com/en/downloads/item/proxmox-ve-4-1-iso-installer-bittorrent

2) Je vérifie l’empreinte (MD5, c’est très faible voir troué…) du fichier en conformité avec celle indiquée sur la page (attention à la consulter en HTTPS)

3) J’installe cette version de Proxmox dans une VM

4) Dans cette VM, je lance :

root@proxmox:~# apt-key list | grep -A2 proxmox
/etc/apt/trusted.gpg
--------------------
pub   1024D/9887F95A 2008-10-28
uid                  Proxmox Release Key <proxmox-release@proxmox.com>
sub   2048g/A87A1B00 2008-10-28

5) L’ID court étant faible, je vérifie l’empreinte de cette clé, et je la compare avec celle fournie en ligne :

root@proxmox:~# apt-key export 9887F95A | gpg --import -a
gpg: key 9887F95A: public key "Proxmox Release Key <proxmox-release@proxmox.com>" imported
gpg: Total number processed: 1
gpg:               imported: 1
root@proxmox:~# gpg --fingerprint 9887F95A
pub   1024D/9887F95A 2008-10-28
      Key fingerprint = BE25 7BAA 5D40 6D01 157D  323E C23A C7F4 9887 F95A
uid                  Proxmox Release Key <proxmox-release@proxmox.com>
sub   2048g/A87A1B00 2008-10-28

6) Ensuite, sur la machine que je suis en train d’installer :

root@fromage:~# wget "http://download.proxmox.com/debian/key.asc"
[…]
root@fromage:~#  gpg --with-fingerprint key.asc 
pub  1024D/9887F95A 2008-10-28 Proxmox Release Key <proxmox-release@proxmox.com>
      Key fingerprint = BE25 7BAA 5D40 6D01 157D  323E C23A C7F4 9887 F95A
sub  2048g/A87A1B00 2008-10-28

7) Enfin, je compare les deux empreintes. Ici, elles sont identiques, mais vérifiez bien par vous-mêmes. Ici, la faiblesse est l’empreinte MD5 de l’ISO. Si ça peut aider, je mets à disposition la clé en question, signée par mes soins, et hébergée sur mon serveur où vous pouvez la récupérer en HTTPS. Si vous me faites confiance (en général, ne le faites pas), ça peut vous faciliter la tâche.

Maintenant que vous avez la possibilité de récupérer les paquets de Proxmox et que vous avez un moyen de savoir que ce sont les bons, continuez de suivre le guide d’installation, vous ne devriez plus rencontrer de problèmes.

Et voilà, vous disposez maintenant d’un serveur chiffré, que vous pouvez déverrouiller proprement à distance, prêt à héberger vos services !


Rebirth

2016-02-15T00:00:00+01:00 - (source)

New version of the blog!

Since the last article was published on july 14th… 2014, you had the right to think that the blog was abandonned. That’s not the case, I just didn’t take the time to take pen.

This is now done, and I take this motivational impulse to also refresh the container, causing a new version, like I said earlier.

Search!

When I announced(fr) my migration to Pelican, it was one of the features I agreed to lose. Eventually, I regretted the capacity to get directly to a given article (which I use often). So I go back to my words: it’s possible, with Pelican, to have an internal search engine. It is done with a piece of Javascript code, Tipue, which will fetch a JSON index file, and search it, before showing the search results on a static page.

I also go back to my previous wish that was to not use JS on my pages. However, this blog is entirely readable without JS, like before. The two usages of JS are integration of free softwares, and only bring new features.

The functioning is as the following: I am using the tipue-search plugin that generates, while generating the website, a JSON file placed in the root dir. Then, there is just to integrate the search form in the template, and everything works mostly automagically. I used the integration of the elegant template for mine. It is important to note that I took the latest version of Tipue instead of taking the files provided with the Pelican plugin.

Comments

Further in my old self(fr) contradiction, I decided to integrate comments, again (if I remember correctly, the v3 of this blog was running Wordpress). Here again, I can’t save comments on the server with Pelican, because Pelican runs on my local machine, and merely deploys static files online. It is nevertheless possible to integrate a comment engine into Pelican, like Disqus (you won’t mind if I don’t make a link to a centralized, privative, service), but obviously, this isn’t a solution for me. However, I heard about free alternatives, among which Isso caught my attention because of the simplicity of its approach. Basically, you just have to install Isso, which is a tiny webapp, on a blog subdomain, and it will serve an API that can be consumed by JS. By integrating a script in the page, we discuss with this API, and we display the comments, and the form to answer them. It’s very simplist, we can comment anonymously, answer comments, it’s written in Python (and it’s the german name of a Pokemon ♥), so I made my choice.

For the Isso deployment, I let you read this excellent article about installation of Isso and its integration into Pelican(fr). I have nothing interessant to add, except that you should read thisblog.

Beyond the technical point of view, you can now comment my articles again. Have fun.

Internationalization

I always wrote in french on my blog, but some technical posts should be useful to non-french readers. So I decided to make this blog bilingual (french/english). I will redact in priority in french, then, as far as possible, I’ll translate those in english. It’s possible that I only take that time for technical posts, though.

Technically (that implies that this article will be translated), I had to:

What is yet to be done:

CSS overhaul

Although I like the appearance of the blog, I have to admit that the realization is far from perfect. So I prefered to subcontract the integration cleaning to someone far better than me in that domain. It’s still a work in progress, but there shouldn’t be much differences (except for less bugs).

That’s not CSS, but I take this moment to talk also about the name change of the blog as well as its subtitle: much sober, such as the future posts.

Change of course about HTTPS

Since now, I managed my own CA to serve this blog and my other websites in HTTPS. Obviously, that forced a security warning to everyone who didn’t trust it, meaning everyone (and that’s not a bad thing(fr)).

With the creation of Let’s Encrypt, I now can generate free certificates nearly-automatically. So I abandon my CA, and migrate my domains to Let’s Encrypt. Yes, that’s still a CA doing X.509, and it’s a broken model for me. But I can’t fight on all fronts, so I make a compromise here.

Since the automatic script provided by Let’s Encrypt doesn’t satisfy me (especially because it requires root access), I prefered to use another that speaks the same protocol): letsencrypt-nosudo.

A change of hosting

With FAImaison at least becomming IP operator, I choose to rent a space in the quarter bay of the association to rack my own machine. So it is probably the first website hosted fully on the FAImaison infrastructure, and I now control mostly every aspect of my service (there is still redundancy weakness in network and electricity, indeed).

That’s all for this update. I’ll try to keep a good dynamic and publish regularly. Beside, I have some things to say.


Planquez (un peu) Proxmox

2014-07-14T00:00:00+02:00 - (source)

Proxmox est un outil intéressant, simple à prendre en main, offrant le moyen de gérer un serveur de virtualisation. Je l’utilise depuis pas mal de temps, avec des VM qemu-kvm comme des conteneurs openvz.

Concrètement, il s’agit d’une distribution GNU/Linux basée sur Debian embarquant les solutions de virtualisation/conteneurs suscitées, et une jolie interface web s’appuyant sur la lib JS ExtJS pour faciliter l’administration de tout ça. Le problème, c’est que cette interface est publique, accessible simplement via une URL, et il est possible de se connecter en root directement dessus (via un module pam). Or, je ne souhaite pas que l’on puisse avoir la main totale sur un serveur avec un simple mot de passe, fut-il d’une robustesse redoutable. Une authentification par mot de passe, c’est trop faible.

Apache

Du coup, ma solution est de reconfigurer le serveur web de Proxmox afin de passer par un tunnel SSH en tant que seul moyen d’accéder à la console. Le serveur web en question est un Apache, et il y a 2 fichiers à modifier pour sécuriser cette partie : pour les trouver, il suffit de rechercher les occurrences de « listen » dans le dossier d’Apache :

# grep -Rl Listen /etc/apache2/
/etc/apache2/sites-enabled/pve.conf
/etc/apache2/sites-available/pve.conf
/etc/apache2/ports.conf

Le fichier /etc/apache2/sites-enabled/pve.conf est une stricte copie de celui dans sites-available, donc la modification sera à répliquer dans les deux fichiers.

C’est la directive « Listen » d’Apache qui nous intéresse. On en trouve 2 dans ports.conf, et un dans pve.conf. Elle indique simplement sur quelle interface on désire faire écouter Apache. Si on précise seulement le port, on écoute sur toutes les interfaces. Mais il est heureusement possible de préciser l’adresse sur laquelle on souhaite écouter via « Listen <address>:<port> ». Concrètement, on va changer ces fichiers pour n’écouter que sur l’adresse locale (127.0.0.1).

Listen 127.0.0.1:80

N’oubliez pas de remplacer le port par celui de la ligne en question. Redémarrez le serveur (« service apache2 restart ») et constatez que l’interface web n’est plus accessible via son adresse publique.

SSH

Si Apache n’écoute plus que sur l’adresse localhost, il vous faut être sur cette machine pour accéder à l’interface. Mais je doute que lynx se comporte très bien avec une interface web pleine de JS, alors il nous faut une autre solution : le tunnel SSH, dont je parlais plus haut. Le principe est de se servir du serveur en tant que proxy encapsulé dans du SSH (ainsi, pas besoin d’ajouter un daemon sur la machine). La requête HTTP telle que vous la tapez sera donc réellement lancée depuis le point de sortie (le serveur). Et si vous faites pointer votre navigateur sur l’adresse 127.0.0.1, c’est bel et bien sur le serveur que vous vous retrouverez.

Mais pour que cette solution soit un tant soit peu efficace, il faut forcer l’utilisation de clés SSH pour se connecter au serveur, à cause de la problématique évoquée plus haut. Ma solution perso est de n’autoriser la connexion SSH qu’avec une clé, et uniquement sur un utilisateur n’ayant aucun droit, si ce n’est celui d’utiliser su pour passer en root. Il faudra donc posséder la bonne clé SSH (et sa passphrase) ainsi que le pass root pour pouvoir se connecter au serveur. Avant de modifier la configuration du serveur, il vaut mieux installer la clé SSH. Si vous n’en avez pas sur votre poste, créez-en une (à taper sur votre poste local) :

$ ssh-keygen

Je vous laisse le soin de choisir les options de votre clé. Par principe, utilisez le maximum de bits possibles selon l’algorithme choisi. Choisissez soigneusement votre passphrase, de sorte à la retenir sans négliger sa robustesse.

Il nous faut maintenant créer un utilisateur sur le serveur, sur lequel on se connectera :

# useradd -m poney # choisissez un nom pas trop évident
# passwd poney # choisissez un mot de passe temporaire

Nous spécifions un mot de passe pour cet utilisateur, car il faudra s’y connecter une première fois pour y stocker la clé. On supprimera le mot de passe tout à l’heure.

Ensuite, utilisez ssh-copy-id pour ajouter votre clé sur le serveur (toujours à taper sur votre poste local) :

$ ssh-copy-id poney@<adresse du serveur>

Si besoin, spécifiez via l’option -i le fichier de clé à envoyer au serveur. Le mot de passe du compte poney vous est demandé.

Ensuite, nous sommes prêts à modifier la configuration du serveur. Voici donc les lignes à modifier dans /etc/ssh/sshd_config :

Port 9347 # on modifie le port par défaut, pour éviter les scanners idiots, choisissez-en un inutilisé au hasard
PermitRootLogin no
PasswordAuthentication no # on ne permet pas la connexion par un mot de passe

Redémarrez le serveur via service ssh restart, ne vous déconnectez surtout pas, au cas où votre configuration ait rendu toute nouvelle connexion impossible, puis tentez de vous connecter depuis votre utilisateur fraîchement créé (par le biais de sa clé) :

$ ssh -p 9347 poney@<adresse du serveur>

Si tout se passe bien, ce ne sera plus le mot de passe du compte poney qui sera demandé, mais la passphrase de votre clé SSH (possiblement dans une fenêtre graphique, selon votre configuration). Si vous accédez au shell de poney après ça, c’est que tout est bon. Vous pouvez maintenant accéder au root en tapant « su - » (le tiret final permet de réinitialiser les variables d’environnement, pour éviter que d’éventuelles saloperies ayant infecté poney ne soient transmises à root), suivi de votre mot de passe root.

Respirez, on est presque à la fin.

Maintenant que l’on sait se connecter au serveur via un utilisateur tiers et via une clé SSH uniquement, on peut supprimer le mot de passe de cet utilisateur :

# passwd -d poney

La partie serveur est maintenant un peu plus sécurisée qu’avant. Il reste une dernière chose à faire pour pouvoir accéder de nouveau à l’interface web.

Poste client

Avant d’oublier, nous allons utiliser le fichier de configuration ~/.ssh/config sur notre poste client pour faciliter la syntaxe de la connexion :

Host mon-serveur # remplacez par le nom que vous souhaitez
HostName <ip du serveur>
User poney
Port 9347

Ceci vous permettra de vous connecter par cette simple commande :

$ ssh mon-serveur

Maintenant, nous allons enfin configurer le tunnel SSH. Rien de bien complexe, c’est intégré dans OpenSSH :

$ ssh -ND 1234 mon-serveur # utilisez un port de votre choix

La connexion SSH se fait, et un proxy SOCKS5 est mis en place sur le port 1234 de votre adresse locale. Celui-ci sort sur le serveur. Il suffit alors de configurer votre navigateur pour utiliser ce proxy. Sous Firefox, par exemple, la configuration se trouve dans Édition → Préférences → Avancé → Réseau → Connexion → Paramètres. Renseignez « 127.0.0.1 » comme hôte SOCKS, en mode SOCKS v5, avec le port choisi plus haut. Surtout, supprimez les exceptions dans le champ du dessous, car sinon vous ne pourrez pas accéder à l’interface Proxmox (étant donné qu’elle sera sur l’adresse 127.0.0.1).

Configuration du proxy
dans Firefox

Maintenant, faites pointer votre navigateur sur l’adresse 127.0.0.1 Si tout va bien, vous serez redirigé sur le port 8006 en SSL, car c’est la configuration par défaut de Proxmox, et vous pourrez accéder à l’interface comme avant.

Lorsque vous aurez fini ce que vous aviez à faire sur l’interface, reconfigurez votre navigateur pour utiliser votre ancienne configuration de proxy, déconnectez votre tunnel SSH (en tapant simplement « exit » ou en appuyant sur ctrl-D). Si vous voulez y accéder de nouveau :

  1. lancez le tunnel SSH (ssh -ND 1234 nom-serveur)
  2. configurez les paramètres de proxy de votre navigateur
  3. visitez http://127.0.0.1 dans votre navigateur

Listes en compréhension en Python

2014-03-05T00:00:00+01:00 - (source)

J’aime principalement deux choses dans le langage Python : la redoutable simplicité de sa syntaxe, et l’incroyable puissance des listes en compréhension, permettant d’effectuer des traitements en une seule ligne imbuvable. Oui, c’est parfaitement contraire au premier point. Je vais donc revenir sur ces listes en compréhensions.

De quoi parle-t-on ?

Les listes en compréhension sont une syntaxe présente dans le langage Python (entre autres) permettant de filtrer un itérable (comme une liste). En gros, cela permet l’écriture d’une boucle for dont la finalité est de créer une liste. Un exemple sera plus parlant.

resultat = []
for i in range(10):
    resultat.append(i*2)

Cette syntaxe classique utilise 3 lignes pour générer la simple liste [0,2,4,6,8,10,12,14,16,18,20]. Voyons maintenant comment écrire cela autrement :

resultat = [i*2 for i in range(10)]

Voila. Rien de plus. Nous arrivons au même résultat avec une écriture bien plus concise. Il est possible de compléter l’exemple précédent :

resultat = []
for i in range(10):
    if(i % 2 == 0):
        resultat.append(i)

On itère i de 0 à 9, et on insère i dans resultat si celui-ci est pair (c’est à dire si le résultat de sa division par 2 est nul).

Voyons maintenant la version en liste en compréhension :

resultat = [i for i in range(10) if i % 2 == 0]

On peut donc, grâce à la version verbeuse de l’expression, isoler les différentes parties :

La puissance des listes en compréhension est incroyable. Pensez que l’itérable source de votre liste en compréhension peut lui aussi être une liste en compréhension !

Expressions génératrices

Si vous ne connaissez pas les générateurs en Python, il s’agit de structures itérables dont la valeur est calculée au moment où on tente d’y accéder, et non pas à l’assignation. Ce qui permet d’itérer sur de très gros volumes de données, mais également d’itérer à l’infini sur une valeur.

>>> def sq(n):
...     print('sq(%d)' % d) # on affiche quelque chose à chaque exécution
...     return n**2
...
>>> l = [sq(i) for i in range(10)]
sq(0)
sq(1)
sq(2)
sq(3)
sq(4)
sq(5)
sq(6)
sq(7)
sq(8)
sq(9)

Comme on le constate, avec une simple liste en compréhension, la fonction sq() est appelée à l’assignation de la liste, car les valeurs sont calculées à ce moment. Ce n’est pas le cas des expressions génératrices.

>>> g = (sq(i) for i in range(10))

Rien n’est affiché. Notre fonction sq() n’est donc pas appelée. Elle le sera à chaque fois qu’on cherchera à accéder à un élément du générateur.

>>> for i in g:
...     print(i)
... 
sq(0)
0
sq(1)
1
sq(2)
4
sq(3)
9
sq(4)
16
sq(5)
25
sq(6)
36
sq(7)
49
sq(8)
64
sq(9)
81

Les lignes « sq(×) » sont le signe que notre fonction sq() est exécutée à ce moment. Et donc, en cas de données lourdes, on ne charge pas tout en mémoire instantanément.

La seule chose qui distingue une expression génératrice d’une liste en compréhension, syntaxiquement parlant, est simplement l’usage de parenthèses autour de l’expression au lieu de crochets.

Sets en compréhension

Enfin, et parce que je préfère évoquer toutes les possibilités de cette syntaxe, sachez qu’il est possible de générer un set (c’est à dire une liste dédoublonnée) à partir d’une liste en compréhension. Il suffit pour cela d’utiliser les accolades au lieu de crochets autour de l’expression.

>>> s = [n % 5 for n in range(10)] # liste en compréhension
>>> s
[0, 1, 2, 3, 4, 0, 1, 2, 3, 4]
>>> s = {n % 5 for n in range(10)} # set en compréhension, sans doublon
>>> s
{0, 1, 2, 3, 4}

Un exemple ?

La raison profonde pour laquelle j’ai voulu écrire cet article est le besoin récent que j’ai eu de convertir une chaîne binaire en texte, par conversion des octets en nombres décimaux, puis correspondance dans la table ascii. Malgré l’existence de nombreux convertisseurs en ligne (j’en ai moi-même écrit), je me suis dit qu’écrire un convertisseur en une ligne serait amusant, le tout sous les yeux d’une amie. Et donc, voici :

>>> s = '01010000011010010110111001101011011010010110010100100000010100000110100101100101001000000110100101110011001000000111010001101000011001010010000001100010011001010111001101110100'
>>> print(''.join([chr(int(b, 2)) for b in [s[i:i+8] for i in range(0, len(s), 8)]]))
Pinkie Pie is the best

Voilà.

Bon, ok, je vous fais la version longue et commentée :

s = '01010000011010010110111001101011011010010110010100100000010100000110100101100101001000000110100101110011001000000111010001101000011001010010000001100010011001010111001101110100'
conversion = [] # on stocke le résultat dans un tableau, qu’on convertira
                # ensuite en chaîne

# commençons par découper notre chaîne en octets (8 bits)
octets = []
# on doit itérer (taille de la chaîne / 8) arrondi au supérieur (au cas où)
for i in range( 0, len(s), 8 ):
    octets.append(s[i:i+8]) # vivent les slices d’itérable : on découpe
                            # à partir de i caractères jusqu’à 8 de
                            # plus au maximum
# on a maintenant nos octets séparés. Il ne reste plus qu’à les convertir en
# décimaux, puis récupérer la valeur de la table ascii correspondante
for octet in octets:
    octet_dec = int(octet, 2) # pour convertir à partir de la base 2
    conversion.append( chr( octet_dec ) )

print( ''.join( conversion ) ) # ENFIN !

Vous ne trouvez pas que la première version est plus, disons, succinte ?

[edit] Rogdham m’a suggéré une amélioration du convertisseur binaire


Exceptions en Python

2013-10-07T00:00:00+02:00 - (source)

Les exceptions sont un mécanisme de développement extrêmement pratique, mais pas forcément clair pour tout le monde. Suite à une petite discussion, voici leur fonctionnement en python3 :

Qu’est-ce qu’une exception ?

Il s’agit d’objets se comportant comme des erreurs de fonctionnement du programme. Toutes les erreurs, en python, sont des exceptions, et sont donc manipulables. Cela sert à pouvoir traiter soi-même les erreurs, au lieu de planter bêtement. Une structure existe pour effectuer ce traitement : son fonctionnement en langue française donnerait ceci :

Bon, effectue ces actions. Si jamais il y a une erreur à l’intérieur, traite-la comme ceci.

En python, ça se traduit par le bloc try … except :

try:
    do_some_actions()
except:
    print('Nous avons une erreur !')

Si le bloc try renvoie une exception (donc une erreur), le bloc except est exécuté. En l’occurrence, on affiche un message, puis on continue le programme. Avec une vraie exception, ça donnerait ça :

try:
    result = 10 / 0 # une division par zéro, c’est paaaaas bien
except:
    print('Nous avons une erreur !')

Cela reste néanmoins un fonctionnement très basique. On ne sait pas, à ce stade, quelle est l’erreur. L’exception étant un objet, pouvoir la manipuler est la base du traitement.

try:
    result = 10 / 0
except Exception, e:
    print('Nous avons une erreur : %s !' % e)

Voici la forme étendue du except : on lui spécifie un type d’exception à gérer, puis un nom de variable, qui contiendra notre exception. L’objet Exception définit la méthode spéciale __str__() appelée automatiquement lorsque l’on cherche à utiliser l’objet comme une chaîne de caractère (comme c’est le cas dans notre exemple), et renvoie l’attribut message. Voici donc le résultat des quelques lignes précédentes :

Nous avons une erreur : integer division or modulo by zero !

Pour savoir quel est le type de notre exception, consultez le nom de la classe : e.__class__.__name__. Ce qui nous permettrait d’effectuer un traitement comme ceci :

try:
    result = 10 / 0
except Exception, e:
    exception_type = e.__class__.__name__
    if exception_type == 'ZeroDivisionError':
        print('Erreur de division par zéro')
    else:
        print('Erreur %s : %s' % (exception_type, e))

Ainsi, vous traiterez différemment votre erreur selon son type. Mais la structure try … except permet de faciliter ça, en conditionnant la récupération des exceptions :

try:
    result = 10 / 0
except ZeroDivisionError, e:
    print('Erreur de division par zéro')
except Exception, e:
    print('Erreur %s : %s' % (e.__class__.__name__, e))

Ici, soit nous récupérons une erreur ZeroDivisionError, soit nous récupérons… n’importe quelle autre erreur, car la récupération conditionnelle traite également les sous-types. Toute exception héritant de Exception, notre except Exception, e saura récupérer n’importe quelle exception, et agit donc comme le default d’un switch. Dans l’exemple précédent, la ligne 4 s’exécutera seulement si on intercepte une exception ZeroDivisionError (ou un type héritant de ZeroDivisionError. Si on obtient n’importe quelle autre exception, on exécute la ligne 6. Si une exception d’un type non-traité est levée, python s’arrêtera avec un traceback et un message d’erreur (qui sera celui de l’exception)

Le bloc inclus dans le try est du code, il n’a rien de particulier. Il est donc évidemment possible d’imbriquer des blocs try … except. Comme ceci :

try:
    do_something()
    try:
        do_something_else()
    except Exception, e:
        raise UserWarning(e.message)

    try:
        do_all_the_things()
    except Exception, e:
        raise UserWarning(e.message)

except UserWarning, e:
    print('Le programme s’est arrêté avec le message suivant : %s' % e)

Si do_something_else() renvoie une exception, celle-ci est récupérée par le except ligne 5, qui renvoie elle-même une exception (avec le mot-clé raise), qui est alors récupérée par le try parent : on entre alors dans le dernier except.

Pour finir, voyons comment créer nos exceptions :

class MyException(Exception):
    pass

C’est aussi simple que ça : créez une classe vide qui hérite d’Exception, ou d’une autre exception (par exemple, pour hiérarchiser vos exceptions), vu que le nom de classe est ce qui permettra de récupérer conditionnellement vos exceptions.

try:
    raise MyException('Message')
except MyException, e:
    print(e)

Notre exception est levée, et est traitable comme une autre.

Une dernière chose : le mot-clé finally, qui est exécuté après une structure try … except, quel que soit le bloc traité, autrement dit peu importe qu’une exception ait été levée ou pas :

try:
    do_something()
except Exception, e:
    print(e)
finally:
    print('Nous avons exécuté le bloc.')

Un finally est utile notamment pour libérer des ressources. Par exemple, si un fichier est ouvert dans le try mais qu’une exception est levée avant sa fermeture, le finally peut le fermer dans tous les cas.


La nécessité des commentaires

2013-05-30T00:00:00+02:00 - (source)

Je suis tombé sur cet article, expliquant en quoi les générateurs de blogs statiques, comme Pelican (utilisé ici même) étaient un danger pour le logiciel libre. En gros, c’est centré sur l’absence de commentaires, hormis en passant par un service tiers intégré en javascript (comme disqus). Je ne suis absolument pas d’accord avec ses arguments, et, suite à un débat avec l’ami Taziden, je pense nécessaire d’expliquer mon point de vue, au sujet des commentaires et le reste.

Pour commencer, et par honnêté, je dois bien dire que le choix de me passer de commentaires a été en partie imposé par le choix technologique de Pelican. En partie seulement, parce que c’est moi qui ai fait ce choix, en connaissance de cause.

Revenons aux principes du blog

Aujourd’hui, tout internaute lisant des blogs est habitué à pouvoir laisser son avis sur un billet en bas de celui-ci. Cette habitude provient des moteurs de blog très utilisés, comme Wordpress, ayant intégré cette fonctionnalité. Mais ne cédons pas à la généralisation technologique. Je me souviens encore de l’époque où « un blog » signifiait un truc sur « .skyblog.com », la fameuse plateforme infestée de contenus d’une médiocrité infinie. Aujourd’hui, on pourrait remplacer Skyblog par Wordpress. On a indéniablement évolué, étant donné que Wordpress est un logiciel libre, et qu’une importante partie des blogs existants sous cette plateforme sont hébergés sur des serveurs persos. Mais dans l’idée, on tend quand même vers cette généralisation : un blog a nécessairement des commentaires, comme un moteur de recherche, des catégories, mots-clés, etc.

Mais pourquoi serait-ce le cas ? Un blog, ce n’est rien de plus qu’un journal personnel. Un site web rédactionnel, peu importe que le contenu soit de la veille technologique, des opinions politiques, ou des créations artistiques. En fait, c’est un espace d’expression pour l’auteur, qui en fait ce qu’il veut. Les moyens techniques pour arriver là sont : le protocole HTTP, et le langage HTML. Le web, en somme, ni plus ni moins. Un simple éditeur de texte, un coin sur un serveur web, et on peut écrire une page basique, et du contenu à l’intérieur. On a un blog, pas moins vrai que le wordpress du voisin ou celui-ci. Un blog n’a donc pas à avoir de fonctionnalités particulières.

La valeur des échanges

Évidemment, je ne cherche pas à nier l’importance des commentaires de blog, parfois bien plus intéressants que l’article lui-même, souvent enrichissants, pour l’auteur et les lecteurs, qui assistent alors à la continuité d’un éventuel débat lancé par le billet, ou des corrections, ajouts, etc…

Mais il s’agit de quelque chose qu’on ne remet pas en question. Les commentaires vont sous l’article, et (Wordpress y a beaucoup contribué) ils sont structurés de façon conventionnelle : un fil de messages chronologiquement ordonnés, avec parfois des avatars récupérés depuis un service tiers fermé (dont le traitement des données personnelles est douteux), parfois avec la possibilité de répondre à un commentaire particulier… Cette dernière fonctionnalité est intéressante, parce qu’elle permet à la discussion d’évoluer. D’un échange entre l’auteur et ses lecteurs, on peut obtenir un débat autonome, mais restant posté sur le blog.

L’avis de Taziden là-dessus est qu’il est indispensable pour un blog de proposer une fonctionnalité de commentaires. Pour moi, c’est plus une habitude tenace qu’un besoin. Pourquoi ne remettrait-on pas plutôt en question cette fonctionnalité, pour la faire évoluer ? Pourquoi, au lieu de commentaires, ne pas développer un système d’annotations collaboratives, fonctionnant telles des calques sur le billet, pour enrichir l’article directement, comme on le peut le faire avec Etherpad ? Il s’agit d’une simple idée d’évolution, et je regrette que si peu de projets aient le courage de remettre à plat les usages et habitudes pour proposer quelque chose de novateur (en cela, le projet Discourse m’intéresse énormément), plutôt que de se sentir obligés d’intégrer les fonctionnalités « habituelles ».

Malgré cela, un blog reste un espace personnel. C’est l’espace de l’auteur. Celui-ci ne souhaite pas nécessairement que ses articles soient commentés. Il arrive d’ailleurs parfois d’écrire pour soi-même, et pas pour être lu. Les commentaires sous un billet sont quelque chose que l’auteur peut proposer. C’est un service supplémentaire. Souvent très enrichissant, mais toujours optionnel. Il n’y a pas, je pense, lieu d’exiger de quelqu’un qu’il mette à disposition un tel service, sous prétexte qu’il blogue. Et c’est tout à fait normal, un blog n’est pas un service public ou quelque chose de démocratique.

INTERNEEEEET

J’ai une confidence à vous faire : mon blog permet les commentaires. D’ailleurs, il en a régulièrement. Le fait qu’ils ne soient pas nécessairement écrits sous chaque billet ne les rend pas inexistants ou invisibles.

Après la publication d’une grande partie de mes billets, il m’arrive de recevoir un mail (chiffré ♥) contenant des remarques sur celui-ci, et avec un patch attaché, contenant des corrections de forme. Je trouve ça génial, et ce n’est pas le genre de chose qui aurait été possible avec un Wordpress. Mais encore, lorsque je publie, je diffuse l’article sur plusieurs canaux, comme IRC ou Statusnet (qui est ensuite renvoyé sur Twitter, qui est fermé), ou encore des mailing-lists. Là, les retours se font, les discussions, débats ou trolls, selon le sujet, se lancent. Et je n’ai aucun contrôle dessus. C’est quelque chose de fondamental. Sur mon blog, je peux valider, dévalider, supprimer, bannir quelqu’un, parce que j’en ai le contrôle (après tout, c’est mon espace). Mais une des grandes forces d’Internet, c’est que la communication est possible, quel qu’en soit le moyen. Je suis fortement attaché à la liberté d’expression, vous le savez. Et je considère simplement qu’on est plus libre de discuter par le moyen ou protocole qu’on veut, plutôt que de façon centralisée sur un site.

Disqus ? CÉMAL !

À ce titre, la solution proposée, notamment par Pelican, pour permettre les commentaires, est généralement le service Disqus. Il s’agit d’une application chargée en javascript qui va permettre de commenter un article via un service externe. Ce service est totalement centralisé, fermé, et sous son propre contrôle. J’ignore si un blogueur a la possibilité de supprimer un commentaire posté sur disqus, mais je sais que la plateforme elle-même a ce droit. Et je ne sais pas qui est derrière. Qu’est-ce qui me garantit que Disqus ne considèrera pas unilatéralement que mon commentaire est un spam, ou contrevient à son éthique ?

L’auteur de l’article évoqué plus haut appelle au développement d’un équivalent libre à Disqus. À cela, je réponds « NOPE NOPE NOPE NOPE ». Un service tiers centralisé, qu’il soit libre ou non, c’est mal. Ne serait-ce que parce que, peu importe sa licence, je ne connais pas les personnes qui s’en occupent. Je n’ai aucune garantie sur le service : est-ce qu’il risque de tomber en panne et ainsi rendre inopérables les commentaires sur un grand nombre de blogs ? Est-ce que les données sont sécurisées ? Est-ce que le service ne risque pas de fermer du jour au lendemain ?

Ceci dit, je suis mauvaise langue. Rien n’empêche la création d’un service de commentaires décentralisé, que l’on pourrait installer sur son propre serveur pour servir ses propres commentaires. Ce serait une solution acceptable. Mais l’inclusion dans le blog se ferait en javascript, ce qui n’est pas très accessible. Je préfère me passer de javascript autant que possible, donc je n’utiliserais vraisemblablement pas une telle solution.

Au lieu de cela, je laisse les retours se faire naturellement. On est sur Internet, bon sang, les moyens de communication ne manquent pas. En réagissant par le biais de protocoles plus adaptés pour ça, on ne dépend plus du blog, de son auteur, d’un service tiers, et de tous les risques de censure en découlant.

Un autre avantage indirect est la possibilité de développer son avis par un autre billet de blog qui se présentera en réponse du premier. Cela favorise la discussion, qui est alors d’autant plus profonde qu’elle ne pourrait l’être en 140 caractères. Mon dernier billet est dans ce cas : je voulais répondre en commentaire à Numendil, mais il les avait désactivés, alors j’ai pris le temps de faire un billet complet.

On n’est pas dans une salle de conférence

On m’a rétorqué qu’il était important que les commentaires soient proposés par le blog lui-même, en faisant le parallèle avec une conférence, où le public est invités à la fin à poser des questions. Cela n’a, pour moi, pas plus de sens que la tristement célèbre comparaison entre Internet et une autoroute. Les contraintes physiques n’ont juste rien à voir. Une conférence se déroule dans un lieu clos et défini, à un moment défini, et les personnes, pour se faire entendre, parlent l’une après l’autre, et s’adressent au conférencier. Un blog n’a aucune de ces contraintes. Par ailleurs, à certaines conférences auxquelles j’ai pu participer (comme Passage En Seine), les retours se faisaient aussi sur IRC, donc leur décentralisation est déjà possibe IRL, donc je ne vois pas le problème de laisser les retours se faire ailleurs que sous l’article.

Bad-buzz friendly

Il n’y a pas que de gentils barbus (visuel non contractuel) qui s’expriment sur les Internets. Il y a aussi des politiques vaguement pourris, des boîtes qui cherchent à nous plumer sans trop que ça se voie, bref des gens qui ont un intérêt économique (entre autres) à ce qu’on ne leur crache pas trop bruyamment dessus. Et donc qui n’hésiteront pas à modérer sévèrement les moyens de communications qu’ils daignent mettre en place, pour éviter tout débordement ou diffusion d’une mauvaise image. Dans ce cas, l’utilisation de canaux tiers est parfaitement indispensable, parce que certaines choses méritent d’être sues. Si on se limite aux moyens autorisés, ce n’est plus du minitel 2.0 qu’on utilise, mais de la télé 2.0, où les diffuseurs sélectionnent ce qui a le droit d’être dit. C’est un modèle du passé, et il faut absolument éviter de restreindre la communication sur Internet à ces espaces de discussion qu’on veut bien nous laisser.

Donc non, un blog statique ne nuit pas au logiciel libre. À l’inverse, il encourage à l’utilisation épanouie d’Internet, c’est à dire à la décentralisation des échanges, à l’utilisation de protocoles prévus pour ça.


La préservation de l’anonymat

2013-04-29T00:00:00+02:00 - (source)

À ceux qui râlent

2013-04-22T00:00:00+02:00 - (source)

Je n’ai pas l’habitude d’écrire ce genre d’articles. Il s’agit d’une réponse à un billet de quelqu’un que je considère encore comme un ami de valeur, au sujet du féminisme, ou plus exactement des débats qu’il occasionne ces derniers temps. J’ai initialement voulu répondre directement en commentaire, mais parce qu’il avait fait le choix de les fermer, je me retrouve à écrire ici. J’en profite pour mettre à plat certaines de mes opinions à ce sujet, en espérant ne pas trop faire doublon avec mes articles précédents, notamment celui-ci.

Le féminisme extrémiste (celui avec des grandes dents)

Bon tout d’abord, je ne sais pas combien de fois il va falloir le rappeler : il n’y a pas « le » féminisme. Pour rappel, il s’agit de revendication de l’égalité des sexes et des genres. L’interprétation là-dessus est libre, ce qui donne un tas de militantismes différents. Et crois-moi Jérôme (prenez l’habitude, je m’adresse à une personne particulière dans ce billet), le féminisme extrémiste, ce n’est pas ce que tu vois. Le féministe extrémiste flirte sans gêne avec la misandrie, et ça se voit très vite. Et de tou·te·s les féministes que je connais, personne ne soutient ces idées. Si vraiment tu veux lire ce que je considère comme du féministe extrémiste (ce qui est revendication de l’auteure), je te conseille de lire ceci. Je préfère te prévenir, c’est vraiment violent. Ce que tu appelles « féminisme extrémiste », ce sont des gens très visibles sur twitter qui passent une grande partie de leur temps à se plaindre. Se plaindre de situations vécues, de réponses qu’on leur fait, d’articles lus, etc. Crois-moi, personne ne fait ça de gaieté de cœur. Seulement, quand on est sensibilisé au problème du sexisme, on a la fâcheuse tendance à le voir partout. Et tu sais particulièrement combien il est difficile de laisser couler des injustices qui sont sous notre nez.

La pilule rouge

Le fait est que ton article dénonce. Et qu’est-ce qu’il dénonce ? Le comportement de certain·e·s féministes. Ce qui est intéressant (et ce qui découle également de la discussion eue plus tôt avec deux autres hacktivistes), c’est justement le sens de cette dénonciation. Pierre, Élodie ou toi, vous êtes évidemment contre le sexisme. Dans les deux sens, je suis d’accord. Et quand vous écrivez sur le sujet, c’est immanquablement pour… taper sur des féministes. C’est révélateur, tu ne trouves pas ? N’aurait-il pas été plus utile, efficace ou que sais-je de dénoncer le sexisme concrètement ? Par exemple, dans ton billet, tu évoques des agressions que tu as subies, et que tu prends avec légèreté. Chacun réagit à sa façon à ce genre d’actes, et je n’ai rien à redire à la façon dont tu le prends. Contrairement à ce que l’on peut croire, je ne considère en aucun cas qu’être victime d’agression doit marquer à vie et nous forcer à vivre dans la honte et la culpabilité. Par contre, notre culture joue un rôle important là-dedans, dans le fait que pour toi ça n’ait pas été très grave. Car beaucoup de femmes prennent ce genre d’acte bien moins légèrement que toi. Peut-être que leur fréquence y est pour quelque chose ? Je soupçonne que oui, mais je n’en ai pas la preuve. Par contre, ce qui est avéré, c’est la culture du viol. Le fait que les jeunes filles, dès la puberté, apprennent à avoir peur, à redouter les agressions sexuelles. Agressions qui arrivent d’ailleurs en très grande majorité dans le cercle privé ou familial, mais c’est un autre sujet. Tu ne peux ignorer qu’on conseille aux femmes de ne pas sortir seules le soir, de faire attention à elles, etc. Face à ça, comprends-tu la gêne, la peur, quand un quelconque gentleman aborde une femme, qui n’a rien demandé, dans un espace qu’elle considère culturellement comme dangereux ? Sachant également que ce qu’on leur apprend à craindre est considéré comme banal, ne serait-ce qu’au moment de porter plainte si l’on en a le courage (difficile de porter plainte contre un membre de sa famille, surtout lorsque c’est sur nous que sera jetée la honte dans la majorité des cas…). Vois-tu où je veux en venir, et pourquoi ton ressenti face à des agressions (qui sont condamnables avec la même fermeté que si tu avais été une femme, fût-il utile de le préciser) est forcément différent de celui d’une femme ?

On me reproche, face à ça, de considérer les femmes comme des victimes en puissance. Ce n’est pas vrai. Les femmes sont ciblées par des actes sexistes à différentes échelles. Elles les subissent. Mais pourquoi une agression devrait-elle entraîner la honte ou peur de sa cible ? Encore une fois, toute cette discussion part de personnes qui ouvrent trop leur gueule. Qui n’ont justement pas du tout une posture de victime, mais au contraire, décident de ne plus se laisser faire, qu’il s’agisse de se défendre d’une agression physique ou de protester contre un geste commun de la vie quotidienne, mais qui participe à la société patriarcale. Car, quand on choisit la pilule rouge, qu’on accepte de remettre en cause notre culture, notre société, et, au fond, nous, on voit l’envers du décor, la matrice. On voit à quel point le sexisme est partout, et on perd facilement les pédales. C’est tout simplement ce qui arrive lorsque quelqu’un comme moi se met à réagir au quart de tour sur twitter. On pense constamment « Mais comment ne peuvent-ils pas voir ces injustices ? ». Je me trompe peut-être à ce sujet, mais je vous vois (vous, qui écrivez pour dénoncer l’attitude ou les propos de féministes énervé·e·s) comme des personnes, non pas ayant choisi la pilule bleue, mais surtout qui n’ont pas été confrontées à ce choix. Imaginez le film Matrix si, au lieu de latter des vilains en costard et lunettes noires, les héros avaient pour but d’expliquer à la population qu’ils sont dans la matrice… J’aime à penser que ça donnerait des situations similaires à ce que nous vivons. Ceci dit, dans mon analogie, le camp des héros est clairement défini, et il s’agit comme par hasard de celui que je m’attribue. J’aimerais beaucoup connaître votre avis là-dessus.

Quoi qu’il en soit, une fois la pilule rouge avalée, on commence à remettre en question notre environnement. Lors d’un débat, on s’aperçoit que la seule femme présente n’arrive pas à en placer une ou à se faire écouter. On remarque également les remarques, davantage tournées vers son apparence que vers ses idées. On tend un peu plus l’oreille quand on entend une femme se plaindre du comportement des gens dans la rue (et pas seulement des hommes). Parfois, et c’est plus difficile, on se prend soi-même à lâcher une parole qui autrefois n’aurait pas posé problème, mais qui aujourd’hui sonne faux, parce que vous remarquez qu’elle est discriminante et gratuite. Ça peut être quelque chose d’aussi insignifiant en apparence que le fait d’user de « Mademoiselle » pour s’adresser à une jeune femme, ou bien un glacial « t’as vu comment tu t’es habillée, aussi ? » à une femme qui raconte comment elle a failli être violée quelques instants plus tôt. Je comprends que cela provoque un agacement : on déterre toutes ces petites choses qui composent notre vie de tous les jours, toutes ces choses acquises, avec lesquelles on vit. Et remettre en question sa culture est une chose quand on a choisi d’ouvrir les yeux, mais se le prendre en pleine gueule alors qu’on ne s’en soucie pas peut effectivement mettre très mal à l’aise, et occasionner le l’agressivité en signe de défense.

C’est ce qui arrive lorsque que l’on dénonce le sexisme dans la société, mais c’est exactement pareil dans les communautés, comme les geeks. À la différence près que, quand on s’identifie comme geek, on s’attache à cette culture. Et inévitablement, on tombe dans le communautarisme. Que ce soit pour le milieu geek ou les autres. La réponse hautaine qu’on va tenir à un nouveau qui demande des conseils candides (et parfois un peu idiots) dans notre domaine d’expertise, c’est du communautarisme. On a notre communauté, et on veut naturellement s’assurer qu’on n’y entre pas n’importe comment, car c’est un espace familier. Quand on tape parfois brutalement sur Tris quand elle parle de sécu, quand bien même c’est argumenté, c’est du communautarisme, et je reconnais en faire moi-même. Voyez, je reconnais reproduire le problème, et j’en suis conscient. Et pourtant, je continue à lui dire ce que je pense, ce qui est rarement glorieux. Ridicule, vous ne trouvez pas ? D’ailleurs, je ne passe pas à côté du sexisme très installé dans certaines œuvres, comme la série Game of Thrones. Et pourtant, je l’aprécie. Parfois, le fait d’avoir cette analyse inconsciente des constructions genrées dans la fiction gâche le plaisir de la découverte. C’est comme ça, c’est ça le revers de la pilule rouge. On voit le monde encore plus sombre qu’avant. Mais on sait qu’on a alors les clés en main pour changer ça à notre échelle, en commençant par moins reproduire ces schémas. « Moins », et pas « plus », car la tâche est monumentale, et la seule façon de refuser du jour au lendemain toute forme de sexisme serait de couper tout lien avec la société, et de subir un lavage de cerveau. Ce qui ne serait pas très efficace. Alors on accepte de vivre dans ce monde sexiste, on continue d’aprécier des œuvres, même si elles se montrent sexistes. Il faut savoir faire des compromis, et connaître les limites de son militantisme.

Revenons au sujet

Dans ton article, Jérôme, tu persistes à expliquer en quoi tu réfutes le terme de féminisme pour parler d’égalité. Je suis égalitariste, tu le sais. Je ne souhaite pas inverser les privilèges, je souhaite que femmes et hommes soient au même niveau social, aient les même chances par défaut dans la vie.

Mais.

Être pour l’égalité sans aller plus loin, c’est une bien belle déclaration, qui n’a d’égale à son ardeur que son inutilité. C’est une déclaration de principe, rien de plus. Parce qu’il est impossible d’évoquer l’égalité hommes-femmes sans évoquer le patriarcat. Or, je te n’ai jamais vu ne serait-ce qu’employer ce mot. Sais-tu seulement ce qu’il signifie ? Il signifie que la société dans laquelle on vit a été pensée par et pour des hommes, et que les femmes y sont réduites à l’état de ressource. Évidemment, la société a évolué, et la place des femmes avec elle. Reste qu’historiquement, la domination était très claire : les hommes gouvernaient, et les femmes obéissaient. Aujourd’hui, est-ce différent ? « On n’est plus dans les années 50 », mais on n’est pas encore dans les années 10 000. Autrement dit, il y a beaucoup de chemin. Des exemples ? Le plafond de verre, qui symbolise l’extrême difficulté qu’ont les femmes à atteindre les postes à responsabilités. Ou l’inégalité salariale, ou encore la culture du viol, conçue pour culpabiliser les victimes et souvent d’excuser les auteurs. Ou encore la banalité que représente l’abordage d’une femme dans la rue, qui n’a rien demandé. Le patriarcat enseigne qu’on le peut, et qu’une femme doit se faire belle pour plaire aux hommes.

Le patriarcat, c’est une composante majeure de notre société. Il est normal que ça couine un peu quand on tape dessus. En tout cas, si tu souhaites lutter pour l’égalité, il y a une chose importante à faire. Et difficile. C’est le choix de la pilule, dont je parlais plus haut. Admettre l’existence du partiarcat, pour commencer (c’est pas si évident). Et surtout, admettre qu’en tant qu’homme, tu es naturellement privilégié. Ce n’est pas une honte, une critique ou quoi que ce soit. D’ailleurs tu n’y peux rien, tu n’as pas choisi ton genre. Mais c’est un fait, tu fais partie de la « caste » mise en avant par le patriarcat. Il y a fort à parier que l’immense majorité des gens qui liront ce billet sont privilégiés, d’une façon ou d’une autre. Toi, Pierre et moi, nous sommes des hommes blancs hétérosexuels cis (c’est à dire qui vivons notre genre assigné à la naissance). Cela fait 4 raisons d’être privilégiés. Nous sommes réellement quasiment au sommet de la pyramide sociale des discriminations. Ce sont les femmes qui sont victimes du patriarcat. Ce sont tous ceux dont la couleur de peau n’est pas celle des anciens conquérants européens qui sont victimes du racisme. Ce sont les homosexuel·le·s, bis, transgenres et queers qui sont victimes d’homophobie ou de transphobie. Pas nous. Je le répète, ce n’est pas un jugement, mais un constat. En admettant ses privilèges, on accepte de faire partie du problème. À ce moment, ça ne devient plus « des râleuses qui nous emmerdent avec leurs problèmes dont on n’a rien à faire ». On sait qu’on en est aussi, qu’on participe à ça. Après, on est libre de changer nos habitudes. D’accepter, non pas de renoncer à ces privilèges, mais de travailler à les partager. Ce n’est bien sûr pas un acte individuel, et je ne peux pas dire « tiens, prends ma place d’homme aux yeux de la société ». Pas par faute d’envie, mais parce que changer mon regard n’est qu’une pierre à l’édifice. Pour que ça ait un impact, il faut naturellement qu’une masse critique ait conscience de ce problème, et fasse un effort pour y remédier. Cette masse critique, c’est vous et moi, pas « les vilains misogynes ». Admettre ça, c’est lutter pour l’égalité des genres. C’est être féministe.

Et toi, Jérôme, qui es pour l’égalité, t’es-tu remis en question ? Ou te contentes-tu de taper sur celles et ceux qui s’efforcent de sensibiliser les autres ? Au contraire, ai-je envie de dire. Car quand tu dénonces le fait que le dossier de Mar_Lard pointe du doigt « les geeks », il est clair que tu te sens vexé, car on sous-entend que ta précieuse communauté pourrait avoir un problème, pire, que tu pourrais en faire partie. Non content d’être du communautarisme bien idiot, il s’agit d’un rejet pur et dur du problème. Tu préfères penser que le sexisme, bien sûr que ça existe, mais ce sont « les autres ». Pas de ça autour de toi, sinon tu l’aurais vu, n’est-ce pas ? Et si c’était le simple fait de ne pas le remarquer qui te mettait mal à l’aise ? Tu sais, depuis le (faible) temps que je milite pour l’égalité, j’ai fini par repérer les patterns courants de ceux qui tapent sur le féminisme, mais qui sont d’accord sur le principe, hein, mais… Mais faut pas crier aussi fort, mais faut pas généraliser sur les geeks, y’en a des biens… Et là, je ne te fais que les réponses construites. Parce que dans leur immense majorité, les réactions sont purement haineuses. As-tu jeté un œil sur les commentaires du moindre article rédigé par Mar_Lard ? Toi qui parles d’égalité, et de ses billets, n’as-tu pas trouvé pertinent, même si tu souhaitais critiquer intelligemment, de signaler cet état de fait ? D’ailleurs, qu’en penses-tu, toi qui, sans le vouloir, participes à la levée de boucliers qui suit inévitablement ce pavé dans la mare ?

Non pas que le dossier dont il est question ne soit pas critiquable. Au contraire. Il s’agit du travail personnel de quelqu’un de passionnée par le sujet, et dont ce n’est pas le métier. Ça se ressent, évidemment. Mais on va lui reprocher de ne pas parler de quelque chose, d’avoir un ton incorrect. Tout est prétexte à critiquer son texte. Ça a quelque chose de malsain, et je dois dire que je t’en veux de ne pas avoir vu plus loin que ça.

Finalement, tu as raison, tas de pixels. C’est bien une question de binaire. La pilule rouge ou bleue. La reconnaissance du problème ou son déni.


Bonjour Axolotl !

2013-04-07T00:00:00+02:00 - (source)

Il y a quelques semaines, on m’a parlé de la possibilité d’adopter des axolotls. Je connaissais vaguement ces bestioles, mais la proposition m’a donné envie d’en apprendre plus sur elles. Après lecture de la page Wikipedia, je suis tombé sous le charme de ces bestioles. Voyez par vous-mêmes : une bestiole qui sourit tout le temps, qui passe sa vie sous forme de larve, qui peut se regénérer jusqu’à des parties de son cerveau (Arme X ?), qui a des branchies rigolotes et poilues, et surtout, qui mange comme un idiot tout ce qui est plus petit que lui. Je trouve ça génial.

Un axolotl

J’ai rapidement eu l’idée de monter un petit site, dans la lignée des Bonjour le chat et autres variations plus portées sur les humain·e·s, c’est à dire un blog simpliste postant une photo chaque matin. La quasi-totalité de ces sites est hébergée et gérée par Tumblr, mais vous connaissez mon amour pour les plateformes centralisées et pseudo-gratuites. Je suis donc parti sur Pelican, comme pour mon blog, avec dans l’idée la possibilité d’automatiser la création de posts.

Le travail a donc consisté à :

Installation

Le bon sens et la conception de Pelican veulent que le contenu soit généré depuis le poste local, pour être envoyé vers le serveur sans qu’il ne soit nécessaire d’avoir un interpréteur Python sur celui-ci. Dans mon cas, souhaitant une génération automatique, il m’a semblé plus propice de tout déléguer au serveur. Ainsi, j’ai installé Pelican sur mon serveur en suivant le guide, et configuré le serveur web pour servir le dossier output.

Je me suis aperçu après coup qu’une fonctionnalité intéressante me manquait dans Pelican : la possibilité, pour chaque article, d’avoir le lien vers son prédécesseur et/ou successeur, pour obtenir une navigation simple. Ce fonctionnement peut être obtenu via un plugin : Neighbors. Celui-ci, une fois téléchargé, se place à la racine du projet Pelican, et on l’inclut via le fichier de configuration, que voici :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#!/usr/bin/env python
# -*- coding: utf-8 -*- #

AUTHOR = u'Axoloto'
SITENAME = u'Bonjour Axolotl'
SITESUBTITLE = u'Le site qui vous fera dire « Bonjour l’<a '\
              +u'href="https://fr.wikipedia.org/wiki/Axolotl">axolotl</a> ! »'\
              +u'<br /><br />Tous les matins, 10h, une nouvelle <strong>photo'\
              +u'</strong> d’<strong>axolotl</strong> !'
SITEURL = 'http://bonjouraxolotl.fr'

TIMEZONE = 'Europe/Paris'

DEFAULT_LANG = u'fr'

# Blogroll
LINKS =  (('Pelican', 'http://docs.notmyidea.org/alexis/pelican/'),
          ('Python.org', 'http://python.org'),
          ('Jinja2', 'http://jinja.pocoo.org'),
          ('You can modify those links in your config file', '#'),)

# Social widget
SOCIAL = (('You can add links in your config file', '#'),
                  ('Another social link', '#'),)

DEFAULT_PAGINATION = 1

THEME = 'template'
FEED_DOMAIN = 'http://bonjouraxolotl.fr'
FEED_RSS = '/feeds'
FEED_ATOM =  None

PLUGINS = ["neighbors"]

Dans les quelques trucs intéressants, notez que j’ai restreint les pages à 1 seul article, et que j’ai inclus le plugin à la dernière ligne. Le reste est très simple. J’ai aussi choisi d’utiliser du RSS au lieu d’Atom.

Création du template

Je me suis basé sur le markup généré par bonjourlechat.fr pour créer mon template. Il n’y a basiquement besoin que de 3 pages : la page d’accueil, la page d’un article, et la page d’archives. D’ailleurs, je ne me suis même pas préoccupé de cette dernière…

Pour commencer, j’ai copié le template notmyidea, situé dans $PELICAN_INSTALL_PATH/themes/notmyidea, dans la racine du projet, renommé en « template ». À l’intérieur de celui-ci, on trouve deux dossiers : static et templates. Le premier sera copié tel quel dans le dossier output, tandis que le second contient le code Jinja permettant de générer les pages. Celles qui nous intéressent sont base.html, index.html, article.html et article_infos.html. La différence entre les deux derniers est que article.html est la page complète d’un article tandis que article_infos* n’est que le bloc HTML d’un article seul. Je l’ai mis dans un fichier à part pour pouvoir l’inclure facilement depuis les différentes pages sans avoir besoin de le réécrire. voici le contenu des fichiers :

base.html

<!DOCTYPE html>
<html lang="en">
<head>
        <title>{% block title %}{{ SITENAME }}{%endblock%}</title>
        <meta charset="utf-8" />
        <link rel="stylesheet" href="{{ SITEURL }}/theme/css/{{ CSS_FILE }}" type="text/css" />
        {% if FEED_ALL_ATOM %}
        <link href="{{ FEED_DOMAIN }}/{{ FEED_ALL_ATOM }}" type="application/atom+xml" rel="alternate" title="{{ SITENAME }} Atom Feed" />
        {% endif %}
        {% if FEED_ALL_RSS %}
        <link href="{{ FEED_DOMAIN }}/{{ FEED_ALL_RSS }}" type="application/rss+xml" rel="alternate" title="{{ SITENAME }} RSS Feed" />
        {% endif %}
</head>

<body id="index" class="home">
    <div id="wrapper">
        <div id="topNav">
            <ul>
                <li><a href="/archives.html">Archives</a></li>
                <li><a href="{{ FEED_DOMAIN }}/{{ FEED_ADD_RSS }}">RSS</a></li>
            </ul>
        </div>
        <div id="contentHolder">
            <div id="mastHead">
                <h1><a href="{{ SITEURL }}/">{{ SITENAME }}</a></h1>
                <p>
                    <span style="font-family: Arial">{{ SITESUBTITLE }}</span>
                </p>
            </div>
            <div id="content">
                <div id="postHolder">
                    {% block content %}
                    {% endblock %}
                </div>
            </div>
        </div>
        <div id="footer">
            <p style="text-align:center">
                Fièrement propulsé par <a href="http://getpelican.com/">Pelican</a>,
                qui se repose sur <a href="http://python.org">Python</a>.
            </p>

            <p style="text-align: center">Thème par
            <a href="http://daelan.com/">Daelan</a>, merci !</p>
        </div><!-- /#contentinfo -->
    </div>
</body>
</html>

article.html

{% extends "base.html" %}
{% block title %}{{ article.title|striptags }}{{ SITENAME }}{%
    endblock %}
{% block content %}
    {% include 'article_infos.html' %}
{% endblock %}

article_infos.html

<div class="post">
    <div class="labels">
        <div class="date">
            <a href="{{ SITEURL }}/{{ article.url }}">
                <span class="month">{{ article.date.strftime('%d %m') }}</span> 
                <span class="year">{{ article.date.year }}</span>
            </a>
        </div>
        <div id="navigation" style="position: absolute; top:0; right: 0;">
            {% if article.prev_article %}
                <a href="{{ article.prev_article.url }}"><b>«</b></a>
            {% endif %}
            {% if article.next_article %}
                <a href="{{ article.next_article.url }}"><b>»</b></a>
            {% endif %}
        </div>
    </div>
    <div class="photo">
        <div class="permalink">
            <a href="{{ SITEURL }}/{{ article.url }}">+</a>
            {{ article.content }}

            <div class="caption"><p>{{ article.title }}</p></div>
        </div>
    </div>
</div>

Un dernier détail : le CSS. J’ai copié bêtement le CSS du site cible, que j’ai mis dans template/css/main.css :

/* -- reset this mutha!  -- */
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, font, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td {
    margin: 0;
    padding: 0;
    border: 0;
    outline: 0;
    font-size: 100%;
    vertical-align: baseline;
    background: transparent;
}

body {
    line-height: 1;
}

ol, ul {
    list-style: none;
}

blockquote, q {
    quotes: none;
}

blockquote:before, blockquote:after,
q:before, q:after {
    content: '';
    content: none;
}

/* remember to define focus styles! */
:focus {
    outline: 0;
}

/* remember to highlight inserts somehow! */
ins {
    text-decoration: none;
}

del {
    text-decoration: line-through;
}

/* -- end reset  -- */

body{
    margin:0;
    padding:6px;
    text-align:center;
    font-family: Helvetica, Arial, Verdana, Sans-Serif;
    background: #fff;
}

#wrapper{
    width:895px;
    height:auto;
    margin:0 auto;
    padding:0;
    text-align:left;
}

#topNav{
    position:relative;
    float:left;
    width:895px;
    height:90px;
    margin:0;
    padding:0;
}

#topNav ul{
    position:relative;
    float:right;
    margin:0 0 0 0;
}

#topNav ul li{
    position:relative;
    float:left;
    margin:0 0 0 20px;
}

#topNav ul li a:link,#topNav ul li a:visited{
    font-size:12px;
    color:#000000;
    text-decoration:none;
    background:#FFFFFF;
    border:0;
    padding:3px 6px 3px 6px;
    line-height:18px;
}

#topNav ul li a:hover{
    color:#FFFFFF;
    background:#0000FF;
}

#contentHolder{
    position:relative;
    float:left;
    width:895px;
    height:auto;
    min-height: 500px;
}

#mastHead{
    position:relative;
    float:left;
    width:395px;
    height:auto;
    margin:0 0 0 0;
    display:inline;
}

#mastHead h1{
    margin:9px 18px 18px 0;
}

#mastHead h1 a:link,#mastHead h1 a:visited{
    letter-spacing:-2px;
    font-size:36px;
    background:#0000FF;
    padding:18px;
    line-height:54px;
    border:0;
    text-decoration:none;
    color:#FFFFFF;
}

#mastHead h1 a:hover{
    background:#0000FF;
    color:#000000;
}

#mastHead p{
    font-family:Georgia, "Times New Roman", Serif;
    font-family:#000000;
    font-size:11px;
    padding:9px 36px 9px 18px;
    line-height:18px;
    margin:0;
}

#mastHead form{
    padding:9px 18px 9px 18px;
}

#mastHead input{
    padding:0;
    margin:0;
}

#mastHead a:link, #mastHead a:visited{
    text-decoration:none;
    color:#0000FF;
    border-bottom: 1px dotted #FFFFFF;
}

#mastHead a:hover{
    text-decoration:none;
    color:#0000FF;
    border-bottom: 1px dotted #FFFFFF;
}

div.photo img{
    border:0;
}

a.hyperLink:link,a.hyperLink:visited{
    color:#0000FF;
    text-decoration:none;
    font-size:18px;
    line-height:18px;
    font-weight:bold;
    border:0;
}

a.hyperLink:hover{
    color:#FFFFFF;
}

.description{
    position:relative;
    float:left;
    width:100%;
    height:auto;
    margin:18px 0 18px 0;
}

.post{
    position:relative;
    float:left;
    border-bottom:1px dotted #ccc;
    padding:0 0 18px 0;
    width:100%;
    height:auto;
    font-size:11px;
    line-height:18px;
    margin:0 0 18px 0;
}

.labels{
    margin:0;
    padding:0;
}

#content{
    position:relative;
    float:left;
    width:500px;
    height:auto;
    margin:0 0 0 0;
    display:inline;
}

#content p{
    font-size:11px;
    line-height:18px;
    margin:0 0 18px 0;
}

#content h2{
    position:relative;
    float:left;
    width:500px;
    padding:0 0 0 0;
    height:auto;
    line-height:18px;
    font-size:18px;
    margin:0 0 18px 0;
}

#content h2 a:link,#content h2 a:visited{
    color:#0000FF;
    text-decoration:none;
    border:0;
}

#content h2 a:hover{
    color:#FFFFFF;
}

.date{
    border-bottom:1px dotted #666;
    float:left;
    font-size:18px;
    font-weight:bold;
    letter-spacing:-1px;
    margin:0 0 18px 0;
    padding:0;
    position:relative;
    text-transform:uppercase;
    line-height:18px;
    width:500px;
}

.year{
    color:#0000ff;
    letter-spacing:0;
}

.month{
    color:#0000FF;
    letter-spacing:0;
}

#content a:link, #content a:visited{
    text-decoration:none;
    color:#0000FF;
    border-bottom: 1px dotted #FFFFFF;
}

#content a:hover{
    text-decoration:none;
    color:#0000FF;
    border-bottom: 1px dotted #FFFFFF;
}

#footer{
    position:relative;
    float:left;
    width:895px;
    height:auto;
    border-top:1px dotted #efefef;
    padding:18px 0 0 0;
    margin:18px 0 18px 0;
}

#footer p{
    font-size:11px;
    line-height:18px;
    margin:0 20px 0 20px;
}

#footer a:link, #footer a:visited{
    text-decoration:none;
    color:#000;
    border-bottom: 1px dotted #FFFFFF;
}

#footer a:hover{
    text-decoration:none;
    color:#0000FF;
    border-bottom: 1px dotted #FFFFFF;
}

.caption{
    position:relative;
    float:left;
    margin:18px 0 18px 0;
    width:100%;
    height:auto;
}

.regular, .quote, .video, .photo, .audio, .conversation, .link{
    position:relative;
    float:left;
    width:100%;
    height:auto;
}

big{
    font-size:24px;
    line-height:18px;
    padding:0 5px 0 0;
}

.source{
    color:#666;
}

.permalink{
    position:absolute;
    top:0;
    left:-18px;
    height:18px;
    margin:0;
    padding:0;
    line-height:18px;
}

#navigation {
    font-size: 22px;
    font-weight: bold;
}

#pages{
    font-weight:normal;
    color:#999;
    padding:0;
    font-size:11px;
    margin:10px 0 0 0;
}

Voilà tout pour la partie Pelican. Vous avez un beau site, vide mais beau. Maintenant, remplissons-le dynamiquement.

Le contenu

Les sites bonjour* ont généralement une légende sous chaque photo. Disons qu’on s’en fout. J’ai opté pour un fonctionnement minimaliste : on pose des images dans un dossier, et le script, appelé une fois par jour, prend la plus ancienne pour en faire un article puis la supprime.

# -*- coding: utf-8 -*-

import os
import locale
from datetime import date
import Image

locale.setlocale(locale.LC_TIME, 'fr_FR.utf-8')

content_dir = 'content'
content_image_dir = 'images'
source_dir = 'source'
max_size = (500, 500)

def create_article(filename):
    today = date.today()
    output_filename = '%s.jpg' % today.strftime('%Y-%m-%d')
    im = Image.open(os.path.join(source_dir, filename))
    im.thumbnail(max_size)
    im.save(os.path.join(content_dir, content_image_dir, output_filename))

    article_vars = {'today_str': date.today().strftime('%a %d %b %Y'),
                    'date': today.strftime('%Y-%m-%d'),
                    'output_filename': output_filename}

    article = """Title: %(today_str)s
Date: %(date)s

![%(today_str)s](./images/%(output_filename)s)""" % article_vars
    with open(os.path.join(content_dir, output_filename.replace('jpg', 'md')), 'w') as article_file:
        article_file.write(article)

file_list = os.listdir(source_dir)
oldest_file = None

for cur_file in file_list:
    cur_mtime = os.path.getmtime(os.path.join(source_dir, cur_file))
    if not oldest_file or cur_mtime < oldest_mtime:
        oldest_file = cur_file
        oldest_mtime = cur_mtime

create_article(oldest_file)
os.remove(os.path.join(source_dir, oldest_file))

Enregistrez ça dans la racine du site, sous le nom content_generator.py par exemple. C’est testé sous python 2.6 et 2.7. À chaque appel, il listera les fichiers dans source (vous devriez créer ce dossier), les classera par date de modification, puis dépilera la première pour en faire un article : il la redimensionnera aux dimensions voulues, puis écrira un article basique en Markdown, contenant seulement l’image et la date.

À ce niveau, le billet sera écrit mais pas encore publié. C’est un script bash qui s’en chargera, et qui sera appelé par cron. Enregistrez ce qui suit dans cron.sh à la racine du site :

1
2
3
4
5
6
#!/bin/bash

PATH="/usr/local/bin:/usr/bin:/bin"
cd /path/to/bonjouraxolotl
python content_generator.py
make html

Adaptez bien évidemment les chemins. Il ne reste plus qu’à éditer la crontab :

# crontab -l
0 10 * * * /path/to/bonjouraxolotl/cron.sh

Tous les matins à 10h, un nouvel axolotl !

EDIT : corrections et amélioration du script python par Rogdham, merci à lui.


[Traduction] Rajouter du napalm sur le feu

2013-03-01T00:00:00+01:00 - (source)

Ça fait longtemps que je n’avais pas traduit de billet d’Okhin. Celui-ci est particulièrement intéressant, et parle à nouveau de sexisme.

Si vous avez la flemme de tout lire, voici un résumé :

Allez vous faire voir, arrêtez d’être fainéant·e et lisez.

Rajouter du napalm sur le feu.

Mise au point

Je suis privilégié. Peu importe ce que je peux dire sur l’état de ce monde, il est clair que je suis né de son meilleur côté. Je peux m’exprimer sans risquer d’être frappé ou torturé. Je peux traverser la rue pour acheter à manger sans risquer de me faire abattre par un sniper. Je sais que je dors au chaud chaque nuit, et que je peux avoir trois repas (ou plus) par jour (tant que je n’oublie pas de les prendre).

Et je ne serai pas insulté, agressé, violé, considéré comme une minorité, je ne me sentirai pas en danger pour le simple fait de marcher dans la rue.

Tout ça parce que je suis un homme blanc. J’ai acquis des privilèges (que je n’ai pas demandés) par le simple fait d’être né. Et c’est naze. Je veux dire, le fait d’avoir des privilèges implique que j’aie du pouvoir sur d’autres personnes. Et c’est naze, parce que ça signifie que ces personnes (celles sur lesquelles j’ai du pouvoir) ne sont pas libres, ce qui entrave ma liberté (si les personnes autour de moi ne sont pas libres, je ne peux pas bénéficier de ma liberté).

Alors oui, être privilégié rend la vie plus facile, mais ça ne va pas. Je ne veux pas de ça. Et se débarasser de ça prendra beaucoup de temps, parce que la société dans laquelle je vis a besoin de changer beaucoup plus globalement. Et ça commence en sensibilisant sur la situation (et ensuite en la changeant en abandonnant ce pouvoir).

Faits et statistiques

S’il n’y avait aucune discrimination dans l’éducation, alors les compétences seraient également réparties dans la population, et donc vous devriez trouver des personnes compétentes n’importe où. Je veux dire, si 20% de la population avait la peau bleue, alors il devrait y avoir 20% de personnes à la peau bleue parmi les gens qui cuisinent. Ça vous semble correct ?

Donc, si notre système éducatif fonctionne bien, il devrait tendre à développer l’intérêt et la curiosité de façon égale. Alors, le fait que j’aie rencontré 5 femmes depuis que j’ai commencé mes études dans un domaine technologique (une dans une société, les 4 autres étaient des camarades de classe) est soit une erreur statistique, soit la preuve que le système ne fonctionne pas aussi bien. J’ai rencontré d’autres femmes dans les départements technologiques dans lesquels j’ai travaillé, mais elles occupaient principalement des postes créatifs (design, intégration…).

Il y a donc quelque chose de cassé là-dedans. J’ai un problème pour rester longtemps dans une société : depuis 13 ans que je travaille (oui, j’ai commencé tôt), hormis la société dans laquelle j’ai fait mon apprentissage, je n’ai jamais passé plus d’un an dans la même entreprise. Donc ça en fait 8. De différentes tailles, de différents horizons.

Et bien, je n’ai jamais rencontré une femme dans les départements IT de ces boîtes. Parfois, c’était moi le département IT, mais même dans les équipes de développement, je n’ai jamais rencontré une seule femme. Les seules femmes que j’ai rencontrées viennent de la scène hacker (et la plupart du temps, je ne l’apprenais qu’en les rencontrant dans le meatspace, mais c’est un autre sujet).

Donc, quand quelqu’un me dit, au sujet du sexisme, que « si ce n’est pas cassé, il ne faut pas essayer de le réparer » comme un argument pour ne pas penser à des politiques anti-harcèlement, je pense qu’ils ont tort. C’est un problème.

Un politicien sauvage apparaît !

L’autre jour (deux ou trois jours avant cette rédaction), @_LaMarquise a été agressée dans la rue par un type se masturbant en public, et elle en a parlé sur twitter. Un illuminé de service, @romain_pp, a trouvé pertinent de faire une plaisanterie à ce propos. Le fait est que cette personne est membre du Parti Pirate Français et Suisse, et si j’ai bien tout compris au fonctionnement de ce parti, n’importe qui peut parler en son nom. C’est même écrit dans le nom de son compte twitter, dans sa description, et même dans son image d’arrière-plan. Alors ouais, c’était la parole du Parti Pirate.

Les choses se sont envenimées sur Twitter, et la principale argumentation contre La Marquise était qu’elle n’était pas rationnelle. Je vais développer plus tard, mais basiquement, je tend à penser qu’on ne peut pas attendre de quelqu’un sous le choc d’être rationnel.

On lui a aussi dit qu’elle était agressive, qu’elle ne devrait pas rendre publiques ces choses personnelles, comme une agression (très bien, alors pourquoi est-ce que les gens tweetent à propos de leur vie privée ?), qu’elle ne devait pas déranger leur petite tranquilité.

Le Parti Pirate a écrit une lettre en réponse à La Marquise. Ils l’ont fait en privé (étant donné que je n’ai pas pu la trouver en ligne). Ce que je trouve étrange pour un parti qui défend la transparence à tous les niveaux de la société. Ceci dit, les systèmes informatisés sont sympas, parce qu’ils permettent de copier les choses à coût négligeable, et donc voici une copie de cette lettre (elle a été fournie par Marquise, et je n’ai aucune raison de douter de sa parole là-dessus). En substance, ils disent regretter ce que l’un de leurs membres a dit, et également le « buzz » qui s’en est suivi. Ils n’ont pas saisi l’opportunité d’opter pour une position plus active, pas plus qu’ils n’ont blâmé le membre.

Basiquement, la lettre est une tentative d’effort pour calmer le jeu sans prendre réellement position pour ou contre le sexisme. S’ils sont contre le sexisme, ils devraient, au moins, renvoyer Romain, sinon ils n’auraient pas besoin d’écrire à ce sujet. Cette lettre prouve que ce qui est important pour eux est d’éteindre l’incendie médiatique plutôt que défendre une position.

Ce qui est honteux est également qu’ils sont généralement les premiers à blâmer ce genre de comportement dans les autres partis. Il y a aussi un problème concernant la liberté d’expression, mais j’y reviendrai.

Au sujet de la violence

Vivre dans la peur d’être agressé·e ou violé·e n’aide pas à garder la tête froide. Comme je l’ai dit (et d’autres l’ont dit aussi), garder la tête froide est un privilège des gens qui ont du pouvoir, ne l’oubliez pas. L’insurrection, et la nécessité du changement, mènent à la violence, c’est inévitable. Ce billet l’explique assez bien, et cette citation est intéressante :

La soumission des opprimés est liée à l’ordre établi. C’est le fait de déranger cet ordre en brisant ses chaînes, et en s’en prenant aux maîtres qui est perçu comme un scandale. Dans le langage des maîtres, qui devient le langage courant, la violence ne vient pas de ceux qui commettent cette violence, mais des vilains qui osent se rebeller.

--

Igor Reitzman

Quand quelqu’un vous crie dessus à propos de quelque chose, vous devriez l’écouter, parce que cette chose est importante pour la personne (si ça ne l’est pas pour vous). Vous n’imagineriez pas les Révolutionnaires Français demander gentiment à Louis XVI s’il accepterait de bien vouloir céder le pouvoir. Un tas de personnes ne voudrait pas abandonner le pouvoir, et il faut parfois les forcer à le faire.

Ça m’a pris un moment pour comprendre ça, parce que ce n’est jamais agréable quand des gens vous hurlent dessus. C’est irritant, et on a tendance à répondre à l’agressivité par l’agressivité. Je ne suis pas sûr d’être totalement d’accord avec ça, mais j’essaie de comprendre les raisons pour lesquelles les gens crient (et j’essaie aussi de ne pas répondre trop rapidement, parce qu’en général ça n’aide pas la situation, quelle qu’elle soit).

Alors oui, des féministes utilisent la violence, qu’elle soit physique ou morale. Et si ça vous dérange, ça veut dire que ça marche. Vous devriez poser des questions et tenter de comprendre pourquoi elles sont en colère, au lieu de leur dire de se calmer.

Au sujet de la liberté d’expression

Par ailleurs, je suis contre la censure. Ça veut dire que je condamne le fait de brider la parole. Je veux que les nazis puissent exprimer leurs idées, car c’est comme cela que vous pourrez trouver leurs idées dangereuses. Et je veux que les misogynes puissent le faire aussi, parce que c’est comme ça que vous les reconnaîtrez. Et c’est également le seul moyen d’échanger avec eux autour du problème.

Mais la liberté d’expression va dans les deux sens. Ce n’est pas parce que quelqu’un est autorisé à dire quelque chose qu’il n’a pas le droit d’être contredit, décrié, puni ou autre. Vous avez le droit de faire des blagues sexistes. Et j’ai le droit de vous dire qu’elles ne sont pas drôles. J’ai même le droit de le dire à tout le monde. Si vous ne le voulez pas, et que vous préférez pouvoir dire ce que vous voulez sans conséquences, alors vous défendez la censure.

Alors oui, ça me dérange, ce qui est arrivé à BSides (voici le point de vue de Violet , et celui de AdaInitiative. Pour résumer, une conférence sur le sexe et les drogues, qui avait été annoncée, a été supprimée de l’agenda à cause de la peur d’une chasse aux sorcières par l’équipe du BSides (que ce soit à l’initiative d’AdaInitiative ou non n’est pas très clair à mes yeux) au prétexte qu’il pourrait y avoir des victimes de viol qui auraient pu être stressées (il semble que c’est comme ça que les troubles de stress post-traumatiques) si elles avaient assisté à la conférence, et que parler de la façon dont les drogues fonctionnent, en particulier le GHB, dans une conférence nommée « Sexe et drogues : vulnérabilités connues et méthodes d’exploitation » serait une incitation au viol.

L’argument est que, dans les conférences de hack, les gens font des sujets nommés « Vulnérabilités connues et méthodes d’exploitation » pour encourager l’exploitation de ces vulnérabilités. Et bien c’est une idée fausse. La plupart de ces conférences se préoccupent plus d’expliquer comment se protéger contre ces vulnérabilités plutôt que comment les exploiter.

En général, ces vulnérabilités sont patchées au cours de la conférence, ou du moins, les gens qui gèrent le logiciel en question travaillent dessus (s’ils prennent leur boulot au sérieux, je veux dire). Bien sûr, certains les utilisent pour leur propre profit, mais il s’agit d’une minorité.

Et en réalité, les gens qui utilisent ces vulnérabilités pour leur propre profit n’ont pas intérêt à ce que celles-ci soient connues. Les rendre publiques est de la prévention et de l’éducation, ce n’est pas fait pour mettre en danger les gens. C’est comme ça que la prévention fonctionne.

Maintenant, faut-il faire de la prévention dans la communauté technophile ? Bien sûr qu’il le faut. Il y a tout un historique d’agressions sexuelles et de viols dans ce genre de conférences. Si vous refusez d’en parler, vous ne pouvez éduquer les gens et vous ne les changerez pas. AdaInitiative disent qu’ils organisent leur propre camp pour en discuter. Mais c’est comme faire une campagne de prévention des drogues dans un camp de scouts : vous n’aiderez pas des drogués à gérer leur addiction.

Alors oui, il nous faut éduquer nos camarades hackers, particulièrement lors d’occasions où il y a beaucoup d’alcool, de drogues et de manque de sommeil, parce que tout cela change votre perception des choses. Alors en parler est une nécessité. Et, si la discussion devient offensante, alors les gens doivent le dire et le condamner, mais vous ne pouvez pas savoir comment ça se passera avant que ça n’arrive.

Il y a toujours ce problème avec les victimes de viols et les TSPT. Je peux comprendre que des gens ayant subi une agression et/ou un viol n’aient pas envie d’être exposés à une conférence qui en parle (hé, c’est à eux de gérer leur souffrance comme ils l’entendent). Et il semble qu’il y ait une coutume autour des « trigger warnings », que je ne comprends pas tout à fait (ça semble fonctionner un peu comme les labels « PEGI » pour les jeux vidéos).

Fin

Hmm, je pense avoir loupé plusieurs points. Ou je peux me planter sur d’autres. Si vous le pensez, faites-le moi savoir. Vous pouvez me trouver assez facilement.


Powered by VroumVroumBlog 0.1.32 - RSS Feed
Download config articles