Self-host your own digital island

matrix bridges

+8 -1
flake.nix
···
outputs = { self, nixpkgs, ... }@inputs: {
nixosModules.default = {
-
imports = [ ./modules/default.nix ];
+
imports = [
+
./modules/default.nix
+
({ config, ... }: {
+
nixpkgs.overlays = [ (final: prev: {
+
mautrix-meta = (prev.callPackage ./pkgs/mautrix-meta.nix { });
+
}) ];
+
})
+
];
};
defaultTemplate.path = ./template;
};
+4 -1
modules/default.nix
···
./mailserver.nix
./gitea.nix
./dns.nix
-
./matrix.nix
+
./matrix/synapse.nix
+
./matrix/mautrix-signal.nix
+
./matrix/mautrix-instagram.nix
+
./matrix/mautrix-messenger.nix
./turn.nix
./headscale.nix
./wireguard/server.nix
-169
modules/matrix.nix
···
-
{ config, pkgs, lib, ... }:
-
-
with lib;
-
let
-
cfg = config.eilean;
-
turnSharedSecretFile = "/run/matrix-synapse/turn-shared-secret";
-
in
-
{
-
options.eilean.matrix = {
-
enable = mkEnableOption "matrix";
-
turn = mkOption {
-
type = types.bool;
-
default = true;
-
};
-
registrationSecretFile = mkOption {
-
type = types.nullOr types.str;
-
default = null;
-
};
-
};
-
-
config = mkIf cfg.matrix.enable {
-
services.postgresql.enable = true;
-
services.postgresql.package = pkgs.postgresql_13;
-
services.postgresql.initialScript = pkgs.writeText "synapse-init.sql" ''
-
CREATE ROLE "matrix-synapse" WITH LOGIN PASSWORD 'synapse';
-
CREATE DATABASE "matrix-synapse" WITH OWNER "matrix-synapse"
-
TEMPLATE template0
-
LC_COLLATE = "C"
-
LC_CTYPE = "C";
-
'';
-
-
services.nginx = {
-
enable = true;
-
# only recommendedProxySettings and recommendedGzipSettings are strictly required,
-
# but the rest make sense as well
-
recommendedTlsSettings = true;
-
recommendedOptimisation = true;
-
recommendedGzipSettings = true;
-
recommendedProxySettings = true;
-
-
virtualHosts = {
-
# This host section can be placed on a different host than the rest,
-
# i.e. to delegate from the host being accessible as ${config.networking.domain}
-
# to another host actually running the Matrix homeserver.
-
"${config.networking.domain}" = {
-
enableACME = true;
-
forceSSL = true;
-
-
locations."= /.well-known/matrix/server".extraConfig =
-
let
-
# use 443 instead of the default 8448 port to unite
-
# the client-server and server-server port for simplicity
-
server = { "m.server" = "matrix.${config.networking.domain}:443"; };
-
in ''
-
default_type application/json;
-
return 200 '${builtins.toJSON server}';
-
'';
-
locations."= /.well-known/matrix/client".extraConfig =
-
let
-
client = {
-
"m.homeserver" = { "base_url" = "https://matrix.${config.networking.domain}"; };
-
"m.identity_server" = { "base_url" = "https://vector.im"; };
-
};
-
# ACAO required to allow element-web on any URL to request this json file
-
# set other headers due to https://github.com/yandex/gixy/blob/master/docs/en/plugins/addheaderredefinition.md
-
in ''
-
default_type application/json;
-
add_header Access-Control-Allow-Origin *;
-
add_header Strict-Transport-Security max-age=31536000 always;
-
add_header X-Frame-Options SAMEORIGIN always;
-
add_header X-Content-Type-Options nosniff always;
-
add_header Content-Security-Policy "default-src 'self'; base-uri 'self'; frame-src 'self'; frame-ancestors 'self'; form-action 'self';" always;
-
add_header Referrer-Policy 'same-origin';
-
return 200 '${builtins.toJSON client}';
-
'';
-
};
-
-
# Reverse proxy for Matrix client-server and server-server communication
-
"matrix.${config.networking.domain}" = {
-
enableACME = true;
-
forceSSL = true;
-
-
# Or do a redirect instead of the 404, or whatever is appropriate for you.
-
# But do not put a Matrix Web client here! See the Element web section below.
-
locations."/".extraConfig = ''
-
return 404;
-
'';
-
-
# forward all Matrix API calls to the synapse Matrix homeserver
-
locations."/_matrix" = {
-
proxyPass = "http://127.0.0.1:8008"; # without a trailing /
-
#proxyPassReverse = "http://127.0.0.1:8008"; # without a trailing /
-
};
-
};
-
};
-
};
-
-
services.matrix-synapse = {
-
enable = true;
-
settings = mkMerge [
-
{
-
server_name = config.networking.domain;
-
enable_registration = true;
-
registration_requires_token = true;
-
registration_shared_secret_path = cfg.matrix.registrationSecretFile;
-
listeners = [
-
{
-
port = 8008;
-
bind_addresses = [ "::1" "127.0.0.1" ];
-
type = "http";
-
tls = false;
-
x_forwarded = true;
-
resources = [
-
{
-
names = [ "client" "federation" ];
-
compress = false;
-
}
-
];
-
}
-
];
-
max_upload_size = "100M";
-
}
-
(mkIf cfg.matrix.turn {
-
turn_uris = with config.services.coturn; [
-
"turn:${realm}:3478?transport=udp"
-
"turn:${realm}:3478?transport=tcp"
-
"turns:${realm}:5349?transport=udp"
-
"turns:${realm}:5349?transport=tcp"
-
];
-
turn_user_lifetime = "1h";
-
})
-
];
-
extraConfigFiles = mkIf cfg.matrix.turn (
-
[ turnSharedSecretFile ]
-
);
-
};
-
-
systemd.services = mkIf cfg.matrix.turn {
-
matrix-synapse-turn-shared-secret-generator = {
-
description = "Generate matrix synapse turn shared secret config file";
-
script = ''
-
mkdir -p "$(dirname '${turnSharedSecretFile}')"
-
echo "turn_shared_secret: $(cat '${config.services.coturn.static-auth-secret-file}')" > '${turnSharedSecretFile}'
-
chmod 770 '${turnSharedSecretFile}'
-
chown ${config.systemd.services.matrix-synapse.serviceConfig.User}:${config.systemd.services.matrix-synapse.serviceConfig.Group} '${turnSharedSecretFile}'
-
'';
-
serviceConfig.Type = "oneshot";
-
serviceConfig.RemainAfterExit = true;
-
after = [ "coturn-static-auth-secret-generator.service" ];
-
requires = [ "coturn-static-auth-secret-generator.service" ];
-
};
-
"matrix-synapse" = {
-
after = [ "matrix-synapse-turn-shared-secret-generator.service" ];
-
requires = [ "matrix-synapse-turn-shared-secret-generator.service" ];
-
};
-
};
-
-
eilean.turn.enable = mkIf cfg.matrix.turn true;
-
-
eilean.dns.enable = true;
-
eilean.services.dns.zones.${config.networking.domain}.records = [
-
{
-
name = "matrix";
-
type = "CNAME";
-
data = "vps";
-
}
-
];
-
};
-
}
+192
modules/matrix/mautrix-instagram.nix
···
+
{
+
lib,
+
config,
+
pkgs,
+
...
+
}: let
+
cfg = config.services.mautrix-instagram;
+
dataDir = "/var/lib/mautrix-instagram";
+
registrationFile = "${dataDir}/instagram-registration.yaml";
+
settingsFile = "${dataDir}/config.json";
+
settingsFileUnsubstituted = settingsFormat.generate "mautrix-instagram-config-unsubstituted.json" cfg.settings;
+
settingsFormat = pkgs.formats.json {};
+
appservicePort = 29319;
+
+
mkDefaults = lib.mapAttrsRecursive (n: v: lib.mkDefault v);
+
defaultConfig = {
+
homeserver.address = "http://localhost:8448";
+
meta.mode = "instagram";
+
appservice = {
+
hostname = "[::]";
+
port = appservicePort;
+
database.type = "sqlite3";
+
database.uri = "${dataDir}/mautrix-instagram.db";
+
id = "instagram";
+
bot.username = "instagrambot";
+
bot.displayname = "Instagram Bridge Bot";
+
bot.avatar = "mxc://maunium.net/JxjlbZUlCPULEeHZSwleUXQv";
+
as_token = "";
+
hs_token = "";
+
};
+
bridge = {
+
username_template = "instagram_{{.}}";
+
double_puppet_server_map = {};
+
login_shared_secret_map = {};
+
permissions."*" = "relay";
+
relay.enabled = true;
+
};
+
logging = {
+
min_level = "info";
+
writers = lib.singleton {
+
type = "stdout";
+
format = "pretty-colored";
+
time_format = " ";
+
};
+
};
+
};
+
+
in {
+
options.services.mautrix-instagram = {
+
enable = lib.mkEnableOption (lib.mdDoc "mautrix-instagram, a puppeting/relaybot bridge between Matrix and Instagram.");
+
+
settings = lib.mkOption {
+
type = settingsFormat.type;
+
default = defaultConfig;
+
description = lib.mdDoc ''
+
{file}`config.yaml` configuration as a Nix attribute set.
+
Configuration options should match those described in
+
[example-config.yaml](https://github.com/mautrix/instagram/blob/master/example-config.yaml).
+
'';
+
example = {
+
appservice = {
+
database = {
+
type = "postgres";
+
uri = "postgresql:///mautrix_instagram?host=/run/postgresql";
+
};
+
id = "instagram";
+
ephemeral_events = false;
+
};
+
bridge = {
+
history_sync = {
+
request_full_sync = true;
+
};
+
private_chat_portal_meta = true;
+
mute_bridging = true;
+
encryption = {
+
allow = true;
+
default = true;
+
require = true;
+
};
+
provisioning = {
+
shared_secret = "disable";
+
};
+
permissions = {
+
"example.com" = "user";
+
};
+
};
+
};
+
};
+
+
serviceDependencies = lib.mkOption {
+
type = with lib.types; listOf str;
+
default = lib.optional config.services.matrix-synapse.enable config.services.matrix-synapse.serviceUnit;
+
defaultText = lib.literalExpression ''
+
optional config.services.matrix-synapse.enable config.services.matrix-synapse.serviceUnits
+
'';
+
description = lib.mdDoc ''
+
List of Systemd services to require and wait for when starting the application service.
+
'';
+
};
+
};
+
+
config = lib.mkIf cfg.enable {
+
+
users.users.mautrix-instagram = {
+
isSystemUser = true;
+
group = "mautrix-instagram";
+
home = dataDir;
+
description = "Mautrix-Instagram bridge user";
+
};
+
+
users.groups.mautrix-instagram = {};
+
+
services.mautrix-instagram.settings = lib.mkMerge (map mkDefaults [
+
defaultConfig
+
# Note: this is defined here to avoid the docs depending on `config`
+
{ homeserver.domain = config.services.matrix-synapse.settings.server_name; }
+
]);
+
+
systemd.services.mautrix-instagram = {
+
description = "Mautrix-Instagram Service - A Instagram bridge for Matrix";
+
+
wantedBy = ["multi-user.target"];
+
wants = ["network-online.target"] ++ cfg.serviceDependencies;
+
after = ["network-online.target"] ++ cfg.serviceDependencies;
+
+
preStart = ''
+
# substitute the settings file by environment variables
+
# in this case read from EnvironmentFile
+
test -f '${settingsFile}' && rm -f '${settingsFile}'
+
old_umask=$(umask)
+
umask 0177
+
${pkgs.envsubst}/bin/envsubst \
+
-o '${settingsFile}' \
+
-i '${settingsFileUnsubstituted}'
+
umask $old_umask
+
+
# generate the appservice's registration file if absent
+
if [ ! -f '${registrationFile}' ]; then
+
${pkgs.mautrix-meta}/bin/mautrix-meta \
+
--generate-registration \
+
--config='${settingsFile}' \
+
--registration='${registrationFile}'
+
fi
+
chmod 640 ${registrationFile}
+
+
umask 0177
+
${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 = {
+
User = "mautrix-instagram";
+
Group = "mautrix-instagram";
+
StateDirectory = baseNameOf dataDir;
+
WorkingDirectory = dataDir;
+
ExecStart = ''
+
${pkgs.mautrix-meta}/bin/mautrix-meta \
+
--config='${settingsFile}' \
+
--registration='${registrationFile}'
+
'';
+
LockPersonality = true;
+
MemoryDenyWriteExecute = true;
+
NoNewPrivileges = true;
+
PrivateDevices = true;
+
PrivateTmp = true;
+
PrivateUsers = true;
+
ProtectClock = true;
+
ProtectControlGroups = true;
+
ProtectHome = true;
+
ProtectHostname = true;
+
ProtectKernelLogs = true;
+
ProtectKernelModules = true;
+
ProtectKernelTunables = true;
+
ProtectSystem = "strict";
+
Restart = "on-failure";
+
RestartSec = "30s";
+
RestrictRealtime = true;
+
RestrictSUIDSGID = true;
+
SystemCallArchitectures = "native";
+
SystemCallErrorNumber = "EPERM";
+
SystemCallFilter = ["@system-service"];
+
Type = "simple";
+
UMask = 0027;
+
};
+
restartTriggers = [settingsFileUnsubstituted];
+
};
+
};
+
}
+192
modules/matrix/mautrix-messenger.nix
···
+
{
+
lib,
+
config,
+
pkgs,
+
...
+
}: let
+
cfg = config.services.mautrix-messenger;
+
dataDir = "/var/lib/mautrix-messenger";
+
registrationFile = "${dataDir}/messenger-registration.yaml";
+
settingsFile = "${dataDir}/config.json";
+
settingsFileUnsubstituted = settingsFormat.generate "mautrix-messenger-config-unsubstituted.json" cfg.settings;
+
settingsFormat = pkgs.formats.json {};
+
appservicePort = 29320;
+
+
mkDefaults = lib.mapAttrsRecursive (n: v: lib.mkDefault v);
+
defaultConfig = {
+
homeserver.address = "http://localhost:8448";
+
meta.mode = "messenger";
+
appservice = {
+
hostname = "[::]";
+
port = appservicePort;
+
database.type = "sqlite3";
+
database.uri = "${dataDir}/mautrix-messenger.db";
+
id = "messenger";
+
bot.username = "messengerbot";
+
bot.displayname = "Messenger Bridge Bot";
+
bot.avatar = "mxc://maunium.net/ygtkteZsXnGJLJHRchUwYWak";
+
as_token = "";
+
hs_token = "";
+
};
+
bridge = {
+
username_template = "messenger_{{.}}";
+
double_puppet_server_map = {};
+
login_shared_secret_map = {};
+
permissions."*" = "relay";
+
relay.enabled = true;
+
};
+
logging = {
+
min_level = "info";
+
writers = lib.singleton {
+
type = "stdout";
+
format = "pretty-colored";
+
time_format = " ";
+
};
+
};
+
};
+
+
in {
+
options.services.mautrix-messenger = {
+
enable = lib.mkEnableOption (lib.mdDoc "mautrix-messenger, a puppeting/relaybot bridge between Matrix and Messenger.");
+
+
settings = lib.mkOption {
+
type = settingsFormat.type;
+
default = defaultConfig;
+
description = lib.mdDoc ''
+
{file}`config.yaml` configuration as a Nix attribute set.
+
Configuration options should match those described in
+
[example-config.yaml](https://github.com/mautrix/messenger/blob/master/example-config.yaml).
+
'';
+
example = {
+
appservice = {
+
database = {
+
type = "postgres";
+
uri = "postgresql:///mautrix_messenger?host=/run/postgresql";
+
};
+
id = "messenger";
+
ephemeral_events = false;
+
};
+
bridge = {
+
history_sync = {
+
request_full_sync = true;
+
};
+
private_chat_portal_meta = true;
+
mute_bridging = true;
+
encryption = {
+
allow = true;
+
default = true;
+
require = true;
+
};
+
provisioning = {
+
shared_secret = "disable";
+
};
+
permissions = {
+
"example.com" = "user";
+
};
+
};
+
};
+
};
+
+
serviceDependencies = lib.mkOption {
+
type = with lib.types; listOf str;
+
default = lib.optional config.services.matrix-synapse.enable config.services.matrix-synapse.serviceUnit;
+
defaultText = lib.literalExpression ''
+
optional config.services.matrix-synapse.enable config.services.matrix-synapse.serviceUnits
+
'';
+
description = lib.mdDoc ''
+
List of Systemd services to require and wait for when starting the application service.
+
'';
+
};
+
};
+
+
config = lib.mkIf cfg.enable {
+
+
users.users.mautrix-messenger = {
+
isSystemUser = true;
+
group = "mautrix-messenger";
+
home = dataDir;
+
description = "Mautrix-Messenger bridge user";
+
};
+
+
users.groups.mautrix-messenger = {};
+
+
services.mautrix-messenger.settings = lib.mkMerge (map mkDefaults [
+
defaultConfig
+
# Note: this is defined here to avoid the docs depending on `config`
+
{ homeserver.domain = config.services.matrix-synapse.settings.server_name; }
+
]);
+
+
systemd.services.mautrix-messenger = {
+
description = "Mautrix-Messenger Service - A Messenger bridge for Matrix";
+
+
wantedBy = ["multi-user.target"];
+
wants = ["network-online.target"] ++ cfg.serviceDependencies;
+
after = ["network-online.target"] ++ cfg.serviceDependencies;
+
+
preStart = ''
+
# substitute the settings file by environment variables
+
# in this case read from EnvironmentFile
+
test -f '${settingsFile}' && rm -f '${settingsFile}'
+
old_umask=$(umask)
+
umask 0177
+
${pkgs.envsubst}/bin/envsubst \
+
-o '${settingsFile}' \
+
-i '${settingsFileUnsubstituted}'
+
umask $old_umask
+
+
# generate the appservice's registration file if absent
+
if [ ! -f '${registrationFile}' ]; then
+
${pkgs.mautrix-meta}/bin/mautrix-meta \
+
--generate-registration \
+
--config='${settingsFile}' \
+
--registration='${registrationFile}'
+
fi
+
chmod 640 ${registrationFile}
+
+
umask 0177
+
${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 = {
+
User = "mautrix-messenger";
+
Group = "mautrix-messenger";
+
StateDirectory = baseNameOf dataDir;
+
WorkingDirectory = dataDir;
+
ExecStart = ''
+
${pkgs.mautrix-meta}/bin/mautrix-meta \
+
--config='${settingsFile}' \
+
--registration='${registrationFile}'
+
'';
+
LockPersonality = true;
+
MemoryDenyWriteExecute = true;
+
NoNewPrivileges = true;
+
PrivateDevices = true;
+
PrivateTmp = true;
+
PrivateUsers = true;
+
ProtectClock = true;
+
ProtectControlGroups = true;
+
ProtectHome = true;
+
ProtectHostname = true;
+
ProtectKernelLogs = true;
+
ProtectKernelModules = true;
+
ProtectKernelTunables = true;
+
ProtectSystem = "strict";
+
Restart = "on-failure";
+
RestartSec = "30s";
+
RestrictRealtime = true;
+
RestrictSUIDSGID = true;
+
SystemCallArchitectures = "native";
+
SystemCallErrorNumber = "EPERM";
+
SystemCallFilter = ["@system-service"];
+
Type = "simple";
+
UMask = 0027;
+
};
+
restartTriggers = [settingsFileUnsubstituted];
+
};
+
};
+
}
+200
modules/matrix/mautrix-signal.nix
···
+
{
+
lib,
+
config,
+
pkgs,
+
...
+
}: let
+
cfg = config.services.mautrix-signal;
+
dataDir = "/var/lib/mautrix-signal";
+
registrationFile = "${dataDir}/signal-registration.yaml";
+
settingsFile = "${dataDir}/config.json";
+
settingsFileUnsubstituted = settingsFormat.generate "mautrix-signal-config-unsubstituted.json" cfg.settings;
+
settingsFormat = pkgs.formats.json {};
+
appservicePort = 29328;
+
+
mkDefaults = lib.mapAttrsRecursive (n: v: lib.mkDefault v);
+
defaultConfig = {
+
homeserver.address = "http://localhost:8448";
+
signal = {
+
socket_path = config.services.signald.socketPath;
+
outgoing_attachment_dir = "/var/lib/signald/tmp";
+
};
+
appservice = {
+
hostname = "[::]";
+
port = appservicePort;
+
database.type = "sqlite3";
+
database.uri = "${dataDir}/mautrix-signal.db";
+
id = "signal";
+
bot.username = "signalbot";
+
bot.displayname = "Signal Bridge Bot";
+
as_token = "";
+
hs_token = "";
+
};
+
bridge = {
+
username_template = "signal_{{.}}";
+
double_puppet_server_map = {};
+
login_shared_secret_map = {};
+
permissions."*" = "relay";
+
};
+
logging = {
+
min_level = "info";
+
writers = lib.singleton {
+
type = "stdout";
+
format = "pretty-colored";
+
time_format = " ";
+
};
+
};
+
};
+
+
in {
+
options.services.mautrix-signal = {
+
enable = lib.mkEnableOption (lib.mdDoc "mautrix-signal, a puppeting/relaybot bridge between Matrix and Signal.");
+
+
settings = lib.mkOption {
+
type = settingsFormat.type;
+
default = defaultConfig;
+
description = lib.mdDoc ''
+
{file}`config.yaml` configuration as a Nix attribute set.
+
Configuration options should match those described in
+
[example-config.yaml](https://github.com/mautrix/signal/blob/master/example-config.yaml).
+
'';
+
example = {
+
appservice = {
+
database = {
+
type = "postgres";
+
uri = "postgresql:///mautrix_signal?host=/run/postgresql";
+
};
+
id = "signal";
+
ephemeral_events = false;
+
};
+
bridge = {
+
history_sync = {
+
request_full_sync = true;
+
};
+
private_chat_portal_meta = true;
+
mute_bridging = true;
+
encryption = {
+
allow = true;
+
default = true;
+
require = true;
+
};
+
provisioning = {
+
shared_secret = "disable";
+
};
+
permissions = {
+
"example.com" = "user";
+
};
+
};
+
};
+
};
+
+
serviceDependencies = lib.mkOption {
+
type = with lib.types; listOf str;
+
default = lib.optional config.services.matrix-synapse.enable config.services.matrix-synapse.serviceUnit;
+
defaultText = lib.literalExpression ''
+
optional config.services.matrix-synapse.enable config.services.matrix-synapse.serviceUnits
+
'';
+
description = lib.mdDoc ''
+
List of Systemd services to require and wait for when starting the application service.
+
'';
+
};
+
};
+
+
config = lib.mkIf cfg.enable {
+
+
services.signald.enable = true;
+
+
users.users.mautrix-signal = {
+
isSystemUser = true;
+
group = "mautrix-signal";
+
home = dataDir;
+
description = "Mautrix-Signal bridge user";
+
};
+
+
users.groups.mautrix-signal = {};
+
+
services.mautrix-signal.settings = lib.mkMerge (map mkDefaults [
+
defaultConfig
+
# Note: this is defined here to avoid the docs depending on `config`
+
{ homeserver.domain = config.services.matrix-synapse.settings.server_name; }
+
]);
+
+
systemd.services.mautrix-signal = {
+
description = "Mautrix-Signal Service - A Signal bridge for Matrix";
+
+
requires = [ "signald.service" ];
+
# voice messages need `ffmpeg`
+
path = [ pkgs.ffmpeg ];
+
+
wantedBy = ["multi-user.target"];
+
wants = ["network-online.target"] ++ cfg.serviceDependencies;
+
after = ["network-online.target" "signald.service"] ++ cfg.serviceDependencies;
+
+
preStart = ''
+
# substitute the settings file by environment variables
+
# in this case read from EnvironmentFile
+
test -f '${settingsFile}' && rm -f '${settingsFile}'
+
old_umask=$(umask)
+
umask 0177
+
${pkgs.envsubst}/bin/envsubst \
+
-o '${settingsFile}' \
+
-i '${settingsFileUnsubstituted}'
+
umask $old_umask
+
+
# generate the appservice's registration file if absent
+
if [ ! -f '${registrationFile}' ]; then
+
${pkgs.mautrix-signal}/bin/mautrix-signal \
+
--generate-registration \
+
--config='${settingsFile}' \
+
--registration='${registrationFile}'
+
fi
+
chmod 640 ${registrationFile}
+
+
umask 0177
+
${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 = {
+
SupplementaryGroups = [ "signald" ];
+
User = "mautrix-signal";
+
Group = "mautrix-signal";
+
StateDirectory = baseNameOf dataDir;
+
WorkingDirectory = dataDir;
+
ExecStart = ''
+
${pkgs.mautrix-signal}/bin/mautrix-signal \
+
--config='${settingsFile}' \
+
--registration='${registrationFile}'
+
'';
+
LockPersonality = true;
+
MemoryDenyWriteExecute = true;
+
NoNewPrivileges = true;
+
PrivateDevices = true;
+
PrivateTmp = true;
+
PrivateUsers = true;
+
ProtectClock = true;
+
ProtectControlGroups = true;
+
ProtectHome = true;
+
ProtectHostname = true;
+
ProtectKernelLogs = true;
+
ProtectKernelModules = true;
+
ProtectKernelTunables = true;
+
ProtectSystem = "strict";
+
Restart = "on-failure";
+
RestartSec = "30s";
+
RestrictRealtime = true;
+
RestrictSUIDSGID = true;
+
SystemCallArchitectures = "native";
+
SystemCallErrorNumber = "EPERM";
+
SystemCallFilter = ["@system-service"];
+
Type = "simple";
+
UMask = 0027;
+
};
+
restartTriggers = [settingsFileUnsubstituted];
+
};
+
};
+
}
+238
modules/matrix/synapse.nix
···
+
{ config, pkgs, lib, ... }:
+
+
with lib;
+
let
+
cfg = config.eilean;
+
turnSharedSecretFile = "/run/matrix-synapse/turn-shared-secret";
+
in
+
{
+
options.eilean.matrix = {
+
enable = mkEnableOption "matrix";
+
turn = mkOption {
+
type = types.bool;
+
default = true;
+
};
+
registrationSecretFile = mkOption {
+
type = types.nullOr types.str;
+
default = null;
+
};
+
bridges = {
+
whatsapp = mkOption {
+
type = types.bool;
+
default = false;
+
description = "Enable WhatsApp bridge.";
+
};
+
signal = mkOption {
+
type = types.bool;
+
default = false;
+
description = "Enable Signal bridge.";
+
};
+
instagram = mkOption {
+
type = types.bool;
+
default = false;
+
description = "Enable Instagram bridge.";
+
};
+
messenger = mkOption {
+
type = types.bool;
+
default = false;
+
description = "Enable Facebook Messenger bridge.";
+
};
+
};
+
};
+
+
config = mkIf cfg.matrix.enable {
+
services.postgresql.enable = true;
+
services.postgresql.package = pkgs.postgresql_13;
+
services.postgresql.initialScript = pkgs.writeText "synapse-init.sql" ''
+
CREATE ROLE "matrix-synapse" WITH LOGIN PASSWORD 'synapse';
+
CREATE DATABASE "matrix-synapse" WITH OWNER "matrix-synapse"
+
TEMPLATE template0
+
LC_COLLATE = "C"
+
LC_CTYPE = "C";
+
'';
+
+
services.nginx = {
+
enable = true;
+
# only recommendedProxySettings and recommendedGzipSettings are strictly required,
+
# but the rest make sense as well
+
recommendedTlsSettings = true;
+
recommendedOptimisation = true;
+
recommendedGzipSettings = true;
+
recommendedProxySettings = true;
+
+
virtualHosts = {
+
# This host section can be placed on a different host than the rest,
+
# i.e. to delegate from the host being accessible as ${config.networking.domain}
+
# to another host actually running the Matrix homeserver.
+
"${config.networking.domain}" = {
+
enableACME = true;
+
forceSSL = true;
+
+
locations."= /.well-known/matrix/server".extraConfig =
+
let
+
# use 443 instead of the default 8448 port to unite
+
# the client-server and server-server port for simplicity
+
server = { "m.server" = "matrix.${config.networking.domain}:443"; };
+
in ''
+
default_type application/json;
+
return 200 '${builtins.toJSON server}';
+
'';
+
locations."= /.well-known/matrix/client".extraConfig =
+
let
+
client = {
+
"m.homeserver" = { "base_url" = "https://matrix.${config.networking.domain}"; };
+
"m.identity_server" = { "base_url" = "https://vector.im"; };
+
};
+
# ACAO required to allow element-web on any URL to request this json file
+
# set other headers due to https://github.com/yandex/gixy/blob/master/docs/en/plugins/addheaderredefinition.md
+
in ''
+
default_type application/json;
+
add_header Access-Control-Allow-Origin *;
+
add_header Strict-Transport-Security max-age=31536000 always;
+
add_header X-Frame-Options SAMEORIGIN always;
+
add_header X-Content-Type-Options nosniff always;
+
add_header Content-Security-Policy "default-src 'self'; base-uri 'self'; frame-src 'self'; frame-ancestors 'self'; form-action 'self';" always;
+
add_header Referrer-Policy 'same-origin';
+
return 200 '${builtins.toJSON client}';
+
'';
+
};
+
+
# Reverse proxy for Matrix client-server and server-server communication
+
"matrix.${config.networking.domain}" = {
+
enableACME = true;
+
forceSSL = true;
+
+
# Or do a redirect instead of the 404, or whatever is appropriate for you.
+
# But do not put a Matrix Web client here! See the Element web section below.
+
locations."/".extraConfig = ''
+
return 404;
+
'';
+
+
# forward all Matrix API calls to the synapse Matrix homeserver
+
locations."/_matrix" = {
+
proxyPass = "http://127.0.0.1:8008"; # without a trailing /
+
#proxyPassReverse = "http://127.0.0.1:8008"; # without a trailing /
+
};
+
};
+
};
+
};
+
+
services.matrix-synapse = {
+
enable = true;
+
settings = mkMerge [
+
{
+
server_name = config.networking.domain;
+
enable_registration = true;
+
registration_requires_token = true;
+
registration_shared_secret_path = cfg.matrix.registrationSecretFile;
+
listeners = [
+
{
+
port = 8008;
+
bind_addresses = [ "::1" "127.0.0.1" ];
+
type = "http";
+
tls = false;
+
x_forwarded = true;
+
resources = [
+
{
+
names = [ "client" "federation" ];
+
compress = false;
+
}
+
];
+
}
+
];
+
max_upload_size = "100M";
+
app_service_config_files =
+
(optional cfg.matrix.bridges.whatsapp "/var/lib/mautrix-whatsapp/whatsapp-registration.yaml") ++
+
(optional cfg.matrix.bridges.signal "/var/lib/mautrix-signal/signal-registration.yaml") ++
+
(optional cfg.matrix.bridges.instagram "/var/lib/mautrix-instagram/instagram-registration.yaml") ++
+
(optional cfg.matrix.bridges.messenger "/var/lib/mautrix-messenger/messenger-registration.yaml");
+
}
+
(mkIf cfg.matrix.turn {
+
turn_uris = with config.services.coturn; [
+
"turn:${realm}:3478?transport=udp"
+
"turn:${realm}:3478?transport=tcp"
+
"turns:${realm}:5349?transport=udp"
+
"turns:${realm}:5349?transport=tcp"
+
];
+
turn_user_lifetime = "1h";
+
})
+
];
+
extraConfigFiles = mkIf cfg.matrix.turn (
+
[ turnSharedSecretFile ]
+
);
+
};
+
+
systemd.services.matrix-synapse-turn-shared-secret-generator = mkIf cfg.matrix.turn {
+
description = "Generate matrix synapse turn shared secret config file";
+
script = ''
+
mkdir -p "$(dirname '${turnSharedSecretFile}')"
+
echo "turn_shared_secret: $(cat '${config.services.coturn.static-auth-secret-file}')" > '${turnSharedSecretFile}'
+
chmod 770 '${turnSharedSecretFile}'
+
chown ${config.systemd.services.matrix-synapse.serviceConfig.User}:${config.systemd.services.matrix-synapse.serviceConfig.Group} '${turnSharedSecretFile}'
+
'';
+
serviceConfig.Type = "oneshot";
+
serviceConfig.RemainAfterExit = true;
+
after = [ "coturn-static-auth-secret-generator.service" ];
+
requires = [ "coturn-static-auth-secret-generator.service" ];
+
};
+
systemd.services."matrix-synapse".after = mkIf cfg.matrix.turn [ "matrix-synapse-turn-shared-secret-generator.service" ];
+
systemd.services."matrix-synapse".requires = mkIf cfg.matrix.turn [ "matrix-synapse-turn-shared-secret-generator.service" ];
+
+
systemd.services.matrix-synapse.serviceConfig.SupplementaryGroups =
+
(optional cfg.matrix.bridges.whatsapp config.systemd.services.mautrix-whatsapp.serviceConfig.Group) ++
+
(optional cfg.matrix.bridges.signal config.systemd.services.mautrix-signal.serviceConfig.Group) ++
+
(optional cfg.matrix.bridges.instagram config.systemd.services.mautrix-instagram.serviceConfig.Group) ++
+
(optional cfg.matrix.bridges.messenger config.systemd.services.mautrix-messenger.serviceConfig.Group);
+
+
services.mautrix-whatsapp = mkIf cfg.matrix.bridges.whatsapp {
+
enable = true;
+
settings.homeserver.address = "https://matrix.${config.networking.domain}";
+
settings.homeserver.domain = config.networking.domain;
+
settings.appservice.hostname = "localhost";
+
settings.appservice.address = "http://localhost:29318";
+
settings.bridge.personal_filtering_spaces = true;
+
settings.bridge.history_sync.backfill = false;
+
settings.bridge.permissions."@${config.eilean.username}:${config.networking.domain}" = "admin";
+
};
+
services.mautrix-signal = mkIf cfg.matrix.bridges.signal {
+
enable = true;
+
settings.homeserver.address = "https://matrix.${config.networking.domain}";
+
settings.homeserver.domain = config.networking.domain;
+
settings.appservice.hostname = "localhost";
+
settings.appservice.address = "http://localhost:29328";
+
settings.bridge.personal_filtering_spaces = true;
+
settings.bridge.permissions."@${config.eilean.username}:${config.networking.domain}" = "admin";
+
};
+
services.mautrix-instagram = mkIf cfg.matrix.bridges.instagram {
+
enable = true;
+
settings.homeserver.address = "https://matrix.${config.networking.domain}";
+
settings.homeserver.domain = config.networking.domain;
+
settings.appservice.hostname = "localhost";
+
settings.appservice.address = "http://localhost:29319";
+
settings.bridge.personal_filtering_spaces = true;
+
settings.bridge.backfill.enabled = false;
+
settings.bridge.permissions."@${config.eilean.username}:${config.networking.domain}" = "admin";
+
};
+
services.mautrix-messenger = mkIf cfg.matrix.bridges.messenger {
+
enable = true;
+
settings.homeserver.address = "https://matrix.${config.networking.domain}";
+
settings.homeserver.domain = config.networking.domain;
+
settings.appservice.hostname = "localhost";
+
settings.appservice.address = "http://localhost:29320";
+
settings.bridge.personal_filtering_spaces = true;
+
settings.bridge.backfill.enabled = false;
+
settings.bridge.permissions."@${config.eilean.username}:${config.networking.domain}" = "admin";
+
};
+
+
eilean.turn.enable = mkIf cfg.matrix.turn true;
+
+
eilean.dns.enable = true;
+
eilean.services.dns.zones.${config.networking.domain}.records = [
+
{
+
name = "matrix";
+
type = "CNAME";
+
data = "vps";
+
}
+
];
+
};
+
}
+28
pkgs/mautrix-meta.nix
···
+
{ lib, buildGoModule, fetchFromGitHub, olm }:
+
+
buildGoModule rec {
+
name = "mautrix-meta";
+
+
src = fetchFromGitHub {
+
owner = "mautrix";
+
repo = "meta";
+
rev = "7941e937055b792d2cbfde5d9c8c4df75e68ff0a";
+
hash = "sha256-QDqN6AAaEngWo4UxKAyIXB7BwCEJqsMTeuMb2fKu/9o=";
+
};
+
+
buildInputs = [ olm ];
+
+
vendorHash = "sha256-ClHg3OEKgXYsmBm/aFKWZXbaLOmKdNyvw42QGhtTRik=";
+
+
doCheck = false;
+
+
excludedPackages = "cmd/lscli";
+
+
meta = with lib; {
+
homepage = "https://github.com/mautrix/meta";
+
description = " A Matrix-Facebook Messenger and Instagram DM puppeting bridge.";
+
license = licenses.agpl3Plus;
+
mainProgram = "mautrix-meta";
+
};
+
}
+