From 1ace4bc9b0a553c70a15f82a8de1a39256540ec4 Mon Sep 17 00:00:00 2001
From: gnacho
Date: Thu, 17 Sep 2026 21:08:43 +0200
Subject: [PATCH] fix(setup): block finishing without the provider's sync
engine (closes #210)
Both engines are alternative optdepends (pacman has no OR dependency), so
the package also warns at install/upgrade when neither binary is present.
---
CHANGELOG.md | 7 ++
Cargo.lock | 2 +-
Cargo.toml | 2 +-
PKGBUILD | 9 ++-
README.es.md | 4 +-
README.md | 4 +-
data/io.github.gnacho.nextsync.metainfo.xml | 1 +
landing/index.html | 2 +-
nextsync.install | 17 +++++
po/es.po | 12 ++++
src/ui/setup.rs | 76 +++++++++++++++++++++
src/util/translations/es.rs | 3 +
version.json | 8 +--
13 files changed, 134 insertions(+), 13 deletions(-)
create mode 100644 nextsync.install
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2744f58..59aedfe 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,13 @@
Todas las versiones notables de NextSync se documentan aquí. El formato sigue [Keep a Changelog](https://keepachangelog.com/es/1.1.0/) y el versionado es **+0.0.2 por release, reiniciado en 0.1.4** (decisión del usuario, 22-Ago-2026; sustituye al +0.02 anterior).
+## [0.2.26] - 2026-09-17
+
+### Added
+
+- The setup wizard blocks finishing when the selected provider's sync engine is not installed, with an actionable hint naming the package (nextcloud-client / opencloud-desktop). The check re-evaluates for the newly selected provider (#210).
+- The Arch package lists both sync engines as optional dependencies and warns on install/upgrade when neither nextcloudcmd nor opencloudcmd is available.
+
## [0.2.24] - 2026-09-17
### Fixed
diff --git a/Cargo.lock b/Cargo.lock
index 61695f9..fda7c49 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1283,7 +1283,7 @@ dependencies = [
[[package]]
name = "nextsync"
-version = "0.2.24"
+version = "0.2.26"
dependencies = [
"async-channel",
"data-encoding",
diff --git a/Cargo.toml b/Cargo.toml
index 97839d0..9e94369 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "nextsync"
-version = "0.2.24"
+version = "0.2.26"
edition = "2021"
rust-version = "1.83"
license = "GPL-3.0-or-later"
diff --git a/PKGBUILD b/PKGBUILD
index 26f2199..284fe64 100644
--- a/PKGBUILD
+++ b/PKGBUILD
@@ -1,6 +1,6 @@
# Maintainer: gnacho
pkgname=nextsync
-pkgver=0.2.24
+pkgver=0.2.26
pkgrel=1
pkgdesc='Nextcloud desktop synchronization client for GNOME (Rust rewrite)'
arch=('x86_64' 'aarch64')
@@ -8,7 +8,12 @@ url='https://github.com/gnacho/nextsync'
license=('GPL-3.0-or-later')
depends=('gtk4' 'libadwaita' 'glibc' 'gcc-libs')
makedepends=('cargo')
-optdepends=('nextcloud-client: nextcloudcmd sync engine')
+# pacman has no "A or B" dependency: the engines are alternative optdepends
+# and nextsync.install warns at install/upgrade time when NEITHER is present
+# (issue #210).
+optdepends=('nextcloud-client: nextcloudcmd sync engine (Nextcloud provider)'
+ 'opencloud-desktop: opencloudcmd sync engine (OpenCloud provider)')
+install=nextsync.install
source=("nextsync-$pkgver.tar.gz")
sha256sums=('SKIP')
diff --git a/README.es.md b/README.es.md
index aad7e6d..823c822 100644
--- a/README.es.md
+++ b/README.es.md
@@ -17,7 +17,7 @@
-
+
@@ -84,7 +84,7 @@ Ambos motores se esconden detrás del mismo trait pequeño, así que un proveedo
Descarga el `.pkg.tar.zst` de la [última release](https://github.com/gnacho/nextsync/releases/latest) e instálalo:
```bash
-sudo pacman -U nextsync-0.2.24-1-x86_64.pkg.tar.zst
+sudo pacman -U nextsync-0.2.26-1-x86_64.pkg.tar.zst
```
El paquete depende de `gtk4` y `libadwaita`. Para cuentas Nextcloud instala `nextcloud-client` (aporta `nextcloudcmd`); para cuentas OpenCloud, el `opencloudcmd` oficial.
diff --git a/README.md b/README.md
index 8cf5609..4b939e1 100644
--- a/README.md
+++ b/README.md
@@ -17,7 +17,7 @@
-
+
@@ -84,7 +84,7 @@ Both engines sit behind the same small trait, so a new provider is a command bui
Download the `.pkg.tar.zst` from the [latest release](https://github.com/gnacho/nextsync/releases/latest) and install it:
```bash
-sudo pacman -U nextsync-0.2.24-1-x86_64.pkg.tar.zst
+sudo pacman -U nextsync-0.2.26-1-x86_64.pkg.tar.zst
```
The package depends on `gtk4` and `libadwaita`. For Nextcloud accounts install `nextcloud-client` (it provides `nextcloudcmd`); for OpenCloud accounts, the official `opencloudcmd`.
diff --git a/data/io.github.gnacho.nextsync.metainfo.xml b/data/io.github.gnacho.nextsync.metainfo.xml
index c5b1b1b..2ca2c35 100644
--- a/data/io.github.gnacho.nextsync.metainfo.xml
+++ b/data/io.github.gnacho.nextsync.metainfo.xml
@@ -32,6 +32,7 @@
io.github.gnacho.nextsync
+
diff --git a/landing/index.html b/landing/index.html
index 67e79e8..cfadeaa 100644
--- a/landing/index.html
+++ b/landing/index.html
@@ -210,7 +210,7 @@
Lo que NextSync no hace (todavía)
Instalación fácil y rápida
En Arch, CachyOS y derivadas hay paquete listo en cada release.
Descarga el paquete .pkg.tar.zst más reciente desde GitHub Releases y ajusta el nombre del fichero.
diff --git a/nextsync.install b/nextsync.install
new file mode 100644
index 0000000..35093d1
--- /dev/null
+++ b/nextsync.install
@@ -0,0 +1,17 @@
+# Issue #210: pacman cannot express "at least one of these two packages", so
+# both engines ship as optdepends and this script surfaces a clear warning
+# when neither sync engine is available after an install or upgrade.
+
+post_install() {
+ if ! command -v nextcloudcmd >/dev/null 2>&1 \
+ && ! command -v opencloudcmd >/dev/null 2>&1; then
+ echo "nextsync needs at least one sync engine to synchronize:"
+ echo " - Nextcloud: install nextcloud-client (provides nextcloudcmd)"
+ echo " - OpenCloud: install opencloud-desktop (provides opencloudcmd)"
+ echo "Without one of them every synchronization stays blocked."
+ fi
+}
+
+post_upgrade() {
+ post_install
+}
diff --git a/po/es.po b/po/es.po
index 5131f5f..d15db36 100644
--- a/po/es.po
+++ b/po/es.po
@@ -2578,6 +2578,18 @@ msgstr "Tu árbol de archivos completo se almacenará físicamente en este equip
msgid "opencloudcmd is missing. Install the OpenCloud desktop package before the first synchronization."
msgstr "Falta opencloudcmd. Instala el paquete de escritorio de OpenCloud antes de la primera sincronización."
+#: src/ui/setup.rs
+msgid "nextcloudcmd is missing. Install the nextcloud-client package (it provides the sync engine) and finish the setup afterwards."
+msgstr "Falta nextcloudcmd. Instala el paquete nextcloud-client (incluye el motor de sincronización) y termina la configuración después."
+
+#: src/ui/setup.rs
+msgid "opencloudcmd is missing. Install the opencloud-desktop package (it provides the sync engine) and finish the setup afterwards."
+msgstr "Falta opencloudcmd. Instala el paquete opencloud-desktop (incluye el motor de sincronización) y termina la configuración después."
+
+#: src/ui/setup.rs
+msgid "Sync Engine Not Installed"
+msgstr "Motor de sincronización no instalado"
+
#: src/core/scheduler.rs
msgid "Automatic synchronization is off"
diff --git a/src/ui/setup.rs b/src/ui/setup.rs
index a4fe560..92838e5 100644
--- a/src/ui/setup.rs
+++ b/src/ui/setup.rs
@@ -1408,6 +1408,18 @@ fn size_threshold_bytes(store: &ConfigStore) -> Option {
/// Create the local roots, persist the account and the TLS trust choice, then
/// invoke `on_complete` with the validated account and close the wizard.
fn finish_setup(ctx: &SetupContext) {
+ // Issue #210: block the finish when the selected provider's sync engine
+ // is not installed. This is the single choke point every finish path
+ // (first-sync confirmation, resume, decision) goes through, and the
+ // provider in the state is the freshly selected one, so switching
+ // providers re-evaluates the check for the new engine.
+ let provider = ctx.state.borrow().provider;
+ if let Some(hint) = engine_install_hint(provider, engine_present_for(provider)) {
+ let dialog = libadwaita::AlertDialog::new(Some(t("Sync Engine Not Installed")), Some(hint));
+ dialog.add_response("ok", t("OK"));
+ dialog.present(Some(&ctx.window));
+ return;
+ }
let (provider, server, username, authentication_type, folders, trust_invalid, size_confirmed) = {
let state = ctx.state.borrow();
(
@@ -1646,6 +1658,34 @@ fn update_provider_warning(banner: &libadwaita::Banner, provider: Provider) {
}
}
+/// Whether the selected provider's sync binary is available on `$PATH`
+/// (issue #210).
+fn engine_present_for(provider: Provider) -> bool {
+ match provider {
+ Provider::Nextcloud => find_binary("nextcloudcmd").is_some(),
+ Provider::OpenCloud => find_binary("opencloudcmd").is_some(),
+ }
+}
+
+/// Issue #210: the setup must not present itself as ready when the selected
+/// provider's sync engine is not installed. Returns the actionable install
+/// hint to block the finish with, or `None` when the engine is present. Kept
+/// pure (binary presence passed in) so the policy is testable without a real
+/// `$PATH`.
+fn engine_install_hint(provider: Provider, engine_present: bool) -> Option<&'static str> {
+ if engine_present {
+ return None;
+ }
+ match provider {
+ Provider::Nextcloud => Some(t(
+ "nextcloudcmd is missing. Install the nextcloud-client package (it provides the sync engine) and finish the setup afterwards.",
+ )),
+ Provider::OpenCloud => Some(t(
+ "opencloudcmd is missing. Install the opencloud-desktop package (it provides the sync engine) and finish the setup afterwards.",
+ )),
+ }
+}
+
// ---------------------------------------------------------------------------
// Widget helpers
// ---------------------------------------------------------------------------
@@ -2451,4 +2491,40 @@ mod tests {
let resolved = wizard_remote_picker_password("https://x.example.net", "").unwrap();
assert!(resolved.is_none());
}
+
+ /// Issue #210: finishing the setup with the selected provider's engine
+ /// missing is blocked with an actionable hint naming the package; with
+ /// the engine present there is no block at all.
+ #[test]
+ fn engine_install_hint_blocks_only_when_the_binary_is_missing() {
+ use crate::nextcloud::driver::Provider;
+ assert!(
+ engine_install_hint(Provider::Nextcloud, true).is_none(),
+ "an installed engine never blocks the setup"
+ );
+ assert!(
+ engine_install_hint(Provider::OpenCloud, true).is_none(),
+ "an installed engine never blocks the setup"
+ );
+ let nextcloud = engine_install_hint(Provider::Nextcloud, false)
+ .expect("a missing Nextcloud engine blocks the setup");
+ assert!(
+ nextcloud.contains("nextcloudcmd"),
+ "the hint names the binary: {nextcloud}"
+ );
+ assert!(
+ nextcloud.contains("nextcloud-client"),
+ "the hint names the Arch package: {nextcloud}"
+ );
+ let opencloud = engine_install_hint(Provider::OpenCloud, false)
+ .expect("a missing OpenCloud engine blocks the setup");
+ assert!(
+ opencloud.contains("opencloudcmd"),
+ "the hint names the binary: {opencloud}"
+ );
+ assert!(
+ opencloud.contains("opencloud-desktop"),
+ "the hint names the Arch package: {opencloud}"
+ );
+ }
}
diff --git a/src/util/translations/es.rs b/src/util/translations/es.rs
index d98de2f..17db55d 100644
--- a/src/util/translations/es.rs
+++ b/src/util/translations/es.rs
@@ -381,6 +381,7 @@ pub static CATALOG: &[(&str, &str)] = &[
("Startup", "Inicio"),
("Suspend automatic synchronization inside a daily time window", "Suspende la sincronización automática dentro de una franja horaria diaria"),
("Sync Activity and Conflicts", "Actividad y conflictos de sincronización"),
+ ("Sync Engine Not Installed", "Motor de sincronización no instalado"),
("Sync Now", "Sincronizar ahora"),
("Sync Once", "Sincronizar una vez"),
("Sync provider", "Proveedor de sincronización"),
@@ -495,8 +496,10 @@ pub static CATALOG: &[(&str, &str)] = &[
("conflict on {file}", "conflicto en {file}"),
("deleting {file}", "eliminando {file}"),
("downloading {file}", "descargando {file}"),
+ ("nextcloudcmd is missing. Install the nextcloud-client package (it provides the sync engine) and finish the setup afterwards.", "Falta nextcloudcmd. Instala el paquete nextcloud-client (incluye el motor de sincronización) y termina la configuración después."),
("nextcloudcmd is missing. Install the nextcloud-desktop-cmd package before the first synchronization.", "Falta nextcloudcmd. Instala el paquete nextcloud-desktop-cmd antes de la primera sincronización."),
("opencloudcmd is missing. Install the OpenCloud desktop package before the first synchronization.", "Falta opencloudcmd. Instala el paquete de escritorio de OpenCloud antes de la primera sincronización."),
+ ("opencloudcmd is missing. Install the opencloud-desktop package (it provides the sync engine) and finish the setup afterwards.", "Falta opencloudcmd. Instala el paquete opencloud-desktop (incluye el motor de sincronización) y termina la configuración después."),
("processing {file}", "procesando {file}"),
("uploading {file}", "subiendo {file}"),
("{account}: open NextSync to check the account credential.", "{account}: abre NextSync para comprobar la credencial de la cuenta."),
diff --git a/version.json b/version.json
index 4a1bbf8..e8d12d3 100644
--- a/version.json
+++ b/version.json
@@ -1,11 +1,11 @@
{
"schema_version": 1,
- "version": "0.2.24",
+ "version": "0.2.26",
"mandatory": false,
- "summary": "Folders recover on their own after connectivity returns, even when the recovery probe fired while the machine was offline.",
+ "summary": "The setup wizard blocks finishing without the selected provider's sync engine, and the package flags when no engine is installed at all.",
"changelog": [
- "After a period without connectivity, folders no longer stay Offline until a manual sync or an app restart: when the network comes back, the server recovery probe is re-armed automatically and synchronization resumes on its own.",
- "Fixes the case where the one-shot recovery probe fired while the machine was offline, died silently, and the NetworkRestored trigger stayed parked behind the server-unreachable gate forever."
+ "The setup wizard now refuses to finish when the selected provider's sync engine is missing, with an actionable hint naming the package to install (nextcloud-client / opencloud-desktop). The welcome banner already warned on provider selection; now the finish is blocked too (issue #210).",
+ "The Arch package lists both sync engines as optional dependencies and prints a clear warning on install/upgrade when neither nextcloudcmd nor opencloudcmd is available, since pacman cannot express an 'at least one' dependency."
],
"released_at": "2026-09-17T00:00:00Z"
}