vcswatch reports that
this package seems to have new commits in its VCS but has
not yet updated debian/changelog. You should consider updating
the Debian changelog and uploading this new version into the archive.
Here are the relevant commit logs:
commit 1ad8c869279ae9eba311b64f1288ae722c18de4d
Author: TheJonaz <44526342+TheJonaz@users.noreply.github.com>
Date: Sat Aug 8 20:15:42 2026 +0200
macOS installer, OpenBSD/NetBSD ports, and a native arm64 AppImage (#7)
* recommend: extend the suggestion manifest to the BSDs
The curated MANIFEST was #[cfg(target_os = "linux")], so FreeBSD, OpenBSD and
NetBSD — all of which have (or will have) ports — fell through to the empty
fallback: `moraine recommend` built, installed and shipped, then answered
"nothing to suggest". A BSD desktop is the same XDG layout, the same ~/.config,
the same Firefox/Thunderbird dot-directories, so it is one manifest, not four.
A typo in a target_os string still compiles, so a new test asserts the manifest
is non-empty on whatever platform it runs — a ports/pkgsrc builder's `make test`
now checks its own platform.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* packaging: OpenBSD ports + NetBSD pkgsrc recipes, generated off-BSD
Both trees build Rust offline from a per-crate list plus a checksum file in a
tree- and OS-specific encoding, normally produced on the target OS. Everything
they compute is a property of bytes on crates.io, so bsd-distinfo.py does it
here: it reads the crate list from the Cargo.lock inside the *tagged tarball*
(never the working tree, which drifts after a release) and verifies every
downloaded .crate against the SHA-256 Cargo.lock already records before taking
any other digest — a wrong file cannot reach the output.
Both recipes are CLI-only: OpenBSD's WANTLIB for a CLI is correct by
construction, whereas a GTK4 WANTLIB needs a real OpenBSD build; NetBSD follows
for symmetry (adding the GUI later is one buildlink3 include).
check-crate-lists.py now covers both lists, and bump-recipes.sh skips them in
its blanket version sed — their distinfo carries a per-crate hash, so a sed
would rewrite a crate sitting at the old version and leave every hash stale;
they are regenerated instead.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* packaging: universal macOS installer (.pkg) for the CLI
build-pkg.sh produces moraine-<version>-macos-universal.pkg: a standard installer
that drops the moraine CLI into /usr/local/bin (plus man page and docs). The
binary is universal — arm64 + x86_64 lipo'd together — so one package covers
Apple Silicon and Intel, and the script fails loudly if the result is not
actually fat.
A .pkg, not a .dmg: the macOS build is CLI-only (bundling GTK4 into a .app is
separate work), and a disk image holding a bare executable teaches the user
nothing about where it goes. Built --features keyring (native Keychain, no extra
runtime dep) so `password = "keyring:"` works out of the box, which the plain
--no-default-features tarball does not — a deliberate, documented difference.
Unsigned: proper signing needs the 99 USD/yr Apple Developer Program plus two
certificates. README.md documents the three ways past Gatekeeper meanwhile.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* appimage: build the aarch64 AppImage natively too
linuxdeploy bundles the host's GTK, so an AppImage's architecture is whatever
machine builds it — there is no cross-compiling this. GitHub's ubuntu-24.04-arm
runners are free for public repos and are the same 24.04 base, so the glibc 2.39
floor (forced by GTK >= 4.10 / features = ["v4_10"]) applies to aarch64
unchanged. Raspberry Pi / ARM-server desktops get the GUI as one portable file,
matching what x86_64 already ships.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* ci: build the macOS installer + native arm64 AppImage on release
Adds a macos-pkg job (universal .pkg via packaging/macos/build-pkg.sh, smoke-
tested with `installer` + `--version` + `man -w`) and turns the appimage job
into a matrix over ubuntu-24.04 and ubuntu-24.04-arm, so both AppImage
architectures are built natively and their arch is asserted before upload.
Also fixes a latent gap: the checksums job hashes whatever is attached when it
runs, so every asset-uploading job must be in its `needs`. macos-pkg was
missing, which would have raced the .pkg out of SHA256SUMS on a real release.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* ci: push-triggered test-build for the two off-workstation artifacts
The macOS .pkg and the aarch64 AppImage are the only release artifacts that
cannot be produced on the Linux workstation, so they had never actually been
built. This branch-scoped workflow builds just those two (as workflow
artifacts, no Release, no recipe bump, main untouched) to prove the recipes
before they reach a real tag. Push-triggered because a workflow_dispatch is
only exposed once the file lives on the default branch. Remove before merging.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* macos: make optional signing safe under set -u on bash 3.2
`productbuild "${sign[@]}"` with an empty `sign` array aborts with
"sign[@]: unbound variable" on macOS's /bin/bash (3.2), where expanding an
empty array under `set -u` is an error rather than the empty string newer bash
produces. This broke every unsigned build — i.e. the default path in CI — after
the universal binary and pkgbuild had already succeeded. Use the portable
${sign[@]+"${sign[@]}"} idiom. Caught by the test-build CI run.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* appimage: fix the aarch64 arch assertion in the smoke test
`file -b` prints the arch token twice for the aarch64 binary — once in the ELF
header ("ARM aarch64") and once in the interpreter path
("ld-linux-aarch64.so.1") — so `grep -o` returned two lines and the
string-equality check against a single "aarch64" always failed. Assert the
expected token is present (grep -qw) instead. This was latent in release.yml
too: the aarch64 AppImage had never been built, so its leg had never run.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* ci: drop the throwaway test-build workflow
It did its job: the macOS .pkg and the aarch64 AppImage built green (run
31211710777) and the two release-breakers it surfaced are fixed. The real jobs
live in release.yml, so this branch-scoped harness is no longer needed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
commit 10d03e816bd3f48c62d3accb38f0f4aea908f9e3
Author: TheJonaz <44526342+TheJonaz@users.noreply.github.com>
Date: Wed Aug 5 22:27:29 2026 +0200
ci: stop vendor apt repositories from failing our builds (#6)
The lint job on 4d74fc6 died at "Install GTK" with exit 100, on a commit that
changed a .gitignore, a README, a nuspec and a Python script — nothing a Rust
build can see. `apt-get update` had returned 403 for
packages.microsoft.com/repos/azure-cli and .../ubuntu/24.04/prod, both
configured by the runner image, neither of any use to a job installing
libgtk-4-dev from Ubuntu's archive.
That is the whole failure mode: apt-get update exits non-zero if *any*
configured source fails, so an outage at a vendor we do not depend on takes down
a build that would otherwise be green. Re-running fixed it, which is exactly what
makes it worth fixing properly — a red main that clears on retry teaches the
habit of retrying, and the next real breakage gets the same shrug.
The five apt steps across three workflows now go through a composite action that
drops those sources first. It selects them by what they point at rather than by
filename, because the filenames move between image versions: anything not
mentioning ubuntu.com goes. That keeps archive.ubuntu.com, azure.archive on the
x86 images and ports.ubuntu.com on the arm64 runner flatpak-aarch64.yml uses,
and removes the Microsoft and Google entries.
Should a future image name Ubuntu's own archive something that does not match,
the failure is a loud "Unable to locate package", not a silent skip.
Verified: the selection logic was run against a directory holding the three
vendor lists plus both Ubuntu layouts (deb822 azure.archive and ports) — the
three go, both Ubuntu sources stay. All four YAML files parse, and no apt-get
call site is left unconverted. ci.yml's own run on this PR exercises the action
for real; release.yml and flatpak-aarch64.yml do not run on pull requests, so
their two call sites are checked by inspection only.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit 4d74fc639f085e25287adb786898c894bf223d8e
Author: TheJonaz <44526342+TheJonaz@users.noreply.github.com>
Date: Wed Aug 5 22:05:09 2026 +0200
chocolatey: build the package on Linux, and pin an icon (#5)
The recipe has been complete since 0.1.x and never submitted, for a practical
reason: `choco pack` runs on Windows only, and the only machine here is a Mint
host. That made the last roadmap channel depend on borrowing a Windows box.
It does not have to. A .nupkg is an OPC container — a zip holding the nuspec,
the payload, and three bookkeeping parts (`[Content_Types].xml`, `_rels/.rels`
and a `.psmdcp` core-properties part) that NuGet clients require. build-nupkg.py
writes those directly, so the package builds anywhere Python does. Its structure
was compared part by part against a real package from nuget.org; the part names,
relationship types and content-type defaults match. The output is reproducible —
identical sha256 across rebuilds — so what was pushed stays checkable later.
This is safe here specifically because the payload is a downloader script rather
than compiled artefacts: nothing about the build host can end up in the package.
A Windows host is still the only way to *test* `choco install`, which is also
what Chocolatey's own verifier does on submission.
Pushing needs no choco either: the endpoint is a plain NuGet PUT with the API
key in X-NuGet-ApiKey, which curl does. Both routes are in the README.
Verified against the actual v0.2.2 release rather than assumed: the Windows zip
contains moraine-windows-x86_64/moraine.exe, matching what the shim comment in
chocolateyinstall.ps1 claims, and the SHA-256 pinned there matches both
SHA256SUMS and the downloaded file.
Also adds <iconUrl>, whose absence moderators flag. It points at jsDelivr rather
than raw.githubusercontent, which Chocolatey asks for, and is pinned to the
release tag so the icon cannot change under an already-approved package. That
pin is now in the per-release checklist, along with the two fields that were
already there; the push example in the README still said 0.1.19.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit 005cb75a488f9d0fc14c63e15c13caaf26126443
Author: Jonaz Thern <44526342+TheJonaz@users.noreply.github.com>
Date: Wed Aug 5 13:54:56 2026 +0200
packaging: fail the release bump when the vendored crate lists are stale
FreeBSD, Gentoo and Flatpak build offline from a hand-maintained list of every
crates.io dependency. Nothing regenerates those lists, and bump-recipes.sh only
printed a reminder at the very end that they had been left alone — after the
commit, and easy to scroll past. A list that is missing a crate then fails
solely on the distributions that build offline, long after the release, in a
place the author has no reason to look.
check-crate-lists.py parses all three formats — FreeBSD's `name-version`,
Gentoo's `name@version`, Flatpak's crates.io URLs — and compares them against a
Cargo.lock. bump-recipes.sh now runs it against the Cargo.lock taken from the
*released tarball it already downloads*, not the working tree: the recipes are
about to point at that tag, and the working tree is usually ahead of it.
The check runs before anything is written, so a mismatch stops the bump outright
rather than leaving the recipes half-edited. --skip-crate-check overrides it.
Calibrated before being enforced: against v0.2.2's Cargo.lock all three lists
match exactly — 157 crates.io dependencies, none missing, none stale — so strict
equality is the right rule and will not cry wolf. Only `registry+` sources are
compared; the root package and any path/git dependency is never vendored.
Against main it reports 50 missing from each, which is the keyring work merged
in f774f02. Worth noting the number: `cargo tree` on Linux shows 27 new crates,
but Cargo.lock carries every platform's, so apple-native-keyring-store and
windows-native-keyring-store and their transitive dependencies count too. Judging
this by eye was never going to work.
The lists are deliberately NOT regenerated here. The recipes still pin v0.2.2,
whose Cargo.lock has none of these crates, so today's lists are correct for what
they build; regenerating now would break the offline builds. That belongs to the
next release bump — which is precisely what this check will insist on.
Verified end to end against a copy of packaging/: a matching list lets the bump
run, a single removed crate aborts it with the crate name and the regeneration
command, no file is touched before the abort, and --skip-crate-check gets past
it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit 920af2585258a7c1f705ac5abc41c4ec88c64ec8
Author: Jonaz Thern <44526342+TheJonaz@users.noreply.github.com>
Date: Tue Aug 4 23:00:35 2026 +0200
ci: allow running the workflow on demand
f774f02 reached main without any CI run: the squash-merge API call errored
after updating the ref, so no push event was dispatched and
`actions/runs?head_sha=f774f02c` returns zero. The tree was covered by the
pull_request run on the identical commit, but there was no way to give main
its own run short of pushing an empty commit.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit ea227a20e13eea310d604f4f11f7fa153e636986
Author: Jonaz Thern <44526342+TheJonaz@users.noreply.github.com>
Date: Tue Aug 4 22:56:06 2026 +0200
snap: make it a classic CLI snap, and build it without snapd
The recipe had never been built. Doing so turned up that its central claim was
wrong: it declared `confinement: strict` and told users the `home`,
`removable-media` and `ssh-keys` interfaces covered "the common case (back up
your home + external drives)". They do not. The `home` interface grants
"access to non-hidden files in the home directory" — snapd's rule is literally
`owner @{HOME}/[^s.]** rwkl` — so every top-level dotfile is excluded. Six of
the eleven locations `moraine recommend` offers on Linux are exactly those:
~/.config, ~/.mozilla, ~/.thunderbird, ~/.gitconfig, ~/.ssh, ~/.gnupg. The five
that would have worked are re-downloadable media; the six that would not are
the configuration and keys a backup exists for.
`personal-files` cannot close the gap — it declares named paths and needs a
store request per path, while the set here is whatever the user chooses — and
scheduling writes to crontab, which strict confinement also denies. So the snap
is now classic, with the reasoning in the yaml rather than in a README nobody
reads at install time.
Classic in turn rules out the GUI: the `gnome` extension only exists for strict
confinement, so a GTK app under classic means bundling GTK 4 and patching rpath
by hand. The desktop is already covered by the Flatpak, the AppImage and the
.deb, so the snap ships the CLI alone — like the Homebrew and Scoop channels.
Two things the first real build exposed. A classic snap runs with the host's
environment and does *not* get $SNAP/usr/bin prepended the way a strict one
does, so without an explicit PATH the bundled rsync/ssh/rclone would be dead
weight and a host without rclone would simply fail. And the metadata linter
wanted title/license/contact/issues/source-code/website, all of which the store
listing needs anyway.
build-in-docker.sh + Dockerfile.build make this buildable on a host that has no
snapd and cannot get it — Linux Mint ships /etc/apt/preferences.d/nosnap.pref,
and snapd wants systemd, which a container has not got. The image unsquashes
snapcraft, core24 and snapd into the absolute /snap/<name>/current paths their
binaries are linked against and runs `snapcraft --destructive-mode` in place.
The build tree is deliberately a mktemp outside the repository: snapcraft
copies all of `source:` into parts/<part>/src, so a build directory inside the
repo copies the repo into itself until the disk fills. --local stages tracked
files only, since `target/` alone is 5 GB snapcraft would copy for nothing.
Verified by building and unpacking the snap, not only by packing it: classic
confinement in meta/snap.yaml, `moraine --version` runs from the payload, and a
real backup to a local rclone target produces a timestamped snapshot that
`moraine list` reads back — using the bundled rclone, rsync 3.2.7 and OpenSSH
9.6p1. Note that Ubuntu 24.04's rclone is v1.60.1 (2022); everything Moraine
calls predates it, but the ceiling is documented in the README.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit f774f02c8891313b963f4ebc9e3275107fb6c61b
Author: TheJonaz <44526342+TheJonaz@users.noreply.github.com>
Date: Tue Aug 4 22:27:02 2026 +0200
secrets: let a target's secrets live outside moraine.toml (#1)
The config holds FTP passwords, SSH key passphrases and crypt_password — the
key that decrypts the destination — in plaintext. Mode 0600 stops other users
reading it, but not the ways secrets actually escape: a backup that happens to
cover the home directory, a config pasted into a bug report, a repo commit.
Each secret field now records *where* the secret is rather than necessarily
being it: "hunter2" keeps it in the file as before, "env:VAR" reads an
environment variable, "keyring:" reads the OS keyring, and "literal:..." covers
a secret that would otherwise be read as one of those prefixes. The TOML keys
are unchanged, so every 0.2.x config keeps working untouched and a config this
build saves is still readable by an older one — pinned by a test in both
directions, because the GUI writes the file back.
Resolving is deliberately fallible and loud. Previously an unavailable secret
could only be the empty string; with an external store that means handing
rclone an empty passphrase and writing an *unencrypted* snapshot to a
destination the user believes is encrypted. askpass_env and env_for return
Result for that reason, and a build without keyring support refuses to run
rather than silently degrading. The fields were renamed to *_spec behind
#[serde(rename)] so the compiler enumerated all 30 read sites instead of
leaving one to fail silently.
Three ways to move a secret: `moraine secrets migrate` for a whole config,
`moraine secrets set` for one, and a Move to keyring button under each secret
field in the GUI. All of them write to the keyring and read it back before
repointing the config — the config is the only copy, so a keyring that accepts
a write it cannot return would otherwise destroy the secret. `moraine secrets
check` reports where each secret comes from without printing any, and
`moraine verify` runs it before connecting; the message names cron explicitly,
since a keyring that works in a desktop session usually is not reachable from
a schedule.
The GUI writes the account into the field (keyring:nas/password) instead of
letting it be derived from the target name. Names are editable per keystroke
there, and a derived account would stop resolving the moment a target was
renamed — surfacing only at the next scheduled backup.
Settings -> Export config now resolves secrets before encrypting, or the export
would be a set of pointers meaning nothing on another machine. The plaintext
goes to gpg on stdin and never touches disk; the passphrase goes in a 0600 file
in the per-user tmpfs and is removed immediately, including when gpg fails.
Import puts the secrets back in the keyring, falling back to leaving them in
the config — and saying so — if it is unreachable.
`gui` implies `keyring`: a machine running a GTK app has the desktop session a
Secret Service needs, while --no-default-features stays clean for servers and
cron. That adds 27 crates to the GUI packages, all pure Rust — the zbus store
shares the one ksni already pulls in, so no libdbus — but the OBS tarball needs
re-vendoring and the FreeBSD port's CARGO_CRATES an update.
Also fixes a secret with leading or trailing whitespace being trimmed, which
turns a working config into an authentication failure that looks exactly like a
wrong password.
Verified against a real Secret Service, not just in tests: a local rclone
target with crypt_password = "keyring:" backs up with filenames and contents
encrypted on disk, and list/check read it back. Removing the keyring entry
stops the run with an actionable error and leaves the destination untouched.
Clicking the GUI button through to `moraine secrets check` reading the result
caught a bug no type check could — the refresh clearing the entry fired the
changed handler, which wrote the empty string over the pointer just before
saving, so the secret reached the keyring but the config lost its way back to
it. Renaming the target afterwards still resolves. Clippy is clean and the
tests pass across all five feature combinations; two integration tests touch
the real keyring and are #[ignore]d so CI never does.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit 015dd3cdd877e98895ebebaf564182f0cd2ddfb6
Author: Jonaz Thern <44526342+TheJonaz@users.noreply.github.com>
Date: Mon Aug 3 19:12:41 2026 +0200
alpine: use the maintainer= variable and shorten pkgdesc
Both from Hoang Nguyen's review on aports MR !106248. The pkgdesc loses its
"(CLI + GTK desktop app)" tail — users can work that out — and the
# Maintainer:/# Contributor: comments become the maintainer= variable aports
is migrating to (alpine/tsc#109; abuild never parsed # Contributor at all).
packaging/alpine/ tracks what is on the MR, so it moves with it.
Verified in an Alpine edge container before pushing: apkbuild-lint clean,
abuild -r builds moraine and moraine-doc in 4m16s. The MR pipeline is now
green on all nine Alpine architectures, riscv64 included.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit 09bbdeedc111a703d3f74dfe274f6ea503fc2b18
Author: Jonaz Thern <44526342+TheJonaz@users.noreply.github.com>
Date: Mon Aug 3 14:57:22 2026 +0200
appimage: build it in CI and ship it through the CDN
The recipe existed but had never been built. Doing so turned up that its own
advice was impossible: it says to build on Ubuntu 22.04 for portability, but
moraine builds gtk4-rs with features = ["v4_10"] and 22.04 ships GTK 4.6.
24.04 is the oldest base that can work, which fixes the glibc floor at 2.39 —
Ubuntu 24.04+, Debian 13+, Fedora 40+ and the rolling distros. Older systems
keep the Flatpak (own runtime, no floor) and the .deb, so nothing is lost, but
the CI job must stay pinned to ubuntu-24.04: moving it to ubuntu-latest would
silently raise the floor and drop working distros.
The job smoke-tests what it built — extracts the AppImage, runs the bundled
CLI, and requires AppRun to reach "Failed to open display". That is the line
between a complete bundle and one that dies on a loader error at a user's
machine. Verified by hand on Ubuntu 24.04 (glibc 2.39) and Arch (2.43); the
GUI renders under Xvfb with its icons and assets resolved out of the bundle.
checksums gains appimage in `needs`: it downloads the whole release and hashes
it, so a job finishing later is silently absent from SHA256SUMS.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit 68a716655e622cff32d75d8c09711fd4f4430dc7
Author: Jonaz Thern <44526342+TheJonaz@users.noreply.github.com>
Date: Mon Aug 3 14:37:49 2026 +0200
gentoo: publish from our own overlay, and make the GUI optional
GURU is an official Gentoo project and is bound by Gentoo's AI policy,
which forbids AI-assisted contributions — so neither GURU nor ::gentoo can
take this ebuild. It ships from TheJonaz/moraine-overlay instead, added
with `eselect repository add`. packaging/gentoo/ stays the source of truth;
the overlay gets a copy per release.
The ebuild gains +gui: gui-libs/gtk:4 is a hard dependency only with
USE=gui, and the desktop entry, icons and runtime assets install only then.
USE=-gui pulls 5 packages instead of 152 and installs the CLI alone, which
is what a backup tool on a headless server wants. cargo_src_configure does
NOT add --no-default-features on its own (it maps myfeatures to --features
and forwards "$@"), so the CLI-only path passes it explicitly — the comment
claiming otherwise, and that myfeatures=( gui ) had dropped the tray icon,
was wrong.
Verified in a stage3 container against the published overlay: eselect
repository add + emaint sync + emerge installs and runs 0.2.2, and pkgcheck
scan is clean — confirmed meaningful by scanning a deliberately broken copy
alongside, which reports three findings.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit ef095aeca051a31981ae4a32716d2d647e4a151c
Author: Jonaz Thern <jonaz.thern@gmail.com>
Date: Sun Aug 2 20:50:54 2026 +0200
flatpak(aarch64): install elfutils explicitly
flatpak-builder only *recommends* elfutils, so --no-install-recommends
left the runner without eu-strip. The job compiled rsync in full and then
died on the debuginfo step of the very first module.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit da456603b628e8db39de700eee595c8af7b2e7ca
Author: Jonaz Thern <jonaz.thern@gmail.com>
Date: Sun Aug 2 20:47:41 2026 +0200
flatpak: build aarch64 too
The rclone module fetched the linux-amd64 zip unconditionally, so an
aarch64 build would have installed an x86-64 binary. The build succeeds
either way — every rclone backend would just die with "Exec format error"
the first time a user ran one. Gate the two archives with only-arches.
(The amd64 checksum already matched rclone's published SHA256SUMS; the
arm64 one is taken from the same file.)
aarch64 is built by a new workflow on ubuntu-24.04-arm — GitHub's ARM
runners are free for public repos and release.yml already uses them, and
emulating a Rust release build on the x86-64 workstation is measured in
hours. It cannot push to the CDN (the firewall drops runner IPs, which is
why every other channel is pull-based), so it attaches a single-file
bundle to the release; flatpak-release.sh imports it next to the x86_64
build. The job installs the bundle and asserts uname -m before uploading,
because a wrong-arch bundle installs cleanly and only fails in use.
x86_64 rebuilt after the change: identical output, rclone still x86-64.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit edc9a5c96cd26d12d9e3e00b26e1461323d3d96c
Author: Jonaz Thern <jonaz.thern@gmail.com>
Date: Sun Aug 2 20:30:57 2026 +0200
style: apply rustfmt
CI's `cargo fmt --all --check` was failing on code from the recommender
and arch-aware-update commits. Those sat unpushed for a while, so CI only
ran on them once the whole batch went up.
No behaviour change: rustfmt output only, clippy and the 41 tests pass.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit 842478b1e5bdbec1b4781fd7854450548e92f9f3
Author: Jonaz Thern <jonaz.thern@gmail.com>
Date: Sun Aug 2 20:25:09 2026 +0200
gui: say in the feedback dialog that the IP address is stored
The form records the submitter's IP so abuse can be traced. That is a
deliberate choice, but it was not stated anywhere the sender could see it.
Say it in the dialog, next to the other things the report attaches.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit 916f20ba80bf9c2353c943e6e31cd4a75316a6d5
Author: Jonaz Thern <jonaz.thern@gmail.com>
Date: Sun Aug 2 20:15:01 2026 +0200
flatpak: fix the stale commit pin, the stale crate list and the tray
The manifest could not build. Three things had gone stale since the
Flathub attempt in July:
- `commit:` pinned 47e5b6f — that is **v0.1.19** — under `tag: v0.2.2`.
flatpak-builder checks the tag against the commit and fails, which is
the only reason this was ever noticed.
- cargo-sources.json was missing 44 crates: the whole ksni/zbus/async
stack the `tray` feature pulled in after the file was generated. The
build is `cargo --offline`, so it would have failed on the first
missing crate.
- the tray was compiled in (it is a default feature) but had no D-Bus
access, so it could never have appeared inside the sandbox. Grant
--talk-name=org.kde.StatusNotifierWatcher. ksni's own well-known name
cannot be granted — flatpak only accepts a `.*` wildcard after a dot —
so it falls back to its unique bus name, which the spec allows.
Also take the metainfo from next to the manifest instead of from the git
checkout. The release entry for X.Y.Z is written after X.Y.Z is tagged,
so the copy inside the tag is always one release behind: the app reported
0.2.1 to software centres while shipping 0.2.2.
Built and installed from https://cdn.thern.io/flatpak with signature
verification on; the CLI, GUI, and the bundled rsync/ssh/rclone all run.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit a113b8ce96985d8bd431f684fe6fc8710ca7bd98
Author: Jonaz Thern <jonaz.thern@gmail.com>
Date: Sun Aug 2 19:39:30 2026 +0200
alpine: apply the aports review — cargo-auditable, arch=all, install -t
Hoang Nguyen's review of aports!106248 raised four points:
- arch was pinned to "x86_64 aarch64" for no technical reason; it was
carried over from the arches our CI publishes binaries for. Alpine
builds from source, and rust, cargo-auditable and gtk4.0 (4.22.4, well
past the 4.10 the GUI needs) are present on all nine arches, so use
arch="all" and let the builders narrow it if 32-bit ever OOMs.
- build with cargo-auditable, as the other Rust aports do. That pulls in
the rest of the idiom from community/ripgrep: fetch the crates in
prepare() and build with --frozen, so the build needs no network and
the aport does not have to set options="net".
- drop --release from check() (aports work item 18294).
- collapse the repeated install calls onto -t. The icons keep an explicit
destination since they are renamed, but they fold into a loop.
Built with abuild -r on Alpine 3.24.1 x86_64: builds clean, tests pass,
and the packaged file list is byte-for-byte what it was before.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit 42973f4278444c749817896489ab7d957f16ac1b
Author: Jonaz Thern <jonaz.thern@gmail.com>
Date: Fri Jul 31 08:58:36 2026 +0200
gentoo: fix the stale crate list and the dropped tray feature
CRATES had the same staleness the FreeBSD port's CARGO_CRATES did: generated
before the ksni/zbus tray work landed, so it listed 113 of the 157 crates in
Cargo.lock and omitted ksni, zbus*, zvariant*, the async-* stack, rustix,
tempfile and uuid. The build would have failed at unpack with
error: no matching package named `ksni` found
Regenerated with `pycargoebuild -i`, which also recomputes the dependent-crate
LICENSE set from crate metadata rather than the hand-guessed list that was
there. That tool needs the marker comment `# Dependent crate licenses` above
LICENSE+=, so add it.
Separately, `myfeatures=( gui )` was silently shipping a GUI with no tray icon.
cargo.eclass appends --no-default-features as soon as myfeatures is non-empty,
and the crate's default is ["gui", "tray"], so Gentoo alone would have dropped
the StatusNotifierItem — with no build error to reveal it. Now ( gui tray ).
Also install the 48px and 64px hicolor icons, which the AUR and Alpine recipes
already ship and this one did not.
Verified in a Gentoo stage3 container: full emerge of all 152 packages exits 0,
`moraine --version` runs, and `strings` on the installed moraine-gui confirms
the StatusNotifier code is compiled in.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit 2a7f07d3fb0b3f4c46037cfb88aa3615ce1ccd42
Author: Jonaz Thern <jonaz.thern@gmail.com>
Date: Fri Jul 31 08:58:23 2026 +0200
alpine: ready the aport for submission to testing/
Three changes aports requires of a new aport. A `# Contributor:` line
alongside `# Maintainer:`; `url` pointing at the homepage rather than the
GitHub repo, with the tarball URL spelled out in `source` instead of being
interpolated from `url`; and `arch="x86_64 aarch64"`, since gtk4.0-dev, rust
and cargo all exist on aarch64 in v3.24 and moraine already ships aarch64
builds.
Build-verified in an Alpine 3.24.1 VM: `abuild -r` clean in 4m07s, 37/37 tests
pass, `apkbuild-lint` silent, and the GUI links against musl and GTK 4.22.4
with nothing unresolved. Submitted as testing/moraine.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit bc8f3956e337ab1d7701cc3269dd3f17a5b43251
Author: Jonaz Thern <jonaz.thern@gmail.com>
Date: Mon Jul 27 20:26:03 2026 +0200
aur: allow aarch64
makepkg builds from source and nothing in the package is architecture-specific,
so Arch Linux ARM users (Raspberry Pi and friends) can build it too. The
x86_64-only arch line was refusing them for no reason.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
commit d7bd92f56afd0999411de8e2aef1377e46a3f605
Author: Jonaz Thern <jonaz.thern@gmail.com>
Date: Mon Jul 27 18:49:10 2026 +0200
gui: offer updates for the running CPU architecture, and ship arm64 builds
The in-app updater picked its download by OS only: every Linux branch spelled
x86_64/amd64, and pick_asset() fell back to `-linux-x86_64.tar.gz`
unconditionally. On arm64 — a Raspberry Pi, an ARM server — that offered a
binary the machine cannot execute.
Asset names now carry the running architecture, with the spelling each format
uses (dpkg says arm64 where rpm/pacman/tarballs say aarch64), and an
architecture with no published build resolves to None so the caller reports
"no downloadable build for this platform" instead. Same fix for an Intel Mac,
which was being offered the Apple-silicon tarball.
Release CI gains a native linux/arm64 job on GitHub's free ARM runner, so
aarch64 users get a tarball and a .deb rather than having to compile; the .deb
name is now driven by the matrix instead of a hardcoded amd64.
The matching half is split into match_asset() so the tests don't shell out to
dpkg/rpm/pacman — probing those took 86s per run.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
commit 2650e7c4499fa361dfc100f560878c9f7549498a
Author: Jonaz Thern <jonaz.thern@gmail.com>
Date: Mon Jul 27 00:42:14 2026 +0200
freebsd: fix the stale crate list, add distinfo, build-verify the port
CARGO_CRATES had been generated before the ksni/zbus tray work landed, so it
was missing 44 crates (ksni, zbus*, zvariant*, the async-* stack, rustix,
tempfile, uuid, …). The port failed at configure with
error: no matching package named `ksni` found
because the vendored cargo-crates directory didn't contain them. Regenerated
with `make cargo-crates`: 113 -> 156 crates. Same staleness the Flatpak
cargo-sources.json had.
Also check in `distinfo` (hashes for the tarball + all 156 crates) so the port
is submittable as-is, reflow pkg-descr under 80 columns (portlint), and write
up the two traps that cost the most time: match the ports tree to your pkg set
(quarterly branch, else lang/rust gets built from source) and regenerate both
generated lists on a version bump.
Verified on FreeBSD 15.1-RELEASE: make install succeeds from a clean tree,
check-plist reports no issues, and both binaries run (moraine 0.2.2).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
commit 130918febf5836b5b5d9ed67667f8f3f4d227df8
Merge: bdb6375 8da5e9f
Author: Jonaz Thern <jonaz.thern@gmail.com>
Date: Fri Jul 24 21:56:51 2026 +0200
Merge branch 'feature/recommender-gui'
commit bdb6375126ed3b370f99f342101e65e930e57470
Author: Jonaz Thern <jonaz.thern@gmail.com>
Date: Fri Jul 24 21:56:34 2026 +0200
docs: refresh the README screenshot to the current UI, size down the logo
The README screenshot was still the v0.1.0 shot from the initial commit (4
tabs, old layout). Replace it with a current v0.2.2 capture (6 tabs, themed
UI). Also constrain the header logo to a fixed 80px width via an <img> tag —
as bare markdown the icon rendered oversized.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
commit 8da5e9f138fab556525992f9b47261f3c9b3cb5e
Author: Jonaz Thern <jonaz.thern@gmail.com>
Date: Fri Jul 24 16:51:35 2026 +0200
gui: "What should I back up?" — wire the OS-aware recommender into the app
The recommender (moraine::recommend) was CLI-only. Add a "✨ Suggest sources"
button to the Quick Backup targets panel that opens a dialog: it scans this
machine (recommend::scan), lists the found locations grouped by category with a
checkbox each, flags credential-bearing paths as "sensitive" with an encryption
note, and turns the ticked picks into a ready-made target (recommend::
suggested_target) with sensible excludes — leaving host/dest as placeholders to
fill in. Verified end to end in the running GTK app.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
commit d25f312db39a0368e1f636cf1c492c87b7d5d0d3
Merge: acdbff5 5d92576
Author: Jonaz Thern <jonaz.thern@gmail.com>
Date: Fri Jul 24 16:43:34 2026 +0200
Merge branch 'feature/recommender' into feature/recommender-gui
commit acdbff52d5fe813cdca1f02c09796dee951ab780
Author: Jonaz Thern <jonaz.thern@gmail.com>
Date: Wed Jul 22 20:04:36 2026 +0200
gui: drop a redundant borrow in a format! argument
clippy::useless_borrows_in_formatting, new in Rust 1.97, has been failing
CI since the ad-hoc-backups commit — seven releases with a red build. The
code was always correct; the lint just wants the borrow gone.
commit 8e0cefbaf282b37104e34ef23b136138317eabae
Author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Date: Wed Jul 22 13:46:16 2026 +0000
packaging: bump downstream recipes to v0.2.2
commit f16cbe55e96bb53696bb57379e03a61e41c97b85
Author: Jonaz Thern <jonaz.thern@gmail.com>
Date: Wed Jul 22 15:42:26 2026 +0200
debian: changelog 0.2.2-1 — the single initial-release entry now matches the release
The 0.2.2 upload to mentors is built from this entry; keeping it in git is
what prevents the packaging from drifting away from the repository again.
commit 5d925762431f6e8170691cf5838cb99da1781559
Author: Jonaz Thern <jonaz.thern@gmail.com>
Date: Sat Jul 18 16:16:40 2026 +0200
recommend: OS-aware "what should I back up?" helper
Add `moraine recommend`: scans the machine for a curated, OS-specific set
of important locations (documents, media, app settings, dev keys, mail)
and prints them grouped, flagging credential-bearing paths as [sensitive]
with a nudge to enable crypt_password. Emits a ready-to-paste [[target]]
with sane default excludes; --write appends it to the config (preserving
existing comments, owner-only, refusing duplicate names).
Paths are `~`-relative templates so the same string drives both the
existence check and the runtime source (no %APPDATA%/$XDG syntax moraine
wouldn't expand). Manifest is a small per-OS table, easy to extend.
Phase 1 of the feature (CLI + engine); GUI wizard to follow.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>