rentczecher is two programs. The engine is Python: it scrapes the portals, matches flats and keeps everything in SQLite. The desktop app is a Tauri shell written in Rust, which shows the SvelteKit panel in a webview and starts the engine as a sidecar process. For a while the window could already show the inbox, but only on my machine, with Python installed and everything started by hand. This step closed that gap.
One package, no Python
A release build now starts a self-contained engine frozen with PyInstaller, shipped as a resource inside the app bundle, so nothing else needs to be installed. A .deb package built, and when I unpacked it, it ran and showed the inbox.
Unpacking it was a lesson of its own. A .deb is a plain ar archive of three members: a version marker, the control files and the payload. Looking inside shows exactly what the installer would put where. The app itself is a single binary with the panel embedded at build time, and the frozen engine sits next to it as a folder of resources.
Neither half dies alone
Two processes can fail in two directions, so both are covered.
If the shell goes away, the engine must not keep running in the background. The shell holds the write end of the engine's stdin and never writes to it. The engine runs with --exit-with-parent and watches that pipe. When the shell dies for any reason, even kill -9, the OS closes the pipe and the engine shuts itself down. Force-killing the shell stopped the engine in 0.21 seconds.
If the engine dies, the window must not keep pretending. When the engine's stdout closes, the shell emits an event to the panel, which shows a banner, 0.11 seconds after the engine was killed. And if the engine never reports healthy at startup, a native error dialog explains it and the app quits, instead of opening an empty window.
A Content Security Policy
The panel runs in a webview, so it got a Content Security Policy: an allowlist of what the page may load and connect to, enforced by the webview itself. It is strict in release builds, with a looser dev policy so Vite's live reload keeps working. Tauri's IPC goes through its own internal addresses, so those have to be on the list too.
Surprises
The release build did not compile at first. The code that starts the engine existed only under #[cfg(debug_assertions)], so the release path had never been compiled. The checks now build both profiles. And AppImage would not build on my machine, because a desktop integration tool I have installed intercepts every AppImage, including the build tools themselves. The .deb builds fine locally, so AppImage moved to the CI runners.