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