Skip to content

Phase 6: Linux distribution packaging (apt / dnf / pacman + signed GitHub Pages repos) - #36

Merged
stslex merged 17 commits into
devfrom
phase-6-packaging
Jun 25, 2026
Merged

Phase 6: Linux distribution packaging (apt / dnf / pacman + signed GitHub Pages repos)#36
stslex merged 17 commits into
devfrom
phase-6-packaging

Conversation

@stslex

@stslex stslex commented Jun 24, 2026

Copy link
Copy Markdown
Owner

Phase 6 — Linux distribution packaging

Gets Splitway onto non-Nix Linux machines via signed apt / dnf / pacman
repos on GitHub Pages (NixOS path unchanged). Implemented as the ordered commit
sequence below.

What ships

  • Two packages, lockstep-versioned (supersedes the ROADMAP's one-package
    sketch — keeps the security-critical core minimal and statically linked, and
    doesn't burden CLI-only installs with the GUI's GL/glibc stack):
    • splitway — daemon + CLI + unit, musl-static (runs on any glibc/musl
      baseline). NetworkManager / systemd-resolved are Recommends.
    • splitway-gui — egui binary, glibc 2.31 floor, Depends: splitway (>=).
  • Socket-group opt-in — the GUI package creates an empty splitway
    group + a service drop-in. Empty group ⇒ posture identical to 0600 root;
    the only grant is a human running usermod -aG splitway. Maintainer scripts
    never add a user (mirrors nix/tests/socket-group.nix).
  • CI (packaging.yml): build (cross-musl core + bullseye-container GUI,
    amd64+arm64) → install tests (debian/ubuntu/fedora/arch) → ephemeral-key
    signed-repo round trip → GPG-signed publish to Pages (dev/release channels,
    merge-not-wipe) → live post-deploy smoke. Arch via a self-hosted signed
    pacman repo (x86_64) + in-repo PKGBUILDs; the automated AUR push is deferred
    (registration disabled).
  • MIT LICENSE added (the repo had none) + license = "MIT" on all crates.

Commits

  1. feat(packaging) unit StateDirectory/--config + GUI Wayland app_id
  2. feat(packaging) musl-static splitway core deb/rpm package
  3. feat(packaging) splitway-gui deb/rpm + socket-group opt-in
  4. ci(packaging) build (musl core + glibc-floor GUI) + install tests
  5. ci(packaging) publish signed apt/dnf repos to Pages (push-only)
  6. feat(packaging) Arch PKGBUILDs + signed pacman repo (AUR deferred)
  7. docs(packaging) install docs + design record + Phase 6 done
  8. refactor(packaging) defensive hardening from self-review

Verification

  • cargo fmt/clippy/build/test (43) green; check-no-leaked-infra,
    actionlint, shellcheck all clean.
  • Built and inspected the real deb + rpm for both packages
    (cargo-deb 3.7.0 / cargo-generate-rpm 0.21.0) — caught the deb ../
    path-resolution, the maintainer-scripts dir needed for systemd scriptlets,
    and the dlopen'd GL/X11/wayland deps that auto-detection misses.
  • Built + verified the signed apt/dnf repos end-to-end — caught that rpm
    signing requires RSA, not EdDSA
    (EdDSA silently produces no signature).
  • Adversarial multi-agent review of the full diff found no real bugs.

Maintainer prerequisites (for the publish job — PRs use no secrets)

  • GPG_PRIVATE_KEY (must be RSA) + GPG_PASSPHRASE repo secrets.
  • Pages = deploy from gh-pages; Actions workflow permissions = read/write.
  • arm64 GUI builds use the ubuntu-24.04-arm runner.

Out of scope (flagged, not changed)

  • build.yml has branches: ["dev, master"] (one string) — a pre-existing bug
    that never matches master. Untouched here.
  • macOS Homebrew and the automated AUR ssh-push remain deferred.

🤖 Generated with Claude Code

https://claude.ai/code/session_018GKGiqawfPb5fzMGHisrrF

stslex and others added 8 commits June 24, 2026 22:47
…d app_id

Prepares the systemd unit and the egui GUI for the deb/rpm/pacman packages.

- splitway.service: ExecStart now passes
  `--config /var/lib/splitway/config.json` and the unit declares
  StateDirectory=splitway (0700), mirroring nix/module.nix. The daemon
  creates its config under the persistent, daemon-owned state dir on first
  run instead of falling back to /root/.config (and logging a warning).
- Add the same --config to the commented socket-group opt-in ExecStart
  override. The bare `ExecStart=` reset fully replaces the command, so
  without this an opt-in user would silently drop --config and reintroduce
  the /root/.config fallback this commit removes. (Minor deviation from the
  "keep the block untouched" plan, for correctness/consistency with the
  main ExecStart and nix/module.nix.)
- splitway-gui: set the ViewportBuilder app_id to
  io.github.stslex.splitway so Wayland compositors map the window to the
  packaged .desktop entry + hicolor icon (shipped under that basename by
  the GUI package).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018GKGiqawfPb5fzMGHisrrF
Adds the MIT LICENSE (the repo had none) and declares `license = "MIT"` on
every workspace crate, then defines the deb + rpm metadata for the core
`splitway` package on splitway-daemon.

