| 1 | { self }: |
| 2 | { |
| 3 | config, |
| 4 | lib, |
| 5 | pkgs, |
| 6 | ... |
| 7 | }: |
| 8 | let |
| 9 | system = pkgs.stdenv.hostPlatform.system; |
| 10 | hostedCfg = config.services.malice; |
| 11 | deviceCfg = config.programs.malice; |
| 12 | inherit (lib) |
| 13 | mkEnableOption |
| 14 | mkIf |
| 15 | mkMerge |
| 16 | mkOption |
| 17 | types |
| 18 | ; |
| 19 | deviceUrl = "http://127.0.0.1:${toString deviceCfg.port}/"; |
| 20 | browserCommand = |
| 21 | if lib.hasInfix "%u" deviceCfg.browser then |
| 22 | lib.replaceStrings [ "%u" ] [ deviceUrl ] deviceCfg.browser |
| 23 | else |
| 24 | "${deviceCfg.browser} ${deviceUrl}"; |
| 25 | malicOpen = pkgs.writeShellScriptBin "malice-open" '' |
| 26 | exec ${browserCommand} |
| 27 | ''; |
| 28 | malicDesktopItem = pkgs.makeDesktopItem { |
| 29 | name = "malice"; |
| 30 | desktopName = "Malice"; |
| 31 | comment = "Open the on-device Malice Matrix client"; |
| 32 | exec = "${malicOpen}/bin/malice-open"; |
| 33 | terminal = false; |
| 34 | categories = [ |
| 35 | "Network" |
| 36 | "InstantMessaging" |
| 37 | ]; |
| 38 | }; |
| 39 | in |
| 40 | { |
| 41 | options.services.malice = { |
| 42 | enable = mkEnableOption "Malice hosted Matrix client"; |
| 43 | |
| 44 | package = mkOption { |
| 45 | type = types.package; |
| 46 | default = self.packages.${system}.default; |
| 47 | description = "Malice server and bundled Svelte frontend."; |
| 48 | }; |
| 49 | |
| 50 | listenAddress = mkOption { |
| 51 | type = types.str; |
| 52 | default = "127.0.0.1"; |
| 53 | description = "Address to bind. Keep behind an HTTPS reverse proxy for secure browser cookies."; |
| 54 | }; |
| 55 | |
| 56 | port = mkOption { |
| 57 | type = types.port; |
| 58 | default = 3000; |
| 59 | description = "HTTP listener port."; |
| 60 | }; |
| 61 | |
| 62 | openFirewall = mkOption { |
| 63 | type = types.bool; |
| 64 | default = false; |
| 65 | description = "Open the HTTP listener port in the firewall."; |
| 66 | }; |
| 67 | }; |
| 68 | |
| 69 | options.programs.malice = { |
| 70 | enable = mkEnableOption "Malice on-device Matrix client"; |
| 71 | |
| 72 | package = mkOption { |
| 73 | type = types.package; |
| 74 | default = self.packages.${system}.local; |
| 75 | description = "The malice binary wrapped for the on-device build (MALICE_WEB_DIR and MALICE_STATIC_ONLY set)."; |
| 76 | }; |
| 77 | |
| 78 | port = mkOption { |
| 79 | type = types.port; |
| 80 | default = 38465; |
| 81 | description = '' |
| 82 | Loopback port the socket-activated server listens on. This port is |
| 83 | part of the browser origin malice runs at, so changing it hides |
| 84 | accounts logged in at the old origin (their data is still on disk, |
| 85 | just unreachable at the new URL). Everyone logged into this machine |
| 86 | at once shares this one port; per-user ports are not supported yet. |
| 87 | ''; |
| 88 | }; |
| 89 | |
| 90 | browser = mkOption { |
| 91 | type = types.str; |
| 92 | default = "xdg-open %u"; |
| 93 | description = '' |
| 94 | Command the desktop entry runs to open malice. `%u` (desktop-entry |
| 95 | convention) is replaced with malice's URL; if the command has no |
| 96 | `%u`, the URL is appended after a space instead. E.g. "chromium |
| 97 | --app=%u" to open malice in an app window instead of a browser tab. |
| 98 | ''; |
| 99 | }; |
| 100 | }; |
| 101 | |
| 102 | config = mkMerge [ |
| 103 | (mkIf hostedCfg.enable { |
| 104 | systemd.services.malice = { |
| 105 | description = "Malice hosted Matrix client"; |
| 106 | wantedBy = [ "multi-user.target" ]; |
| 107 | after = [ "network-online.target" ]; |
| 108 | wants = [ "network-online.target" ]; |
| 109 | environment = { |
| 110 | MALICE_BIND = "${hostedCfg.listenAddress}:${toString hostedCfg.port}"; |
| 111 | MALICE_DATA_DIR = "/var/lib/malice"; |
| 112 | MALICE_WEB_DIR = "${self.packages.${system}.web}/share/malice"; |
| 113 | MALICE_SESSION_COOKIE_SECURE = "true"; |
| 114 | }; |
| 115 | serviceConfig = { |
| 116 | ExecStart = lib.getExe' hostedCfg.package "malice"; |
| 117 | DynamicUser = true; |
| 118 | StateDirectory = "malice"; |
| 119 | StateDirectoryMode = "0700"; |
| 120 | UMask = "0077"; |
| 121 | Restart = "on-failure"; |
| 122 | RestartSec = 5; |
| 123 | NoNewPrivileges = true; |
| 124 | ProtectSystem = "strict"; |
| 125 | ProtectHome = true; |
| 126 | PrivateTmp = true; |
| 127 | RestrictAddressFamilies = [ |
| 128 | "AF_INET" |
| 129 | "AF_INET6" |
| 130 | "AF_UNIX" |
| 131 | ]; |
| 132 | }; |
| 133 | }; |
| 134 | networking.firewall.allowedTCPPorts = lib.optionals hostedCfg.openFirewall [ hostedCfg.port ]; |
| 135 | }) |
| 136 | (mkIf deviceCfg.enable { |
| 137 | systemd.user.sockets.malice = { |
| 138 | description = "Malice on-device Matrix client socket"; |
| 139 | wantedBy = [ "sockets.target" ]; |
| 140 | socketConfig.ListenStream = "127.0.0.1:${toString deviceCfg.port}"; |
| 141 | }; |
| 142 | systemd.user.services.malice = { |
| 143 | description = "Malice on-device Matrix client"; |
| 144 | environment = { |
| 145 | MALICE_STATIC_ONLY = "1"; |
| 146 | MALICE_IDLE_EXIT_SECS = "300"; |
| 147 | }; |
| 148 | serviceConfig = { |
| 149 | ExecStart = lib.getExe' deviceCfg.package "malice"; |
| 150 | }; |
| 151 | }; |
| 152 | environment.systemPackages = [ |
| 153 | malicOpen |
| 154 | malicDesktopItem |
| 155 | ]; |
| 156 | }) |
| 157 | ]; |
| 158 | } |