Malice
Malice is a hosted Matrix client with a Rust backend built on macltok and a Svelte frontend. The Nix package serves both the API and the built frontend.
NixOS module
Add this repository to your NixOS flake inputs, then include its module:
# In your flake.nix
inputs.malice.url = "git+file:///absolute/path/to/malice";
# In the modules list for your nixosSystem:
malice.nixosModules.defaultUse a published Git URL instead of the local path if you deploy from another machine. For a local Git flake, add new source files to Git before rebuilding; Nix does not include untracked files.
Enable the service in your NixOS configuration. For access from another machine, put it behind an HTTPS reverse proxy; the service binds to localhost and uses Secure session cookies by default:
{
services.malice.enable = true;
# Optional: services.malice.port = 3000;
services.nginx.enable = true;
networking.firewall.allowedTCPPorts = [ 80 443 ];
services.nginx.virtualHosts."chat.example.org" = {
enableACME = true;
forceSSL = true;
locations."/" = {
proxyPass = "http://127.0.0.1:3000";
extraConfig = ''
proxy_set_header Host $host;
proxy_buffering off; # Keep event-stream updates timely.
'';
};
};
security.acme.acceptTerms = true;
security.acme.defaults.email = "you@example.org";
}Replace the domain and ACME email, then rebuild your NixOS configuration as
usual. Check systemctl status malice and visit https://chat.example.org/.
The module stores the master key and sessions in /var/lib/malice; back up that
entire directory together. Its listenAddress, port, package, and
openFirewall options can be overridden. Do not expose the HTTP listener
without a trusted TLS proxy.
Client behavior
The home list shows non-space rooms (channels); Matrix spaces have their own navigation and show their advertised child rooms. A Matrix homeserver is the network address used at sign-in, not a space. Sending a message returns a queued command, then the timeline shows whether delivery is queued, sending, confirmed by the server, or failed. Opening a room loads its timeline; scrolling up loads older history. Images and avatars are served through authenticated, bounded media endpoints; SVG content is not rendered. Link previews use URL text only and do not fetch third-party pages.
The Security panel accepts incoming device-verification requests and supports emoji/number comparison, confirmation, rejection, and recovery-key entry. Recovery keys and encryption state are per Matrix account; preserve the local session data when migrating an installation.
Development
From the repository root:
nix developThe dev shell links web/node_modules from a nix build of web/package-lock.json,
so there is no install step. After changing dependencies in web/package.json,
refresh the lockfile with npm --prefix web install --package-lock-only and
re-enter the shell.
For live frontend changes, start these in separate terminals inside nix develop:
MALICE_SESSION_COOKIE_SECURE=false cargo run --bin malice
npm --prefix web run devOpen http://127.0.0.1:5173/. Vite proxies /api to the Rust server on port
3000. The cookie override is for local HTTP development only. By default,
Malice accepts HTTPS homeservers; set MALICE_ALLOW_HTTP_HOMESERVER=true only
when testing against a trusted local HTTP homeserver.
To serve the built frontend directly from Rust instead of using Vite:
npm --prefix web run build
MALICE_SESSION_COOKIE_SECURE=false cargo run --bin maliceOpen http://127.0.0.1:3000/. Rust uses web/dist unless MALICE_WEB_DIR
points at another built frontend directory. Local session data defaults to
local-data/; set MALICE_DATA_DIR to use another directory. Keep its
master.key and session files private.
Storybook
Component stories live next to their components as web/src/**/*.stories.ts.
Storybook runs inside nix develop:
cd web
npm run storybook
npm run build-storybookOpen http://localhost:6006/. While it runs, @storybook/addon-mcp serves an
MCP endpoint at http://localhost:6006/mcp, which the repository .mcp.json
registers for Claude Code in this project only.
Testing
In nix develop:
cargo fmt -- --check
cargo clippy --all-targets -- -D warnings
cargo test --all-targets
npm --prefix web run check
npm --prefix web test
npm --prefix web run buildThe Rust integration test uses a mocked Matrix homeserver; it does not require live account credentials. To verify the deployable package and NixOS module:
nix build .#server
nix flake checknix build creates result/bin/malice with the frontend bundled. On a NixOS
host, curl -fsS http://127.0.0.1:3000/api/health checks the local backend;
opening the proxied URL checks the frontend and TLS setup.