1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
|
{ depot, pkgs, lib, config, ... }:
let
cfg = config.profpatsch.webchat;
webchat = depot.users.Profpatsch.webchat;
in
{
options.profpatsch.webchat = {
enable = lib.mkEnableOption "webchat minimal E2EE Matrix chat webapp";
# Note there is no `room` option: webchat serves every room the account has
# joined (hicli syncs them all regardless), listing them at `/`.
address = lib.mkOption {
type = lib.types.str;
example = "100.89.52.54";
description = ''
Address to bind to. webchat has NO authentication of its own — anyone
who can reach this address can read and send messages as you, in every
room the account has joined. Bind it to a tailscale address only, and
do not open the port in the public firewall (see the
interfaces.tailscale0 stanza in legosi.nix).
'';
};
port = lib.mkOption {
type = lib.types.port;
default = 8775;
description = "Port for the webchat service.";
};
user = lib.mkOption {
type = lib.types.str;
default = "profpatsch";
description = ''
User to run as. Note this is NOT a DynamicUser service: the Matrix
session (access token, olm identity, cross-signing keys) is bootstrapped
interactively with `webchat login` and must persist across restarts and
redeploys, so it needs a stable uid owning the state directory.
'';
};
stateDir = lib.mkOption {
type = lib.types.str;
default = "/var/lib/webchat";
description = ''
Directory holding webchat.db and the crypto pickle key. Losing the
pickle key invalidates the olm account and requires a fresh login.
'';
};
};
config = lib.mkIf cfg.enable {
systemd.services.webchat = {
description = "webchat — minimal E2EE Matrix chat webapp";
wantedBy = [ "multi-user.target" ];
after = [ "network-online.target" "tailscaled.service" ];
wants = [ "network-online.target" ];
serviceConfig = {
ExecStart = lib.escapeShellArgs [
"${webchat}/bin/webchat"
"serve"
"--addr"
"${cfg.address}:${toString cfg.port}"
"--db"
"${cfg.stateDir}/webchat.db"
];
User = cfg.user;
Restart = "on-failure";
RestartSec = 5;
# The state dir holds long-lived secrets (access token, olm keys).
StateDirectory = builtins.baseNameOf cfg.stateDir;
StateDirectoryMode = "0700";
# Hardening. Not DynamicUser (see the `user` option), so lock it down
# by hand instead.
NoNewPrivileges = true;
PrivateTmp = true;
PrivateDevices = true;
ProtectSystem = "strict";
ProtectHome = true;
ProtectKernelTunables = true;
ProtectKernelModules = true;
ProtectControlGroups = true;
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
LockPersonality = true;
MemoryDenyWriteExecute = true;
SystemCallArchitectures = "native";
SystemCallFilter = [ "@system-service" "~@privileged" "~@resources" ];
# Only needs to talk to the homeserver over TCP and listen locally.
RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
};
};
};
}
|