Tired of having a shitty integrated webcam ?

Use your GoPro Hero 8 as video4linux video source!

Basically, we will use ffmpeg as rtmp server and feed video4linux with the video stream.

I’m using Debian GNU/Linux for the demonstration

Install

sudo apt install ffmpeg v4l2loopback-dkms
sudo modprobe v4l2loopback exclusive_caps=1

Note: exclusive_caps=1 is required for many applications to see the v4l device as a source as soon as an output is present. (by default v4l2loopback obviously exposes source and output capabilities)

Run the rtmp server

ffmpeg -f flv -listen 1 -i rtmp://0.0.0.0:1935/app/live -fflags nobuffer -f:v mpegts -probesize 8192 -map 0:v -pix_fmt yuv420p -r 20 -f v4l2 /dev/video2

Note: on my machine, v4l2loopback was using /dev/video2, it may be different on your machine.

Pour faire des cadeaux, parfois il est intéressant de faire un petit bricolage électronique afin d’ajouter quelque rafinements à des créations simples.

Ma dernière réalisation rapide, j’ai ajouté la partie “flash” d’un radar, fabriqué par une amie.

Introduction

J’ai utilisé exclusivement des pièces qui trainait dans mon atelier électronique.

J’ai utilisé un sonar ultrason MaxSonar EZ0, piloté numériquement.

Le capteur ultrason envoie un nombre de pics proportionnel à la distance de l’objet en face.

Il suffit alors d’ajuster les valeurs et d’ajouter un latch simple avec un reset timer. L’objectif du reset timer est d’éviter de flasher à tout bout de champ lorsque qu’un reste en face du radar.

Pour le flash, c’est rudimentaire, j’ai utilisé un mosfet N-Channel, avec une résistance pull-down sur la gate.

L’arduino envoie un signal sur la gate lorsque le latch est déclenché. Une led 1W connectée en 12V directement est alors très brièvement allumée.

Code

const int pwPin = 7;
const int led = 11;

void setup()

{
  Serial.begin(115200);
  pinMode(pwPin, INPUT);
  pinMode(led, OUTPUT);
}


void loop() {
  long pulses;
  static int latch = 0;
  static long nextlatch = 0;
  pulses = pulseIn(pwPin, HIGH);
  long now = millis();
  if (pulses < 3500 && pulses > 300) {
    if (nextlatch < now) {
      latch = 1;
    }
    nextlatch = now + (10*1000);
  } else {
    latch = 0;
  }
  if (latch == 1) {
    analogWrite(led,250); delay(100);
    analogWrite(led,0); delay(50);
    analogWrite(led,250); delay(50);
    analogWrite(led,0);
    latch = 0;
  }
  delay(500);
}

Suite de l’article sur LVM thin provisioning, avec un script de sauvegarde simple.

Le script que je vais présenter ne fait que réaliser un snapshot puis le chiffre avec OpenSSL et enfin le transmet sur un serveur distant.

Le script bash utilise les cgroups linux afin de limiter la bande passante sur les disques et perturber le moins possible la production.

Il y a aussi une limitation du nombre de parts CPU maximum à utiliser si le CPU est utilisé par d’autre processus.

Les valeurs sont a ajuster en fonction du serveur de destination.

#!/bin/bash

################################################################################
# Simple Encrypted LVM(-thin) Backup
# Author: Laurent Coustet (c) 2011-2016
# http://ed.zehome.com/
################################################################################

export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

# CGROUP Bandwidth limits
CGROUP_ROOT="/sys/fs/cgroup"
CGROUP_NAME="backup"
# cpu.shares 1024 = 100% (of all cpus in group)
CGROUP_CPUSHARES="512" # 50% of all cpus maximum (only matters if machine is very busy)
# blkio.throttle.read_bps_device
# 100MiB/s on /dev/sda and /dev/sdb (minor:major bandwidth) (SSD and iSCSI)
CGROUP_BLKIO_THROTTLE_READ_BPS="8:0 $((100*1024*1024)),8:16 $((100*1024*1024))"

# You should modify this
DEST="/backup"
VG=rootvg
LV="lvol1 lvol2"
BACKUP_ONLY_AT=""
SSH_SERVER="myremote@server.com"
AES_PASSPHRASE="pleasechangemeoryourdumb"
OPENSSL_COMMAND="openssl enc -e -salt -aes-256-ctr -pass env:AES_PASSPHRASE"
LOG=/var/log/backup.log
declare -g CLEANUP_LV=""