The package ships both binaries — `splitway-daemon` and the `splitway` CLI
(from splitway-cli) — plus the systemd unit and README/LICENSE. It is built
musl-static (*-unknown-linux-musl) so it has no shared-library dependencies
and installs on any glibc/musl baseline; the desktop GUI is a separate
package (next commit) that Depends on this one.

- [package.metadata.deb]: name=splitway, binaries to /usr/bin, copyright from
  the MIT text, Recommends network-manager + systemd-resolved (not Depends),
  empty Depends (static). An (empty) maintainer-scripts dir enables cargo-deb's
  systemd-units integration to generate the postinst/postrm: enable+start on
  install, restart on upgrade, stop on remove, daemon-reload.
- [package.metadata.generate-rpm]: same layout, auto-req disabled (static),
  weak-dep Recommends, raw /bin/sh systemd scriptlets (cargo-generate-rpm does
  not expand %systemd_* macros).

Asset-path note (verified by building both packages with cargo-deb 3.7.0 and
cargo-generate-rpm 0.21.0): cargo-deb resolves non-`target/` asset paths
relative to THIS crate's manifest dir (so workspace-root files use `../`),
while cargo-generate-rpm resolves relative to the invocation dir (workspace
root, bare paths) — hence the intentional path skew between the two blocks.

Version is stamped at the packaging layer (cargo deb --deb-version /
cargo generate-rpm --set-metadata) so dev builds get <ver>~dev.<utc>.<sha>
without a non-semver string in Cargo.toml. CI builds per-triple with --target
(remaps target/release -> target/<triple>/release and stamps the arch).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018GKGiqawfPb5fzMGHisrrF
…opt-in

A separate `splitway-gui` package for the egui desktop GUI (glibc, dynamic),
which Depends on the musl-static core `splitway` (>=, the IPC compat contract).

- .desktop (io.github.stslex.splitway.desktop, Exec=splitway-gui) +
  hicolor icons rasterized from assets/icon/splitway-icon.svg (8 PNG sizes +
  scalable SVG, basename = app_id) via packaging/icons/generate-hicolor.sh.
  The icon tree is committed so every packaging path ships it without a
  rasterizer; the .desktop passes desktop-file-validate.
- Dependencies (both formats, verified by building real packages): the
  eframe/glow windowing libs are DLOPEN'd by winit/glow at runtime, so they
  are absent from the ELF DT_NEEDED and neither cargo-deb's $auto nor
  cargo-generate-rpm's auto-req can detect them — they MUST be hardcoded.
  deb: libgl1, libx11-6, libxcursor1, libxi6, libxrandr2, libwayland-client0,
  libxkbcommon0, libc6 (>= 2.31 floor). rpm: mesa-libGL, libX11, libXcursor,
  libXi, libXrandr, libwayland-client, libxkbcommon (auto-req still pins the
  glibc floor from libc/libgcc sonames). Recommends an XDG desktop portal +
  backend for rfd's file dialog.
- Socket-group opt-in (security-sensitive): the maintainer scripts create an
  EMPTY `splitway` group and install a service drop-in switching the daemon
  to group-socket mode (0660 root:splitway, dir 0750). EMPTY-GROUP INVARIANT:
  with no members the posture is identical to the default 0600 root-only; the
  scripts NEVER add a user — the only grant is a human running
  `usermod -aG splitway <user>` + re-login. postinst installs the drop-in +
  reloads + restarts; postrm removes it, groupdel only if empty, reverting to
  root-only. A loud first-install message prints the exact opt-in one-liner.

Validated by building both packages with cargo-deb 3.7.0 /
cargo-generate-rpm 0.21.0 and inspecting deps, files, and scriptlets.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018GKGiqawfPb5fzMGHisrrF
…tests

New packaging.yml (no secrets — gates the PR). The publish/sign job is added
in the next commit.

- meta: compute the channel version once (compute-version.sh). Release (push
  to master) = clean <X.Y.Z>; dev/PR/dispatch = <X.Y.Z>~dev.<utc>.<sha>, which
  sorts below the release in dpkg and rpm.
- build-core (amd64 + arm64): `cross` builds splitway-daemon + splitway CLI
  musl-static, asserts `file` reports "statically linked", then cargo-deb /
  cargo-generate-rpm with --target (remaps target/release + sets arch) and the
  stamped version. Emits deb + rpm + tarball.
- build-gui (amd64 + arm64): builds the egui binary INSIDE debian:bullseye
  (glibc 2.31 floor, so the libc6 (>= 2.31) / rpm GLIBC requires are true),
  arm64 on a native arm64 runner (no QEMU). Rewrites the core-dependency floor
  to the built version for dev channels, then packages on the host.
- test-install (debian:bookworm, ubuntu:22.04, fedora:latest): installs the
  built artifacts directly; asserts the binaries run, the unit validates
  (systemd-analyze verify), the GUI pulls splitway + the GL deps, the empty
  `splitway` group exists with no members, and the .desktop validates.
- test-signed-repo: generates a THROWAWAY gpg key, builds + signs local apt
  (build-apt-repo.sh) and dnf (build-dnf-repo.sh) repos from the artifacts,
  serves them over localhost, and installs with signature verification ON —
  proving metadata + signing + verify end-to-end with no production secret.
  (Those two repo scripts are reused by the real publish job next commit.)
