Files
DarkRoom/packaging/flatpak/paris.tourolle.darkroom.yml
T
dtourolleandClaude Opus 5 a56ac87e2c Build a Flatpak that asks for no filesystem at all
FR-PLAT-LIN-3 says that where DarkRoom is distributed as a Flatpak, filesystem
access uses portals. There was no manifest, so the sandboxed path had never been
exercised and the requirement had never been tested against the code.

The manifest is YAML rather than JSON because it has more to explain than to
declare, and every permission in finish-args carries the argument for itself.
The two that are not obvious:

- --device=dri is not an optimisation. The develop pipeline is compute shaders
  through wgpu with nothing behind it while NFR-R8 is open, so without the
  render node the application starts and cannot develop.
- --talk-name=org.freedesktop.secrets rather than the Secret portal. These are
  different things and the requirement's wording invites the wrong one: the
  portal hands an app a master key for a store it keeps itself, whereas the
  session's secret daemon is what keeps the Nextcloud app password visible to
  secret-tool and Seahorse, and therefore individually revocable by the user.
  FR-NC-2's degraded mode is what happens if nothing answers, inside the sandbox
  exactly as outside it.

There is no --filesystem= line, and that absence is the substance rather than an
oversight. It leaves the document-portal path working — a photograph opened from
a file manager arrives in argv under /run/user/$UID/doc and opens with no code
change — and leaves library selection broken, because dr-sync-folder takes a
typed absolute path and nothing in the tree calls the FileChooser portal.
--filesystem=host would fix that and is precisely what the requirement forbids;
--filesystem=xdg-pictures would fix it by not testing the design, in a smaller
directory. docs/distribution.md §4 records what actually closes the gap and the
flatpak override to use in the meantime.

The runtime version is chosen from what the binary needs rather than from what
is newest. ldd on a release build names fontconfig, freetype, expat, libpng,
zlib, brotli and bzip2 and nothing more: wgpu dlopens libvulkan.so.1, and x11rb
and wayland-client speak the wire protocols in Rust rather than binding libxcb
or libwayland. So what the runtime must supply at runtime is a Vulkan loader and
an ICD, which is the GL extension's job, and freedesktop 25.08 carries a
rust-stable extension at 1.98.0 — comfortably above the workspace's 1.92
minimum. That extension is not rustup, so rust-toolchain.toml's pin is ignored
here; the manifest says why that is correct rather than a violation of
CONTRIBUTING.md, since the pin exists to make fmt and clippy agree and neither
runs in a packaging build.

Like the PKGBUILD, it builds from the local checkout, so a build is of what you
are working on. That needs network for cargo, which Flathub forbids — the
comment says what a submission there would need instead and why generating
30,000 lines of vendored sources buys nothing yet. The LFS pointer check is
carried over from the PKGBUILD for the same reason it exists there: a dir source
copies a 130-byte pointer in without complaint, and the failure would land on a
user's machine rather than the packager's.

Not verified: nothing has been built. flatpak-builder is not installed here and
the machine is under a build embargo. The manifest parses, its keys are the ones
flatpak-builder reads, and the desktop entry and metainfo it installs both
validate — but no Flatpak has been produced from it and no permission has been
observed to be sufficient.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-29 20:18:56 +02:00

187 lines
9.1 KiB
YAML

