Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,10 @@ jobs:
echo "Running tests for: $CRATES"
PFLAGS=""
for c in $CRATES; do PFLAGS="$PFLAGS -p $c"; done
cargo nextest run $PFLAGS --no-fail-fast
# `--no-tests=pass` keeps PRs that only touch a crate without
# tests (e.g. librefang-desktop) from failing the selective lane
# — nextest defaults to exit-4 when -p X resolves to zero tests.
cargo nextest run $PFLAGS --no-fail-fast --no-tests=pass
fi

# ── Windows tests: selective on PR, full on main ───────────────────────────────
Expand Down Expand Up @@ -405,7 +408,10 @@ jobs:
echo "Running tests for: $CRATES"
PFLAGS=""
for c in $CRATES; do PFLAGS="$PFLAGS -p $c"; done
cargo nextest run $PFLAGS --no-fail-fast
# `--no-tests=pass` keeps PRs that only touch a crate without
# tests (e.g. librefang-desktop) from failing the selective lane
# — nextest defaults to exit-4 when -p X resolves to zero tests.
cargo nextest run $PFLAGS --no-fail-fast --no-tests=pass
fi

# ── macOS tests: selective on PR, full on main ─────────────────────────────────
Expand Down Expand Up @@ -439,5 +445,8 @@ jobs:
echo "Running tests for: $CRATES"
PFLAGS=""
for c in $CRATES; do PFLAGS="$PFLAGS -p $c"; done
cargo nextest run $PFLAGS --no-fail-fast
# `--no-tests=pass` keeps PRs that only touch a crate without
# tests (e.g. librefang-desktop) from failing the selective lane
# — nextest defaults to exit-4 when -p X resolves to zero tests.
cargo nextest run $PFLAGS --no-fail-fast --no-tests=pass
fi
40 changes: 40 additions & 0 deletions crates/librefang-desktop/src/updater.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,20 @@ pub fn spawn_startup_check(app_handle: tauri::AppHandle) {
tauri::async_runtime::spawn(async move {
tokio::time::sleep(std::time::Duration::from_secs(10)).await;

// Probe the updater endpoint before invoking the plugin. When the
// release pipeline ships platform bundles but no `latest.json`
// manifest (current state — `TAURI_SIGNING_PRIVATE_KEY` is missing
// so `tauri-action` silently skips manifest generation), the plugin
// emits an `ERROR tauri_plugin_updater::updater: update endpoint
// did not respond with a successful status code` on every startup.
// Skipping the plugin call when the manifest 404s keeps the daemon
// log clean; once the release ships a manifest the probe passes and
// the plugin works normally.
if !manifest_reachable(&app_handle).await {
info!("Update manifest not reachable at endpoint; skipping startup update check");
return;
}

match do_check(&app_handle).await {
Ok(info) if info.available => {
let version = info.version.as_deref().unwrap_or("unknown");
Expand Down Expand Up @@ -75,6 +89,32 @@ pub async fn download_and_install_update(app_handle: &tauri::AppHandle) -> Resul
app_handle.restart()
}

/// Pre-flight HEAD against the configured updater endpoint. Returns false on
/// any network error, non-2xx response (404 from GitHub when the release has
/// no `latest.json`), missing config, or timeout.
async fn manifest_reachable(app_handle: &tauri::AppHandle) -> bool {
let endpoint = app_handle
.config()
.plugins
.0
.get("updater")
.and_then(|v| v.get("endpoints"))
.and_then(|v| v.as_array())
.and_then(|a| a.first())
.and_then(|v| v.as_str())
.map(str::to_owned);
let Some(url) = endpoint else {
return false;
};
let Ok(client) = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(5))
.build()
else {
return false;
};
matches!(client.head(&url).send().await, Ok(r) if r.status().is_success())
}

async fn do_check(app_handle: &tauri::AppHandle) -> Result<UpdateInfo, String> {
let updater = app_handle.updater().map_err(|e| e.to_string())?;
match updater.check().await {
Expand Down
28 changes: 23 additions & 5 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,10 @@
cairo
gdk-pixbuf
pango
# tray-icon dlopens at runtime, not a link dep — patchelf below
# adds it to RPATH so the tray plugin can find it (#3052).
# tray-icon dlopens libayatana-appindicator3.so.1 at runtime, not
# a link dep. wrapGAppsHook3 + gappsWrapperArgs in the desktop
# derivation below puts this lib dir on LD_LIBRARY_PATH so the
# dlopen resolves (#3052, #3192).
libayatana-appindicator
]);

Expand Down Expand Up @@ -143,10 +145,26 @@
# `copyDesktopItems` is a no-op on darwin; gating the hook on
# Linux keeps the macOS build path unchanged.
nativeBuildInputs = nativeBuildInputs
++ pkgs.lib.optionals pkgs.stdenv.isLinux [ pkgs.copyDesktopItems ];
++ pkgs.lib.optionals pkgs.stdenv.isLinux [
pkgs.copyDesktopItems
# wrapGAppsHook3 injects LD_LIBRARY_PATH (via gappsWrapperArgs
# below) and the GTK runtime env (XDG_DATA_DIRS,
# GIO_MODULE_DIR, GSETTINGS_SCHEMA_DIR, …) the webview needs.
pkgs.wrapGAppsHook3
];
desktopItems = pkgs.lib.optionals pkgs.stdenv.isLinux [ librefangDesktopItem ];
postFixup = pkgs.lib.optionalString pkgs.stdenv.isLinux ''
patchelf --add-rpath "${pkgs.libayatana-appindicator}/lib" "$out/bin/librefang-desktop"
# tray-icon → libappindicator-sys dlopens
# `libayatana-appindicator3.so.1` at runtime with no DT_NEEDED
# entry. patchelf --add-rpath writes DT_RUNPATH, which ld.so only
# consults for DT_NEEDED deps — never for dlopen string lookups —
# so the previous RPATH fix (#3052) never actually worked, the
# tray icon silently failed to appear on NixOS (#3192). Wrapping
# with gappsWrapperArgs prepends the appindicator lib dir to
# LD_LIBRARY_PATH so the dlopen call resolves.
preFixup = pkgs.lib.optionalString pkgs.stdenv.isLinux ''
gappsWrapperArgs+=(
--prefix LD_LIBRARY_PATH : "${pkgs.libayatana-appindicator}/lib"
)
'';
postInstall =
let
Expand Down
Loading