name: adom-desktop-installer
description: How to invoke the Adom Desktop NSIS installer from a parent installer (especially Hydrogen Desktop's setup steps) — silent install, /NOLAUNCH switch for HD-managed bundling, live progress via the structured log file at %TEMP%\adom-desktop-install.log. Use when bundling AD inside another Windows installer, writing an install supervisor / setup-step UI, detecting AD's installed version, or upgrading an existing AD install programmatically.

Adom Desktop installer integration

Adom Desktop (AD) ships as a single NSIS installer:

Adom Desktop_<version>_x64-setup.exe   (~4.8 MB on v1.8.45)

It installs to %LOCALAPPDATA%\Adom Desktop\ — no UAC prompt, no admin needed. Tauri-2 bundled NSIS with our customisations in src-tauri/installer-hooks.nsh.

Command-line surface

Flag Meaning
(none) Installs silently, auto-launches adom-desktop.exe after install
/S Standard NSIS silent flag — same behavior since the top-level SilentInstall silent directive already suppresses UI
/NOLAUNCH v1.8.45+ — install silently, do NOT auto-launch after install. Caller is responsible for spawning AD with the desired flags
/D=<path> Install destination override. Default is $LOCALAPPDATA\Adom Desktop. Rarely needed
<uninstall.exe> /S Silent uninstall. The uninstaller lives at %LOCALAPPDATA%\Adom Desktop\uninstall.exe

UI behavior: zero UI in all cases. SilentInstall silent is set at the top of the NSIS script, so even setup.exe invoked from Explorer runs without a window. No Welcome page, no progress bar, no Finish page.

Auto-launch: by default the installer calls ExecShell "" "$INSTDIR\adom-desktop.exe" at the end of POSTINSTALL. Pass /NOLAUNCH to suppress this. Used by HD when bundling AD because HD wants to spawn AD with --embedded --start-hidden --relay-url ... --session-token ... itself, not let AD launch standalone first and get respawned via single-instance.

Live progress log

The installer writes structured progress to %TEMP%\adom-desktop-install.log (Windows expands %TEMP% to the user's temp dir, typically C:\Users\<name>\AppData\Local\Temp). Each line is [STAGE] message\r\n:

[START] adom-desktop installer 1.8.45 starting
[PREINSTALL] Cleaning legacy desktop shortcuts
[PREINSTALL] Done. Tauri will now copy bundle files.
[POSTINSTALL] Bundle files copied. Cleaning Tauri's auto-created desktop shortcut.
[POSTINSTALL] /NOLAUNCH passed — skipping auto-launch (caller will spawn)
[DONE] adom-desktop installer complete (no-launch mode)

[START] opens with a truncated write (fresh log per install). [DONE] is always the final line — readers tailing the file can use that as the completion sentinel without needing to wait for the process exit.

Stage tags currently emitted:

  • [START] — once, at the top
  • [PREINSTALL] — Tauri's beforeBuildCommand hook ran, files about to be copied
  • [POSTINSTALL] — files copied; Tauri's standard shortcuts created and our hook is cleaning the unwanted ones
  • [DONE] — final line, installer about to exit

The uninstaller writes the same log with [PREUNINSTALL] / [POSTUNINSTALL] stages.

Recipe: HD's setup-step UI tails the log

Spawn the installer with /NOLAUNCH, poll the log file on a 200 ms timer, surface each new line to HD's UI. When [DONE] shows up OR the process exits, the install is complete.

Rust (HD's installer driver)

use std::{env, fs, path::PathBuf, process::Command, time::Duration};
use std::os::windows::process::CommandExt;
use tokio::time::sleep;

const CREATE_NO_WINDOW: u32 = 0x08000000;

async fn install_adom_desktop<F: FnMut(String)>(
    installer_path: &str,
    mut on_line: F,
) -> Result<(), String> {
    let log_path: PathBuf = [&env::var("TEMP").map_err(|e| e.to_string())?, "adom-desktop-install.log"]
        .iter()
        .collect();
    // Pre-clear so we don't read stale lines from a previous install.
    let _ = fs::remove_file(&log_path);

    let mut child = Command::new(installer_path)
        .args(["/S", "/NOLAUNCH"])
        .creation_flags(CREATE_NO_WINDOW)
        .spawn()
        .map_err(|e| format!("spawn installer: {e}"))?;

    let mut last_pos = 0usize;
    loop {
        // Tail any new content
        if let Ok(content) = fs::read_to_string(&log_path) {
            if content.len() > last_pos {
                let slice = &content[last_pos..];
                if let Some(last_nl) = slice.rfind('\n') {
                    let consumed = &slice[..=last_nl];
                    for line in consumed.lines() {
                        on_line(line.to_string());
                        if line.starts_with("[DONE]") {
                            // Don't return yet — wait for process exit
                            // so we surface the actual exit code, but
                            // we know progress streaming is finished.
                        }
                    }
                    last_pos += consumed.len();
                }
            }
        }
        // Check if installer has exited
        if let Some(status) = child.try_wait().map_err(|e| e.to_string())? {
            return if status.success() {
                Ok(())
            } else {
                Err(format!("installer exited with {}", status))
            };
        }
        sleep(Duration::from_millis(200)).await;
    }
}

// Caller:
// install_adom_desktop(r"C:\path\to\Adom Desktop_1.8.45_x64-setup.exe", |line| {
//     hd_setup_step.append_live_output(&line);
// }).await?;

Bash (debugging / scripting)

# Pre-clear stale log
rm -f "$TEMP/adom-desktop-install.log" 2>/dev/null

# Spawn installer in background, tail the log
"./Adom Desktop_1.8.45_x64-setup.exe" /S /NOLAUNCH &
INSTALLER_PID=$!

# Tail until process exits OR [DONE] appears
while kill -0 $INSTALLER_PID 2>/dev/null; do
  [ -f "$TEMP/adom-desktop-install.log" ] && tail -F "$TEMP/adom-desktop-install.log" &
  sleep 0.2
done
wait
echo "Installer exited"

Recipe: detect if AD is already installed (and at what version)

AD writes its uninstall info to the Windows registry under HKCU (current-user install, no admin needed):

HKCU\Software\Microsoft\Windows\CurrentVersion\Uninstall\AdomDesktop
  DisplayName       = "Adom Desktop"
  DisplayVersion    = "1.8.45"
  InstallLocation   = "C:\Users\<name>\AppData\Local\Adom Desktop"
  UninstallString   = "C:\Users\<name>\AppData\Local\Adom Desktop\uninstall.exe"
  Publisher         = "Adom Industries, Inc."
use winreg::enums::HKEY_CURRENT_USER;
use winreg::RegKey;

fn adom_desktop_installed_version() -> Option<String> {
    let hkcu = RegKey::predef(HKEY_CURRENT_USER);
    let key = hkcu
        .open_subkey(r"Software\Microsoft\Windows\CurrentVersion\Uninstall\AdomDesktop")
        .ok()?;
    key.get_value("DisplayVersion").ok()
}

Compare with semver::Version::parse to decide whether to skip / upgrade / leave-alone.

Recipe: HD's setup-step ladder for embedding AD

The full pattern HD's setup-step UI runs when embedding AD:

1. Check installed version          → registry read above
2. Decide:
     no install         → install bundled AD
     older than bundled → install bundled AD (upgrade)
     same or newer      → skip install entirely
3. Run installer (silent + /NOLAUNCH)   → tail log, surface to UI
4. Write embedded marker file       → %LOCALAPPDATA%\Adom Desktop\embedded.json
5. Delete AD's user-facing shortcuts → Start Menu, Desktop, Startup folder
                                       (so user can't double-launch standalone)
6. Spawn AD with embedded flags     → adom-desktop.exe --embedded
                                          --start-hidden
                                          --relay-url ws://127.0.0.1:8765
                                          --relay-name hydrogen-desktop
                                          --session-token <hd's token>
7. Poll AD's direct API for liveness → GET http://127.0.0.1:47200/health

Steps 4 and 5 are also covered by AD's installer if you pass --embedded at AD's first launch (the embedded-mode detection writes the marker and AD's autostart-upgrade routine handles its own shortcuts). Doing them in HD's installer is belt-and-suspenders.

Bundle-resource pattern

HD's tauri.conf.json should include the AD installer as a bundle resource so it ships inside HD's NSIS installer:

"bundle": {
  "resources": {
    "resources/Adom Desktop_1.8.45_x64-setup.exe": "resources/"
  }
}

At runtime HD finds it under its install dir at $INSTDIR\resources\Adom Desktop_<ver>_x64-setup.exe — pass that path to the install function above.

Versioning + upgrade strategy

Scenario What HD's installer should do
No AD installed Install bundled version
AD older than bundled Install bundled version (upgrade)
AD same as bundled Skip install step; still spawn with embedded flags
AD newer than bundled Skip install step; spawn with embedded flags. Don't downgrade — embedded mode works for any AD ≥ v1.8.42

The bundled AD installer is the minimum supported version for the HD release. HD can spawn newer ADs without modification.

Exit codes

NSIS returns:

  • 0 — install succeeded
  • 2 — user cancelled (won't happen with SilentInstall silent since there's no UI to cancel)
  • Other — install error (rare with our hooks; typical cause is disk full or %LOCALAPPDATA% not writable)

Treat anything non-zero as a hard failure and surface the log file's last 20 lines to the user.

Where the installer lives

Public download (the user's wiki, which is what end users hit):

https://wiki-ufypy5dpx93o.adom.cloud/static/apps/adom-desktop/Adom%20Desktop_<version>_x64-setup.exe

The wiki page's metadata.install.windows_installer field points at the canonical filename for the current published version. HD's release build can fetch the latest via:

curl -fL https://wiki-ufypy5dpx93o.adom.cloud/static/apps/adom-desktop/version.json | jq -r .windows.installer_filename
# → "Adom Desktop_1.8.45_x64-setup.exe"

curl -fL "https://wiki-ufypy5dpx93o.adom.cloud/static/apps/adom-desktop/$FILENAME" -o resources/ad-setup.exe

version.json also carries the SHA256 hash of the installer so HD's build pipeline can verify the download before bundling.