# Flatpak manifest — FR-PLAT-LIN-3 (sandboxed distribution), NFR-COMPAT-2.
#
# YAML rather than JSON because this file has more to explain than to declare,
# and JSON cannot hold a comment. flatpak-builder reads both.
#
# Build it, from the repository root:
#
# flatpak-builder --user --install --force-clean \
# build/flatpak packaging/flatpak/paris.tourolle.darkroom.yml
#
# Read docs/distribution.md before changing any permission below. Every line in
# `finish-args` is a hole in the sandbox, and the one that is conspicuously
# absent — `--filesystem=` — is absent on purpose and is explained there.
id: paris.tourolle.darkroom
# 25.08 is the current freedesktop runtime, and the choice is made by what the
# binary needs rather than by what is newest. `ldd` on a release build names
# fontconfig, freetype, expat, libpng, zlib, brotli and bzip2 — all in the
# Platform — and nothing else: Vulkan, libxkbcommon and the two display-server
# protocols are reached without a link-time dependency (wgpu dlopens
# libvulkan.so.1, and x11rb and wayland-client speak the wire protocols in Rust
# rather than binding libxcb or libwayland). So the runtime has to supply a
# Vulkan loader and an ICD at *runtime*, which is the GL extension's job, and
# not much else.
runtime: org.freedesktop.Platform
runtime-version: '25.08'
sdk: org.freedesktop.Sdk
# The Rust toolchain is an SDK extension rather than something this manifest
# installs, so the build is offline-capable in the part that matters and the
# compiler is the one freedesktop tested against its own glibc.
#
# Note what this quietly overrides: `rust-toolchain.toml` pins 1.92.0, and that
# pin is honoured by *rustup*, which is not what the extension provides. The
# extension's cargo therefore ignores the file and builds with its own stable
# (1.98.0 on 25.08). That is fine here and deliberately different from
# CONTRIBUTING.md's "do not override the toolchain": the pin exists so `cargo
# fmt --check` and `clippy -D warnings` agree between a laptop and CI, and
# neither runs in this build. A *release binary* only needs a compiler at or
# above the workspace's `rust-version`.
sdk-extensions:
- org.freedesktop.Sdk.Extension.rust-stable
command: darkroom-desktop
finish-args:
# FR-PLAT-LIN-2 asks for both display servers. `fallback-x11` rather than
# `x11`: it grants the X socket only when Wayland is unavailable, so a
# Wayland session does not leave an X11 hole open beside the socket actually
# in use. `--share=ipc` goes with it — without it X11 cannot use shared
# memory and every frame is pushed through the socket instead.
- --socket=wayland
- --socket=fallback-x11
- --share=ipc
# The GPU. The develop pipeline is compute shaders through wgpu and there is
# no CPU renderer behind it, so this is not an optimisation: without
# /dev/dri the application starts and cannot develop anything.
#
# `dri` rather than `all`: it covers the render nodes and the NVIDIA device
# nodes, which is the whole of what a Vulkan ICD opens. `all` would add every
# other device on the machine for no gain.
- --device=dri
# Nextcloud (FR-NC-*). Nothing else here reaches the network — face grouping,
# segmentation and lens correction are all local and stay local (NFR-SEC-5).
- --share=network
# Credential storage (FR-NC-2). The keyring crate speaks the Secret Service
# D-Bus interface directly, which GNOME Keyring and KWallet's `ksecretd` both
# implement, so what it needs is a talk hole to that well-known name.
#
# Worth being precise, because FR-PLAT-LIN-3 says "Secret Service portal" and
# these are two different things: xdg-desktop-portal's `org.freedesktop.
# portal.Secret` hands an application a master key for a store it keeps
# itself, whereas this talks to the session's secret daemon. Only the latter
# puts the app password where `secret-tool` and Seahorse can see it, which is
# what makes a credential individually revocable by the user rather than
# opaque inside our own data directory. If no daemon answers, FR-NC-2's
# degraded mode is what the user gets — the same behaviour as outside a
# sandbox, which is the point.
- --talk-name=org.freedesktop.secrets
# No `--filesystem=` line of any kind, and this is the substance of
# FR-PLAT-LIN-3 rather than an omission.
#
# What that leaves working: /run/user/$UID/doc is mounted in every sandbox, so
# a photograph opened from a file manager — the .desktop entry declares the
# RAW MIME types and `Exec=darkroom-desktop %F` — arrives as a document-portal
# path in argv and opens. That path is genuinely portal-mediated and needs no
# code change.
#
# What that leaves broken: choosing a *library root*. The folder connector
# takes a typed absolute path (`SignIn::EndpointOnly`, placeholder
# `/home/you/Pictures`) and checks it with `std::fs`, and nothing in the tree
# calls the FileChooser portal — there is no ashpd, no rfd, no toolkit dialog.
# A path typed into that field does not exist in this sandbox, so the launch
# screen refuses it with "that folder does not exist", which is at least an
# honest error.
#
# `--filesystem=host` would make that work today and is exactly what the
# requirement forbids, so it is not here. docs/distribution.md §4 records what
# closes the gap and how to run a Flatpak build in the meantime.
modules:
- name: darkroom
buildsystem: simple
build-options:
append-path: /usr/lib/sdk/rust-stable/bin
env:
# Inside the build sandbox rather than in $HOME, so a rebuild starts
# from the state flatpak-builder is managing and not from whatever the
# host's cargo cache happens to hold.
CARGO_HOME: /run/build/darkroom/cargo
# Cargo fetches 826 crates, and Flathub's builders forbid this — a
# submission there needs `cargo-sources.json` generated by
# flatpak-builder-tools' `flatpak-cargo-generator.py` from Cargo.lock,
# listing every crate as its own source, plus a vendored-registry
# `.cargo/config.toml`. That file is ~30k lines, has to be regenerated on
# every dependency change, and buys nothing for a build from a local
# checkout, which is what this manifest is for and what packaging/PKGBUILD
# is for as well. Add it when there is a Flathub submission, not before.
build-args:
- --share=network
build-commands:
# `--locked` for the reason CI uses it: a lockfile that resolves
# differently in the packaging build than in the tree is a release whose
# dependency versions nobody chose.
- cargo build --release --locked -p darkroom-desktop
- install -Dm755 target/release/darkroom-desktop /app/bin/darkroom-desktop
- install -Dm644 packaging/paris.tourolle.darkroom.desktop
/app/share/applications/paris.tourolle.darkroom.desktop
- install -Dm644 packaging/paris.tourolle.darkroom.metainfo.xml
/app/share/metainfo/paris.tourolle.darkroom.metainfo.xml
# The icon's name is the contract, not its path — the desktop entry says
# `Icon=paris.tourolle.darkroom` and the shell resolves that through the
# hicolor theme. 256x256 because that is the source's actual size;
# installing it under a size it is not makes scaled icons look wrong.
- install -Dm644 ui/dr-ui/ui/app-icon.png
/app/share/icons/hicolor/256x256/apps/paris.tourolle.darkroom.png
# The face models, where `system_face_models_dirs()` looks: it reads
# $XDG_DATA_DIRS, which includes /app/share inside a Flatpak, so this is
# the same lookup that finds /usr/share/darkroom/models from the Arch
# package. Last in the search order, so a pair the user dropped in their
# own data directory still outranks these.
#
# These live in Git LFS. A checkout made without `git lfs pull` has
# ~130-byte pointers here, and `type: dir` below would copy the pointers
# in without complaint — producing a Flatpak whose face indexing fails
# inside the graph loader on the user's machine. Refuse instead, with the
# command that fixes it.
- |
for m in scrfd_500m_640.onnx arcface_mbf_b1.onnx; do
if [ "$(stat -c%s "models/face/$m")" -lt 100000 ]; then
echo "error: $m is an LFS pointer, not a model — run: git lfs pull" >&2
exit 1
fi
install -Dm644 "models/face/$m" "/app/share/darkroom/models/$m"
done
- install -Dm644 README.md /app/share/doc/darkroom/README.md
sources:
# The local checkout, for the same reason packaging/PKGBUILD builds from
# one: this makes a Flatpak of what you are actually working on. Swap it
# for an `archive` or `git` source with a tag when there is a release to
# point at.
#
# `skip` is not tidiness. `target/` is tens of gigabytes and `.git` with
# LFS objects is not small; flatpak-builder copies a `dir` source
# wholesale, so without these two lines the copy is the slowest part of
# the build by a wide margin.
- type: dir
path: ../..
skip:
- target
- target-android
- .git
- build