Compare commits

..

3 commits

Author SHA1 Message Date
avi
0589dd0396 chore(release): bump to 0.1.10 2026-10-04 13:11:44 -05:00
avi
3e0c7f69bf chore(release): bump to 0.1.9 2026-10-04 13:09:55 -05:00
avi
12977e1a77 fix(updater): run the handoff script via systemd-run so it survives the app's scope
On uwsm/Hyprland the app runs in a systemd scope cgroup; a detached child is
swept away when the app quits, which killed the swap mid-flight on every
in-app update attempt. systemd-run gives the installer its own transient
unit that outlives the scope; detached spawn stays as the non-systemd
fallback.
2026-10-04 13:09:55 -05:00
3 changed files with 36 additions and 8 deletions

4
package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "folio",
"version": "0.1.7",
"version": "0.1.8",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "folio",
"version": "0.1.7",
"version": "0.1.8",
"license": "MIT",
"dependencies": {
"@tiptap/core": "^2.27.2",

View file

@ -1,6 +1,6 @@
{
"name": "folio",
"version": "0.1.8",
"version": "0.1.10",
"description": "A simple, local, open-source desktop writing app.",
"productName": "Folio",
"author": "Folio Contributors",

View file

@ -1286,12 +1286,40 @@ handleIpc("folio:performUpdate", async () => {
}),
{ mode: 0o700 }
);
// IMPORTANT: on systemd desktops (Hyprland/uwsm) the app runs inside a
// systemd scope cgroup, and a plain detached child stays in that cgroup —
// when the app quits, systemd sweeps the scope and kills the swap script
// mid-run (root cause of the "update failed" reports: the handoff died
// before swapping, and the app was relaunched from the old tree).
// systemd-run puts the script in its own transient unit that outlives us;
// verified to survive on this machine. Fall back to a detached spawn for
// non-systemd sessions.
const { spawn } = await import("child_process");
const child = spawn("/bin/sh", [script], { detached: true, stdio: "ignore" });
child.on("error", (err) => {
console.error("[folio] updater spawn failed:", err.message);
});
child.unref();
let handedOff = false;
try {
const { execFileSync } = await import("child_process");
execFileSync("systemd-run", [
"--user", "--quiet", "--collect",
`--description=Folio updater handoff`,
"/bin/sh", script,
], { timeout: 10_000, stdio: "ignore" });
handedOff = true;
} catch {
try {
const child = spawn("/bin/sh", [script], { detached: true, stdio: "ignore" });
child.on("error", (err) => {
console.error("[folio] updater spawn failed:", err.message);
});
child.unref();
handedOff = true;
} catch (e) {
void e;
}
}
if (!handedOff) {
try { fs.appendFileSync(updateLog, new Date().toISOString() + " FAIL: could not start swap script\n"); } catch { /* ignore */ }
return { error: "Could not start the installer. Your current version is unchanged." };
}
// Quit so the swap script can replace the app directory.
setTimeout(() => app.quit(), 300);
return { ok: true, version: rel.version };