This repository has no description
4.9 kB
156 lines
1{
2 self,
3 config,
4 lib,
5 pkgs,
6 ...
7}:
8let
9 cfg = config.lass.parley;
10 generators = config.clan.core.vars.generators;
11in
12{
13 options.lass.parley = {
14 enable = lib.mkEnableOption "parley, federated chat that speaks plain IRC";
15
16 package = lib.mkOption {
17 type = lib.types.package;
18 default = self.packages.${pkgs.stdenv.hostPlatform.system}.parley;
19 };
20
21 settings = lib.mkOption {
22 type = lib.types.attrsOf lib.types.str;
23 default = { };
24 example = {
25 PARLEY_DOMAIN = "example.com";
26 PARLEY_ENDPOINT = "https://chat.example.com";
27 };
28 description = "PARLEY_* environment variables for parleyd (upstream README, Configuration).";
29 };
30
31 users = lib.mkOption {
32 default = { };
33 description = ''
34 Local accounts. Each gets a generated password
35 (`clan vars get <machine> parley-user-<nick>/password`); after every
36 start the account is created, or reset to its declared role and
37 password. Accounts not listed here are left alone.
38 '';
39 type = lib.types.attrsOf (
40 lib.types.submodule {
41 options.role = lib.mkOption {
42 type = lib.types.enum [
43 "admin"
44 "user"
45 "bot"
46 ];
47 default = "user";
48 };
49 }
50 );
51 };
52 };
53
54 config = lib.mkIf cfg.enable {
55 lass.parley.settings = {
56 PARLEY_DATA_DIR = "/var/lib/parley";
57 PARLEY_IRC_LISTEN = lib.mkDefault "127.0.0.1:6667";
58 PARLEY_HTTP_LISTEN = lib.mkDefault "127.0.0.1:8443";
59 };
60
61 clan.core.vars.generators = {
62 parley = {
63 # The instance key is the domain's identity: peers cache its public
64 # half, so regenerating it is a key rotation every peer has to re-learn.
65 files."identity.key" = { };
66 files."env" = { };
67 runtimeInputs = [ pkgs.openssl ];
68 script = ''
69 # parley's key format: base64 of a 32-byte ed25519 seed
70 openssl rand -base64 32 > "$out/identity.key"
71 echo "PARLEY_ADMIN_TOKEN=$(openssl rand -hex 32)" > "$out/env"
72 '';
73 };
74 }
75 // lib.mapAttrs' (
76 nick: _:
77 lib.nameValuePair "parley-user-${nick}" {
78 files."password" = { };
79 runtimeInputs = [ pkgs.openssl ];
80 script = ''
81 openssl rand -hex 24 > "$out/password"
82 '';
83 }
84 ) cfg.users;
85
86 users.users.parley = {
87 isSystemUser = true;
88 group = "parley";
89 };
90 users.groups.parley = { };
91
92 environment.systemPackages = [ cfg.package ];
93
94 systemd.services.parley = {
95 wantedBy = [ "multi-user.target" ];
96 after = [ "network-online.target" ];
97 wants = [ "network-online.target" ];
98 environment = cfg.settings;
99 path = [
100 cfg.package
101 pkgs.curl
102 ];
103 preStart = ''
104 install -m 0600 "$CREDENTIALS_DIRECTORY/identity.key" identity.key
105 '';
106 postStart = lib.optionalString (cfg.users != { }) ''
107 export PARLEY_ENDPOINT=http://$PARLEY_HTTP_LISTEN
108 until curl -fs -o /dev/null "$PARLEY_ENDPOINT/healthz"; do sleep 1; done
109 ${lib.concatStrings (
110 lib.mapAttrsToList (nick: user: ''
111 pw="$CREDENTIALS_DIRECTORY/user-${nick}"
112 parleyctl accounts update ${nick} -role ${user.role} -password-stdin < "$pw" > /dev/null \
113 || parleyctl accounts create ${nick} -role ${user.role} -password-stdin < "$pw" > /dev/null
114 '') cfg.users
115 )}
116 '';
117 serviceConfig = {
118 ExecStart = lib.getExe' cfg.package "parleyd";
119 User = "parley";
120 Group = "parley";
121 StateDirectory = "parley";
122 StateDirectoryMode = "0700";
123 WorkingDirectory = "/var/lib/parley";
124 UMask = "0077";
125 EnvironmentFile = generators.parley.files."env".path;
126 LoadCredential = [
127 "identity.key:${generators.parley.files."identity.key".path}"
128 ]
129 ++ lib.mapAttrsToList (
130 nick: _: "user-${nick}:${generators."parley-user-${nick}".files."password".path}"
131 ) cfg.users;
132 Restart = "on-failure";
133 NoNewPrivileges = true;
134 ProtectSystem = "strict";
135 ProtectHome = true;
136 PrivateTmp = true;
137 PrivateDevices = true;
138 };
139 };
140
141 # identity.key also lives in clan vars; the SQLite databases are dumped
142 # consistently instead of copied.
143 lass.backup.state.parley = {
144 folders = [ "/var/lib/parley" ];
145 exclude = [ "/var/lib/parley/*.db*" ];
146 preBackup = ''
147 mkdir -p /var/backup/parley
148 for db in parley history; do
149 ${pkgs.sqlite}/bin/sqlite3 -cmd ".timeout 60000" "/var/lib/parley/$db.db" ".backup /var/backup/parley/$db.db"
150 done
151 '';
152 # WAL-mode SQLite readers need to write the -shm file
153 readWritePaths = [ "/var/lib/parley" ];
154 };
155 };
156}