umask 0077

cleanup()
{
  [ ! -z "$CLEANUP_LV" ] && lvremove -f "$CLEANUP_LV"
}

cgroup_init()
{
    [ -z "${CGROUP_NAME}" ] && return
    CGROUP_BLK_PATH="$CGROUP_ROOT/blkio/$CGROUP_NAME"
    CGROUP_CPU_PATH="$CGROUP_ROOT/cpu/$CGROUP_NAME"
    # We assume /cgroup is already mounter obviously ;)
    # Create cgroup if not already created
    [ -d "$CGROUP_BLK_PATH" ] || mkdir "$CGROUP_BLK_PATH"
    [ -d "$CGROUP_CPU_PATH" ] || mkdir "$CGROUP_CPU_PATH"

    [ ! -f "${CGROUP_BLK_PATH}/tasks" ] && (echo "No cgroup blkio support."; return 1)
    [ ! -f "${CGROUP_CPU_PATH}/tasks" ] && (echo "No cgroup cpu support."; return 1)

    # Setup limits
    if [ ! -z "$CGROUP_CPU_SHARES" ]; then
        echo $CGROUP_CPU_SHARES > ${CGROUP_CPU_PATH}/cpu.shares
    fi
    if [ ! -z "$CGROUP_BLKIO_THROTTLE_READ_BPS" ]; then
        OIFS="$IFS"; IFS=","; for i in $CGROUP_BLKIO_THROTTLE_READ_BPS; do
            echo $i > ${CGROUP_BLKIO_PATH}/blkio.throttle.read_bps_device
        done; IFS=$OIFS
    fi

    # Move us in the cgroup!
    echo $$ > $CGROUP_CPU_PATH/tasks
    echo $$ > $CGROUP_BLK_PATH/tasks
    return 0
}

lvm_backup()
{
    errors=0
    for volume in ${LV}; do
        if check_only_at ${volume}; then
            output="${HOSTNAME}_${volume}.lzop.aes256ctr"
            # LVM thin snapshot
            lvcreate -kn --name "backup_${volume}" -s "/dev/${VG}/${volume}"
            CLEANUP_LV="/dev/${VG}/backup_${volume}"
            pv -fper -i 1 "/dev/${VG}/backup_${volume}" | lzop -1 -c | AES_PASSPHRASE=$AES_PASSPHRASE $OPENSSL_COMMAND | ssh ${SSH_SERVER} "dd of=zehome/$output bs=1M"
            lvremove -f "/dev/${VG}/backup_${volume}"
            CLEANUP_LV=""
            if [ "$?" != "0" ]; then
                echo failed backup ${volume}: $?
                errors=$(($errors + 1))
                continue
            fi
        fi
    done
    return $errors
}

check_only_at()
{
    lv_to_dump="$1"
    day_now=$(date +%a)
    for tmp in ${BACKUP_ONLY_AT}; do
        lv=$(echo ${tmp} | cut -f1 -d':')
        day=$(echo ${tmp} | cut -f2 -d':')
        if [ "$lv" = "$lv_to_dump" ]; then
            if [ "$day" = "${day_now}" ]; then
                return 0
            else
                return 1
            fi
        fi
    done
    return 0
}

[ -x $(which lzop) ] || (echo please install lzop; exit 1)

echo "Starting backup $(date -R)" >&2
echo "Setting up CGROUP limits..." >&2
cgroup_init
(
    trap cleanup EXIT
    echo "Starting backup $(date -R)"
    echo "Launch LVM backup..."
    lvm_backup
    echo "Done backup $(date -R)"
) | tee -a $LOG

exit 0

Depuis de nombreuses années, j’héberge un certain nombre de machines virtuelles en utilisant LVM2 comme principal outil de gestion des volumes de stockage.

Le but de cet article est de présenter une façon de réaliser des sauvegardes de ces volumes de manière atomique et performante.

Pour les impatients