- test-arm64-smoke: best-effort arm64 deb install under QEMU.

Validated locally: actionlint clean (incl. shellcheck of run blocks); all
helper scripts shellcheck-clean; the deb/rpm builds + --target/--deb-version/
--set-metadata flags exercised against cargo-deb 3.7.0 / cargo-generate-rpm
0.21.0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018GKGiqawfPb5fzMGHisrrF
Adds the publish-pages job (secrets) and the signing plumbing reused by the
PR-time ephemeral-key test.

- publish-pages: runs on push to master (release) / dev (dev) and dispatch,
  NEVER on pull_request, gated behind the build + test jobs. Serialized by a
  single `pages-deploy` concurrency group (cancel-in-progress: false) so two
  deploys queue rather than clobber. Imports the RSA signing key, checks out
  (or bootstraps) the persistent gh-pages branch, drops the artifacts into the
  correct channel pool, regenerates + signs ONLY that channel's apt + dnf
  metadata, publishes the armored pubkey to splitway.gpg, renders index.html,
  and commits + pushes — MERGE, never wipe, so old versions and the other
  channel survive. Release additionally attaches the tarballs to the v<ver>
  GitHub Release. A post-deploy smoke waits for Pages to go live, then installs
  from the real repo with signature verification ON (apt + dnf).
- build-apt-repo.sh / build-dnf-repo.sh: optional SPLITWAY_GPG_PASSFILE feeds
  the real key's passphrase via loopback (never on a command line); unset for
  the passphrase-less ephemeral key.
- render-index.sh: the Pages landing page with per-distro, per-channel
  add-repo snippets + the key fingerprint.
- RSA fix: the throwaway test key (and, by requirement, GPG_PRIVATE_KEY) is RSA
  — rpm --addsign only produces a verifiable signature with RSA; an EdDSA key
  silently yields no RPMTAG_RSAHEADER (found + fixed via local signing tests).

Validated locally: apt InRelease + Release.gpg verify Good; per-arch Packages
filtering correct; dnf repomd.xml.asc verifies; RSA-signed rpm passes rpm -K
("digests signatures OK"). actionlint + shellcheck clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018GKGiqawfPb5fzMGHisrrF
Closes Arch via a self-hosted, GPG-signed pacman repo (reusing the apt/dnf key
+ Pages), since AUR registration is disabled. x86_64 for the hosted repo;
aarch64 users use the in-repo splitway-bin PKGBUILD.

PKGBUILDs in packaging/aur/ (also usable now via `makepkg -si`):
- splitway: source build of daemon + CLI from the release tag; makedepends
  cargo; ships both binaries + unit; splitway.install prints the enable hint
  (Arch policy: no auto-enable); optdepends networkmanager / systemd-resolvconf.
- splitway-bin: prebuilt from the release tarball (x86_64 + aarch64);
  provides/conflicts splitway.
- splitway-gui: source egui GUI; depends splitway + libglvnd/libxkbcommon/
  wayland/libx11/libxcursor/libxi/libxrandr; optdepends xdg-desktop-portal;
  splitway-gui.install mirrors the deb/rpm empty-group + drop-in invariant.

CI (packaging.yml):
- build-arch: archlinux container, non-root makepkg of the two source
  PKGBUILDs from THIS checkout (clean version — pacman has no ~dev channel
  because vercmp does not treat ~ as a pre-release marker), validates
  .SRCINFO + namcap (advisory). x86_64 .pkg.tar.zst artifacts.
- test-arch: pacman -U local install (asserts binaries + unit + empty group),
  then a throwaway-RSA-key signed repo — repo-add --sign, pacman-key
  --add/--lsign, SigLevel = Required DatabaseOptional, pacman -Sy with
  verification ON.
- publish-pages (release only): copies the .pkg.tar.zst into the persistent
  arch/release/x86_64 subtree, detach-signs each with the real key, repo-add
  (in a container) incrementally (old packages preserved), replaces Pages-
  hostile db/files symlinks with real signed copies, reuses splitway.gpg.
  Post-deploy: live `pacman -Sy splitway` with verification ON.
- render-index.sh: Arch section (signed repo primary, makepkg alternative,
  AUR pending).

DEFERRED (not here): the automated AUR ssh push — blocked on AUR registration
reopening. The in-repo PKGBUILDs are the bridge.

Validated locally: all PKGBUILD/.install bash-syntax-clean; actionlint clean;
index renders with $arch left literal for pacman.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018GKGiqawfPb5fzMGHisrrF
Docs only — no code.

- README: Install sections for apt / dnf (both channels) and Arch (signed
  pacman repo primary, in-repo PKGBUILD makepkg alternative; AUR pending), with
  the key-verification note. NixOS section left as-is.
- packaging/README.md: a Distribution packages (deb / rpm / pacman) section —
  the two-package split, dev vs release channels + ~dev versioning, the glibc
  2.31 floor, the dlopen'd GL deps, the GUI socket-group drop-in + empty-group
  invariant, and the pacman specifics (x86_64-only, no ~dev channel, detached
  signing, repo-add incrementality).
- docs/design/linux-distro-packaging.md: the durable record — decisions 1-7,
  the channel/version topology, dep lists, signing + merge mechanics across all
  three formats (incl. the RSA-not-EdDSA and cargo-deb-vs-generate-rpm
  path-resolution gotchas), the two-layer test design, the pacman-now/AUR-later
  Arch strategy, and the signing key.
