feat(matrix): add new bridges and improve E2EE defaults

Add mautrix-linkedin, mautrix-meta (Instagram, Messenger), and mautrix-telegram (initial setup).
Enable E2EE by default for mautrix-discord and mautrix-signal, ensuring stable pickle_key generation.
Update matrix-synapse dependencies for new bridges.
This commit is contained in:
Hermes Agent
2026-09-24 23:57:01 -04:00
parent dc8e39c546
commit ed4c50c557
7 changed files with 379 additions and 8 deletions
+1
View File
@@ -25,6 +25,7 @@
./matrix-signal
./matrix-discord
./matrix-meta
./matrix-linkedin
# ./matrix-telegram # attendre api_id/api_hash de my.telegram.org
./navidrome
./tor
+26 -5
View File
@@ -1,6 +1,8 @@
# services/matrix-discord/default.nix
# Pont Matrix <-> Discord (mautrix-discord), serveur : ../matrix
{
pkgs,
lib,
...
}: let
dataDir = "/var/lib/mautrix-discord";
@@ -51,18 +53,37 @@ in {
"matrix.marcpartensky.com" = "user";
"@marc:matrix.marcpartensky.com" = "admin";
};
# défaut du bridge : pas de chiffrement (Discord sert surtout pour des
# salons de serveurs). Passer allow/default/require à true pour des
# portails chiffrés.
# portails chiffrés par défaut (comme whatsapp), require=false pour ne
# pas casser les salles qui ne peuvent pas être chiffrées (salons de
# serveurs notamment). pickle_key stable hors du store, cf. l'export
# ajouté plus bas dans mautrix-discord-registration.
encryption = {
allow = false;
default = false;
allow = true;
default = true;
require = false;
pickle_key = "$ENCRYPTION_PICKLE_KEY";
};
};
};
};
# Le module NE génère pas la registration dans le preStart du bridge : c'est
# mautrix-discord-registration.service qui fait le envsubst du config.yaml puis
# --generate-registration. L'export de la clé doit donc être DANS ce script
# (un export dans un ExecStartPre ne survivrait pas au process suivant).
systemd.services.mautrix-discord-registration.script = lib.mkBefore ''
if [ ! -f ${dataDir}/pickle_key.txt ]; then
${pkgs.openssl}/bin/openssl rand -hex 32 > ${dataDir}/pickle_key.txt
chmod 600 ${dataDir}/pickle_key.txt
fi
export ENCRYPTION_PICKLE_KEY=$(cat ${dataDir}/pickle_key.txt)
'';
# Le config.yaml du bridge est régénéré par l'unité de registration, mais le
# bridge ne le relit pas tout seul : on déclare un trigger de restart pour que
# le switch redémarre mautrix-discord.service quand ce fichier change.
systemd.services.mautrix-discord.restartTriggers = [./default.nix];
# Pas d'inversion de dépendance ici : le module crée
# mautrix-discord-registration.service et fait démarrer matrix-synapse après
# lui (contrairement à whatsapp/signal qui génèrent la registration dans le
+184
View File
@@ -0,0 +1,184 @@
# services/matrix-linkedin/default.nix
# Pont Matrix <-> LinkedIn (mautrix-linkedin).
# Contrairement aux autres bridges, il n'existe PAS de module nixpkgs pour
# mautrix-linkedin : paquet, unité systemd et registration sont donc câblés ici.
# Le binaire amont est un binaire statique publié dans les releases GitHub
# (les bridges Go de mautrix n'ont pas de build nixpkgs pour LinkedIn).
{
pkgs,
lib,
...
}: let
dataDir = "/var/lib/mautrix-linkedin";
appservicePort = 29329;
mautrix-linkedin = pkgs.stdenvNoCC.mkDerivation {
pname = "mautrix-linkedin";
version = "0.2609.0";
src = pkgs.fetchurl {
url = "https://github.com/mautrix/linkedin/releases/download/v0.2609.0/mautrix-linkedin-amd64";
hash = "sha256-6FrEDJRQWJm4Ib/WJtpmRPJVp9RYsxQ1X0YtaePQ1k0=";
};
dontUnpack = true;
installPhase = ''
install -Dm755 $src $out/bin/mautrix-linkedin
'';
meta = {
description = "A Matrix-LinkedIn puppeting bridge";
homepage = "https://github.com/mautrix/linkedin";
license = lib.licenses.agpl3Only;
platforms = ["x86_64-linux"];
mainProgram = "mautrix-linkedin";
};
};
settingsFormat = pkgs.formats.yaml {};
settings = {
homeserver = {
address = "http://127.0.0.1:8008";
domain = "matrix.marcpartensky.com";
software = "standard";
};
appservice = {
id = "linkedin";
# address = ce que synapse utilisera pour joindre l'appservice (et ce qui
# finit dans le champ url de la registration) : il DOIT correspondre au
# port écouté, sinon la registration annonce le défaut 29341.
address = "http://127.0.0.1:${toString appservicePort}";
hostname = "127.0.0.1";
port = appservicePort;
bot = {
username = "linkedinbot";
displayname = "LinkedIn bridge bot";
avatar = "mxc://maunium.net/CqzBEHjrLsfdqixWZgNHMlRT";
};
ephemeral_events = true;
username_template = "linkedin_{{.}}";
};
# ATTENTION (vérifié à la main) : en config "nouvelle génération", database
# et encryption sont au niveau RACINE, pas sous appservice/bridge. Les y
# mettre déclenche "Legacy bridge config detected" au démarrage.
database = {
type = "sqlite3-fk-wal";
uri = "file:${dataDir}/mautrix-linkedin.db?_txlock=immediate";
};
encryption = {
allow = true;
default = true;
require = false;
# le défaut du bridge est "generate" (clé différente à chaque démarrage,
# donc DB crypto illisible après restart) : valeur stable hors du store.
pickle_key = "$ENCRYPTION_PICKLE_KEY";
};
bridge = {
command_prefix = "!linkedin";
permissions = {
"matrix.marcpartensky.com" = "user";
"@marc:matrix.marcpartensky.com" = "admin";
};
};
};
settingsFileUnsubstituted = settingsFormat.generate "mautrix-linkedin-config.yaml" settings;
settingsFile = "${dataDir}/config.yaml";
registrationFile = "${dataDir}/linkedin-registration.yaml";
in {
users.users.mautrix-linkedin = {
isSystemUser = true;
group = "mautrix-linkedin";
home = dataDir;
description = "Mautrix-LinkedIn bridge user";
};
users.groups.mautrix-linkedin = {};
systemd.services.mautrix-linkedin = {
description = "Mautrix-LinkedIn, a Matrix-LinkedIn puppeting bridge";
wantedBy = ["multi-user.target"];
# pas de dépendance à synapse : c'est synapse qui nous attend, parce que la
# registration est générée dans notre preStart (cf. services/matrix).
wants = ["network-online.target"];
after = ["network-online.target"];
path = [
pkgs.envsubst
pkgs.yq
pkgs.openssl
pkgs.ffmpeg-headless
];
preStart = ''
if [ ! -f ${dataDir}/pickle_key.txt ]; then
${pkgs.openssl}/bin/openssl rand -hex 32 > ${dataDir}/pickle_key.txt
chmod 600 ${dataDir}/pickle_key.txt
fi
export ENCRYPTION_PICKLE_KEY=$(cat ${dataDir}/pickle_key.txt)
# substitution des $VAR du store dans le vrai config.yaml (umask 0177 :
# il contient les tokens appservice)
rm -f '${settingsFile}'
old_umask=$(umask)
umask 0177
${pkgs.envsubst}/bin/envsubst -o '${settingsFile}' -i '${settingsFileUnsubstituted}'
if [ ! -f '${registrationFile}' ]; then
${lib.getExe mautrix-linkedin} \
--config='${settingsFile}' \
--generate-registration \
--registration='${registrationFile}'
fi
chmod 640 '${registrationFile}'
# remet les tokens de la registration dans le config : sans ça, un restart
# repartirait sur le placeholder du store et casserait le sync.
${pkgs.yq}/bin/yq -s '.[0].appservice.as_token = .[1].as_token
| .[0].appservice.hs_token = .[1].hs_token
| .[0]' \
'${settingsFile}' '${registrationFile}' > '${settingsFile}.tmp'
mv '${settingsFile}.tmp' '${settingsFile}'
umask $old_umask
'';
serviceConfig = {
Type = "simple";
User = "mautrix-linkedin";
Group = "mautrix-linkedin";
WorkingDirectory = dataDir;
StateDirectory = "mautrix-linkedin";
Environment = ["HOME=${dataDir}"];
ExecStart = "${lib.getExe mautrix-linkedin} --config='${settingsFile}'";
Restart = "on-failure";
RestartSec = 30;
# même durcissement que les modules nixpkgs
NoNewPrivileges = true;
PrivateDevices = true;
PrivateTmp = true;
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectSystem = "strict";
RestrictAddressFamilies = ["AF_UNIX" "AF_INET" "AF_INET6"];
RestrictRealtime = true;
RestrictSUIDSGID = true;
SystemCallArchitectures = "native";
};
restartTriggers = [settingsFileUnsubstituted];
};
# Lecture de la registration par synapse (fichier 0640, groupe du bridge).
services.matrix-synapse = {
settings.app_service_config_files = [registrationFile];
};
systemd.services.matrix-synapse.serviceConfig.SupplementaryGroups = ["mautrix-linkedin"];
# Après déploiement, connexion (à faire par marc) : DM à
# @linkedinbot:matrix.marcpartensky.com, `login`, puis identifiants LinkedIn.
}
+85
View File
@@ -0,0 +1,85 @@
# services/matrix-meta/default.nix
# Ponts Matrix <-> Instagram et Matrix <-> Messenger (mautrix-meta : un même
# binaire sert les deux réseaux, une instance par réseau), serveur : ../matrix
{
pkgs,
lib,
...
}: let
# Clé e2ee stable, hors du store nix. Le défaut du module est la constante
# publique "mautrix.bridge.e2ee" : la DB crypto serait chiffrée avec une clé
# connue de tous, donc on génère la nôtre au premier démarrage.
mkPickleKey = instance: ''
if [ ! -f /var/lib/mautrix-meta-${instance}/pickle_key.txt ]; then
${pkgs.openssl}/bin/openssl rand -hex 32 > /var/lib/mautrix-meta-${instance}/pickle_key.txt
chmod 600 /var/lib/mautrix-meta-${instance}/pickle_key.txt
fi
export ENCRYPTION_PICKLE_KEY=$(cat /var/lib/mautrix-meta-${instance}/pickle_key.txt)
'';
homeserver = {
address = "http://127.0.0.1:8008";
domain = "matrix.marcpartensky.com";
};
permissions = {
"matrix.marcpartensky.com" = "user";
"@marc:matrix.marcpartensky.com" = "admin";
};
in {
services.mautrix-meta.instances = {
# bot : @instagrambot:matrix.marcpartensky.com (port 29320 du preset)
instagram = {
enable = true;
settings = {
inherit homeserver;
appservice.hostname = "127.0.0.1";
bridge.permissions = permissions;
encryption.pickle_key = "$ENCRYPTION_PICKLE_KEY";
};
};
# bot : @messengerbot:matrix.marcpartensky.com
# Le module n'a de preset que pour instagram et facebook, donc tout est
# explicite ici (mode messenger = API de messenger.com).
messenger = {
enable = true;
settings = {
network.mode = "messenger";
inherit homeserver;
appservice = {
id = "messenger";
hostname = "127.0.0.1";
port = 29322;
bot = {
username = "messengerbot";
displayname = "Messenger bridge bot";
avatar = "mxc://maunium.net/ygtkteZsXnGJLJHRchUwYWak";
};
};
bridge.permissions = permissions;
encryption.pickle_key = "$ENCRYPTION_PICKLE_KEY";
};
};
};
# Le envsubst qui écrit config.yaml vit dans l'unité de registration (pas dans
# le preStart du bridge) : la clé doit être exportée DANS ce script, comme
# pour matrix-discord.
systemd.services = {
mautrix-meta-instagram-registration.script = lib.mkBefore (mkPickleKey "instagram");
mautrix-meta-messenger-registration.script = lib.mkBefore (mkPickleKey "messenger");
};
# Après déploiement, connexion (à faire par marc), une fois par réseau :
# 1. DM au bot (@instagrambot ou @messengerbot)
# 2. envoyer : login
# 3. le bridge demande des cookies : se connecter normalement dans une
# fenêtre privée sur instagram.com ou messenger.com, devtools + onglet
# réseau + filtre XHR graphql, clic droit sur une requête, "Copy as cURL
# (POSIX)", et coller le tout dans le DM.
# Sinon, cookies utiles : Instagram sessionid, csrftoken, mid, ig_did,
# ds_user_id ; Facebook/Messenger datr, c_user, sb, xs.
# Meta peut bloquer un compte jugé suspect (captcha / téléphone) : active la
# double authentification pour limiter le risque.
}
+8 -1
View File
@@ -28,7 +28,14 @@ in {
};
};
# pickle key e2ee : valeur stable hors du store nix, cf. preStart.
encryption.pickle_key = "$ENCRYPTION_PICKLE_KEY";
# allow/default à true = portails chiffrés par défaut (require=false pour
# ne pas casser une salle non chiffrable).
encryption = {
allow = true;
default = true;
require = false;
pickle_key = "$ENCRYPTION_PICKLE_KEY";
};
};
# Le bridge génère sa registration dans SON preStart : c'est synapse qui
+73
View File
@@ -0,0 +1,73 @@
# services/matrix-telegram/default.nix
# Pont Matrix <-> Telegram (mautrix-telegram, bridge Python historique),
# serveur : ../matrix
#
# PAS ENCORE ACTIVÉ, volontairement : il manque api_id et api_hash, qui
# s'obtiennent uniquement avec un compte Telegram (https://my.telegram.org,
# section "API development tools", connexion avec son numéro de téléphone).
# api_id est un identifiant public, api_hash est le secret.
#
# Pour activer, trois choses :
# 1. renseigner api_id ci-dessous (nombre, pas un secret) ;
# 2. créer secrets/telegram.yml avec la clé telegram_api_hash (fichier neuf :
# chiffrable avec les seules clés publiques de .sops.yaml) et ajouter la
# creation_rule correspondante dans .sops.yaml ;
# 3. décommenter ./matrix-telegram dans services/default.nix.
{
config,
lib,
...
}: {
sops.secrets.telegram_api_hash = {
sopsFile = ../../secrets/telegram.yml;
owner = "mautrix-telegram";
mode = "0400";
restartUnits = ["mautrix-telegram.service"];
};
# Le nix store ne contient que la référence $TELEGRAM_API_HASH, la valeur est
# injectée par envsubst au démarrage (preStart du module), depuis ce fichier.
sops.templates."matrix-telegram.env" = {
content = ''
TELEGRAM_API_HASH=${config.sops.placeholder.telegram_api_hash}
'';
owner = "mautrix-telegram";
group = "mautrix-telegram";
mode = "0400";
};
services.mautrix-telegram = {
enable = true;
environmentFile = config.sops.templates."matrix-telegram.env".path;
settings = {
homeserver = {
address = "http://127.0.0.1:8008";
domain = "matrix.marcpartensky.com";
};
appservice = {
hostname = "127.0.0.1";
# défaut du module : 8080, à éviter (déjà pris par jupyterhub sur tower)
port = 29317;
};
bridge.permissions = {
"matrix.marcpartensky.com" = "full";
"@marc:matrix.marcpartensky.com" = "admin";
};
telegram = {
# TODO: nombre récupéré sur my.telegram.org
api_id = 0;
api_hash = "$TELEGRAM_API_HASH";
};
};
# La registration est générée dans le preStart du bridge, donc c'est synapse
# qui doit l'attendre (cf. services/matrix), pas l'inverse.
serviceDependencies = lib.mkForce [];
};
# Après déploiement, connexion (à faire par marc) : DM à
# @telegrambot:matrix.marcpartensky.com, `login`, puis suivre les instructions
# (numéro de téléphone, code reçu dans Telegram, mot de passe 2FA si activé).
}
+2 -2
View File
@@ -66,8 +66,8 @@ in {
# synapse doit démarrer APRÈS eux sinon app_service_config_files pointe sur
# un fichier qui n'existe pas encore et synapse refuse de démarrer.
# (mautrix-discord gère ça tout seul via mautrix-discord-registration.)
after = ["mautrix-whatsapp.service" "mautrix-signal.service" "matrix-synapse-db.service"];
wants = ["mautrix-whatsapp.service" "mautrix-signal.service"];
after = ["mautrix-whatsapp.service" "mautrix-signal.service" "mautrix-linkedin.service" "matrix-synapse-db.service"];
wants = ["mautrix-whatsapp.service" "mautrix-signal.service" "mautrix-linkedin.service"];
requires = ["matrix-synapse-db.service"];
# mkBefore : tourne avant le --generate-keys du module
preStart = lib.mkBefore ''