Run fully confined, clone from git, adopt ~/dev Rust conventions #1

Merged
schaefera merged 6 commits from confined into main 2026-09-30 11:39:06 +00:00
Collaborator

Brings the confined build to main:

  • Fully confined (content_exchange + networking policy groups; passes click-review): in-process OpenPGP via rPGP, app-owned store and keyring, key import through ContentHub.
  • Wizard can clone an existing store over SSH (git2, generated ed25519 deploy key, TOFU host keys, happy-eyeballs address selection for broken DNS64).
  • .gpg-id matches any non-revoked user ID.
  • ~/dev Rust conventions: rustfmt hook, workspace clippy lints + clippy.toml, cargo-make, Forgejo CI (rust-ci), cargo audit.

Verified on the device (Plinius, UT 24.04). This PR also runs the CI workflow for the first time.

🤖 Generated with Claude Code

Brings the confined build to `main`: - Fully confined (content_exchange + networking policy groups; passes click-review): in-process OpenPGP via rPGP, app-owned store and keyring, key import through ContentHub. - Wizard can clone an existing store over SSH (git2, generated ed25519 deploy key, TOFU host keys, happy-eyeballs address selection for broken DNS64). - `.gpg-id` matches any non-revoked user ID. - ~/dev Rust conventions: rustfmt hook, workspace clippy lints + clippy.toml, cargo-make, Forgejo CI (`rust-ci`), cargo audit. Verified on the device (Plinius, UT 24.04). This PR also runs the CI workflow for the first time. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Confined click apps cannot exec /usr/bin/gpg, reach gpg-agent, or read
~/.password-store and ~/.gnupg, so:

- passcore: replace the gpg wrapper with a Keyring on rPGP 0.20. Secret
  keys are armored files (0600) in the app's data dir; generate, import
  (full or subkeys-only/stub exports), encrypt (SEIPDv1) and decrypt.
  Store takes a Keyring and gains recipients() and insert().
  Tests cross-check against real GnuPG in both directions.
- App: store and keys live in ~/.local/share/touchpass.austinschaefer.
  The last working passphrase is remembered in memory only, with a Lock
  action to forget it.
- Wizard: import the key via the ContentHub picker (e.g. File Manager)
  instead of scanning Downloads; the transfer is finalized only after
  the worker thread has read the file.
- AppArmor: standard policy with only content_exchange. click-review
  passes, so no --skip-review needed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
passcore::git: libgit2 (git2 0.21) built in with vendored libssh2 and
OpenSSL, since a confined app can't run git or ssh. The app has its own
ed25519 SSH key (ssh-key crate, OpenSSH format, 0600) to add as a deploy
key, and a known_hosts of trusted host key fingerprints (SHA256, as
ssh-keygen -l shows them). An unknown host key fails the clone with
UnknownHostKey so the UI can ask; a changed one is a hard error. Clones
go to a .partial folder and are moved into place only when complete.

Store::has_key / Keyring::has_key_for: the wizard now also covers a
store that exists without its key (cloned or copied in), asking for the
recipients' key.

Wizard: clone step (public key + copy, URL, host key confirmation
dialog); after cloning, finish or ask for the key. AppArmor gains the
networking policy group; click-review still passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Set libgit2 connect (10 s) and idle (60 s) timeouts at startup. On a
  network whose DNS64 synthesizes an IPv6 address but whose NAT64 drops
  traffic, the clone otherwise sat in SYN_SENT for the kernel's ~2 min
  before trying IPv4, with the spinner running.
- Drop z: 1 from the wizard Loader: Lomiri popups go on the root item at
  z 0, so the host key dialog rendered underneath and was not tappable.
- Add a Paste button to the URL field: the field's own Paste stays
  disabled after copying in another app (Morph, Qt 6) because canPaste
  isn't refreshed; reading Clipboard.data.text explicitly works.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
libgit2 stops at the first address whose connect times out
(socket.c: 'if (error == GIT_TIMEOUT) break;') instead of trying the
next, so with the new connect timeout a dead DNS64/NAT64 IPv6 address
made the clone fail outright. Resolve the SSH host ourselves, try all
addresses in parallel, and clone from the first that answers; the host
key is still checked against the name, and origin is set back to the
URL as entered. Also drop the temporary clipboard debug log.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A store's .gpg-id may name a key by a full user ID that isn't the key's
first one (the real store names 'Name (Linux pass key) <email>' while
rPGP lists a since-revoked user ID first), so the imported key wasn't
recognized after cloning. Match any non-revoked user ID, as gpg does,
and show the primary non-revoked one in key lists.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adopt the ~/dev Rust conventions (fmt, clippy, cargo-make, CI)
All checks were successful
CI / ci (pull_request) Successful in 26m33s
CI / audit (pull_request) Successful in 13s
73f3fc67cb
Mirrors doubleo7/pkgwatch:
- rustfmt defaults (whole tree reformatted), tracked .githooks/pre-commit
  running cargo fmt --all; enable with cargo make install-hooks.
- [workspace.lints.clippy] all + cognitive_complexity, clippy.toml with
  complexity 15 / at most 3 arguments; members opt in via [lints].
- Makefile.toml: format(-check), lint (-D warnings), test,
  coverage-report (llvm-cov), audit, build, release-check, ci (default),
  plus click / click-install for the phone package.
- .forgejo/workflows/ci.yml on rust-ci, triggered on main and PRs; installs
  Qt 5 + gettext first, which the app crate's build script needs.

TouchPass specifics: workspace rust-version 1.94 (clickable's toolchain) so
the MSRV-aware resolver keeps Cargo.lock buildable for the phone; the app
binary has test = false (Qt glue, no unit tests; avoids linking Qt for
tests); QMAKE pinned to Qt 5 in Makefile.toml unless set; RUSTSEC-2023-0071
(rsa Marvin, no fix) ignored in .cargo/audit.toml with the reasoning.

Lint fixes: Keyring::generate takes a NewKey struct (validation moved onto
it); the QObject moved to src/bridge.rs, which alone allows the
useless_transmute qmetaobject's derive generates; 3-argument signals
reshaped (decrypted(entry, secret map), failed split into failed +
passphrase_needed) and generate_key takes a {name, email, passphrase} map;
build.rs if collapsed. Also fix a flaky test that expected a just-closed
ephemeral port to refuse connections (TCP self-connect); probe port 1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
schaefera deleted branch confined 2026-09-30 11:39:09 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
schaefera/TouchPass!1
No description provided.