- ROADMAP.md: Phase 6 marked done (Linux), noting the two-package design
  supersedes the original one-package sketch; macOS Homebrew + the automated
  AUR push remain deferred.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018GKGiqawfPb5fzMGHisrrF
No behavior change for the current (validated) inputs — robustness only,
flagged by an adversarial self-review of the diff:

- build-apt-repo.sh: `break` after the first Architecture line in the per-arch
  Packages filter (the canonical value; a valid stanza has exactly one).
- packaging.yml: `g` flag on the GUI core-dep-floor seds (defensive if more
  `splitway (>=` references are ever added).
- packaging.yml: comment that test-arm64-smoke is intentionally not a publish
  gate (best-effort under QEMU, continue-on-error).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018GKGiqawfPb5fzMGHisrrF

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e6f221f05d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/workflows/packaging.yml
Comment thread packaging/aur/splitway-bin/PKGBUILD

@stslex stslex left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: Phase 6 — Linux distribution packaging

Note: posting as a comment because GitHub won't let me formally "Request changes" on this account's own PR — but treat the must-fix list as change-requesting.

Reviewed the full diff (39 files) both as independent slices (deb/apt, rpm/dnf, pacman/Arch, CI structure, signing/versioning/desktop) and as one cohesive change. Overall this is strong, careful work — the two-package split, the musl-static core vs glibc-floor GUI, the empty-splitway-group opt-in invariant (consistently mirrored across deb/rpm/pacman scriptlets), the upgrade-safe maintainer scripts ($1/$2 and remove|purge guards, members-empty check before groupdel, systemd-presence guards), and the merge-not-wipe channel model are all well thought through and well documented.

CI is currently red on two jobs, one of which is a real build-blocking bug, plus a second bug that makepkg would hit but CI never exercises.

Must-fix (blocking)

  1. build-core (amd64) fails — static-linkage gate greps the wrong string. musl static-PIE binaries are static-pie linked, not statically linked, so the gate aborts before any deb/rpm is built and kills the whole downstream pipeline. (.github/workflows/packaging.yml:69)
  2. packaging/aur/splitway-bin/PKGBUILD references a non-existent install scriptlet (splitway-bin.install vs the committed splitway.install) → makepkg fails. Not caught because build-arch never builds splitway-bin. (PKGBUILD:15)
  3. test-arch extracts the GPG fingerprint from the wrong colon field ($5 instead of $10), yielding an empty FPR that breaks the Arch signed-repo test. The publish-pages job gets this right. (packaging.yml:428)

build-gui (arm64) failure

The second red job is a transient crates.io reset (HTTP2 framing layer … Connection reset by peer), not a code defect — but the container build has no cargo retry/cache, so it's a flakiness vector worth hardening (commented at packaging.yml:136).

Should-fix (security / correctness, non-blocking)

  • Publish before verify: gh-pages is pushed before any signature is checked; a misconfigured (non-RSA) key would poison the live repo and only fail afterward. Add a pre-push gpg --verify / rpm -K gate. (packaging.yml:558)
  • apt Release has no Valid-Until → no freeze/replay protection. (build-apt-repo.sh:69)
  • sha256sums=('SKIP') on the prebuilt root-daemon tarballs disables integrity checking on privileged binaries. (splitway-bin/PKGBUILD:18)
  • Dev-channel apt doc snippet never installs the keyring → users following only the dev instructions hit a failing apt-get update. (render-index.sh:50)

Nits / consistency

  • Arch GUI depends=('splitway') drops the >= floor the deb/rpm contract encodes (splitway-gui/PKGBUILD:15); lockstep version is hand-kept in three places (splitway-gui/Cargo.toml:3); dev-floor sed also rewrites a comment and doesn't escape/verify $PKGVER (packaging.yml:145); design-doc signing-key fingerprint is still a placeholder while README/index tell users to verify against it; README/index pacman snippets omit the pacman-key --init that CI itself runs.

Note on coverage

The signed-repo tests (apt + dnf + pacman) only install the core package, so the GUI package's header signature and Requires are never verified under signature enforcement (the unsigned test-install does install the GUI). Worth closing that gap.

One earlier candidate finding — that the arm64 GUI ships a libc6 (>= 2.31) floor it doesn't honor — was checked and dismissed: the debian:bullseye build step (packaging.yml:125) is not arch-gated, so it runs in an arm64 bullseye container on the arm runner and the 2.31 floor holds on both arches.

Reviewed with an independent multi-agent pass (per-build-type checks + holistic full-diff reviews); findings above were individually verified against the code and CI logs, not taken on trust.


Generated by Claude Code

Comment thread .github/workflows/packaging.yml Outdated
Comment thread packaging/aur/splitway-bin/PKGBUILD
Comment thread .github/workflows/packaging.yml Outdated
Comment thread .github/workflows/packaging.yml
Comment thread packaging/ci/build-apt-repo.sh
Comment thread packaging/aur/splitway-bin/PKGBUILD
Comment thread packaging/aur/splitway-gui/PKGBUILD Outdated
Comment thread .github/workflows/packaging.yml Outdated
Comment thread .github/workflows/packaging.yml
Comment thread splitway-gui/Cargo.toml
Resolves the Codex + self-review findings on the Phase 6 packaging PR.

