← Commit history

Migrate asset from Helium wiki

Kyle Bergstedt ·d68b294d72 ·4mo ago ·parent 7aee9e7
1 file changed +242
installer-SKILL.mdadded+242
@@ -0,0 +1,242 @@+---+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)++```rust+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)++```bash+# 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."+```++```rust+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:++```json+"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:++```bash+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.