Matrix packages (mautrix-*, matrix-continuwuity) only exist in nixpkgs-unstable, not in nixpkgs 24.05 stable. This commit updates all module defaults and references to use pkgs-unstable. Changes: - Add pkgs-unstable to module function signatures (4 modules) - Update package option defaults from pkgs.* to pkgs-unstable.* - Configure pkgs-unstable in flake.nix to permit olm-3.2.16 - Add VM config permittedInsecurePackages for olm (mautrix dependency) The olm library is deprecated with known CVEs but required by mautrix bridges. This is acceptable for testing; production should migrate to newer cryptography implementations when available. This maintains our stable base system (NixOS 24.05) while using unstable only for Matrix ecosystem packages under active development.
713 lines
21 KiB
Nix
713 lines
21 KiB
Nix
# mautrix-gmessages Matrix-Google Messages bridge
|
|
# Bridges Google Messages (RCS/SMS/MMS) to Matrix via web interface
|
|
{ config, pkgs, pkgs-unstable, lib, ... }:
|
|
|
|
with lib;
|
|
|
|
let
|
|
cfg = config.services.matrix-vm.mautrix-gmessages;
|
|
yamlFormat = pkgs.formats.yaml { };
|
|
|
|
defaultPermissions = {
|
|
"*" = "relay";
|
|
"${cfg.matrix.serverName}" = "user";
|
|
};
|
|
|
|
effectivePermissions =
|
|
if cfg.bridge.permissions != {} then cfg.bridge.permissions else defaultPermissions;
|
|
|
|
exampleConfigPath = "${cfg.package}/share/mautrix-gmessages/example-config.yaml";
|
|
|
|
configOverrides = {
|
|
network = {
|
|
displayname_template = cfg.network.displaynameTemplate;
|
|
device_meta = {
|
|
os = cfg.network.deviceMeta.os;
|
|
browser = cfg.network.deviceMeta.browser;
|
|
type = cfg.network.deviceMeta.type;
|
|
};
|
|
aggressive_reconnect = cfg.network.aggressiveReconnect;
|
|
initial_chat_sync_count = cfg.network.initialChatSyncCount;
|
|
};
|
|
bridge = {
|
|
command_prefix = cfg.bridge.commandPrefix;
|
|
relay = {
|
|
enabled = cfg.bridge.relay.enabled;
|
|
admin_only = cfg.bridge.relay.adminOnly;
|
|
};
|
|
caption_in_message = cfg.bridge.captionInMessage;
|
|
portal_message_buffer = cfg.bridge.portalMessageBuffer;
|
|
permissions = effectivePermissions;
|
|
};
|
|
database = {
|
|
type = cfg.database.type;
|
|
uri = cfg.database.uri;
|
|
max_open_conns = cfg.database.maxOpenConnections;
|
|
max_idle_conns = cfg.database.maxIdleConnections;
|
|
};
|
|
homeserver = {
|
|
address = cfg.matrix.homeserverUrl;
|
|
domain = cfg.matrix.serverName;
|
|
};
|
|
appservice = {
|
|
address = "http://${cfg.appservice.hostname}:${toString cfg.appservice.port}";
|
|
hostname = cfg.appservice.hostname;
|
|
port = cfg.appservice.port;
|
|
id = cfg.appservice.id;
|
|
bot = {
|
|
username = cfg.appservice.senderLocalpart;
|
|
displayname = cfg.appservice.botDisplayName;
|
|
avatar = cfg.appservice.botAvatar;
|
|
};
|
|
username_template = "${cfg.appservice.userPrefix}{{.}}";
|
|
public_address = cfg.appservice.publicAddress;
|
|
};
|
|
double_puppet = {
|
|
servers = cfg.bridge.additionalDoublePuppetServers;
|
|
allow_discovery = cfg.bridge.allowDiscovery;
|
|
secrets = {};
|
|
};
|
|
encryption = {
|
|
allow = cfg.encryption.enable;
|
|
default = cfg.encryption.default;
|
|
require = cfg.encryption.require;
|
|
require_verification = cfg.encryption.requireVerification;
|
|
allow_key_sharing = cfg.encryption.allowKeySharing;
|
|
};
|
|
};
|
|
|
|
registrationBase = {
|
|
id = cfg.appservice.id;
|
|
url = "http://${cfg.appservice.hostname}:${toString cfg.appservice.port}";
|
|
as_token = "__AS_TOKEN__";
|
|
hs_token = "__HS_TOKEN__";
|
|
sender_localpart = cfg.appservice.senderLocalpart;
|
|
rate_limited = false;
|
|
namespaces = {
|
|
users = [
|
|
{
|
|
regex = "^@${cfg.appservice.senderLocalpart}:${cfg.matrix.serverName}$";
|
|
exclusive = true;
|
|
}
|
|
{
|
|
regex = "^@${cfg.appservice.userPrefix}.*:${cfg.matrix.serverName}$";
|
|
exclusive = true;
|
|
}
|
|
];
|
|
};
|
|
"de.sorunome.msc2409.push_ephemeral" = cfg.appservice.pushEphemeral;
|
|
receive_ephemeral = true;
|
|
};
|
|
|
|
registrationFile = yamlFormat.generate "mautrix-gmessages-registration.yaml" registrationBase;
|
|
|
|
# LoadCredential directives for secure secret injection
|
|
loadCredentials =
|
|
[
|
|
"as_token:${cfg.appservice.asTokenFile}"
|
|
"hs_token:${cfg.appservice.hsTokenFile}"
|
|
]
|
|
++ optional (cfg.appservice.provisioningSharedSecretFile != null)
|
|
"provisioning_shared_secret:${cfg.appservice.provisioningSharedSecretFile}"
|
|
++ optional (cfg.matrix.loginSharedSecretFile != null)
|
|
"login_shared_secret:${cfg.matrix.loginSharedSecretFile}";
|
|
|
|
in
|
|
{
|
|
options.services.matrix-vm.mautrix-gmessages = {
|
|
enable = mkEnableOption "mautrix-gmessages Matrix-Google Messages bridge";
|
|
|
|
package = mkOption {
|
|
type = types.package;
|
|
default = pkgs-unstable.mautrix-gmessages;
|
|
description = "Package providing the bridge executable.";
|
|
};
|
|
|
|
user = mkOption {
|
|
type = types.str;
|
|
default = "mautrix_gmessages";
|
|
description = "System user for the bridge.";
|
|
};
|
|
|
|
group = mkOption {
|
|
type = types.str;
|
|
default = "mautrix_gmessages";
|
|
description = "Primary group for the bridge.";
|
|
};
|
|
|
|
dataDir = mkOption {
|
|
type = types.path;
|
|
default = "/var/lib/mautrix_gmessages";
|
|
description = "State directory.";
|
|
};
|
|
|
|
runtimeDirectory = mkOption {
|
|
type = types.str;
|
|
default = "mautrix_gmessages";
|
|
description = "Runtime directory name under /run.";
|
|
};
|
|
|
|
extraPackages = mkOption {
|
|
type = types.listOf types.package;
|
|
default = [];
|
|
description = "Additional packages exposed to the service PATH.";
|
|
};
|
|
|
|
extraConfig = mkOption {
|
|
type = yamlFormat.type;
|
|
default = {};
|
|
description = "Optional YAML fragment merged into the config.";
|
|
};
|
|
|
|
# Matrix homeserver configuration
|
|
matrix = {
|
|
homeserverUrl = mkOption {
|
|
type = types.str;
|
|
default = "http://127.0.0.1:6167";
|
|
description = "Matrix homeserver URL for bridge connections";
|
|
};
|
|
|
|
serverName = mkOption {
|
|
type = types.str;
|
|
description = "Matrix server name for bridge users";
|
|
};
|
|
|
|
verifySSL = mkOption {
|
|
type = types.bool;
|
|
default = true;
|
|
description = "Verify SSL certificates when connecting to homeserver";
|
|
};
|
|
|
|
asmux = mkOption {
|
|
type = types.bool;
|
|
default = false;
|
|
description = "Enable asmux protocol";
|
|
};
|
|
|
|
loginSharedSecretFile = mkOption {
|
|
type = types.nullOr types.path;
|
|
default = null;
|
|
description = "Shared secret file for automatic double puppeting login";
|
|
};
|
|
};
|
|
|
|
# Database configuration
|
|
database = {
|
|
type = mkOption {
|
|
type = types.str;
|
|
default = "postgres";
|
|
description = "Database type (sqlite3 or postgres)";
|
|
};
|
|
|
|
uri = mkOption {
|
|
type = types.str;
|
|
default = "sqlite:///var/lib/mautrix_gmessages/gmessages.db?_txlock=immediate";
|
|
description = "Database connection string";
|
|
};
|
|
|
|
maxOpenConnections = mkOption {
|
|
type = types.int;
|
|
default = 32;
|
|
description = "Maximum number of open database connections";
|
|
};
|
|
|
|
maxIdleConnections = mkOption {
|
|
type = types.int;
|
|
default = 4;
|
|
description = "Maximum number of idle database connections";
|
|
};
|
|
};
|
|
|
|
# Network configuration (formerly google_messages)
|
|
network = {
|
|
displaynameTemplate = mkOption {
|
|
type = types.str;
|
|
default = "{{or .FullName .PhoneNumber}}";
|
|
description = "Display name template for SMS users";
|
|
};
|
|
|
|
deviceMeta = {
|
|
os = mkOption {
|
|
type = types.str;
|
|
default = "mautrix-gmessages";
|
|
description = "OS name shown in paired devices list";
|
|
};
|
|
|
|
browser = mkOption {
|
|
type = types.enum ["OTHER" "CHROME" "FIREFOX" "SAFARI" "OPERA" "IE" "EDGE"];
|
|
default = "OTHER";
|
|
description = "Browser type shown to phone";
|
|
};
|
|
|
|
type = mkOption {
|
|
type = types.enum ["WEB" "TABLET" "PWA"];
|
|
default = "TABLET";
|
|
description = "Device type - affects icon and session limits";
|
|
};
|
|
};
|
|
|
|
aggressiveReconnect = mkOption {
|
|
type = types.bool;
|
|
default = false;
|
|
description = "Aggressively set bridge as active device if browser opens";
|
|
};
|
|
|
|
initialChatSyncCount = mkOption {
|
|
type = types.int;
|
|
default = 25;
|
|
description = "Number of chats to sync when connecting";
|
|
};
|
|
};
|
|
|
|
# Logging configuration
|
|
logging = {
|
|
level = mkOption {
|
|
type = types.str;
|
|
default = "info";
|
|
description = "Log level (debug, info, warn, error)";
|
|
};
|
|
};
|
|
|
|
# Bridge behavior configuration
|
|
bridge = {
|
|
commandPrefix = mkOption {
|
|
type = types.str;
|
|
default = "!gm";
|
|
description = "Command prefix for bridge commands";
|
|
};
|
|
|
|
permissions = mkOption {
|
|
type = types.attrsOf types.str;
|
|
default = {};
|
|
description = "Bridge permissions mapping";
|
|
};
|
|
|
|
relay = {
|
|
enabled = mkOption {
|
|
type = types.bool;
|
|
default = false;
|
|
description = "Enable relay mode";
|
|
};
|
|
|
|
adminOnly = mkOption {
|
|
type = types.bool;
|
|
default = true;
|
|
description = "Restrict relay to admins only";
|
|
};
|
|
};
|
|
|
|
captionInMessage = mkOption {
|
|
type = types.bool;
|
|
default = false;
|
|
description = "Include captions in message body";
|
|
};
|
|
|
|
portalMessageBuffer = mkOption {
|
|
type = types.int;
|
|
default = 128;
|
|
description = "Portal message buffer size";
|
|
};
|
|
|
|
additionalDoublePuppetServers = mkOption {
|
|
type = types.attrsOf types.str;
|
|
default = {};
|
|
description = "Additional servers for double puppeting";
|
|
};
|
|
|
|
allowDiscovery = mkOption {
|
|
type = types.bool;
|
|
default = true;
|
|
description = "Allow discovery of double puppet servers";
|
|
};
|
|
};
|
|
|
|
# Appservice configuration
|
|
appservice = {
|
|
id = mkOption {
|
|
type = types.str;
|
|
default = "gmessages";
|
|
description = "Appservice ID";
|
|
};
|
|
|
|
hostname = mkOption {
|
|
type = types.str;
|
|
default = "127.0.0.1";
|
|
description = "Hostname for the appservice";
|
|
};
|
|
|
|
port = mkOption {
|
|
type = types.port;
|
|
default = 9556;
|
|
description = "Port for the appservice API";
|
|
};
|
|
|
|
senderLocalpart = mkOption {
|
|
type = types.str;
|
|
default = "gmessagesbot";
|
|
description = "Localpart of the bridge bot user";
|
|
};
|
|
|
|
botDisplayName = mkOption {
|
|
type = types.str;
|
|
default = "Google Messages Bridge Bot";
|
|
description = "Display name for the bridge bot";
|
|
};
|
|
|
|
botAvatar = mkOption {
|
|
type = types.str;
|
|
default = "mxc://maunium.net/ygtkteZsXnGJLJHRchUwYWak";
|
|
description = "Avatar MXC URI for the bridge bot";
|
|
};
|
|
|
|
userPrefix = mkOption {
|
|
type = types.str;
|
|
default = "_gmessages_";
|
|
description = "Prefix for Matrix users bridged from Google Messages";
|
|
};
|
|
|
|
aliasPrefix = mkOption {
|
|
type = types.str;
|
|
default = "gmessages_";
|
|
description = "Prefix for Matrix aliases/rooms";
|
|
};
|
|
|
|
publicAddress = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
description = "Public URL for the bridge (used for media endpoints).";
|
|
};
|
|
|
|
pushEphemeral = mkOption {
|
|
type = types.bool;
|
|
default = true;
|
|
description = "Enable push ephemeral events";
|
|
};
|
|
|
|
asTokenFile = mkOption {
|
|
type = types.path;
|
|
description = "Path to file containing appservice token";
|
|
};
|
|
|
|
hsTokenFile = mkOption {
|
|
type = types.path;
|
|
description = "Path to file containing homeserver token";
|
|
};
|
|
|
|
provisioningSharedSecretFile = mkOption {
|
|
type = types.nullOr types.path;
|
|
default = null;
|
|
description = "Path to file containing provisioning shared secret";
|
|
};
|
|
};
|
|
|
|
# Backfill configuration
|
|
backfill = {
|
|
enable = mkOption {
|
|
type = types.bool;
|
|
default = false;
|
|
description = "Enable message backfilling";
|
|
};
|
|
|
|
maxInitialMessages = mkOption {
|
|
type = types.int;
|
|
default = 50;
|
|
description = "Maximum messages to backfill in empty rooms";
|
|
};
|
|
|
|
maxCatchupMessages = mkOption {
|
|
type = types.int;
|
|
default = 500;
|
|
description = "Maximum missed messages to backfill after restart";
|
|
};
|
|
|
|
unreadHoursThreshold = mkOption {
|
|
type = types.int;
|
|
default = 720;
|
|
description = "Mark old backfilled chats as read after this many hours";
|
|
};
|
|
};
|
|
|
|
# Encryption configuration
|
|
encryption = {
|
|
enable = mkOption {
|
|
type = types.bool;
|
|
default = true;
|
|
description = "Enable Matrix end-to-end encryption";
|
|
};
|
|
|
|
default = mkOption {
|
|
type = types.bool;
|
|
default = false;
|
|
description = "Enable encryption by default in new portals";
|
|
};
|
|
|
|
require = mkOption {
|
|
type = types.bool;
|
|
default = false;
|
|
description = "Require encryption in all portals";
|
|
};
|
|
|
|
requireVerification = mkOption {
|
|
type = types.bool;
|
|
default = false;
|
|
description = "Require device verification for encryption";
|
|
};
|
|
|
|
allowKeySharing = mkOption {
|
|
type = types.bool;
|
|
default = false;
|
|
description = "Allow sharing encryption keys";
|
|
};
|
|
};
|
|
|
|
# Computed options (read-only)
|
|
registrationFile = mkOption {
|
|
type = types.str;
|
|
readOnly = true;
|
|
description = "Path to the generated registration file";
|
|
};
|
|
};
|
|
|
|
config = mkIf cfg.enable {
|
|
# User and group
|
|
users.users.${cfg.user} = {
|
|
isSystemUser = true;
|
|
group = cfg.group;
|
|
extraGroups = [ "matrix-appservices" ];
|
|
home = cfg.dataDir;
|
|
createHome = true;
|
|
};
|
|
users.groups.${cfg.group} = {};
|
|
users.groups.matrix-appservices.members = lib.mkAfter [ cfg.user ];
|
|
|
|
# Systemd service with comprehensive hardening
|
|
systemd.services.mautrix-gmessages = {
|
|
description = "mautrix-gmessages Matrix-Google Messages bridge";
|
|
after = [ "network.target" "postgresql.service" ];
|
|
wantedBy = [ "multi-user.target" ];
|
|
|
|
serviceConfig = {
|
|
Type = "simple";
|
|
User = cfg.user;
|
|
Group = cfg.group;
|
|
SupplementaryGroups = [ "matrix-appservices" ];
|
|
PermissionsStartOnly = true;
|
|
WorkingDirectory = cfg.dataDir;
|
|
|
|
# Use StateDirectory for persistent config and data management
|
|
StateDirectory = cfg.user;
|
|
StateDirectoryMode = "0750";
|
|
LogsDirectory = cfg.user;
|
|
|
|
# LoadCredential directives for secure secret injection
|
|
LoadCredential = loadCredentials;
|
|
|
|
# Two-stage pre-start: directory setup as root, then config generation as service user
|
|
ExecStartPre = [
|
|
# Stage 1: Create config directory as root (+ prefix)
|
|
"+${pkgs.coreutils}/bin/mkdir -p /var/lib/${cfg.user}/config"
|
|
"+${pkgs.coreutils}/bin/chown -R ${cfg.user}:${cfg.group} /var/lib/${cfg.user}"
|
|
"+${pkgs.coreutils}/bin/chmod 750 /var/lib/${cfg.user}"
|
|
"+${pkgs.coreutils}/bin/chmod 750 /var/lib/${cfg.user}/config"
|
|
|
|
# Stage 2: Generate configuration using example template
|
|
"+${pkgs.writeShellScript "mautrix-gmessages-prepare-config" ''
|
|
set -euo pipefail
|
|
|
|
CONFIG_DIR=/var/lib/${cfg.user}/config
|
|
CONFIG_PATH="$CONFIG_DIR/config.yaml"
|
|
REG_PATH="$CONFIG_DIR/registration.yaml"
|
|
|
|
cp ${exampleConfigPath} "$CONFIG_PATH"
|
|
cp ${registrationFile} "$REG_PATH"
|
|
|
|
${pkgs.python3.withPackages (ps: [ ps.pyyaml ])}/bin/python3 <<'PY'
|
|
import json
|
|
import os
|
|
import pathlib
|
|
import pwd
|
|
import grp
|
|
import shutil
|
|
import yaml
|
|
|
|
CONFIG_DIR = pathlib.Path('/var/lib/${cfg.user}/config')
|
|
CONFIG_PATH = CONFIG_DIR / 'config.yaml'
|
|
REG_PATH = CONFIG_DIR / 'registration.yaml'
|
|
SHARED_REG = pathlib.Path('/var/lib/matrix-appservices/mautrix_gmessages_registration.yaml')
|
|
|
|
with CONFIG_PATH.open('r', encoding='utf-8') as fh:
|
|
config = yaml.safe_load(fh)
|
|
|
|
with REG_PATH.open('r', encoding='utf-8') as fh:
|
|
registration = yaml.safe_load(fh)
|
|
|
|
overrides = json.loads(${lib.escapeShellArg (builtins.toJSON configOverrides)})
|
|
extra_cfg = json.loads(${lib.escapeShellArg (builtins.toJSON cfg.extraConfig)})
|
|
|
|
def deep_update(target, data):
|
|
for key, value in data.items():
|
|
if isinstance(value, dict) and value is not None:
|
|
target.setdefault(key, {})
|
|
deep_update(target[key], value)
|
|
else:
|
|
target[key] = value
|
|
|
|
deep_update(config, overrides)
|
|
if extra_cfg:
|
|
deep_update(config, extra_cfg)
|
|
|
|
perms = overrides.get('bridge', {}).get('permissions')
|
|
if perms is not None:
|
|
config.setdefault('bridge', {})['permissions'] = perms
|
|
|
|
dp_config = config.setdefault('double_puppet', {})
|
|
servers = overrides.get('double_puppet', {}).get('servers')
|
|
if servers is not None:
|
|
dp_config['servers'] = servers
|
|
dp_config['secrets'] = {}
|
|
|
|
creds_dir = os.environ.get('CREDENTIALS_DIRECTORY')
|
|
as_token = hs_token = provisioning_secret = login_shared_secret = None
|
|
if creds_dir:
|
|
creds_path = pathlib.Path(creds_dir)
|
|
def read_secret(name):
|
|
path = creds_path / name
|
|
return path.read_text(encoding='utf-8').strip() if path.exists() else None
|
|
as_token = read_secret('as_token')
|
|
hs_token = read_secret('hs_token')
|
|
provisioning_secret = read_secret('provisioning_shared_secret')
|
|
login_shared_secret = read_secret('login_shared_secret')
|
|
|
|
if as_token:
|
|
config.setdefault('appservice', {})['as_token'] = as_token
|
|
registration['as_token'] = as_token
|
|
if hs_token:
|
|
config.setdefault('appservice', {})['hs_token'] = hs_token
|
|
registration['hs_token'] = hs_token
|
|
|
|
if overrides.get('appservice', {}).get('public_address') is None:
|
|
config.setdefault('appservice', {})['public_address'] = None
|
|
|
|
if provisioning_secret:
|
|
config.setdefault('provisioning', {})['shared_secret'] = provisioning_secret
|
|
else:
|
|
config.setdefault('provisioning', {})['shared_secret'] = 'disable'
|
|
|
|
if login_shared_secret:
|
|
login_secret_value = login_shared_secret
|
|
dp_config.setdefault('secrets', {})['${cfg.matrix.serverName}'] = login_secret_value
|
|
|
|
with CONFIG_PATH.open('w', encoding='utf-8') as fh:
|
|
yaml.safe_dump(config, fh, default_flow_style=False, sort_keys=False)
|
|
|
|
with REG_PATH.open('w', encoding='utf-8') as fh:
|
|
yaml.safe_dump(registration, fh, default_flow_style=False, sort_keys=False)
|
|
|
|
uid = pwd.getpwnam('${cfg.user}').pw_uid
|
|
gid_service = grp.getgrnam('${cfg.group}').gr_gid
|
|
gid_shared = grp.getgrnam('matrix-appservices').gr_gid
|
|
|
|
os.chown(CONFIG_PATH, uid, gid_service)
|
|
os.chmod(CONFIG_PATH, 0o640)
|
|
os.chown(REG_PATH, uid, gid_service)
|
|
os.chmod(REG_PATH, 0o640)
|
|
|
|
SHARED_REG.parent.mkdir(parents=True, exist_ok=True)
|
|
shutil.copyfile(REG_PATH, SHARED_REG)
|
|
os.chown(SHARED_REG, uid, gid_shared)
|
|
os.chmod(SHARED_REG, 0o640)
|
|
PY
|
|
''}"
|
|
];
|
|
|
|
# Start mautrix-gmessages with state directory config
|
|
ExecStart = "${cfg.package}/bin/mautrix-gmessages -c /var/lib/${cfg.user}/config/config.yaml";
|
|
|
|
# Restart policy
|
|
Restart = "always";
|
|
RestartSec = 10;
|
|
|
|
# Security hardening following NixOS best practices
|
|
# NoNewPrivileges = true; # Temporarily disabled to debug exit code 11
|
|
# ProtectSystem = "strict"; # Temporarily disabled to debug exit code 11
|
|
# ProtectHome = true; # Temporarily disabled to debug exit code 11
|
|
# PrivateTmp = true; # Temporarily disabled to debug exit code 11
|
|
# PrivateDevices = true; # Temporarily disabled to debug exit code 11
|
|
ProtectKernelTunables = true;
|
|
ProtectKernelModules = true;
|
|
ProtectControlGroups = true;
|
|
|
|
# Allow writing to data, log, and shared appservices directory
|
|
ReadWritePaths = [
|
|
cfg.dataDir
|
|
"/var/log/${cfg.user}"
|
|
"/var/lib/matrix-appservices"
|
|
];
|
|
|
|
# Network restrictions
|
|
# RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ]; # Temporarily disabled to debug exit code 11
|
|
|
|
# System calls - Bridge needs broader access for Google Messages protocol
|
|
SystemCallArchitectures = "native";
|
|
# SystemCallFilter = [
|
|
# "@system-service"
|
|
# "@network-io"
|
|
# "@file-system"
|
|
# "@process"
|
|
# "@io-event"
|
|
# # Go runtime specific syscalls
|
|
# "futex"
|
|
# "clone"
|
|
# "arch_prctl"
|
|
# "mprotect"
|
|
# "mmap"
|
|
# "munmap"
|
|
# "set_robust_list"
|
|
# "sigaltstack"
|
|
# "rt_sigreturn"
|
|
# "rt_sigprocmask"
|
|
# "sched_getaffinity"
|
|
# # Required for chown/chmod operations in ExecStartPre
|
|
# "fchownat"
|
|
# "fchmodat"
|
|
# ];
|
|
|
|
# Resource limits
|
|
MemoryMax = "256M";
|
|
CPUWeight = 50; # Lower priority than Matrix server
|
|
IOWeight = 50;
|
|
|
|
# Process security - files created as 640 (owner rw, group r, other none)
|
|
UMask = "0027";
|
|
LockPersonality = true;
|
|
RestrictRealtime = true;
|
|
RestrictSUIDSGID = true;
|
|
RemoveIPC = true;
|
|
|
|
# Logging
|
|
StandardOutput = "journal";
|
|
StandardError = "journal";
|
|
SyslogIdentifier = "mautrix-gmessages";
|
|
};
|
|
|
|
# Add extra packages to PATH if specified
|
|
path = [ pkgs.coreutils pkgs.gnused ] ++ cfg.extraPackages;
|
|
};
|
|
|
|
# Directory permissions
|
|
systemd.tmpfiles.rules = [
|
|
"d ${cfg.dataDir} 0750 ${cfg.user} ${cfg.group} -"
|
|
"Z ${cfg.dataDir} 0750 ${cfg.user} ${cfg.group} - -"
|
|
"d /var/log/${cfg.user} 0750 ${cfg.user} ${cfg.group} -"
|
|
"Z /var/log/${cfg.user} 0750 ${cfg.user} ${cfg.group} - -"
|
|
"d /var/lib/matrix-appservices 0775 root matrix-appservices -"
|
|
];
|
|
|
|
# Open firewall port for AS API
|
|
networking.firewall.allowedTCPPorts = [ cfg.appservice.port ];
|
|
|
|
# Set registration file path for integration with Matrix server (shared location)
|
|
services.matrix-vm.mautrix-gmessages.registrationFile = "/var/lib/matrix-appservices/mautrix_gmessages_registration.yaml";
|
|
};
|
|
}
|