Migration Plesk 18 > ISPconfig 3

Pour l’association le Retzien.fr (dont je fais parti) j’ai effectué une migration de Plesk 18 vers ISPconfig 3. Plesk avait été mis en œuvre avant mon arrivé car l’administrateur en place connaissait le produit. Mais c’est un produit payant, de plus en plus cher, et pour une association qui a du mal à avoir un équilibre financier c’était difficile à assumer. Plesk est très « beau » et user friendly mais en tant qu’administrateur système je le trouve plutôt opaque (on ne sais pas exactement ce qu’il fait, pas trop de respect des « us et coutumes » de Linux (position des fichiers de config…), ET CE N’EST PAS LIBRE !!!

ISPconfig est libre et propose un script de migration (ISPConfig Migration Toolkit) mais ne supporte pas les dernières versions de Plesk, car le chiffrement des mots de passe n’est plus compatible. Du coup j’ai dû bricoler… Ce bricolage est l’objet de cet article.

Note : ici il n’est question que de la partie mail/DNS et compte client, car c’est la seule fonctionnalité du serveur, les fonctions web n’ont pas été testées.

Installation ISPconfig + Migration Toolkit

Je ne détaille pas ici la procédure d’installation d’ISPconfig 3 et l’usage du « Migration Toolkit » qui sont déjà bien documentés, dans mon cas :

Suite à l’usage du « Migration toolkit », même si le Plesk source n’est pas supporté, je n’ai pas d’erreur mais j’ai plusieurs problèmes que je vais essayer de régler par différents scripts.

Préambules : Les scripts suivants utilisent l’API d’ISPconfig (il faut donc créer un utilisateur distant, dans Système/utilisateur distant). ils doivent donc les lancer depuis le serveur source (Plesk).

Authentification IMAP

Le problème

Il est impossible de se connecter au serveur avec les comptes IMAP. Le log dit :

Error: sql(xxxxxxx@retzien.fr,::1,<MdFia/2k3toAAAAAAAAAAAAAAAAAAAAB>): Invalid password in passdb: crypt() failed: Invalid argument

Effectivement le chiffrement en base est différent :

Mais on va s’en sortir en rusant, en effet Plesk ne crypte/ »hash » pas les mots de passe mais les chiffre :

  • Crypter / « hasher » : c’est convertir définitivement un chaîne (ici un mot de passe) en un code. Pour savoir si le mot de passe proposé pour l’authentification est bon il faut crypter / « hasher » le mot de passe et le comparer.
  • Chiffrer : c’est convertir une chaîne (le mot de passe ici) avec une clé, cette fonction est réversible, et qui détient la clé peut donc déchiffrer la chaîne (le mot de passe)
  • Plus d’info sur la différence ici

Les mots de passe dans Plesk sont donc « lisibles » pour qui possède la clé de déchiffrement (l’administrateur par exemple ou un hacker qui obtiendrait l’accès au serveur…)

Pour vous en persuader, lancez la commande ci après et vous obtiendrez un beau tableau 1er colonne « émail », 2ème « mot de passe en clair »

/usr/local/psa/admin/sbin/mail_auth_view

Mais pourquoi les mots de passes ne sont ils pas cryptés ?

Plesk propose le support de l’authentification « mot de passe chiffré ». Et cette fonctionnalité est de fait contrainte à avoir le mot de passe en clair sur le serveur car il faut pouvoir le déchiffrer.

Exemple de méthode d’authentification sous Thunderbird

ISPconfig ne supporte pas cette fonctionnalité, car qu’il considère que ce serait un trop gros trou dans la sécurité du serveur, et qu’avec le chiffrement de la connexion SSL/TLS ou START/TLS ce n’est pas nécessaire d’en remettre une couche.

Effet de bord important : si vos utilisateurs ont configurés leur client de messagerie en « mot de passe chiffré » il faudra leur faire modifier ce paramétrage (IMAP & SMTP)

De ce que je comprends, cette méthode d’authentification chiffrée avait sa place quand les serveur IMAP/SMTP ne proposait pas de chiffrement de connexion SSL/TLS ou START/TLS.

Les sources de ce que j’avance :

La solution

La solution consiste, sur le serveur Plesk à lancer le script ci-après qui :

  • Lance la commande Plesk « magique » pour voir les mots de passe déchiffrés
  • « Parse » le résultat… Se connecte à ISPconfig et change le mot de chaque boîte émail
<?php
 
/* 
 * licence Beerware
 * By David Mercereau : http://david.mercereau.info
 */
 
// Plan B : https://www.besuchet.net/2016/06/plesk-11-encrypted-hashed-password-authentication-php-on-psa-database/
 
// ISPconfig Remote Config
$CONFIG['remoteUser'] = 'retzien';
$CONFIG['remotePassword'] = 'XXXXXXXXXX';
$CONFIG['remoteSoapLocation'] = 'https://192.168.1.64:8080/remote/index.php';
$CONFIG['remoteSoapUri'] = 'https://192.168.1.64:8080/remote/';
$CONFIG['logfile'] = '/tmp/migration-email-password.log';
 
function toLog($txt, $level) {
	file_put_contents($GLOBALS['CONFIG']['logfile'],date("[j/m/y H:i:s]")." - ".$level." - $txt \r\n",FILE_APPEND);
	echo "[$level] $txt\n";
}
 
// Connexion to ISPconfig
$client = new SoapClient(null, array('location' => $CONFIG['remoteSoapLocation'],
	'uri'      => $CONFIG['remoteSoapUri'],
	'stream_context'=> stream_context_create(array('ssl'=> array('verify_peer'=>false,'verify_peer_name'=>false))),
	'trace' => 1));
 
// Login
if($session_id = $client->login($CONFIG['remoteUser'], $CONFIG['remotePassword'])) {
	toLog('Login Ok. Session ID:'.$session_id, 'info');
}
 
# Launch plesk command for auth view in plain text
exec('/usr/local/psa/admin/sbin/mail_auth_view', $out, $return);
if ($return == 0 || $out[0] == null){
	foreach ($out as $line) {
		$lineExplode = explode('|', $line);
		if (isset($lineExplode[1]) &amp;&amp; isset($lineExplode[3])) {
			$email = str_replace(' ', '', $lineExplode[1]);
			$password = str_replace(' ', '', $lineExplode[3]);
			# Verification data
			if (filter_var($email, FILTER_VALIDATE_EMAIL) &amp;&amp; $password != '') {				
				# mail_user_get
				try {
					$mail_user_get = $client->mail_user_get($session_id, array('email' => $email));
					if (count($mail_user_get) > 0) {
						# Update data
						$mail_user_get['password']=$password;
						$mail_user_get['move_junk']='y';
						$affected_rows = $client->mail_user_update($session_id, 0, $mail_user_get[0]['mailuser_id'], $mail_user_get);
						if ($affected_rows == 0) {
							toLog('No change for '.$email, 'error');
						} else {
							toLog('Ok for '.$email, 'info');
						}
					} else {
						toLog('email '.$email.' not present in ISPconfig', 'warn');
					}
				} catch (SoapFault $e) {
					echo $client->__getLastResponse();
					die('SOAP Error: '.$e->getMessage());
				}
			} 
		}
	}
}
 
if($client->logout($session_id)) {
	toLog('Logged out.', 'info');
}
 
?>

IMAP subscriptions

La souscription au dossier IMAP (le fait de voir ou non certains dossiers et/ou d’en cacher d’autres) n’a pas migré, donc si l’utilisateur avait ajouté un dossier IMAP, il ne le voyez plus sur le nouveau serveur, pourtant le dossier est bien là.. c’est fâcheux. Pour corriger ça, lancez ce petit script sur le Plesk (source) :

#!/bin/bash
target='root@192.168.1.64'
find  /var/qmail/mailnames/ -name subscriptions | while IFS=$'\n' read f ; do
	domain=$(echo $f | cut -d/ -f5)
	user=$(echo $f | cut -d/ -f6)
	echo ${user}@${domain}
	scp /var/qmail/mailnames/${domain}/${user}/Maildir/subscriptions ${target}:/var/vmail/${domain}/${user}/Maildir/
done

DKIM & Spam policy

Ce script ajoute une politique de spam “normal” (modifiable) et récupère les anciennes clefs DKIM générées par Plesk pour les appliquer dans ISPconfig. Ceci pour éviter d’avoir à retoucher au DNS pendant la migration (toujours à lancer depuis le Plesk) :

<?php
 
/* 
 * licence Beerware
 * By David Mercereau : http://david.mercereau.info
 */
 
// ISPconfig Remote Config
$CONFIG['remoteUser'] = 'retzien';
$CONFIG['remotePassword'] = 'XXXXXXX';
$CONFIG['remoteSoapLocation'] = 'https://192.168.1.64:8080/remote/index.php';
$CONFIG['remoteSoapUri'] = 'https://192.168.1.64:8080/remote/';
$CONFIG['logfile'] = '/tmp/migration-email-password.log';
$CONFIG['DKIM-Selector'] = 'default';
$CONFIG['policy_id'] = 5;
$CONFIG['priority'] = 5;
 
function toLog($txt, $level) {
	file_put_contents($GLOBALS['CONFIG']['logfile'],date("[j/m/y H:i:s]")." - ".$level." - $txt \r\n",FILE_APPEND);
	echo "[$level] $txt\n";
}
 
// Connexion to ISPconfig
$client = new SoapClient(null, array('location' => $CONFIG['remoteSoapLocation'],
	'uri'      => $CONFIG['remoteSoapUri'],
	'stream_context'=> stream_context_create(array('ssl'=> array('verify_peer'=>false,'verify_peer_name'=>false))),
	'trace' => 1));
 
// Login
if($session_id = $client->login($CONFIG['remoteUser'], $CONFIG['remotePassword'])) {
	toLog('Login Ok. Session ID:'.$session_id, 'info');
}
 
$mail_domain_record = $client->mail_domain_get($session_id, array('active' => 'y'));
 
foreach ($mail_domain_record as $mail_domain) {
	# DKIM
	if (is_dir('/etc/domainkeys/'.$mail_domain['domain'])) {
		$getPrivateKey=file_get_contents('/etc/domainkeys/'.$mail_domain['domain'].'/'.$CONFIG['DKIM-Selector']);
		exec('openssl rsa -in /etc/domainkeys/'.$mail_domain['domain'].'/'.$CONFIG['DKIM-Selector'].' -pubout', $out, $return);
		if ($return == 0 || $out[0] == null){
			$getPublicKey='';
			foreach ($out as $line) {
				$getPublicKey.=$line."\n";
				//~ if (!preg_match('/^---/', $line))  {
					//~ $getPublicKeyDns.=$line;
				//~ }
			}
			$mail_domain['dkim'] = 'y';
			$mail_domain['dkim_selector'] = $CONFIG['DKIM-Selector'];
			$mail_domain['dkim_private'] = $getPrivateKey;
			$mail_domain['dkim_public'] = $getPublicKey;
			$affected_rows = $client->mail_domain_update($session_id, 0, $mail_domain['domain_id'], $mail_domain);
			if ($affected_rows == 0) {
				toLog('DKIM No change for '.$mail_domain['domain']. ' already exist ?', 'error');
			} else {
				toLog('DKIM Ok for '.$mail_domain['domain'], 'info');
			}
		}
	} else {
		toLog($mail_domain['domain'].' haven\'t got dkim key on plesk', 'warn');
	}		
	# Spam Policy add in domain 
	try {
		$client_id = 1;
		$params = array(
			'server_id' => 1,
			'priority' => $CONFIG['priority'],
			'policy_id' => $CONFIG['policy_id'],
			'email' => '@'.$mail_domain['domain'],
			'fullname' => '@'.$mail_domain['domain'],
			'local' => 'Y'
		);
		$affected_rows = $client->mail_spamfilter_user_add($session_id, $client_id, $params);
		toLog('Spam policy Ok for '.$mail_domain['domain'], 'info');
	} catch (SoapFault $e) {
		toLog('Spam policy no change for '.$mail_domain['domain']. ' already exist ?', 'error');
	}
}
 
if($client->logout($session_id)) {
	toLog('Logged out.', 'info');
}
 
?>

Les mailings lists

Les mailings lists ne sont pas migrées, pourtant c’est du « Mailman 2 » des 2 côtés donc ça ne devrait pas être très compliqué.

Ce script est à lancer depuis le Plesk en mode « CLI » (ligne de commande) et vous avez quelques paramètres à modifier au début.

Le script :

  • Liste les mailings lists avec la commande /var/lib/mailman/bin/list_lists
  • Les créer dans ispconfig
  • Donne les commandes rsync pour synchroniser les données (archives, paramètres…)
 <?php
 
/* 
 * licence Beerware
 * By David Mercereau : http://david.mercereau.info
 */
 
// ISPconfig Remote Config
$CONFIG['remoteUser'] = 'retzien';
$CONFIG['remotePassword'] = 'XXXXX';
$CONFIG['remoteSoapLocation'] = 'https://192.168.1.64:8080/remote/index.php';
$CONFIG['remoteSoapUri'] = 'https://192.168.1.64:8080/remote/';
$CONFIG['logfile'] = '/tmp/migration-email-password.log';
$CONFIG['clientId'] = 3;
 
function toLog($txt, $level) {
	file_put_contents($GLOBALS['CONFIG']['logfile'],date("[j/m/y H:i:s]")." - ".$level." - $txt \r\n",FILE_APPEND);
	echo "[$level] $txt\n";
}
 
// Connexion to ISPconfig
$client = new SoapClient(null, array('location' => $CONFIG['remoteSoapLocation'],
	'uri'      => $CONFIG['remoteSoapUri'],
	'stream_context'=> stream_context_create(array('ssl'=> array('verify_peer'=>false,'verify_peer_name'=>false))),
	'trace' => 1));
 
// Login
if($session_id = $client->login($CONFIG['remoteUser'], $CONFIG['remotePassword'])) {
	toLog('Login Ok. Session ID:'.$session_id, 'info');
}

# Launch plesk command for auth view in plain text
exec('/var/lib/mailman/bin/list_lists -b', $out, $return);
if ($return == 0 || $out[0] == null){
	foreach ($out as $line) {
			unset($host_name);
			unset($owner);
		exec('/usr/lib/mailman/bin/config_list -o /tmp/'.$line.' '.$line.' ; grep ^host_name /tmp/'.$line.' | cut -d"\'" -f2', $host_name, $return);
		exec('/usr/lib/mailman/bin/config_list -o /tmp/'.$line.' '.$line.' ; grep ^owner /tmp/'.$line.' | cut -d"\'" -f2', $owner, $return);
		try {
			$params = array(
				'server_id' => 1,
				'domain' => $host_name[0],
				'listname' => $line,
				'email' => $owner[0],
				'password' => 'temp123!'
			);
			$mailinglist_id = $client->mail_mailinglist_add($session_id, $CONFIG['clientId'], $params);
			toLog('Ok for '.$line.'@'.$host_name[0]);
		} catch (SoapFault $e) {
			toLog('SOAP Error: '.$line. ' - '. $e->getMessage(), 'error');
		}
	}
}
 
if($client->logout($session_id)) {
	toLog('Logged out.', 'info');
}
 
echo "Patienteez 5min que Ispconfig est tout ajouté puis : \n";
echo "for list in \$(/var/lib/mailman/bin/list_lists -b); do\n
	rsync -az --progress  /var/lib/mailman/archives/public/\$list/ root@192.168.1.64:/var/lib/mailman/archives/public/\$list/\n
	rsync -az --progress  /var/lib/mailman/archives/private/\$list/ root@192.168.1.64:/var/lib/mailman/archives/private/\$list/\n
	rsync -az --progress  /var/lib/mailman/archives/private/\${list}.mbox root@192.168.1.64:/var/lib/mailman/archives/private/\n
	rsync -az --progress  /var/lib/mailman/lists/\$list/ root@192.168.1.64:/var/lib/mailman/lists/\$list/\n
done\n";
 
?>

En sortie le script donne quelques commandes « rsync » à exécuter par un copier / coller direct dans le terminal du serveur source (Plesk) pour synchroniser les données (archives, paramètres…)

Ensuite sur le serveur decdestination (ISPconfig) lancez la commande :

/var/lib/mailman/bin/check_perms -f

Conclusion

La migration c’est bien passée, c’est transparent pour l’utilisateur, à part le problème de « mot de passe chiffré » à « mot de passe normal », qu’il faut faire changer avant la migration de préférence.

Héberger un WordPress : sécurité, optimisation, économie d’énergie

WordPress occupe une part très importante dans l’usage des CMS à l’heure ou j’écris ce billet (62%). Voilà ce qui peut l’expliquer à mon sens :

  • Simple d’utilisation côté utilisateur
  • Plutôt simple côté développeur
  • Un nombre incalculable de thèmes et de plugins sont à disposition (communauté conséquente).

Mais il a aussi les défaut de ses qualités :

  • Côté utilisation de ressources énergétiques (serveur), WordPress est une catastrophe… Surtout dès qu’on lui ajoute des plugins (qui n’en ajoute pas ?).
  • Côté sécurité, il est très attaqué (car très utilisé). Le sport préféré des hackers, c’est le brute force…

Je vais tâcher de balayer ici mes trucs pour transformer un wordpress énergivore en wordpress sobre et sécurisé 🙂

Transformer en site statique (parfait pour les sites vitrines)

Un site « statique » c’est quoi ? C’est un site ou le code est uniquement exécuté sur le client et le serveur ne fait rien d’autre que « servir la page ». Alors qu’un site dynamique par opposition (sous wordpress pour l’exemple) va générer des pages « à la volée » et donc utiliser des ressources sur le serveur.

Mon parti-pris, c’est de convertir le wordpress « visible pour les visiteurs » en site statique « html ». L’utilisateur continue d’utiliser / d’alimenter son site via l’interface (bien faite et connue) wordpress, mais le visiteur lui ce qu’il voit c’est un site « html », sans aucun code dynamique (php ici) exécuté. De cette façon :

  • Le site est inattaquable par les hackers/spameurs, il n’y a plus de code dynamique, donc plus de moyen de corrompre le site / le serveur
  • L’affichage pour le visiteur est beaucoup plus rapide.
  • Le serveur consomme beaucoup moins de ressources (moins d’électricité, plus petit serveur…).
  • Les utilisateurs qui connaissent déjà wordpress ne sont pas bouleversés / impactés par cette optimisation.

Pour cela j’utilise un plugin wordpress qui s’appelle wp2static. Voici la structure du répertoire « web » qui est la racine du site :

  • /wp/ : site wordpress avec le plugin wp2static. Adresse utilisée par l’administrateur du site pour faire ces modifications
  • /static/ : le site statique généré par le plugin
  • /.htaccess : contient les redirections pour que le visiteur ne voit pas de différence

Dans le wordpress installé dans /wp/ rendez -vous dans wp2statics et configuré :

  • Where will you host the optimized version of your site? : Subdirectory
  • Destination URL : http://votresite.fr/
  • Target Directory : /var/www/votreiste.fr/web/static/

Puis dans l’onglet Crawling indiqué :

  • Exclude certain URLs :
    • /wp/
    • /wp-content/uploads/
    • /wp-admin/

Quand c’est fait, vous pouvez cliquer sur Start Static Export.

Ajouter dans votre configuration apache ou dans un .htaccess à la racine du site :

RewriteEngine on
# Pas de ré-écriture pour le contenu uploadé (images, 
RewriteRule ^/wp-content/uploads/(.*)$          /wp/wp-content/uploads//$1 [L]
# Rediriger toutes les requêtes vers /static sauf quand l'accès est souhaité sur le site wordpress original (/wp)
RewriteCond %{REQUEST_URI} !^/wp/ 
RewriteRule ^(.*)$          /static/$1 [L]

Inconvénient majeur de ce type de « transformation » en site full static

  • A chaque modification l’utilisateur doit penser à re-générer le site statique
  • Le contenu dynamique (formulaire de contact / commentaire) est impossible :
    • Pour les commentaires, c’est possible avec des commentaires chargés en Javascript, typiquement HashOver Next (alternative auto-hébergable à DISQUS). J’ai fait un script de migration WordPress > HashOver : wp2hashover
    • Pour les commentaires, il est possible d’utiliser des formulaires en iframe ou formulaire javascript/ajax

Transformer en (presque) site statique (W3 Total Cache)

La solution wp2static n’était pas complètement satisfaisante et j’ai continué mes recherches. En testant le plugin W3 Total Cache, je me suis aperçu qu’il faisait exactement ce que je cherchais à faire et même plus !

Après paramétrage, j’ai trouvé ceci dans le fichier .htaccess :

<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteBase /
[...]
    RewriteCond %{REQUEST_METHOD} !=POST
[...]
    RewriteCond "%{DOCUMENT_ROOT}/wp-content/cache/page_enhanced/%{HTTP_HOST}/%{REQUEST_URI}/_index%{ENV:W3TC_SSL}%{ENV:W3TC_PREVIEW}.html%{ENV:W3TC_ENC}" -f
    RewriteRule .* "/wp-content/cache/page_enhanced/%{HTTP_HOST}/%{REQUEST_URI}/_index%{ENV:W3TC_SSL}%{ENV:W3TC_PREVIEW}.html%{ENV:W3TC_ENC}" [L]
</IfModule>

Pour résumer, ce que font les règles c’est :

  • Si la page HTML static a été générée (le fichier existe) dans /var/www/decroissant-au-beurre.com/web/wp-content/cache/page_enhanced/decroissant-au-beurre.com/services/_index_ssl.html (pour l’appel à https://decroissant-au-beurre.com/services/ alors on l’appelle directement (pas d’exécution de code PHP… ) SINON on appelle la page « normale » dans wordpress et celui-ci génère la page de cache /static pour le prochain visiteur
  • Dès qu’il y a une requête POST, le cache n’est pas appelé. Donc tous les formulaires restent compatibles avec le cache et seules les pages de résultats ne seront pas tirées du cache.

Ca veut dire qu’a performance égale avec la solution précédente, ce plugin fait mieux car il gomme les défauts précédents et permet de continuer d’utiliser des formulaires/les commentaires.

C’est parfait ! Le cache peut aussi être stocké via memcache et non sur disque (ce qui rend l’accès / l’affichage encore plus rapide (d’autres technologies possibles : APC / Xcache…)

Les fonctionnalités supplémentaires :

Minifier : diminuer trafic réseau

W3 Total Cache propose une fonctionnalité de Minification

minifier signifie réduire la taille du code. C’est un processus très utilisé en programmation web pour réduire la taille d’un programme à télécharger depuis un serveur et ainsi réduire l’encombrement du réseau. Cela peut aussi être considéré comme une forme d’offuscation du code.

Pour cela on supprime tous les commentaires et les espaces qui ne gêneront pas le bon fonctionnement de l’application. On remplace aussi le nom des variables interne à l’application pour les réduire à un seul ou deux caractères. Il est aussi possible d’utiliser certaines écritures compactes propres aux langages (couleur en hexadécimal, raccourcis…)

https://fr.wikipedia.org/wiki/Minification

Si on analyse le code après avoir activé la Minification on observe :

<!DOCTYPE html><html lang=fr-FR><head><script>window.w3tc_lazyload=1,window.lazyLoadOptions={elements_selector:".lazy",callback_loaded:function(t){var e;try{e=new CustomEvent([...]

Alors qu’un site non-minifié donne plutôt :

<!DOCTYPE html>
<html lang="fr-FR">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    
<link rel="stylesheet" id="extend-builder-css-css" href="assets/static/css/theme.css" type="text/css" media="all">
<style id="extend-builder-css-inline-css" type="text/css">
/* page css */
/* part css : theme */

.h-y-container > *:not(:last-child), .h-x-

Vous pouvez minifier tout ou partie du code (HTML/ CSS / Javascript…). Certains plugins peuvent ne plus fonctionner à cause de la minification. Il est donc important de tester votre site après avoir activé ces options.

Lazy Loading : Afficher les images au besoin

Si vous avez un site très long, il est possible que des images soient présentes en bas de page. Il est pertinent de ne charger ces pages qu’au besoin (que si vous allez jusqu’en bas de la page). Et bien la fonction Lazy Loading permet cela.

Cette fonctionnalité permet aussi un gain non négligeable de bande passante.

Mise en cache navigateur

Vous pouvez améliorer les entêtes navigateurs en spécifiant à celui-ci des dates d’expiration de fichier. De cette façon il ne va pas retourner charger la/les pages sur le serveur en cas de nouvelles visites.

Mesures performance

Voilà un stress test (envoi de requêtes massives) du même site avec ou sans génération en site statique (fait avec W3 total cache ici) :

On constate que c’est 20 fois plus rapide à l’affichage et que c’est plutôt constant. Est-ce que c’est 20 fois moins énergivore ? ça c’est difficile à mesurer mais c’est forcément moins…

Voici ci-après le poids de chargement d’une même page sans optimisation et avec Lazy Loading + Minification

  • Sans optimisation : 21,5Mo
  • Avec Minification et Lazy : 3,9Mo

Soit 5,5 fois moins lourd…

Sécurité

Le minimum pour sécuriser un wordpress c’est :

  • Faire les mises à jour (de nombreux plugins existent pour automatiser cela)
  • Ne pas utiliser « admin » en nom d’utilisateur admin
  • Avoir des mots de passe forts
  • Bloquer les tentatives de brutes forces :

WordPress se fait pas mal attaquer du fait qu’il soit populaire. La principale attaque (hors SPAM sur les commentaires) est faite par brute-force sur la page wp-login.php et xmlrpc. J’ai consacré un article dédié pour intégrer les logs de WordPress dans Fail2ban, et ainsi pouvoir bloquer les tentatives au niveau firewall : WordPress & fail2ban : stopper la brute-force « POST /wp-login.php » (uniquement possible sur un hébergement dédié). C’est particulièrement efficace comme vous pouvez le voir sur ce graphique :

Graphique fail2ban sur 1 semaine

Gestion serveur mutualisé

Si vous gérez un serveur mutualisé, vous pouvez déployer le plugin de cache et faire les mise à jour de tous les wordpress installé sur votre serveur avec wp-cli-isp (taillé pour les serveurs sous ISPconfig, mais c’est adaptable)

file2link : Stockage de fichier temporaire contre lien

Depuis que Framasoft à annoncé sa Déframasoftisons Internet, en tant que membre des CHATONS (avec zici.fr & retzien.fr), je me suis dis que c’était l’heure de se retrousser les manches.

Faire un framadrop ça me semblait accessible, par contre Lufi (utilisé par framadrop) c’est codé en Perl et sur les serveurs de l’asso on est plus en mode PHP (et j’essaie de pas trop me disperser et pas héberger : un coup du perl, un coup du python, un coup du ruby, du node.js… c’est plus facile à maintenir) Sauf qu’en PHP je n’ai pas trouvé de logiciel maintenu similaire qui répondent à mes attentes, du coup j’en ai pondu un : file2link.

Bien sûr tout est libre de droit, file2link est sous licence beerware.

Petit tour en images :

Si vous l’utilisez, dites le moi, ça me parait pertinent qu’on fasse du lien avec toutes les instances…

Fonctionnalités

Dans sa version 0.1 le logiciel permet :

  • Uploader des fichier (plusieurs possible) et de recevoir des liens en échanges :
    • Lien direct
    • Lien de téléchargement forcé
    • Lien vers un zip contenant tout les fichiers
  • Images redimensionné à l’upload possible
  • Mémorisation des fichiers précédaient uploadé (via le storages local du navigateur
  • Se configure avec un fichier YAML
  • Configure with YAML file :
    • Limite dans le temps
    • Limite le type de fichier (via mime)
    • Limite le nombre de fichier envoyé simultanément
    • Limite de type de fichier
  • Bar de progression en HTML5
  • Multi-langue (français / anglais pour le moment)
  • Ménage des fichiers expiré par tâche cron (cron.php) ou par traffic sur la page d’accueil (web cron)
  • Personnalisation du contenu de la page avec la possibilité de créer des pages header-page.php, header.php, footer-page.php, footer.php, start-home.php, end-home.php
  • Ajout d’un menu vers des pages HTML pour créer des pages « contact » « mention légal »…

Installation

Pré-requis :

  • Un serveur http configuré compatible avec htaccess / url rewriting (apache2 typiquement
  • PHP 5.6 minimum
  • Lib php : GD & ZIP

Envoyer le contenu du dépôt sur votre serveur :

git clone https://framagit.org/kepon/file2link.git

Déplacer le fichier de conf distribué :

mv config.yaml.exemple config.yaml

Modifier le fichier config.yaml, changé le baseUrl et autres paramètres que vous souhaitez personnalisé. Assuré vous de la cohérence des paramètres :

  • maxUploadPerFile doit être similaire à la config PHP upload_max_filesize and post_max_size
  • maxUploadNbdoit doit être similaire à la config PHP config max_file_uploads

Assurez vous que le répertoire « files » soit accessible en écriture pour le servuer

Option : si vous voulez faire le ménage par tâche cron modifier la valeur expireCron par « cron » dans le fichier config.yaml et ajouter votre tâche cron :

* * * * * cd /var/www/file2link/ ; php cron.php

Et voilà, fin de chantier !

PvMonit v1.0 : Monitoring de mon installation photovoltaïque autonome

PvMonit est arrivé à une version « mature », une version 1.0. PvMonit est un logiciel sous licence Beerware qui vous permet de monitorer votre installation électrique solaire autonome, plus particulièrement avec les appareils Victron.

Nouvelle version pvmonit dispo ici. Avec une « sur-couche » à PvMonit pour gérer le surplus d’électricité : Déclencher des actions à la demande. Exemple : les batteries sont pleines, on allume une pompe à eau puis la résistance électrique d’un chauffe eau. Ou encore, les batteries sont sous un seuil critique, on coupe tout sauf l’éclairage…. toutes les applications sont possibles !

PvMonit C’est quoi ?

PvMonit, c’est donc un petit logiciel de monitoring photovoltaïque pour matériel Victron compatible Ve.direct (le minimum pour que cela fonctionne est d’avoir un BMV 600, 700 ou 702…), particulièrement adapté pour les installations autonomes (hors réseau). Il permet une vue « en direct » par interface web et un enregistrement de l’historique (avec emoncms, branche d’OpenEnergyMonitor).

Mon usage

Je collecte les information de mon système photovoltaïque (température, état des batteries, production solaire, etc…) par une carte électronique (Arduino) qui se trouve dans un local à 35m de mon habitation. Je transporte ces données par un 3 fils dans un câble réseau. Celui-ci est connecté à un mini ordinateur (raspberry pi 0) sur lequel j’ai un écran LCD qui m’affiche l’état du système et j’ai un interface web (démo) ou j’ai plus de détails. Il y a aussi un historique qui est enregistré via emoncms (démo).

Au niveau Matériel

2 versions possibles :

  • Une version Raspberry PI 3B, si vous avez un point wifi actif (même occasionnellement) et que votre matériel solaire est à porté de wifi. C’est une solution plutôt simple (si on touche un peu sous linux).
  • Une version Raspberry Pi 0 + Arduino : plus complexe à mettre en œuvre (il faut savoir souder et avoir plus de connaissances), mais beaucoup plus souple et moins chère. Particulièrement adapté si votre installation réseau est loin (max 60m) de votre maison ;
Version Raspberry PI 3BVersion Arduino + Raspberry Pi 0
Consommation électrique0,37A (pi 3b) * 5V = ~1,85W0,22A (pi 0) + 0,08A (arduino MEGA) = 0,30A * 5V = ~1,5W
Difficulté********
Prix matériel (détails)200 €110 €

Raspberry Pi 3B & Ve.direct USB (officiel)

L’usage des câbles ve.direct USB officiel permet de simplifier le montage.

Arduino + Raspbery Pi 0

L’usage d’un arduino pour collecter les données donne de la souplesse pour pouvoir ajouter des sondes à volonté et permet de parcourir de grande distance jusqu’au Raspberry PI qui récupère les informations. Un schéma de câblage détaillé :

Le schéma de câblage détaillé

Voilà ce que ça donne, c’est plus de boulot, plus de soudure mais plus DIY, plus fun :-p

Installation

Il ne faut pas se mentir, ça demande de bonnes connaissances techniques en linux/réseau voir arduino/soudure (si vous choisissiez cette option).

J’ai fais un très long tuto d’installation dans le fichier INSTALL.md du dépôt git : https://framagit.org/kepon/PvMonit/blob/master/INSTALL.md

Script API Ispconfig : changement d’IP dans toutes les zones DNS

Je m’occupe d’un service d’hébergement destiné aux actions politiques / citoyennes / artistiques… Ce service va rejoindre physiquement des voisins/copains eux aussi CHATONS : https://retzien.fr/. Nous allons mutualiser les serveurs et le temps de cerveau alloué au tâches administrative qui incombe à la gestion de ce type de service.

Pour préparer la migration de serveur j’ai fais un petit script qui se connecte à l’API d’ISPconfig et qui change tout les enregistrements DNS A ayant l’IP « X » par l’IP « Y » dans toutes les zones DNS du serveur. Ce script peut aussi s’avérer très utile en cas de changement de box internet Il se lance comme ceci :

php dnsChange.php -o=IP_ORIGINALE -n=NOUVELLE_IP

Le voici

<?php
/* 
 * Script de modification des enregistrements DNS A en cas de changement d'IP serveur
 * 
 * Exemple de lancement : 
 * 		php dnsChange.php -o=IP_ORIGINAL -n=NOUVELLE_IP
 * 
 * Sous licence Beerware
 * Par David Mercereau : http://david.mercereau.info
 */

// Configuration de la connexion ISPconfig
$CONFIG['remoteUser'] = 'UTILISATEUR DISTANT API ISPCONFIG';
$CONFIG['remotePassword'] = 'MOT DE PASSE DE L'UTILISATEUR DISTANT API ISPCONFIG';
$CONFIG['remoteSoapLocation'] = 'https://localhost:8080/remote/index.php';
$CONFIG['remoteSoapUri'] = 'https://localhost:8080/remote/';


// Test arguments
$options = getopt('o:n:');
if (count($options) < 2) {
	exit("Certains arguments sont manquants. -o ANCIENNEIP -n NOUVELLEIP .\n");
}
if (!filter_var($options['o'], FILTER_VALIDATE_IP)) {
        exit("L'origin n'est pas une IP valide.\n");
}
if (!filter_var($options['n'], FILTER_VALIDATE_IP)) {
        exit("La nouvelle IP n'est pas une IP valide.\n");
}

// Connexion à ISPconfig
$client = new SoapClient(null, array('location' => $CONFIG['remoteSoapLocation'],
	'uri'      => $CONFIG['remoteSoapUri'],
	'stream_context'=> stream_context_create(array('ssl'=> array('verify_peer'=>false,'verify_peer_name'=>false))),
	'trace' => 1));

// Login
if($session_id = $client->login($CONFIG['remoteUser'], $CONFIG['remotePassword'])) {
	echo "Login Ok. Session ID:".$session_id."\n\n";
}

echo "############################################################################################################################\n";
echo "Recherche de l'IP ".$options['o']." dans tout les enregistrements A des zones DNS du serveur pour la remplacer par ".$options['n']."\n";
echo "############################################################################################################################\n\n";

try {

	$dns_a_gets = $client->dns_a_get($session_id, -1);
        $nb_dns_a_get=0;
	foreach ($dns_a_gets as $dns_a_get) {
                if ($dns_a_get['type'] == 'A' && $dns_a_get['data'] == $options['o']) {
                        $nb_dns_a_get++;
                        $dns_zone_get = $client->dns_zone_get($session_id, $dns_a_get['zone']);
                        echo "Ip trouvé dans la zone ".$dns_zone_get['origin']." avec l'enregistrement ".$dns_a_get['name'].".".$dns_zone_get['origin'].". Voulez-vous la remplacer ? (Y/n) \n";
                        $handle = fopen ("php://stdin","r");
                        $line = fgets($handle);
                        if(trim($line) == 'yes' || trim($line) == 'y' || trim($line) == 'Y' || trim($line) == 'Yes'){
                                $dns_record = $client->dns_a_get($session_id, $dns_a_get['id']);
                                //print_r($dns_record);
                                $dns_record['data']=$options['n'];
                                //print_r($dns_record);
                                $affected_rows = $client->dns_a_update($session_id, $dns_a_get['sys_userid'], $dns_a_get['id'], $dns_record);
                                echo "Le changement à bien été opéré sur ".$affected_rows." enregistrement\n";
                        } else {
                                echo "Aucun changement effectué sur ".$dns_a_get['name'].".".$dns_zone_get['origin']."\n";
                        }
                        fclose($handle);
                }
        }

        if ($nb_dns_a_get == 0) {
                echo "Aucun enregistrement A avec l'IP  ".$options['o']."  n'a été trouvé. \n";
        }
	if($client->logout($session_id)) {
		echo "\nLogged out\n";
	}


} catch (SoapFault $e) {
	echo $client->__getLastResponse();
	die('SOAP Error: '.$e->getMessage()."\n");
}

?>

Crowdin : intégration automatique des traduction (script)

Depuis mon précédent article d’appel à la traduction de CalcPvAutonome , datant d’il y a moins d’un mois. Il c’est passé un truc que je ne me serais pas imaginé. En effet, le projet à été traduit (partiellement ou complètement) en Néerlandais, Russe, Polonais, Indonésien, Espagnol, Italien, Japonais, Polonais, Turc, Ukrainien… j’en passe et des meilleurs…

Il semble que nombre des contributeurs (tous ?) arrivent de utopian.io, une plateforme qui rémunère les contributions des aux projets opensource (via steemit.com/). Je ne sais pas trop quoi en penser… c’est génial, ça a été très vite, mais je ne suis pas certain de la qualité (et j’ai pas trop moyen de le savoir). Même si il semble y avoir de la modération sur utopian, j’ai déjà eu un retour d’une Russe qui repassait derrière une autre qui avait fait vraiment n’importe quoi (selon elle…) mais bon moi et le Russe… c’est complexe de savoir qui dit vrai, je peux juste faire confiance et laisser la communauté autogérée.

En tout cas je n’aurai pas imaginé avoir autant de petit drapeau de traduction en haut de ce machin.

L’engouement à été tellement soudain que je me suis retrouvé débordé devant la quantité de traduction à intégrer. Du coup j’ai fait une moulinette pour l’intégration dans le projet avec l’API Crowdin.  Le script est en PHP et il permet :

  • Compilation sur crowdin
  • Téléchargement des traductions
  • Compilation en .mo
  • Mise à jour du lang.ini qui contient :
    • % d’avancement de la traduction
    • Liste des contributeurs (pour dire merci)

C’est à voir dans le dépot framagit.

Merci à Crowdin qui permet met à disposition sa plateforme hyper complète gratuitement pour les logiciels libres.

Pour vous en montrer un aperçu de sa puissance, Crowdin permet une traduction « en contexte », directement sur l’interface du site à traduire. A tester sur CalcPvAutonome par ici : calcpv.net/aa

CalcPvAutonome en V4.0 : ouverture sur l’international

D’abord un grand merci à nednet, coucou39, guillerette, mirrim, ppmt qui ont œuvré à la traduction vers l’anglais de CalcPvAutonome suite à mon appel.

Je passe la seconde en lançant un nouvel appel à la traduction. Cette fois-ci de l’anglais vers ce que vous voulez/pouvez, Espagnol, Portugais, Italien, Espéranto, Grec… Faites-vous plaisir !

La plateforme de traduction collaborative se trouve par ici : crwd.in/calcpvautonome

Petit rappel : CalcPvAutonome est un logiciel libre (licence Beerware) et gratuit de dimensionnement d’installation photovoltaïque en site isolé (autonome). Il se veut transparent (dans la méthode), pédagogique et surtout détaché de toute structure commercial.

Autre petite nouveauté depuis mon dernier article :

Pour tester, c’est par ici :