# Sous Debian GNU/Linux
apt install lvm2
# création du physicalvolume
pvcreate /dev/sdxx
# volume group lvm2 classique
vgcreate rootvg /dev/sdxx
# création du pool
lvcreate --size 1G --thin rootvg/thinpool
# création d'un volume "thin provisioning"
lvcreate --name tvol1 -T rootvg/thinpool -V 256M
# Pour créer un snapshot, il n'est pas nécessaire de spécifier la taille,
# c'est la taille globale du pool qui sera utilisée
lvcreate -s --name tvolsnap1 rootvg/thinpol

Thin Provisioning

LVM2, et DM (Device Mapper) sont liés dans le noyau linux. Depuis quelque années, il est possible de créer des volumes logiques avec LVM en utilisant la technologie “thin provisioning”.

Non seulement thin-provisioning permet de réaliser une allocation encore plus flexible que des volumes logique classique, mais a aussi des avantages sur les performances lors de la rélaisation de snapshots.

Pour plus d’information, je recommande la documentation de RedHat qui est bien réalisée à ce sujet. RedHat thin-provisioning

Une histoire de snapshot

Sous LVM, lorsque l’on créée un snapshot d’un volume logique, un nouveau volume logique est ajouté, lié à sa source.

Lorsqu’une écriture interviens, sur le snapshot lui même, ou sur la source (le plus probable), LVM doit copier les données à un autre endroit, afin de conserver les données inchangées par rapport au moment ou le snapshot à été lancé.

Avec des volumes logique classique, LVM nécessite de réaliser plusieurs opérations lors d’une écriture:

  1. Lecture du bloc de donnée concernée par l’écriture
  2. Copie du bloc “dans” le snapshot
  3. Copie/Écriture des métadonnées dans le snapshot
  4. Écriture sur le volume source
  5. Écriture des métadonnées sur le volume source

                   LVOL1            LVOL1 (SNAP)
    
    +------+         +------+   read     +------+
    |      |   write |     +------------->      |
    | WOW +----------->HELL |   write    | HELL |
    |      |         |      |            |      |
    +------+         +------+            +------+
                 |      |            |      |
                 | IAM  +------------+      |
                 |      |            |      |
                 +------+            +------+
                 |      |            |      |
                 |  ON  +------------+      |
                 |      |            |      |
                 +------+            +------+
                 |      |            |      |
                 | DISK +------------+      |
                 |      |            |      |
                 +------+            +------+
                 | meta | write      | meta | write
                 +------+            +------+
    

Voici un article expliquant plus en détails l’amplification d’écriture avec LVM.

A noter que toute ces opérations sont cumulées au fur et à mesure que l’on ajoute des snapshot.

Avec thin-provisioning, les choses sont différentes. Les données ne sont pas stockées dans un volume, puis copiées dans un snapshot.

Un pool de donnée est créée, et tous les volumes (y compris la source) ont une table qui associe le disque vers ce pool de donnée.

Schéma simplifié:

TVOL1 (SNAP)         TPOOL               TLVOL 1

 +------+           +------+            +------+
 |      |           |      |            |      |
 |    +-------------> WOW <---------------+    |
 |      |           |      |            |      |
 +------+           +------+            +------+
 |      |           |      |            |      |
 |    +-------------> IAM <---------------+    |
 |      |           |      |            |      |
 +------+           +------+            +------+
 |      |           |      |            |      |
 |    +------------->  ON <--  remove +--+     |
 |      |           |      |          | |      |
 +------+           +------+          | +------+
 |      |           |      |          | |      |
 |    +-------------> DISK <-------------+     |
 |      |           |      |          | |      |
 +------+           +------+          | +------+
                    |      |          |
                    | NOT <-----------+
                    |      |
                    +------+

Ainsi, un volume “thin provisioning” ne comporte pas réllement de donnée, il contiens une table de pointeur vers le pool.

Cette methodologie de stockage ne provoque pas d’étape supperflue lorsque l’on utilise des snapshot.

Par exemple, lorsque l’on écrie des données alors qu’un volume dispose d’un snapshot:

  1. Allocation et écriture d’un block dans le pool
  2. Association du block dans les métadonnées du volume

Il n’y a pas d’amplification d’écriture ou de lecture avec l’utilisation des snapshot LVM thin-provisioning.

Dans la prochaine partie, on verra comment réaliser des sauvegardes des volumes logiques sans (trop) perturber une machine en production.

Après une dixaine de jours, je me suis rendu compte qu’un petit malin a laissé le contact branché sur mon kart.

