Extracted modules: - Matrix homeserver (matrix-continuwuity.nix) - mautrix bridges (slack, whatsapp, gmessages) - Security modules (fail2ban, ssh-hardening) - Development services module - Matrix secrets module All modules sanitized to remove personal information: - Domains: example.com, matrix.example.org - IPs: 10.0.0.x, 203.0.113.10 - Paths: /home/user, /path/to/ops-base - Emails: admin@example.com Configuration: - Updated flake.nix with sops-nix and nixpkgs-unstable - Updated hosts/ops-jrz1.nix to import all extracted modules - Added example files (secrets, minimal config) - Generated flake.lock Generated with Claude Code - https://claude.com/claude-code
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, 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.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";
|
|
};
|
|
}
|