# Flatpak manifest for the Bulwark GUI. # # Build locally with scripts/flatpak-build-local.sh (it stages a CLEAN git tree so # the multi-GB target/ and node_modules are never copied into the sandbox — a # plain `type: dir` copy of this repo would drag all of that in). See # packaging/README.md, especially the privileged-scan limitation. # # Offline deps (Flatpak builds with no network) are pre-generated by # scripts/flatpak-gen-sources.sh into cargo-sources.json + node-sources.json. app-id: com.vietanhdev.bulwark # tauri.conf.json's identifier matches this exactly, and must keep matching: Flathub's # linter rejects owning a D-Bus name outside the app-id prefix, and the single-instance # name below is derived from the identifier. They disagreed until 0.8.6 (a `vietanhnv` # typo), which is what finally forced the rename. runtime: org.gnome.Platform runtime-version: '50' sdk: org.gnome.Sdk sdk-extensions: - org.freedesktop.Sdk.Extension.rust-stable - org.freedesktop.Sdk.Extension.node20 command: bulwark-app finish-args: - --share=ipc # REQUIRED for the UI to render at all, which is not obvious and cost a long hunt. # # Inside any Flatpak, GLib installs the *portal-backed* proxy resolver and network # monitor unconditionally — gproxyresolverportal.c picks the portal purely on the bus # name being owned, never checking whether a lookup would be permitted. WebKit's network # process then calls them while loading the app, and xdg-desktop-portal answers from # proxy-resolver.c / network-monitor.c: # # if (!xdp_app_info_has_network (app_info)) # ... "This call is not available inside the sandbox" # # `xdp_app_info_has_network()` is true exactly when the app was built with # --share=network. Without it the page load fails and Tauri paints that error string # into the webview — which, on a window that is `transparent: true`, reads as an empty # window with a line of text floating in it. The backend is entirely healthy: setup() # completes, the WebKit process spawns, and the frontend's first invoke never arrives. # # The app itself makes no network calls — bulwark-core's no-network invariant still # holds, and this grants the *sandbox* what WebKit needs to load a local page. 6 of 7 # Tauri apps surveyed on Flathub ship this. - --share=network - --socket=wayland - --socket=fallback-x11 - --device=dri # Read the host it audits (read-only; the GUI never writes host state). - --filesystem=host:ro # NOTE: no --filesystem=xdg-data/bulwark:create. The app's findings database now # resolves via XDG_DATA_HOME, which Flatpak already points at the writable # ~/.var/app//data, so no extra grant is needed. (Requesting one is what # flatpak-builder-lint flags as finish-args-unnecessary-xdg-data-access.) - --talk-name=org.freedesktop.Notifications - --talk-name=org.kde.StatusNotifierWatcher # Neither WEBKIT_DISABLE_DMABUF_RENDERER nor WEBKIT_DISABLE_COMPOSITING_MODE is set. # # Both were added here while hunting the blank window, and neither was the cause — that # was the missing --share=network above plus a build without --features custom-protocol. # They are not free: DMABUF disables WebKit's GPU buffer sharing, which forces a slower # CPU path and makes the UI visibly sluggish on hardware that renders fine, and # COMPOSITING_MODE additionally disables the path `transparent: true` depends on. Five of # the seven Tauri apps surveyed on Flathub set neither. # # If a blank or corrupted window ever reappears on some driver, they are the usual # workarounds (tauri-apps/tauri#8970, #10626) — but reach for them with evidence, and # prefer per-user `flatpak override` over shipping the cost to everyone. # tauri-plugin-single-instance claims a session-bus name at startup, and Flatpak blocks # owning any name the manifest does not declare, so this declaration is required for the # plugin to work at all. It does NOT explain the blank window that was being chased when # it was added: a refused name ownership returns org.freedesktop.DBus.Error.AccessDenied, # a bus error, never a portal error. That was --share=network (see above). Documented at # https://v2.tauri.app/plugin/single-instance/#usage-in-snap-and-flatpak # # The name is derived from tauri.conf.json's *identifier*, with dots and dashes turned # into underscores. It must match what the binary actually asks for, so never edit it # here alone — change the identifier and let check-packaging-consistency.sh re-derive it. # Flathub additionally requires an owned name to sit under the app-id, which is why the # identifier had to stop being com.vietanhnv.bulwark. # # ORDER MATTERS, and not in a way the manifest hints at. Both flags write the same key # in the built metadata, so the last one wins — and `own` is strictly stronger than # `talk`. Listing own-name first (as this file did) silently produced # org.com_vietanhdev_bulwark.SingleInstance=talk # and the plugin still could not claim the name, so the app stayed broken while the # manifest looked correct. Keep talk-name first, exactly as the Tauri docs show it. # Verify with: flatpak info --show-permissions com.vietanhdev.bulwark (must read `=own`) - --talk-name=org.com_vietanhdev_bulwark.SingleInstance - --own-name=org.com_vietanhdev_bulwark.SingleInstance # Run two specific tools on the host, because neither can live in the sandbox. # # flatpak-builder-lint flags this, and rightly — it is a genuine widening. The case: # # * ClamAV. The engine and its ~250 MB signature database are maintained on the host by # the distribution. Bundling a second copy would mean shipping an AV engine (its own # CVE stream to track) plus a database that is stale the day it ships, and asking every # user to re-download it. Running the host's clamscan uses signatures the machine # already keeps current. # * Privileged scans. Auditing a host as root is what a security scanner is for. The # elevation must happen on the host, against a host-installed `bulwarkctl` — the app # checks for one and explains itself if it is absent, rather than failing obscurely. # # Both are gated in code on the sandbox actually being reachable # (bulwark_core::sandbox::can_reach_host), so a build without this permission degrades to # an honest message instead of a broken feature. Unprivileged configuration scanning — the # majority of what Bulwark does — never touches this path. # # Precedent: flathub/io.github.linx_systems.ClamUI, a published ClamAV GUI, ships this # same permission together with a full read-write `--filesystem=host`. Bulwark asks for # this plus a read-only host filesystem, which is strictly narrower. - --talk-name=org.freedesktop.Flatpak modules: # The system-tray stack, built into the app because the GNOME runtime ships no # appindicator or dbusmenu at all. Without it libappindicator-sys's dlopen fails and # the app has no tray — which for Bulwark is not cosmetic: the tray is what keeps # continuous monitoring running after the window is closed (see tray.rs). # # This is flathub/shared-modules, vendored as a git submodule, which is the canonical # way Flathub apps pull this chain (libdbusmenu + libindicator + libappindicator, plus # intltool and dbus-glib). Don't hand-copy the JSON: the patches travel with it, and # upstream maintains the FTBFS fixes this 2012-era autotools code needs. # # `git clone --recurse-submodules`, or `git submodule update --init` in an existing # checkout, or the build fails with a missing-module error. - shared-modules/libappindicator/libappindicator-gtk3-12.10.json - name: bulwark buildsystem: simple build-options: # rust-stable + node20 from the SDK extensions; offline caches wired per the # flatpak-builder-tools generators (module name 'bulwark' -> /run/build/bulwark). append-path: /usr/lib/sdk/rust-stable/bin:/usr/lib/sdk/node20/bin env: CARGO_HOME: /run/build/bulwark/cargo CARGO_NET_OFFLINE: 'true' # npm's offline cache, populated by node-sources.json under flatpak-node/. XDG_CACHE_HOME: /run/build/bulwark/flatpak-node/cache npm_config_cache: /run/build/bulwark/flatpak-node/npm-cache # The frontend build needs devDependencies (vite, typescript), which drags # in @playwright/test — used only by the e2e suite, never by the app. Its # postinstall would try to download a browser, which fails in the offline # build, so suppress it. (The package itself is still vendored so # `npm install --offline` can resolve the lockfile.) PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: "1" build-commands: # 1. Frontend deps from the offline cache, then the vite build -> apps/bulwark-app/dist # (which Tauri's build.rs embeds; plain `cargo build` does NOT run beforeBuildCommand). - npm install --prefix=apps/bulwark-app --offline - npm --prefix=apps/bulwark-app run build # 2. GUI binary + the CLI sidecar named `bulwark` (matching resolve_cli_binary). # --features custom-protocol is REQUIRED, not optional. Tauri's build script does # `let dev = !custom_protocol`, so without it this is a dev build that loads # devUrl (http://localhost:1420) instead of the embedded frontend, and ships a GUI # that can never render. `cargo tauri build` sets it implicitly; plain cargo does not. - cargo build --release --offline -p bulwark-app --features custom-protocol - cargo build --release --offline -p bulwarkctl - install -Dm755 target/release/bulwark-app ${FLATPAK_DEST}/bin/bulwark-app - install -Dm755 target/release/bulwarkctl ${FLATPAK_DEST}/bin/bulwark # 3. Rule pack beside the binaries, which is where the CLI sidecar's resolver # looks (next-to-exe, no --rules-dir needed). # # The GUI does NOT share that resolver. It has its own in src-tauri/src/lib.rs, # and it used to check only Tauri's resource_dir() — which in this layout points # at a directory containing no rules. So the shipped Flatpak started with # "couldn't find a 'rules' directory" and silently ran with continuous monitoring # disabled, while the comment here claimed the path was verified. It wasn't: the # CLI was tested and the GUI was *inferred* from it. resolve_rules_dir now has an # explicit next-to-exe fallback, and scripts/test-gui-packages-docker.sh launches # the real GUI and fails if that warning ever appears again. Test the binary that # ships, not a sibling that shares a directory with it. - cp -r rules decoders log-rules ${FLATPAK_DEST}/bin/ # 4. Desktop entry, icon, AppStream metainfo (Flathub requires the last two). # Flathub requires >=256px (or SVG); icon.png is 512x512. The smaller copies # are shipped natively rather than left to the loader to downscale — GNOME # asks for 48px constantly (dash, alt-tab, window list) and a 48 rendered # from a 512 is visibly softer than one authored at 48. - install -Dm644 apps/bulwark-app/src-tauri/icons/512x512.png ${FLATPAK_DEST}/share/icons/hicolor/512x512/apps/com.vietanhdev.bulwark.png - install -Dm644 apps/bulwark-app/src-tauri/icons/256x256.png ${FLATPAK_DEST}/share/icons/hicolor/256x256/apps/com.vietanhdev.bulwark.png - install -Dm644 apps/bulwark-app/src-tauri/icons/128x128.png ${FLATPAK_DEST}/share/icons/hicolor/128x128/apps/com.vietanhdev.bulwark.png - install -Dm644 apps/bulwark-app/src-tauri/icons/64x64.png ${FLATPAK_DEST}/share/icons/hicolor/64x64/apps/com.vietanhdev.bulwark.png - install -Dm644 apps/bulwark-app/src-tauri/icons/48x48.png ${FLATPAK_DEST}/share/icons/hicolor/48x48/apps/com.vietanhdev.bulwark.png - install -Dm644 apps/bulwark-app/src-tauri/icons/32x32.png ${FLATPAK_DEST}/share/icons/hicolor/32x32/apps/com.vietanhdev.bulwark.png - install -Dm644 apps/bulwark-app/src-tauri/icons/24x24.png ${FLATPAK_DEST}/share/icons/hicolor/24x24/apps/com.vietanhdev.bulwark.png - install -Dm644 apps/bulwark-app/src-tauri/icons/16x16.png ${FLATPAK_DEST}/share/icons/hicolor/16x16/apps/com.vietanhdev.bulwark.png - install -Dm644 packaging/flatpak/com.vietanhdev.bulwark.desktop ${FLATPAK_DEST}/share/applications/com.vietanhdev.bulwark.desktop - install -Dm644 packaging/flatpak/com.vietanhdev.bulwark.metainfo.xml ${FLATPAK_DEST}/share/metainfo/com.vietanhdev.bulwark.metainfo.xml # 5. Licence text. Flathub requires it installed under share/licenses/$FLATPAK_ID. - install -Dm644 LICENSE ${FLATPAK_DEST}/share/licenses/${FLATPAK_ID}/LICENSE sources: - type: dir path: ../.. # Generated by scripts/flatpak-gen-sources.sh (do not hand-edit): - cargo-sources.json - node-sources.json