Skip to content

Commit 6dbc073

Browse files
Ryanmello07claude
andcommitted
build(packaging): Flatpak (GUI-only) + immutable-distro install layout
FLATPAK GUI only, and that is not a corner cut: a Flatpak sandbox has no /dev/net/tun, no CAP_NET_ADMIN and no way to install a system unit. Mozilla VPN — the one official VPN on Flathub — DELETED its privileged daemon and drives NetworkManager over D-Bus instead, forfeiting split tunneling. That trade contradicts this product's leak-prevention requirements, so it is not taken. Instead this follows Trayscale's pattern: the sandboxed GUI reaches the HOST's urnetworkd over /run/urnetwork/control.sock (read-only is sufficient — connect(2) on a unix socket is not a filesystem write, and Linux exempts sockets from the read-only-mount EROFS check). A missing host daemon already renders as a distinct, actionable state, so the sandbox explains itself instead of failing blank. - packaging/flatpak/network.ur.urnetwork.yml: builds the whole gtkmm stack (libsigc++/glibmm/cairomm/pangomm/gtkmm) against GNOME SDK 49, which carries GTK4 and libadwaita but no C++ bindings. Verified built, installed and running on Bazzite. - packaging/make-flatpak.sh: runs flatpak-builder AS a flatpak, so an immutable host needs nothing layered onto /usr. - -Dhost_integration=false: drops the daemon and the four files that install to ABSOLUTE paths (/lib/systemd/system, /etc/urnetwork, /etc/NetworkManager, /etc/udev). Nothing outside /app is writable in a Flatpak build, so without it the install step fails outright. - AppStream metainfo (required by Flathub; also read by GNOME Software and Discover for the .deb/.rpm). Flathub still needs two things deliberately NOT done here: a git/archive source instead of the local dir, and the brand-font redistribution audit. IMMUTABLE HOSTS (ostree/bootc — Silverblue, Bazzite, Kinoite, MicroOS) /usr is a read-only image mount there, so install.sh now DETECTS that (it identifies this Bazzite host with no flag) and remaps: /usr/bin -> /usr/local/bin, /usr/lib/urnetwork -> /usr/local/lib/urnetwork, /usr/share -> /usr/local/share, /lib/systemd/system -> /etc/systemd/system It rewrites the unit's ExecStart to match, runs restorecon (a wrong SELinux label on a systemd-exec'd binary is an AVC denial that looks like a mystery), and writes the manifest in INSTALL paths. uninstall.sh derives its own location instead of hardcoding /usr/lib. --layout forces either mode for testing. Install -> upgrade -> uninstall round-trips to zero files left in both layouts. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011zv2X6ZPH8h6uBudVCGvG7
1 parent 9409d7a commit 6dbc073

5 files changed

Lines changed: 563 additions & 33 deletions

File tree

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
AppStream metadata. Required by Flathub, and read by GNOME Software / KDE
4+
Discover for every other channel too. The component id MUST equal the .desktop
5+
basename and the GTK application id in src/main.cpp (network.ur.urnetwork) or
6+
the store shows a nameless entry and D-Bus activation misses.
7+
8+
project_license is the CODE license (MPL-2.0). The four commercial brand
9+
faces under assets/fonts are separately licensed and NOT MPL — they are
10+
declared here as a non-free content attribute rather than folded into the
11+
project license, because claiming MPL over them would be false.
12+
13+
SPDX-License-Identifier: MPL-2.0
14+
-->
15+
<component type="desktop-application">
16+
<id>network.ur.urnetwork</id>
17+
18+
<name>URnetwork</name>
19+
<summary>Private, decentralized VPN powered by a peer network</summary>
20+
21+
<metadata_license>CC0-1.0</metadata_license>
22+
<project_license>MPL-2.0</project_license>
23+
24+
<developer id="ur.io">
25+
<name>URnetwork</name>
26+
</developer>
27+
28+
<description>
29+
<p>
30+
URnetwork routes your traffic through a decentralized network of peers
31+
instead of a single provider's servers. Choose a country or let the app
32+
pick the best available provider, and connect.
33+
</p>
34+
<p>
35+
Provide bandwidth back to the network and earn payouts in USDC. Track
36+
what you have provided, what you are owed, and where your network ranks.
37+
</p>
38+
<p>
39+
This Flatpak ships the graphical client only. Connecting requires the
40+
URnetwork system service, which must be installed from your
41+
distribution's native package: a sandboxed application cannot create a
42+
tunnel device or enforce a kill switch. The app talks to that service
43+
over a local socket and tells you plainly when it is missing or out of
44+
date.
45+
</p>
46+
</description>
47+
48+
<launchable type="desktop-id">network.ur.urnetwork.desktop</launchable>
49+
50+
<url type="homepage">https://ur.io</url>
51+
<url type="bugtracker">https://github.com/urnetwork/urnetwork-linux/issues</url>
52+
<url type="help">https://ur.xyz</url>
53+
54+
<categories>
55+
<category>Network</category>
56+
<category>Security</category>
57+
</categories>
58+
59+
<keywords>
60+
<keyword>VPN</keyword>
61+
<keyword>privacy</keyword>
62+
<keyword>proxy</keyword>
63+
<keyword>decentralized</keyword>
64+
</keywords>
65+
66+
<supports>
67+
<control>pointing</control>
68+
<control>keyboard</control>
69+
<control>touch</control>
70+
</supports>
71+
72+
<requires>
73+
<display_length compare="ge">480</display_length>
74+
</requires>
75+
76+
<content_rating type="oars-1.1"/>
77+
78+
<releases>
79+
<release version="0.0.1" date="2026-08-14">
80+
<description>
81+
<p>First Linux release of the GTK4 client.</p>
82+
</description>
83+
</release>
84+
</releases>
85+
</component>
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
# URnetwork — Flatpak manifest (GUI ONLY).
2+
#
3+
# WHY GUI-ONLY (docs/linux_agent_help.md §10.1/§10.2, and it is not a
4+
# limitation we chose): a Flatpak sandbox has no /dev/net/tun, no
5+
# CAP_NET_ADMIN, and no way to install a system unit. No official VPN with a
6+
# kill switch ships its daemon in a Flatpak — Mozilla VPN, the one on Flathub,
7+
# DELETED its privileged daemon and drives NetworkManager over D-Bus instead,
8+
# forfeiting split tunneling. That trade contradicts this product's
9+
# leak-prevention requirements, so it is not taken.
10+
#
11+
# Instead this follows the Trayscale pattern: the GUI runs sandboxed and
12+
# reaches the host's urnetworkd over its Unix control socket, exposed with
13+
# --filesystem=/run/urnetwork:ro. Read-only is correct AND sufficient:
14+
# connect(2) on a unix socket is not a filesystem write, and Linux exempts
15+
# sockets from the read-only-mount EROFS check (only REG/DIR/LNK are covered).
16+
# The daemon itself comes from the host's native .deb/.rpm.
17+
#
18+
# The app already renders the missing-daemon case as a distinct, actionable
19+
# state (MainWindow::StartTunnelUi: "service not running" / "service out of
20+
# date" / "app out of date" / "builds differ"), so a Flatpak with no host
21+
# daemon explains itself instead of failing blank.
22+
#
23+
# LOCAL INSTALL (the current target — Flathub submission comes later):
24+
# flatpak install -y flathub org.gnome.Platform//49 org.gnome.Sdk//49
25+
# flatpak run org.flatpak.Builder --force-clean --user --install \
26+
# build-flatpak packaging/flatpak/network.ur.urnetwork.yml
27+
# flatpak run network.ur.urnetwork
28+
#
29+
# FOR FLATHUB, two changes are required and are deliberately NOT made here:
30+
# 1. `sources: [{type: dir, path: ../..}]` must become a git/archive source
31+
# with a commit or sha256 — Flathub forbids local directory sources.
32+
# 2. The four commercial brand faces under app/assets/fonts are NOT MPL-2.0.
33+
# Flathub requires the licenses of every bundled binary asset to permit
34+
# redistribution; that audit (§7.17) must land before submission.
35+
#
36+
# SPDX-License-Identifier: MPL-2.0
37+
id: network.ur.urnetwork
38+
runtime: org.gnome.Platform
39+
runtime-version: '49'
40+
sdk: org.gnome.Sdk
41+
command: urnetwork-gui
42+
43+
finish-args:
44+
# Display. Wayland first (this is a Wayland-native GTK4 app); X11 fallback
45+
# for the sessions that still need it.
46+
- --socket=wayland
47+
- --socket=fallback-x11
48+
- --share=ipc
49+
- --device=dri
50+
51+
# The SDK talks to the URnetwork API over the internet, and the GUI reaches
52+
# the daemon's loopback mTLS device RPC on 127.0.0.1. Both need the host
53+
# network namespace.
54+
- --share=network
55+
56+
# THE HOST DAEMON'S CONTROL SOCKET. Without this the app runs, shows the
57+
# login screen, and reports "service not running" — it never silently fails.
58+
- --filesystem=/run/urnetwork:ro
59+
60+
# The SNI tray (Tray.cpp speaks StatusNotifier over raw GDBus). Only the
61+
# WATCHER is declared: flatpak's name policy can grant a dotted subtree but
62+
# not the hyphenated org.kde.StatusNotifierItem-<pid>-<id>, so no --own-name
63+
# can match it (flatpak rejects the manifest outright if you try). Tray.cpp
64+
# handles this by falling back to registering under the connection's unique
65+
# bus name, which the SNI spec allows and which needs no ownership.
66+
- --talk-name=org.kde.StatusNotifierWatcher
67+
68+
# Opening sign-in / wallet-connect / support links in the user's browser.
69+
- --talk-name=org.freedesktop.portal.OpenURI
70+
71+
# Autostart is requested through the Background portal — the sandboxed
72+
# equivalent of the ~/.config/autostart symlink the native build writes.
73+
- --talk-name=org.freedesktop.portal.Background
74+
75+
# GeoClue: the app can pin the device's reported location to the connected
76+
# provider. Optional — a refused permission costs the override, nothing else.
77+
- --system-talk-name=org.freedesktop.GeoClue2
78+
79+
cleanup:
80+
- /include
81+
- /lib/pkgconfig
82+
- /lib/cmake
83+
- /share/pkgconfig
84+
- /share/aclocal
85+
- /share/devhelp
86+
- /share/doc
87+
- /share/man
88+
- '*.a'
89+
- '*.la'
90+
91+
modules:
92+
# ---- the gtkmm stack --------------------------------------------------
93+
# org.gnome.Sdk 49 carries GTK 4.20 and libadwaita 1.8 but NO C++ bindings,
94+
# so the five -mm libraries are built here. Versions are pinned to the ones
95+
# that pair with the runtime's C libraries (glib 2.86, cairo 1.18, pango
96+
# 1.57, GTK 4.20) — do not bump one without the others.
97+
- name: libsigc++
98+
buildsystem: meson
99+
config-opts:
100+
- -Dbuild-documentation=false
101+
- -Dbuild-examples=false
102+
sources:
103+
- type: archive
104+
url: https://download.gnome.org/sources/libsigc++/3.6/libsigc++-3.6.0.tar.xz
105+
sha256: c3d23b37dfd6e39f2e09f091b77b1541fbfa17c4f0b6bf5c89baef7229080e17
106+
107+
- name: glibmm
108+
buildsystem: meson
109+
config-opts:
110+
- -Dbuild-documentation=false
111+
- -Dbuild-examples=false
112+
sources:
113+
- type: archive
114+
url: https://download.gnome.org/sources/glibmm/2.86/glibmm-2.86.0.tar.xz
115+
sha256: 39c0e9f6da046d679390774efdb9ad564436236736dc2f7825e614b2d4087826
116+
117+
# cairomm 1.16+ is released by cairographics.org, NOT download.gnome.org
118+
# (the GNOME mirror stops at the old 1.12/1.15 API series).
119+
- name: cairomm
120+
buildsystem: meson
121+
config-opts:
122+
- -Dbuild-documentation=false
123+
- -Dbuild-examples=false
124+
- -Dbuild-tests=false
125+
sources:
126+
- type: archive
127+
url: https://www.cairographics.org/releases/cairomm-1.18.0.tar.xz
128+
sha256: b81255394e3ea8e8aa887276d22afa8985fc8daef60692eb2407d23049f03cfb
129+
130+
- name: pangomm
131+
buildsystem: meson
132+
config-opts:
133+
- -Dbuild-documentation=false
134+
sources:
135+
- type: archive
136+
url: https://download.gnome.org/sources/pangomm/2.56/pangomm-2.56.2.tar.xz
137+
sha256: f1e984c85a85b6a0e61616366521f51dd8282a072bb45d15b5084762b62f4c0e
138+
139+
- name: gtkmm
140+
buildsystem: meson
141+
config-opts:
142+
- -Dbuild-documentation=false
143+
- -Dbuild-demos=false
144+
- -Dbuild-tests=false
145+
sources:
146+
- type: archive
147+
url: https://download.gnome.org/sources/gtkmm/4.20/gtkmm-4.20.0.tar.xz
148+
sha256: daad9bf9b70f90975f91781fc7a656c923a91374261f576c883cd3aebd59c833
149+
150+
# ---- nlohmann_json (header-only) --------------------------------------
151+
- name: nlohmann-json
152+
buildsystem: cmake-ninja
153+
config-opts:
154+
- -DJSON_BuildTests=OFF
155+
sources:
156+
- type: archive
157+
url: https://github.com/nlohmann/json/archive/refs/tags/v3.12.0.tar.gz
158+
sha256: 4b92eb0c06d10683f7447ce9406cb97cd4b453be18d7279320f7b2f025c10187
159+
160+
# ---- the app ----------------------------------------------------------
161+
- name: urnetwork
162+
buildsystem: meson
163+
subdir: app
164+
config-opts:
165+
- -Dgui=enabled
166+
# Drops urnetworkd and the four host-integration files that install to
167+
# ABSOLUTE paths (/lib/systemd/system, /etc/urnetwork, /etc/NetworkManager,
168+
# /etc/udev) — nothing outside /app is writable in a Flatpak build, so
169+
# without this the install step fails outright. It also relocates the
170+
# vendored SDK to the libdir root, where the GUI's own rpath
171+
# ($ORIGIN/../lib) finds it.
172+
- -Dhost_integration=false
173+
post-install:
174+
# The installed .desktop points Exec at /usr/bin/urnetwork, the daemon
175+
# package's launcher that finds and execs the user's AppImage. There is
176+
# no AppImage here and no /usr/bin to put a launcher in, so the Flatpak
177+
# runs the GUI binary directly.
178+
- desktop-file-edit --set-key=Exec --set-value="urnetwork-gui %u"
179+
/app/share/applications/network.ur.urnetwork.desktop
180+
- desktop-file-edit --remove-key=TryExec
181+
/app/share/applications/network.ur.urnetwork.desktop
182+
sources:
183+
# LOCAL SOURCE — replace with a git/archive source before any Flathub
184+
# submission (see the header). The path is relative to this manifest.
185+
- type: dir
186+
path: ../..

packaging/make-flatpak.sh

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
#!/bin/bash
2+
# Build (and optionally install) the URnetwork Flatpak — the GUI-only artifact.
3+
#
4+
# The daemon is NOT in here and cannot be: a Flatpak sandbox has no
5+
# /dev/net/tun, no CAP_NET_ADMIN and no way to install a system unit. The app
6+
# reaches the HOST's urnetworkd over /run/urnetwork/control.sock, which the
7+
# manifest exposes read-only (Trayscale's pattern). Install the daemon from the
8+
# native .deb/.rpm — see packaging/flatpak/network.ur.urnetwork.yml for the
9+
# full reasoning.
10+
#
11+
# Works on an immutable host (Bazzite/Silverblue): flatpak-builder itself runs
12+
# as a flatpak (org.flatpak.Builder), so nothing is layered onto /usr.
13+
#
14+
# ./packaging/make-flatpak.sh # build only
15+
# ./packaging/make-flatpak.sh --install # build + install --user
16+
# ./packaging/make-flatpak.sh --install --run # ...and launch it
17+
# ./packaging/make-flatpak.sh --bundle # build + export a .flatpak file
18+
#
19+
# SPDX-License-Identifier: MPL-2.0
20+
set -euo pipefail
21+
22+
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
23+
MANIFEST="${MANIFEST:-$REPO_ROOT/packaging/flatpak/network.ur.urnetwork.yml}"
24+
APP_ID="network.ur.urnetwork"
25+
RUNTIME_VERSION="${RUNTIME_VERSION:-49}"
26+
# The build dir MUST live inside the repo. flatpak-builder runs sandboxed and
27+
# gets a PRIVATE /tmp, so a build dir under the host's /tmp vanishes between
28+
# stages ("Build directory not initialized, use flatpak build-init").
29+
BUILD_DIR="${BUILD_DIR:-$REPO_ROOT/build-flatpak}"
30+
OUT_DIR="${OUT_DIR:-$REPO_ROOT/out}"
31+
32+
DO_INSTALL=0
33+
DO_RUN=0
34+
DO_BUNDLE=0
35+
for arg in "$@"; do
36+
case "$arg" in
37+
--install) DO_INSTALL=1 ;;
38+
--run) DO_INSTALL=1; DO_RUN=1 ;;
39+
--bundle) DO_BUNDLE=1 ;;
40+
*) echo "unknown argument: $arg" >&2; exit 2 ;;
41+
esac
42+
done
43+
44+
command -v flatpak >/dev/null || { echo "flatpak is not installed" >&2; exit 1; }
45+
[[ -f "$MANIFEST" ]] || { echo "manifest not found: $MANIFEST" >&2; exit 1; }
46+
47+
# flatpak-builder: prefer a native one, fall back to the flatpak'd builder
48+
# (the only option on an immutable host).
49+
if command -v flatpak-builder >/dev/null; then
50+
BUILDER=(flatpak-builder)
51+
else
52+
if ! flatpak info org.flatpak.Builder >/dev/null 2>&1; then
53+
echo "installing org.flatpak.Builder (no native flatpak-builder found)"
54+
flatpak install -y --noninteractive flathub org.flatpak.Builder
55+
fi
56+
BUILDER=(flatpak run org.flatpak.Builder)
57+
fi
58+
59+
echo "==> ensuring runtime + sdk ${RUNTIME_VERSION}"
60+
flatpak install -y --noninteractive flathub \
61+
"org.gnome.Platform//${RUNTIME_VERSION}" "org.gnome.Sdk//${RUNTIME_VERSION}" >/dev/null
62+
63+
# --disable-rofiles-fuse: rofiles-fuse needs FUSE inside the builder sandbox,
64+
# which is not reliably available on an immutable host. It only costs build
65+
# isolation, not correctness.
66+
BUILD_ARGS=(--force-clean --disable-rofiles-fuse)
67+
[[ "$DO_INSTALL" == 1 ]] && BUILD_ARGS+=(--user --install)
68+
if [[ "$DO_BUNDLE" == 1 ]]; then
69+
mkdir -p "$OUT_DIR"
70+
BUILD_ARGS+=(--repo="$BUILD_DIR-repo")
71+
fi
72+
73+
echo "==> building $APP_ID"
74+
( cd "$REPO_ROOT" && "${BUILDER[@]}" "${BUILD_ARGS[@]}" "$BUILD_DIR" "$MANIFEST" )
75+
76+
if [[ "$DO_BUNDLE" == 1 ]]; then
77+
VERSION="${VERSION:-0.0.0-dev}"
78+
BUNDLE="$OUT_DIR/URnetwork-${VERSION}.flatpak"
79+
echo "==> exporting $BUNDLE"
80+
flatpak build-bundle "$BUILD_DIR-repo" "$BUNDLE" "$APP_ID" \
81+
--runtime-repo=https://flathub.org/repo/flathub.flatpakrepo
82+
echo "built: $BUNDLE"
83+
fi
84+
85+
if [[ "$DO_INSTALL" == 1 ]]; then
86+
echo "==> installed. The GUI needs the HOST daemon to connect:"
87+
echo " sudo systemctl status urnetworkd # install it from the .deb/.rpm"
88+
fi
89+
90+
[[ "$DO_RUN" == 1 ]] && exec flatpak run "$APP_ID"
91+
exit 0

0 commit comments

Comments
 (0)