See exactly what rsync will change — before a single byte moves.
Foresight is a GTK4 / libadwaita front-end for rsync on Linux. It is a GNOME-native app built around one idea: every sync can be previewed as a grouped change list — created / updated / deleted / attribute-only — so you know exactly what will happen before you run it.
Foresight never reimplements rsync. It composes an argv vector, spawns the bundled rsync as a subprocess, and parses its output into structured events. No shell strings are ever constructed, and the engine version is pinned — the version pin is the behaviour contract.
Rust · GTK4 · gtk4-rs · libadwaita · Blueprint · Meson · Flatpak, with rsync
3.5.0 bundled and version-pinned. See PLAN.md for the phased
build plan and the guardrails it holds to.
Status: 1.0.1 — released and self-hosted. Multi-source transfers, the grouped dry-run preview, live progress with a structured streaming log, cancel,
--deletewith confirmation, the advanced flag set with saved presets, an ordered include/exclude filter list, remote sync over SSH, and an in-app capability inventory all work today in a sandboxed Flatpak, covered by 90 tests across the workspace and 53 headless widget checks — withrsync-eventsstaying UI-free. Installed from the project's own GPG-signed repository soflatpak updateworks, with a standalone bundle on GitHub Releases for offline installs and the project page on GitHub Pages — not Flathub.
A GNOME-native rsync front-end whose headline is seeing changes before they happen, running fully sandboxed:
| Change preview before sync | GNOME-native (GTK4/Adwaita) | Sandboxed (portals only) | Bundled, pinned engine | |
|---|---|---|---|---|
| Grsync | ~ (raw dry-run log) | ✗ (GTK3) | ✗ | ✗ |
| Back In Time | ✗ | ✗ | ✗ | ✗ |
rsync CLI (rsync -n) |
~ (raw text) | ✗ | ✗ | n/a |
| Foresight | ✓ (grouped list) | ✓ | ✓ | ✓ |
Where Foresight aims to win, not just match:
- Dry-run preview as a first-class view — the change list is grouped by kind, deletions rendered destructively, so a mistake is obvious before it runs.
--deletecan never surprise you — turning it on runs a fresh dry run and makes you confirm the exact list of files it will remove.- Sandboxed by design — a Flatpak that bundles rsync and reaches your files
only through XDG portals. No
--filesystem=home, no blanket host access.
- Preview any sync as a grouped change list (created / updated / deleted /
attributes-only) from a real
rsync -a -n -idry run — before anything moves. - Multiple sources from anywhere — add files and folders from different locations (e.g. one from Downloads, one from Documents), each with a remove button, or drop them in from the file manager.
- What you add is what you get — a folder lands in the destination as that folder, a file as that file. No surprise spills. If you want rsync's other form — a single folder's contents copied straight into the destination — Advanced → Sync folder contents does exactly that.
- Live transfer — a real progress bar, current-file label, and a structured
streaming log (one typed row per file and rsync message, not a wall of text)
that opens with the exact
rsync …command — with cancel at any time. --deletewith a confirmation that lists the exact deletions from the dry run.- Saved presets — store an Advanced-option set (e.g. a throttled
--remove-source-filesmove) and reapply it in one click. Paths are never saved. - Know exactly what it can do — a What Foresight Can Do dialog: an honest,
in-app inventory of every rsync flag this build exposes, driven by a registry
that a test keeps in lockstep with the code, plus the bundled
rsync --help. - Advanced options for the flags you actually reach for:
- Move files —
--remove-source-files(remove each source after it transfers). - Bandwidth limit —
--bwlimitwith a unit picker (KB/s · MB/s · GB/s), so capping a big transfer to a disk's speed is85+MB/s, not85000. - Filter rules — an ordered list of Include and Exclude patterns, one
--include=/--exclude=each. rsync obeys the first rule that matches, so the arrows on a rule decide which one wins: put*.jpgabove*and only JPEGs come across; putbuild/above*.jpgand nothing inbuild/does. Rules are passed whole, soMy Documents/is one rule rather than two broken ones. A rule that cannot match is pointed out before anything runs. A leading/means the top of the transfer, not of the disk, so a full path pasted as an exclude matches nothing — and rsync says nothing. The row says why and offers the rule that was meant. After a Dry Run every rule shows what it matched, and any that matched nothing is named. With Move on, Foresight checks first and asks before starting if an exclude is holding nothing back. - Extra arguments — a free-text escape hatch for any other rsync switch.
- Move files —
- Remote sync over SSH — push to, or pull from, a
user@host:/pathendpoint. Authentication uses the keys already in your desktop's SSH agent: the agent signs on request, so no private key ever enters the sandbox and~/.sshis never read. Foresight keeps its ownknown_hostsand shows you the host's fingerprint before trusting a machine for the first time; after that, strict host-key checking means an unexpected key is refused rather than accepted quietly. Either side may be remote, but not both — rsync refuses that, so the two controls lock each other out and say why. IPv6 link-local endpoints work, scope id and all. - New Job — clear the whole form for the next transfer in one click.
- Ships as a Flatpak with rsync 3.5.0 bundled — portals only, no host filesystem access by design.
The Advanced → Extra arguments field is a free-text escape hatch: whatever you type is tokenised on whitespace and passed straight to rsync as argv (never through a shell). It's for the long tail of rsync flags that don't each earn a dedicated control. A few useful ones:
| Switch | What it does |
|---|---|
--checksum |
Compare by checksum, not size+mtime — catches same-size edits |
--compress (-z) |
Compress data in transit (useful over slow links) |
--partial --append-verify |
Resume interrupted transfers safely |
--backup --backup-dir=DIR |
Keep replaced/deleted files instead of losing them |
--chmod=…, --chown=… |
Rewrite permissions / ownership on the destination |
--max-size=, --min-size= |
Skip files outside a size range |
Extra arguments are placed before Foresight's own reporting flags (
--info=progress2,--out-format,-n -i), so they can never break the change parser. Tokens are split on spaces — there is no shell, so brace expansion like--exclude={a,b}does not apply; list patterns separately.
Not on Flathub — Foresight ships from its own signed repository, with the project page on GitHub Pages.
flatpak install --user https://superuser-miguel.github.io/foresight-repo/foresight.flatpakref
flatpak run io.github.superuser_miguel.ForesightThat one command adds the remote and installs the app, so new versions arrive
with a normal flatpak update — no re-downloading a bundle. The remote is
GPG-signed with key D67DB8E03D50A8C0; flatpak verifies every pull against the
key embedded in the .flatpakref and refuses the remote if it doesn't match.
To add the remote without installing anything:
flatpak remote-add --user --if-not-exists \
foresight https://superuser-miguel.github.io/foresight-repo/foresight.flatpakrepoA Foresight.flatpak bundle is also published on
GitHub Releases
for offline or air-gapped installs:
flatpak install --user Foresight.flatpakA bundle install has no origin to pull from, so
flatpak updatecannot upgrade it — moving versions means downloading the next bundle by hand. Prefer the repository unless you specifically need a single offline file.
Either way you need the GNOME runtime it builds against; if you don't have it:
flatpak install flathub org.gnome.Platform//49Release tags are GPG-signed with the same key. Verify with
git verify-tag v1.0.1.
crates/rsync-events/ UI-free parser: itemize / progress / stats + exit classifier
crates/foresight/ GTK4/libadwaita app (binary: `foresight`)
data/ Blueprint UI, gresource, desktop + AppStream metainfo, icon
build-aux/ Meson → cargo bridge
reference/ rsync_events.py — the parser's executable spec
rsync-events has zero GTK/GLib dependencies (regex + once_cell only), so
the output-parsing contract is testable on its own — and
reference/rsync_events.py is kept in lockstep with it.
flatpak install flathub org.gnome.Platform//49 org.gnome.Sdk//49 \
org.freedesktop.Sdk.Extension.rust-stable//25.08
build-aux/run-dev.sh # build the dev manifest and run it
build-aux/run-dev.sh --no-build # run the last build again
build-aux/run-dev.sh rsync --version # the bundled enginerun-dev.sh runs the build straight out of build-dir, in the sandbox the
manifest describes, without installing it. That is deliberate. Installing a
dev build (flatpak-builder --install) puts it on the master branch next to a
published install on stable, where it takes over the desktop icon — and two
branches of one app id make xdg-document-portal and a bare
flatpak run <id> fail with "Multiple branches available". If you have no
published install, --install is fine.
Needs gtk4-devel, libadwaita-devel, blueprint-compiler, Meson, and rsync
on PATH (the engine tests drive real rsync).
cargo test # parser + argv + engine tests
cargo clippy --all-targets -- -D warnings
meson setup builddir -Dprofile=debug && meson compile -C builddir
meson test -C builddir # includes AppStream metainfo validationThe published bundle is built from a separate release manifest,
io.github.superuser_miguel.Foresight.release.yml: it takes its source from the
signed release tag rather than the working tree, and builds with no network
against the vendored crate graph in cargo-sources.json.
flatpak-builder --user --force-clean --repo=repo-release build-dir-release \
io.github.superuser_miguel.Foresight.release.yml
flatpak build-bundle repo-release Foresight.flatpak \
io.github.superuser_miguel.Foresight stable \
--runtime-repo=https://flathub.org/repo/flathub.flatpakrepoRegenerate cargo-sources.json whenever Cargo.lock changes
(python3 flatpak-cargo-generator.py Cargo.lock -o cargo-sources.json; needs a
venv with tomlkit + aiohttp).
Released bundles ship on the
stablebranch, pinned bybranch: stablein the release manifest. This is load-bearing: flatpak-builder's default ismaster, and a bundle on a different branch than the one users already have installs beside it instead of upgrading it. The dev manifest stays onmasteron purpose, so a localflatpak-builder --installcan't clobber a real install.
- Dry-run preview — grouped change list from a real
-n -irun. - Multi-source transfers — files and folders from different locations, with per-item remove and drag-and-drop.
- Predictable placement — everything you add lands inside the destination, with an opt-in Sync folder contents for the other form.
- Live progress, cancel, and a structured streaming log of the run.
-
--deletewith a confirmation listing the deletions from the dry run. - Advanced options — move (
--remove-source-files), unit-aware bandwidth limit (--bwlimit), filter rules, and a free-form extra-arguments field. - Saved presets for Advanced-option sets.
- Help / capability disclosure — a registry-driven, test-enforced in-app
inventory of the flags Foresight exposes, cross-referenced to the bundled
rsync --help. - AppStream metainfo, screenshots, and a landing page.
- First
.flatpakrelease — an offline, reproducible bundle built from a GPG-signed tag, published on GitHub Releases. - Self-hosted repo — a GPG-signed OSTree remote served from GitHub
Pages, plus a
.flatpakref, soflatpak updatepulls new versions instead of re-downloading a bundle. - Excludes editor — exclude rules as a managed list with per-rule removal, replacing the space-separated field that could not express a pattern containing a space.
- Remote sync over SSH — push to or pull from another machine, keys from your desktop's SSH agent, and Foresight's own strict host-key checking. No key ever enters the sandbox.
- Include rules — the exclude list became an ordered filter list where each rule is an Include or an Exclude and can be moved up or down. rsync applies the first rule that matches, so position is the whole meaning: an Include above a broader Exclude carves an exception out of it, and an Exclude above an Include shuts a subtree even to it. Modelling the rules as one ordered list rather than an includes set plus an excludes set is what makes the second of those expressible at all.
1.0 is not "more features"; it's the point where the advertised surface is complete and the saved formats stop moving. Both are now true.
- Remote sync over SSH — done. Push to, or pull from, a
user@host:/pathendpoint with key-based auth. All three parts: 1. ✅ Agent access — done.--share=networkand the runtime'ssshwere already there, but the sandbox inherited a deadSSH_AUTH_SOCKand~/.sshis (correctly) invisible, so no key was reachable at all.--socket=ssh-authforwards the host agent's socket: your keys become usable while no private key material ever enters the sandbox — the agent stays outside and only signs on request, which is exactly why this is the right grant and--filesystem=~/.sshis not.~/.sshremains invisible with it on. 2. ✅ Host-key trust — done. Foresight keeps its ownknown_hostsbeside your presets and never reads or writes~/.ssh. It can't use ssh's default file, because the sandbox home is ephemeral — anything written to~/.sshinside it is gone on the next launch, so trust-on-first-use would never actually remember. Transfers run with strict host-key checking against that file: a host you have not confirmed is refused outright, never silently accepted, and a host key that changes stops the transfer and says so. 3. ✅ The endpoint UI — done. A remote source or a remote destination, entered as user / host / port / path. Either side may be remote, but not both — rsync refuses that, so the two buttons lock each other out and say why. A remote source replaces the local ones rather than mixing with them, for the same reason. IPv6 link-local endpoints work, scope id and all (fe80::1%wlo1), which is what syncing to a phone over a hotspot needs. - A frozen preset format. It churned three times on the way to 1.0; from 1.0 it is a compatibility promise, not an implementation detail. Every reader keeps reading all three encodings, so no saved rule set is ever lost to an upgrade — including ones written by the first release.
Two findings from a month of daily use, and a newer engine.
- New Job after a remote transfer no longer leaves the remote buttons greyed out with nothing to do but restart. The lock that guards the endpoints during a run was never lifted after it.
- Rules that match nothing are no longer silent. rsync anchors a leading
/to the top of the transfer, so/home/me/Photos/privateas an exclude matches nothing, without a warning — and with Move on, the folder it was meant to keep back moved with everything else. (Nothing was lost; it was at the destination.) Two checks now, because they catch different things: one by construction, before any run, with a Fix button; one by evidence, from the dry run's--debug=FILTERreport, which also catches a valid rule that simply matches nothing here. A move whose exclude held nothing back stops and asks. With a remote source only the first applies — the far end does the filtering and reports nothing back. - rsync 3.5.0 bundled (was 3.4.4): 33 security fixes. Its output formats are unchanged, and because it reworked path resolution it was run inside the sandbox against real document-portal folders before shipping.
Today a job is transient: presets store your options but deliberately not your paths, because portal grants don't outlive the session. Inverting that is the whole of the next major version — jobs become durable, schedulable and auditable objects — and each step below is unbuildable before the one above it.
- Durable jobs — a named job that remembers its source and destination across restarts. The hard one, and the gate on everything else: portal paths are handles rather than locations, so this gets solved through the documents portal, not by asking for your whole home directory.
- Scheduling — "every night", or "when this drive appears", as generated systemd user timers and mount/path units. No daemon of Foresight's own.
- Run history — what ran, when, what changed, what failed. An unattended run nobody watched is worthless without a record, so this ships with scheduling, not after it.
- Snapshot backups (
--link-dest) — hardlinked incremental trees, the thing that turns copies into backups. Still pure rsync.
Deliberately not planned, at any version: root or whole-system backups (they would gut the sandbox this app is built on), cloud-storage backends (that's rclone's job, not rsync's), and rsyncd hosting.
Worth saying plainly: steps 1–3 would give Foresight background behavior,
and today's inertness-when-closed is part of why it can be trusted with a
--delete. Scheduled runs will hold the same dry-run-first discipline as
interactive ones, or they won't ship.
- Saved remote credentials via the system keyring. For password-based SSH
or an rsync-daemon password, optionally remember it in the login keyring
(Secret Service /
org.freedesktop.secrets) instead of a config file — a deliberate, opt-in sandbox permission we'd only add for this convenience. Key-based auth would never need it.
- rsync by Andrew Tridgell, Wayne Davison and contributors — the engine Foresight bundles and drives. Foresight is not a fork; rsync is built unmodified as a separate Flatpak module.
- Built with gtk4-rs, libadwaita, Blueprint, and Meson — following the conventions of Amberol, Fractal and friends.
Foresight is GPL-3.0-or-later — see LICENSE. The bundled rsync
remains its own GPL-3.0-or-later work, built as a separate module.