Le résultat, la batterie acide plomb YUASA 12V 6 cellules rend moins de 1 volt à vide.

Cet article vous indiquera comment faire pour recharger une batterie de ce type qui à été complètement vidée.

Batterie YUASA YT7B

Il faut savoir qu’une batterie acide plomb déteste être déchargée complètement, et une trop grande période déchargée conduira à la destruction de la batterie.

Afin de respecter au mieux la chimie de la batterie, il est important de lire le datasheet correspondant.

La batterie en question fait 6 cellules et 6.8Ah (Ampères heures) de capacité.

Le manuel nous indique que la meilleure façon de recharger une telle batterie est de présenter une tension et un courant limité afin de pouvoir réaliser une charge en trois phases.

La première phase consiste a présenter une tension précise de 2.35V par cellule (la tension peut varier selon la température et les carractéristiques de charge que l’on souhaite) avec un courant limité a environs 10% de la capacité en ampères.

Dans mon cas, 2.35 * 6 = 14.10V et 700mA.

En utilisant une alimentation programmable de laboratoire, il suffit de régler la tension et la limite de courant sur les valeurs ci-dessus.

On laisse faire l’alimentation qui va dans un premier temps charger à courant constant. La tension va progressivement chutter, puis, progressivement remonter jusqu’a 14.1V.

A 14.1V, on entre dans la seconde phase de charge, le courant va progressivement diminuer.

Après stabilisation, on abaisse a nouveau la tension a 12V et on laisse quelque heures supplémentaires. (Cela aide la chimie interne de la batterie a se stabiliser).

La batterie est de nouveau prête à l’usage.

Following my article on upgrading to OpenBSD 5.9 using ansible, here is the playbook to upgrade to OpenBSD 6.0.

Take note this playbook requires ansible 2.0+.

Note that this playbook will automatically reboot your server twice.

https://gist.github.com/zehome/1570a5a748e2633ed3a76522c942cf69

