rentczecher is open source. All the code is on GitHub, a star is always appreciated →
rentczecher
← All posts

A real desktop app

30 September 2026 · 4 min read

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.

Desktop app Tauri, Rust Panel SvelteKit, webview Engine Python, PyInstaller checks the portals matches flats remembers what you saw stdin, held open closes → engine stops stdout closes → app shows a banner HTTP API, localhost REST calls, Server-Sent Events
The shell and the engine watch each other through stdin and stdout. The panel talks to the engine over HTTP.

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.