Files
DarkRoom/packaging/flatpak/paris.tourolle.darkroom.yml
T
dtourolleandClaude Opus 5 7312aceded Get the scene model onto the devices that need it
The decoder can load from a path; nothing yet put a file at one. Four
packaging routes, and one lookup that finds the result.

## Not `include_bytes!`, unlike the instance model

The instance model is 11 MB and compiled in, which was the right call for
it: Android hands the app no filesystem path (ARCH §6.9) and 11 MB is
tolerable. The scene model is 24 MB, and 35 MB of constants in the binary
is paid by every install whether or not the tab is ever opened.

So it follows `models/face/` instead — carried as an APK asset, unpacked
once at first launch into the shared directory a desktop install already
uses, after which every lookup finds it where it finds a desktop user's.
Assets are stored rather than deflated in the APK, so unpacking is a copy
rather than an inflate.

`embedded-scene-model` exists for the desktop build with nowhere else to
read from, and for tests wanting the real graph. Off by default, which is
the asymmetry with `embedded-model` and the reason for a separate
feature.

## Three files, all or none

`scene_model` insists on the graph, its vocabulary and the category
descriptor together, for the reason `face_models` insists on its pair: a
graph alone decodes to 150 anonymous channels. Reporting the set missing
beats starting and failing at the first inference.

## The two model sets are not the same kind of thing

`install_bundled_models` now carries both, and the distinction is worth
keeping in view. Face weights are absent from the repository *by design*
— the InsightFace grant is research-only (docs/faces.md §2) — so a build
carrying none is ordinary. The scene model is committed, so a build
carrying none means a checkout without `git lfs pull`.

Neither is fatal. A photo editor that refuses to start over a missing
grading feature is worse than one that starts without it, so both report
themselves unavailable exactly as face indexing already did.

The LFS-pointer guards apply to the `.onnx` only. The vocabulary and the
descriptor are legitimately a few kilobytes, and a size check that fails
on them would be a guard against the wrong thing.

`scene_model` is exported ahead of the tab that will consume it so the
packaging added here has something to be verified against — assets
written where no lookup looks would be a silent mistake for as long as
the tab took to arrive.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-30 10:48:54 +02:00

202 lines
9.9 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
# The scene model, for the per-category grades. In the repository, unlike
# the face weights — AGPL, and GPLv3 §13 permits the combination
# (models/LICENCE.md) — so it installs unconditionally. Three files: the
# graph, its vocabulary, and the category descriptor; dr_ui reports the
# tab unavailable without any one of them. The pointer check is on the
# graph alone, the other two being legitimately small.
- |
if [ "$(stat -c%s models/scene/yolo26s-sem-ade20k.onnx)" -lt 100000 ]; then
echo "error: the scene model is an LFS pointer — run: git lfs pull" >&2
exit 1
fi
for m in yolo26s-sem-ade20k.onnx yolo26s-sem-ade20k.classes.json categories.txt; do
install -Dm644 "models/scene/$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