atagen

malice

public
Star0
Watch0
5b0525b663533f6b8aef29faefd47305462f4f0f
2
ssh://brewery@pls.lobotomise.me:33/atagen/malice.git

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.default

Use 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 embedded by the sender always show. Otherwise the homeserver fetches previews in unencrypted rooms, and in encrypted rooms only when the user enables it in the Security panel. Room admins can disable previews per room.

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 develop

The 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 dev

Open 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 malice

Open 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-storybook

Open 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 build

The 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 check

nix 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.