---
- hosts: all
  gather_facts: yes
  vars:
    mirror: http://ftp.fr.openbsd.org/pub/OpenBSD
    release: 6.0
    arch: amd64
    files:
      - SHA256
      - SHA256.sig
      - bsd.rd
      - bsd.mp
      - bsd
      - man60.tgz
      - base60.tgz
      - comp60.tgz
      - game60.tgz
  tasks:
    - name: installboot on sd0
      command: installboot -v sd0

    - name: Ensure /usr/rel exists
      file: dest=/usr/rel state=directory

    - name: Clean /usr/rel
      shell: rm /usr/rel/*
      ignore_errors: true

    - name: Download packages
      command: ftp -o /usr/rel/{{item}} {{mirror}}/{{release}}/{{arch}}/{{item}}
      args:
        creates: /usr/rel/{{item}}
      with_items: '{{files}}'

    - name: Check SHA256
      raw: cd /usr/rel && sha256 -C *[!.sig]

    - name: Check with signify
      raw: cd /usr/rel && signify -C -p /etc/signify/openbsd-60-base.pub -x *[!SHA256]

    - shell: cp /sbin/reboot /sbin/oreboot && cp /usr/rel/bsd /bsd.sp && cp /usr/rel/bsd.mp /bsd && cp /usr/rel/bsd.rd /bsd.rd

    - name: Install packages and reboot
      shell: cd /usr/rel && for _f in [!b]*60.tgz base60.tgz; do tar -C / -xzphf "$_f" || break; done && /sbin/oreboot
      args:
        executable: /bin/sh
      async: 0
      poll: 0
      ignore_errors: true

    - name: waiting for server to come back
      local_action: wait_for host={{ inventory_hostname }} state=started delay=30 timeout=300

    - name: MAKEDEV
      command: chdir=/dev ./MAKEDEV all

    - name: upgrade bootloader
      command: installboot -v sd0

    - name: sysmerge non interractive
      command: sysmerge -b
      ignore_errors: true

    - name: firmware update
      command: fw_update -v

    - name: update pkg.conf
      lineinfile:
        regexp="^installpath ="
        line="installpath = {{mirror}}/%c/packages/%a"
        dest=/etc/pkg.conf

    - name: upgrade packages
      command: pkg_add -u

    - name: reboot again
      command: /sbin/reboot
      async: 1
      poll: 0
      ignore_errors: true

    - name: waiting for server to come back
      local_action: wait_for host={{ inventory_hostname }} state=started delay=30 timeout=300

    - name: check uname
      command: uname -a

Sur mon réseau à plusieurs interfaces réseau, naturellement, impossible de faire fonctionner mon périphérique mDNS (chromecast) entre les différents réseaux.

Par exemple, connecté au réseau freebox, on peut voir le chromecast, mais si on est connecté sur mon wifi hors freebox, alors pas de discovery.

Cet article peut donner quelque astuces pour régler ce petit problème.

Spoiler alert: utiliser avahi avec la fonctionnalité “reflector”.

Introduction

Lorsque l’on a plusieurs réseaux interconnectés, mais que sur l’un deux, la configuration du routeur par défaut ne peut être altérée (FreeBOX), il peut parfois être nécessaire de bidouiller un peu.

La FreeBOX mini 4k (android tv) dispose d’une fonctionnalité ChromeCast.

La configuration d’un réflecteur mDNS permet d’utiliser l’appareil chromecast, alors que l’on est sur des interfaces réseau physiques différentes.

Mon réseau est séparé en plusieurs morceaux:

+------------------+                   +-----------------+
| FreeBOX (server) |  réseau free      | Freebox mini 4k |
| 192.168.2.254/24 | <---------------> | 192.168.2.XX/24 |
+------------------+                   +-----------------+
         |          +---------------------+
         +-->  eth1 | Routeur linux (APU) |           +----------------+
                    | 192.168.0.1/24      | wlan0 <-- | Réseau wifi    |
         +-->  eth2 |                     |           | 192.168.3.0/24 |
         |          +---------------------+           +----------------+
+-------------------------+
| Routeur autre opérateur | -> Internet 2
| 192.168.1.254/24        |
+-------------------------+

La version de chromecast installée sur la FreeBOX mini 4k utilise mDNS afin de faire fonctionner son système de découverte de périphériques. (discovery)

mDNS est un protocole réseau qui utilise multicast afin de propager a intervale régulier des informations sur les périphériques.

La configuration du routeur freebox serveur n’est pas modifiable, on ne peut pas l’informer que pour joindre 192.168.0.0/24 ou 192.168.3.0/24 il faut passer par mon routeur “APU” 192.168.2.1.

La propagation des paquets multicast ne se fera pas automatiquement entre les différentes interfaces réseau.

Il est possible de configurer un routage multicast afin de faire transiter les paquets multicast discovery entre les interfaces physiques, mais ce n’est pas la route que j’ai choisi de prendre.

Dans cet exemple, mon routeur fonctionne sous Debian GNU/Linux (jessie).

Avahi-daemon

apt install avahi-daemon

Ensuite, concernant la configuration de avahi-daemon, quelques paramètres nous intéressent:

# Cette configuration est incomplète
# Les paramètres par défaut ne sont pas mentionnés
[server]
use-ipv4=yes
allow-interfaces=eth0,eth1,eth2,wlan0

[reflector]
enable-reflector=yes
reflect-ipv=no

J’espère ne pas avoir a décrire ce que use-ipv4=yes fait..

Concernant allow-interfaces, cela permet a avahi-daemon de savoir sur quelles interfaces il peut travailler. Dans mon cas, toute les interfaces lui sont accessibles.

La section intéressante est [reflector].

Le paramétrage de enable-reflector=yes permet d’activer le relay des paquets entrants vers toute les intrafaces déclarées plus haut.

reflect-ipv permet de faire des transformations IPv6 -> IPv4 et vice versa.

Pour plus de détails, consulter la page de manuel avahi-daemon.conf(5).

Netfilter

Mais ce n’est pas tout. Comme décrit plus haut, la freebox ne connait pas 192.168.0.0/24 ou 192.168.3.0/24.

Il faut donc lui faire croire que toute les communications proviennent de 192.168.2.0/24. Rien de plus simple:

iptables -t nat -A POSTROUTING -o eth1 -s 192.168.0.0/24 -d 192.168.2.0/24 -j SNAT --to-source 192.168.2.1
iptables -t nat -A POSTROUTING -o eth1 -s 192.168.3.0/24 -d 192.168.2.0/24 -j SNAT --to-source 192.168.2.1

I needed a quick way to upgrade my OpenBSD 5.8 servers with the recent release of OpenBSD 5.9 and because

I like ansible, i’ve written an incomplete and dangerous playbook to do it.

This playbook is incomplete because it’s ONLY for server using comp.tgz and man.tgz. Add your sets as required. Don’t mess up the order of the sets.

Please don’t use it, understand the manual in place upgrade procedure before even thinking of using this playbook.

Take note this playbook requires ansible 2.0+

https://gist.github.com/zehome/060be435cbc4d19e72f0e28fb050691a

---
- hosts: all
  gather_facts: yes
  vars:
    mirror: http://ftp.eu.openbsd.org/pub/OpenBSD
    release: 5.9
    arch: amd64
  tasks:
    - name: installboot on sd0
      command: installboot -v sd0

    - name: Ensure /usr/rel exists
      file: dest=/usr/rel state=directory

    - name: Clean /usr/rel
      shell: rm /usr/rel/*
      ignore_errors: true

    - name: Download packages
      command: ftp -o /usr/rel/{{item}} {{mirror}}/{{release}}/{{arch}}/{{item}}
      with_items:
        - SHA256
        - SHA256.sig
        - bsd.rd
        - bsd.mp
        - bsd
        - man59.tgz
        - base59.tgz
        - comp59.tgz
        - game59.tgz
        - xbase59.tgz
        - xshare59.tgz
    - name: Check SHA256
      command: chdir=/usr/rel sha256 -C SHA256 bsd.rd bsd.mp bsd man59.tgz comp59.tgz base59.tgz game59.tgz xbase59.tgz xshare59.tgz
    - name: Check with signify
      command: chdir=/usr/rel signify -C -p /etc/signify/openbsd-59-base.pub -x SHA256.sig bsd.rd bsd.mp bsd man59.tgz comp59.tgz base59.tgz game59.tgz xbase59.tgz xshare59.tgz
    - shell: cp /sbin/reboot /sbin/oreboot && cp /usr/rel/bsd /bsd.sp && cp /usr/rel/bsd.mp /bsd && cp /usr/rel/bsd.rd /bsd.rd

    - name: Extract packages
      command: tar -C / -xzphf {{item}}
        chdir=/usr/rel
      with_items:
        - comp59.tgz
        - man59.tgz
        - game59.tgz
        - xbase59.tgz
        - xshare59.tgz

    - name: Extract base and reboot
      shell: tar -C / -xzphf /usr/rel/base59.tgz && /sbin/oreboot
      async: 1
      poll: 0
      ignore_errors: true

    - name: waiting for server to come back
      local_action: wait_for host={{ inventory_hostname }} state=started delay=30 timeout=300

    - name: MAKEDEV
      command: chdir=/dev ./MAKEDEV all

    - name: upgrade bootloader
      command: installboot -v sd0

    - name: sysmerge non interractive
      command: sysmerge -b
      ignore_errors: true

    - name: firmware update
      command: fw_update -v

    - name: update pkg.conf
      lineinfile:
        regexp="^installpath ="
        line="installpath = {{mirror}}/{{release}}/packages/{{arch}}"
        dest=/etc/pkg.conf

    - name: upgrade packages
      command: pkg_add -u

    - name: reboot again
      command: /sbin/reboot
      async: 1
      poll: 0
      ignore_errors: true

    - name: waiting for server to come back
      local_action: wait_for host={{ inventory_hostname }} state=started delay=30 timeout=300

    - name: check uname
      command: uname -a

Update 08/04/2016

The new playbook now checks base tarballs with signify(1)

Now fixes automatically the pkg.conf(5).

Une partie intéressante de hugo, c’est la mise en place des fichiers générés. Pour ça j’utilise une technique très basique, une simple copie de fichiers via rsync au travers d’openssh.

J’utilise pour me simplifier la vie un fichier Makefile dans les sources de mon site:

all: gulp hugo

hugo:
    .go/bin/hugo -s src/

gulp:
    ./node_modules/.bin/gulp build

upload: all
    rsync -r --delete src/public/ ed@ed.zehome.com:www/ed.zehome.com/

.PHONY: hugo gulp upload

La partie qui nous intéresse c’est upload qui dépend de gulp et hugo.

Donc pour lancer l’upload:

make upload

Gulpjs

Gulp est un système de construction écrit en javascript pour nodejs.

Il est similaire à grunt que j’utilise aussi, mais cette fois j’ai décidé d’utiliser quelque chose d’autre. Gulp à l’avantage de pouvoir en très peu de lignes décrire un pipeline de tâches à executer selon des déclencheurs.

Par exemple, pour mon site, j’ai besoin de recompiler les fichiers .less de bootstrap avec mes modifications, puis de les copier dans les répertoires de source qu’attend Hugo.

var gulp = require('gulp');
var less = require('gulp-less');
var path = require('path');

var scripts = [
    path.join(__dirname, 'node_modules/bootstrap/dist/js/bootstrap.min.js'),
    path.join(__dirname, 'node_modules/jquery/dist/jquery.min.js'),
];

gulp.task('watch', ['build:styles'], function() {
    gulp.watch(['src/static/less/**/*.less'], ['build:styles', ]);
});