Build-blocking CI fixes:
- static-linkage gate: assert no dynamic interpreter instead of requiring the
  literal "statically linked" (musl x86_64 is "static-pie linked") — unblocks
  build-core (amd64) and the whole deb/rpm pipeline
- test-arch: read the GPG fingerprint from fpr field 10, not 5 (5 is empty)
- AUR splitway-bin: rename splitway.install -> splitway-bin.install so
  install="$pkgname.install" resolves (makepkg no longer aborts)
- build-gui: CARGO_NET_RETRY + a retrying `cargo fetch --locked` before the
  offline build, hardening the transient arm64 crates.io failure

Supply-chain / correctness:
- apt: dearmor the published (armored) key into the binary keyring at every
  consumption site (works on every apt version; the published file stays armored
  for rpm --import / pacman-key)
- publish: fail-closed signature verification (apt InRelease+Release.gpg, dnf
  repomd + per-rpm RSA header, pacman db+pkg sigs) BEFORE the gh-pages push
- apt Release: Valid-Until (APT_VALID_DAYS, default 90d; LC_ALL=C) for
  freeze/replay protection
- dev-floor sed: anchor to the dependency lines (leave the comment), and assert
  the floor was actually stamped
- signed-repo tests (apt/dnf/pacman) now install splitway-gui too, exercising the
  GUI package under signature enforcement

Arch / consistency / docs:
- splitway-gui PKGBUILD: splitway>=$pkgver floor + hicolor-icon-theme,
  desktop-file-utils
- new check-pkgver-sync.sh (meta job): daemon version == every PKGBUILD pkgver
- render-index/README: dearmor --yes for apt, pacman-key --init, dev-channel
  keyring install
- splitway-bin SKIP digest documented + tracked for the deferred AUR-push phase

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@stslex

stslex commented Jun 25, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@stslex

stslex commented Jun 25, 2026

Copy link
Copy Markdown
Owner Author

Review addressed — 3cb9760

Thanks for the thorough pass (Codex + the self-review). All 13 inline threads are replied-to and resolved; summary of the changes in 3cb9760:

Build-blocking CI (the two red jobs):

  • build-core (amd64) — static-linkage gate now asserts the absence of a dynamic interpreter, so it accepts musl static-pie linked (amd64) and statically linked (arm64) alike.
  • build-gui (arm64) — the transient crates.io reset is hardened: CARGO_NET_RETRY=10 + CARGO_HTTP_MULTIPLEXING=false and a retrying cargo fetch --locked before the offline build.
  • test-arch FPR now reads fpr: field $10, and splitway-bin's install= is repaired by the splitway.install → splitway-bin.install rename (would have broken makepkg).

Supply-chain / correctness:

  • Publish-before-verify closed: a fail-closed "Verify all signatures BEFORE publishing" step (apt InRelease+Release.gpg, dnf repomd.xml.asc + per-rpm RSA-header, release-channel pacman db+pkg sigs) now runs before the gh-pages push.
  • apt Valid-Until added (freeze/replay), LC_ALL=C for stable date parsing.
  • apt key is dearmored into the binary keyring at every site (the published key stays armored for rpm --import / pacman-key).
  • signed-repo tests (apt/dnf/pacman) now install splitway-gui too — the GUI is exercised under signature enforcement.
  • dev-floor sed anchored to the dependency lines (+ anchored assert that the floor was actually stamped).

Arch / consistency / docs:

  • splitway-gui PKGBUILD: splitway>=$pkgver floor + hicolor-icon-theme / desktop-file-utils.
  • new check-pkgver-sync.sh in the meta job guards the lockstep (daemon version == every PKGBUILD pkgver).
  • README + landing page: dearmor --yes, pacman-key --init, dev-channel keyring install.

Deferred (tracked): splitway-bin's sha256sums=('SKIP') — documented in the PKGBUILD and docs/design/linux-distro-packaging.md; the deferred AUR-push automation must stamp real per-tag digests (only knowable post-release). Fails no CI today (splitway-bin isn't built/pushed in this phase).

Findings were re-verified with an independent adversarial multi-agent pass over the fix diff (no must-fix issues; its suggestions — LC_ALL=C, dearmor --yes, anchored assert, fetch-retry ordering — are folded in). actionlint + shellcheck clean.

Generated by Claude Code

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3cb9760de3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/workflows/packaging.yml Outdated
Comment thread packaging/systemd/splitway.service
Comment thread packaging/aur/splitway-bin/PKGBUILD Outdated
…oling

Follow-up to the second Codex pass on PR #36.

- splitway-bin: `provides=("splitway=$pkgver")` (was unversioned). pacman only
  satisfies a versioned dependency from a versioned provision, so splitway-gui's
  new `splitway>=$pkgver` floor could not install against the prebuilt core.
- test-signed-repo: install `rpm` in the tooling step. build-dnf-repo.sh needs
  `rpm --addsign`; it passes today only because ubuntu-latest pre-installs rpm —
  make it explicit (matches the publish job) so the dnf signing path is robust.