gulp.task('build', ['build:styles', 'build:js']);

gulp.task('build:js', function() {
    return gulp.src(scripts)
        .pipe(gulp.dest('src/static/js/'));
});

gulp.task('build:styles', function () {
    return gulp.src('src/static/less/**/*.less')
        .pipe(less({
            paths: [path.join(__dirname, 'node_modules', 'bootstrap', 'less')]
        }))
        .pipe(gulp.dest('src/static/css'));
});

gulp.task('default', ['watch']);

Ce fichier source me permet de lancer par défaut une tâche qui réagit lorsqu’un fichier source est modifié (tout fichier src/statc/less/*.less) et lancer la tâche “build:less”.

Les fichiers arrivent alors uen fois la compilation réalisée dans src/static/css.

Gérer l’environnement

Avec ces nouveaux projets, il faut souvent utiliser des binaires externe au système d’exploitation et donc gérer l’environnement qui leur est nécessaire.

Par exemple, il faut rajouter les binaires installés par npm et par go dans le PATH.

J’utilise ZSH qui offre une fonctionnalité permettant d’executer du code lorsque le chemin courrant du shell change. (lorsque vous faites cd blabla/)

Voici ce que ça donne: Attention cela peut être dangeureux d’utiliser ce snippet. Si vous ne comprenez pas complètement ce que vous faites, ne l’utilisez PAS.

function chpwd {
    # Go modules in .go
    if [ -d .go/bin ]; then
        export GOPATH=$(readlink -f .go)
        export PATH=$PATH:$(readlink -f .go/bin)
    fi
    if [ -d node_modules/.bin ]; then
        export NPMPATH=$(readlink -f node_modules)
        export PATH=$PATH:$(readlink -f node_modules/.bin)
    fi
}

Cette fonction est très incomplète et simplifiée pour les besoins de la démonstration mais globalement, chaque fois que vous entrez dans un répertoire contenant un répertoire .go/bin GOPATH sera défini de PATH sera ajusté. De même pour node_modules/.bin.

Cette fois c’est fait, il était temps de sortir un nouveau site internet.

Les pages sont générées par hugo. Hugo est un moteur de génération de site statique, écrit en go. (Un peu comme jekyll qui est utilisé pour github.io)

Détails

J’ai choisi ce moteur pour plusieurs raisons.

Premièrement, parceque je souhaite pouvoir écrire rapidement et facilement du contenu, en utilisant la syntaxe markdown.

Ensuite, parceque hugo est très rapide pour générer le contenu, et lorsque l’on souhaite re-générer tout le contenu “à la volée”, c’est pratique.

Et pour finir, parecque la documentation de Hugo est très bien écrite, ce qui facilite grandement la mise en place.

Mise en place

pacman -S go git mercurial npm
# apt install golang git-core mercurial npm
[ -d .go ] || mkdir .go
export GOPATH=$(readlink -f .go)
export PATH=$GOPATH/bin:$PATH
go get -u -v github.com/spf13/hugo
npm install

Développement

Afin de développer le site, je souhaitais être en mesure d’utiliser bootstrap sous sa forme “less”, et pouvoir visualiser les modifications “en direct”.

Hugo à un système pour prévisualiser les modifications en direct, mais pas si on utilise des fichiers sources less.

Pour faire ça, j’ai utilisé gulpjs.

En gros le processus est relativement simple:

Dans un terminal on lance gulp en “attente de modification”

gulp watch

Et dans un autre on lance un serveur http embarqué par hugo

hugo server --config src/config.toml -s src