- packaging/README.md: document the one-time config relocation for users who ran
  the daemon by hand as root before packaging (old XDG fallback
  /root/.config/splitway -> /var/lib/splitway). No maintainer-script migration:
  no published package used the old path, it is config-not-read (not data loss),
  and the deb core postinst is cargo-deb-generated (#DEBHELPER#) from an empty
  dir, so a hand-written migration would risk breaking systemd enablement that
  the docker install-test (no systemd) could not catch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@stslex

stslex commented Jun 25, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 66634a2276

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/workflows/packaging.yml
The `meta` gate (check-pkgver-sync.sh) requires every packaging/aur/*/PKGBUILD
`pkgver=` to equal the daemon version, but release.yml's bump-version job only
bumped splitway-daemon/Cargo.toml. So the first post-release auto-bump would
leave the three PKGBUILDs behind and fail every later packaging run in `meta`
until a human hand-edited all three.

- Add packaging/ci/sync-pkgver.sh: the write side of the lockstep invariant
  (symmetric with check-pkgver-sync.sh, same daemon-version read). It stamps the
  daemon version into each PKGBUILD `pkgver=` and resets `pkgrel=1` (Arch
  convention on a version change). The `$pkgver`-derived fields (source URLs,
  provides, depends floor) follow automatically.
- release.yml bump-version: run sync-pkgver.sh after the Cargo.toml bump and
  `git add` the PKGBUILDs into the same commit.

Verified locally: no-op when already in sync (no diff), correct bump on a
simulated 0.0.5 -> 0.0.6, and check-pkgver-sync.sh passes against the result.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@stslex

stslex commented Jun 25, 2026

Copy link
Copy Markdown
Owner Author

@codex review

1 similar comment
@stslex

stslex commented Jun 25, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b3260367b1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread splitway-gui/Cargo.toml Outdated
Comment thread .github/workflows/packaging.yml Outdated
…review)

Two findings from Codex's re-review of b326036:

- P2 (splitway-gui/Cargo.toml): the deb declared libgl1 but not libegl1,
  and the rpm required mesa-libGL but not mesa-libEGL. glow/glutin creates
  its GL context via EGL on Wayland (glutin_egl_sys is in Cargo.lock; GLX on
  X11), and libgl1 does NOT pull libegl1 — so a minimal Wayland-only install
  would succeed and then fail at GL-context creation. Add libegl1 (deb) and
  mesa-libEGL (rpm); both pull the libglvnd EGL loader, mirroring how
  libgl1/mesa-libGL pull GLX. Arch is unaffected (libglvnd already provides
  libGL + libEGL). Doc + comments updated.

- P1 (packaging.yml test-arch): the local `pacman -U` smoke preinstalled the
  GL/X11/wayland libs but not hicolor-icon-theme/desktop-file-utils, which
  the splitway-gui PKGBUILD declares as hard deps. `pacman -U` on local files
  only auto-resolves deps from a synced repo DB, so the smoke must not lean
  on that — preinstall the full declared set explicitly, matching the PKGBUILD.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@stslex

stslex commented Jun 25, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@stslex stslex left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review — Phase 6 Linux packaging

Reviewed via a multi-agent fan-out: one reviewer per packaging target (apt/deb, dnf/rpm, pacman/AUR, CI workflows, GPG signing + Pages publish, GUI desktop/socket-group) plus three broad passes over the whole diff (correctness bug-hunt, security/supply-chain, docs/consistency/redaction). CI is fully green (23 checks) and all 19 prior review threads are resolved.

Verdict: no P1 / blocking issues. The PR is mergeable. The signing path (RSA-required, fail-closed verify-before-publish, dearmored apt keyring at every site), the empty-splitway-group privilege model, the no-secret-PR / push-only-publish trust split, and the lockstep version invariant are all sound and were each independently confirmed. A few non-blocking items worth a look:

P2 — worth addressing (none block merge)

  • Stale README contradicts this PR's own change. README.md:451-453 states the interim egui GUI "does not carry the io.github.stslex.splitway app id, so the window rule is Tauri-only" — but splitway-gui/src/app.rs:41 now adds .with_app_id("io.github.stslex.splitway"), and the packaged splitway-gui (egui) ships that .desktop + StartupWMClass + icons. The passage is now incorrect. Relatedly, README frames io.github.stslex.splitway as the Tauri app while the deb/rpm/pacman splitway-gui install instructions actually ship the egui binary under that id — one clarifying line would prevent reader confusion.
  • GUI deb postinst: groupadd is unguarded under set -e. getent group splitway || groupadd --system splitway (step 1) — a genuine groupadd failure aborts the postinst and dpkg marks the package half-configured. The rpm scriptlet runs without set -e so it's tolerant there. Impact is "install fails cleanly" (drop-in not yet written ⇒ no half-applied DNS), hence P2 — but the abort path looks unintended.
  • grep '^version' … | head -1 under set -o pipefail in compute-version.sh:21, check-pkgver-sync.sh:12, sync-pkgver.sh:14, release.yml:120. Harmless today (one ^version line), but a latent SIGPIPE trap if a second top-level version ever appears — would make the guard scripts themselves flake. awk/sed -n '0,/^version/{...}' is robust.
  • splitway-bin PKGBUILD drops optdepends (networkmanager/systemd-resolvconf) that the source splitway PKGBUILD and the deb/rpm Recommends carry — same package contents, inconsistent prereq hints.
  • splitway-bin sha256sums=('SKIP') on prebuilt privileged-daemon binaries — already documented + deferred to the AUR-push automation (which must stamp per-tag digests). Flagging only so it's tracked to closure before splitway-bin is actually published; fails no CI today.

P3 — nits

  • docs/design/linux-distro-packaging.md Debian dep list omits libegl1 (the deb Depends and packaging/README.md have it).
  • deb-vs-rpm portal-backend skew: deb Recommends lists gtk/wlr/kde portals, rpm weak-deps only gtk.
  • rpm signature proof uses %{RSAHEADER:pgpsig} (rpm ≤5 tag); already noted in-workflow as a future-runner (rpm 6) landmine — fails closed, so safe direction.
  • build-arch is a hard needs: gate for publish-pages, so an Arch-only failure also blocks dev-channel apt/dnf publishes — worth a deliberate comment if intentional.
  • AUR # Maintainer: lines commit a real personal email — standard AUR convention, just confirm it's the intended address.
  • Post-deploy smoke "live after Ns" message is off by one sleep (cosmetic); generate-hicolor.sh is dev-only / not CI-exercised.

Confirmed correct

Static-linkage gate (accepts musl static-pie + statically linked, rejects dynamic); verify-all-signatures-before-gh-pages-push fail-closed ordering; merge-not-wipe publish + pages-deploy concurrency serialization (no stale-base push race); apt Valid-Until replay/freeze bound; GUI EGL+GL deps on both deb & rpm; empty-group invariant across deb/rpm/AUR/nix (no script ever adds a user); versioned provides/depends floors; release.yml sync-pkgver.sh keeps the three PKGBUILDs in lockstep; no real-infra leakage (RFC-5737/3849 placeholders throughout); MIT LICENSE + license = "MIT" on all crates.

Reviewed with a Claude Code multi-agent fan-out.


Generated by Claude Code

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6de36d7be4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread splitway-gui/Cargo.toml Outdated

@stslex stslex left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inline notes for the four P2 items from the summary review above — none blocking.


Generated by Claude Code

Comment thread packaging/gui-maintainer-scripts/postinst Outdated
Comment thread packaging/ci/compute-version.sh Outdated
Comment thread packaging/aur/splitway-bin/PKGBUILD
Comment thread README.md
…iew round 3)

Codex re-review (1) + self-review (4) of 6de36d7:

- P2 (Codex, splitway-gui): add the X11 xkb runtime lib. winit's default `x11`
  feature has xkbcommon-dl dlopen libxkbcommon-x11.so (confirmed in Cargo.lock),
  a SEPARATE package from libxkbcommon on Debian (libxkbcommon-x11-0), Fedora
  (libxkbcommon-x11) AND Arch (libxkbcommon-x11 — verified via the Arch package
  API: it provides libxkbcommon-x11.so and is NOT bundled in the base
  libxkbcommon). Without it an X11 session can fail to load the lib before the
  window opens. Added to the deb Depends, rpm Requires, the Arch GUI PKGBUILD
  depends, and both Arch CI preinstall lists.

- P2 (postinst): the groupadd under `set -e` was the only unguarded mutating
  step. Keep the abort (the drop-in is meaningless without the group) but make
  it deliberate + diagnosable with an explicit error message + exit.

- P2 latent (version reads): `grep '^version' … | head -1` SIGPIPEs grep under
  `set -o pipefail` if a second match ever appears. Replaced with a SIGPIPE-free
  `awk -F'"' '/^version/{print $2; exit}'` in compute-version.sh,
  check-pkgver-sync.sh (+ the pkgver read), sync-pkgver.sh and release.yml (x3).

- P2 (splitway-bin): surface the same networkmanager / systemd-resolvconf
  optdepends as the source splitway PKGBUILD (identical daemon, same prereqs).

- P2 (README niri): the interim egui GUI now sets `.with_app_id(...)` and the
  packaged splitway-gui ships the matching .desktop + icons, so the app-id
  window rule is no longer Tauri-only; clarified the packaged GUI is the egui build.

Also quoted "$GITHUB_OUTPUT" in the release.yml blocks touched above
(pre-existing SC2086). actionlint + shellcheck clean; awk reads verified to
yield 0.0.5 and check-pkgver-sync passes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@stslex

stslex commented Jun 25, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9e811e16ef

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/workflows/release.yml Outdated
…emon (Codex P2)

The post-release auto-bump stamped the AUR PKGBUILDs with the *next*
(unreleased) daemon version, so `makepkg -si` from master pointed at a
`v$pkgver` tag / release assets that do not exist yet.

- release.yml: run sync-pkgver.sh BEFORE the daemon bump so the PKGBUILDs
  pin the just-released version; bump the daemon afterwards for the next cycle.
- check-pkgver-sync.sh: validate the pinned pkgver names a release tag that
  EXISTS (all three PKGBUILDs agreeing), with a no-tags bootstrap fallback —
  instead of requiring equality with the in-tree daemon version.
- packaging.yml: fetch-depth: 0 on the meta checkout so the gate sees tags.
- docs/design: record the invariant and why the gate checks existence, not
  "latest" (avoids spurious dev/PR and release-window failures).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EKokH93URrCizdQdVpNCNM
@stslex

stslex commented Jun 25, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9998f4cd6b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/workflows/packaging.yml
… daemon (Codex P2)

Follow-up to pinning the AUR pkgver to the released tag: the daemon version
now intentionally runs ahead of the committed pkgver. build-arch pre-placed
the checkout tarball as <pkg>-$VERSION.tar.gz (daemon version), but the
PKGBUILD's source= expects <pkg>-$pkgver.tar.gz. When the two diverge makepkg
can't find the local archive and silently downloads the old v$pkgver tag,
building stale code while the smoke test still passes.

Read pkgver from each PKGBUILD and key both the archive filename and the
git-archive --prefix on it, so the pre-placed checkout is always the source
makepkg uses. Drop the now-unused VERSION env.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EKokH93URrCizdQdVpNCNM
@stslex

stslex commented Jun 25, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7154872a62

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/workflows/packaging.yml Outdated
Comment thread packaging/ci/render-index.sh Outdated
stslex and others added 2 commits June 25, 2026 23:41
…inned tag (Codex P1)

The committed PKGBUILD pkgver intentionally lags to the last released tag (so a
user's `makepkg -si` resolves an existing tag), but the hosted pacman repo must
ship the version being released. The prior approach keyed the build on the
committed pkgver, so a release push would publish Arch packages under the
PREVIOUS version and Arch users would never receive the new one.

build-arch now stamps the daemon/meta version into the EPHEMERAL PKGBUILD (never
committed) and pre-places the matching <pkgname>-<VERSION>.tar.gz so makepkg
builds THIS checkout as VERSION — no tag download, correct published version.
The committed PKGBUILDs and the check-pkgver-sync.sh gate are untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EKokH93URrCizdQdVpNCNM
`pacman -Sy <pkg>` refreshes the sync databases without upgrading, leaving the
DB ahead of installed packages and resolving deps from a partial-upgrade state.
Use `-Syu` in the hosted-index and README install snippets. The CI smoke tests
keep `-Sy` (throwaway containers, not user systems).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EKokH93URrCizdQdVpNCNM
@stslex

stslex commented Jun 25, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Bravo.

Reviewed commit: a1aa009d8d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@stslex
stslex merged commit b210631 into dev Jun 25, 2026
27 of 29 checks passed
@stslex
stslex deleted the phase-6-packaging branch June 25, 2026 22:00
stslex added a commit that referenced this pull request Jun 26, 2026
Resolves the Codex + self-review findings on the Phase 6 packaging PR.

Build-blocking CI fixes:
- static-linkage gate: assert no dynamic interpreter instead of requiring the
  literal "statically linked" (musl x86_64 is "static-pie linked") — unblocks
  build-core (amd64) and the whole deb/rpm pipeline
- test-arch: read the GPG fingerprint from fpr field 10, not 5 (5 is empty)
- AUR splitway-bin: rename splitway.install -> splitway-bin.install so
  install="$pkgname.install" resolves (makepkg no longer aborts)
- build-gui: CARGO_NET_RETRY + a retrying `cargo fetch --locked` before the
  offline build, hardening the transient arm64 crates.io failure

Supply-chain / correctness:
- apt: dearmor the published (armored) key into the binary keyring at every
  consumption site (works on every apt version; the published file stays armored
  for rpm --import / pacman-key)
- publish: fail-closed signature verification (apt InRelease+Release.gpg, dnf
  repomd + per-rpm RSA header, pacman db+pkg sigs) BEFORE the gh-pages push
- apt Release: Valid-Until (APT_VALID_DAYS, default 90d; LC_ALL=C) for
  freeze/replay protection
- dev-floor sed: anchor to the dependency lines (leave the comment), and assert
  the floor was actually stamped
- signed-repo tests (apt/dnf/pacman) now install splitway-gui too, exercising the
  GUI package under signature enforcement

Arch / consistency / docs:
- splitway-gui PKGBUILD: splitway>=$pkgver floor + hicolor-icon-theme,
  desktop-file-utils
- new check-pkgver-sync.sh (meta job): daemon version == every PKGBUILD pkgver
- render-index/README: dearmor --yes for apt, pacman-key --init, dev-channel
  keyring install
- splitway-bin SKIP digest documented + tracked for the deferred AUR-push phase

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
stslex added a commit that referenced this pull request Jun 26, 2026
…oling

Follow-up to the second Codex pass on PR #36.

- splitway-bin: `provides=("splitway=$pkgver")` (was unversioned). pacman only
  satisfies a versioned dependency from a versioned provision, so splitway-gui's
  new `splitway>=$pkgver` floor could not install against the prebuilt core.
- test-signed-repo: install `rpm` in the tooling step. build-dnf-repo.sh needs
  `rpm --addsign`; it passes today only because ubuntu-latest pre-installs rpm —
  make it explicit (matches the publish job) so the dnf signing path is robust.
- packaging/README.md: document the one-time config relocation for users who ran
  the daemon by hand as root before packaging (old XDG fallback
  /root/.config/splitway -> /var/lib/splitway). No maintainer-script migration:
  no published package used the old path, it is config-not-read (not data loss),
  and the deb core postinst is cargo-deb-generated (#DEBHELPER#) from an empty
  dir, so a hand-written migration would risk breaking systemd enablement that
  the docker install-test (no systemd) could not catch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant