pushary 1.9.133 → 1.9.134
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.9.134
|
|
4
|
+
|
|
5
|
+
- Linux: on a machine you reach over SSH, phone-start keeps running after you log out, and starts again after a reboot. Setup, `pushary upgrade` and `pushary daemon install` now turn on systemd lingering for your user unless you are signed in to a desktop session there, and say so. Where the system refuses, they print the `sudo loginctl enable-linger` command instead. Before, systemd stopped the daemon when your last SSH session closed, while setup still reported phone-start as working. `pushary doctor` has a new row, "Remote-control service survives logout".
|
|
6
|
+
|
|
3
7
|
## 1.9.133
|
|
4
8
|
|
|
5
9
|
- The background daemon starts again by itself after its lock file was left empty or cut off, for example when the disk filled up or the computer lost power while it was starting, and, on macOS and Linux, after a power cut or forced restart left a lock that names a process number another program now uses. Before, it exited at once and was restarted again and again, writing an error each time, and even `pushary daemon restart` could not clear the second case.
|
|
@@ -8,6 +8,10 @@ import {
|
|
|
8
8
|
} from "../chunk-6JY44IZI.js";
|
|
9
9
|
import "../chunk-FT5MTCKU.js";
|
|
10
10
|
import "../chunk-PG23ENSN.js";
|
|
11
|
+
import {
|
|
12
|
+
keepLinuxDaemonAfterLogout,
|
|
13
|
+
lingerReportOf
|
|
14
|
+
} from "../chunk-AIZYJJGD.js";
|
|
11
15
|
import {
|
|
12
16
|
HEARTBEAT_INTERVAL_MS,
|
|
13
17
|
sendHeartbeat
|
|
@@ -2305,6 +2309,9 @@ var main = async () => {
|
|
|
2305
2309
|
return EXIT.UNREACHABLE;
|
|
2306
2310
|
}
|
|
2307
2311
|
process.stdout.write("[pushary] daemon service installed and online\n");
|
|
2312
|
+
const linger = lingerReportOf(keepLinuxDaemonAfterLogout());
|
|
2313
|
+
if (linger) (linger.tone === "warning" ? process.stderr : process.stdout).write(`[pushary] ${linger.text}
|
|
2314
|
+
`);
|
|
2308
2315
|
return EXIT.OK;
|
|
2309
2316
|
}
|
|
2310
2317
|
if (action === "start") {
|
|
@@ -32,6 +32,11 @@ import {
|
|
|
32
32
|
roundTripEvidence,
|
|
33
33
|
verifyRoundTrip
|
|
34
34
|
} from "../chunk-L5RSLIBQ.js";
|
|
35
|
+
import {
|
|
36
|
+
daemonStopsAtLogout,
|
|
37
|
+
lingerDoctorDetail,
|
|
38
|
+
readLinuxLinger
|
|
39
|
+
} from "../chunk-AIZYJJGD.js";
|
|
35
40
|
import {
|
|
36
41
|
readMachineReadiness,
|
|
37
42
|
readManagedControlEvidence
|
|
@@ -1198,6 +1203,8 @@ var main = async () => {
|
|
|
1198
1203
|
} else if (serviceFile) {
|
|
1199
1204
|
console.log(` ${warn} Remote-control service runtime ${dim(`(could not read the node path out of ${serviceFile.path})`)}`);
|
|
1200
1205
|
}
|
|
1206
|
+
const linger = serviceFile && controlPolicy === "enabled" ? readLinuxLinger() : null;
|
|
1207
|
+
if (linger) check(!daemonStopsAtLogout(linger), "Remote-control service survives logout", lingerDoctorDetail(linger));
|
|
1201
1208
|
let serverProof = null;
|
|
1202
1209
|
if (controlPolicy === "enabled" && apiKey && daemon.machineId) {
|
|
1203
1210
|
serverProof = await readMachineReadiness(apiKey, expectedLaunchCapabilities(configuredManagedProviders), {
|
|
@@ -91,6 +91,10 @@ import {
|
|
|
91
91
|
roundTripWasAnswered,
|
|
92
92
|
verifyRoundTrip
|
|
93
93
|
} from "../chunk-L5RSLIBQ.js";
|
|
94
|
+
import {
|
|
95
|
+
keepLinuxDaemonAfterLogout,
|
|
96
|
+
lingerReportOf
|
|
97
|
+
} from "../chunk-AIZYJJGD.js";
|
|
94
98
|
import {
|
|
95
99
|
readManagedControlEvidence,
|
|
96
100
|
waitForMachineReadiness
|
|
@@ -894,6 +898,15 @@ var warnVersionManagerPin = (execPath) => {
|
|
|
894
898
|
const manager = versionManagerOf(execPath);
|
|
895
899
|
if (manager) console.log(` ${yellow("!")} ${describeVersionManagerPin(execPath, manager)}`);
|
|
896
900
|
};
|
|
901
|
+
var reportLinger = (report) => {
|
|
902
|
+
if (!report) return;
|
|
903
|
+
if (report.tone === "note") {
|
|
904
|
+
console.log(` ${dim(report.text)}`);
|
|
905
|
+
return;
|
|
906
|
+
}
|
|
907
|
+
console.log(` ${yellow("!")} ${report.text}`);
|
|
908
|
+
noteManual(report.text);
|
|
909
|
+
};
|
|
897
910
|
var installGlobally2 = async () => {
|
|
898
911
|
if (globalInstallReady) return;
|
|
899
912
|
const version = getPackageVersion();
|
|
@@ -2810,6 +2823,7 @@ var main = async () => {
|
|
|
2810
2823
|
const service = await replaceDaemonService();
|
|
2811
2824
|
if (service.kind === "ok") {
|
|
2812
2825
|
warnVersionManagerPin(serviceNodeFor(process.execPath, existsSync));
|
|
2826
|
+
reportLinger(lingerReportOf(keepLinuxDaemonAfterLogout()));
|
|
2813
2827
|
controlFailureStage = "daemon-start";
|
|
2814
2828
|
daemon = await ensureDaemonRunning({ spawnDaemon: () => {
|
|
2815
2829
|
}, waitTimeoutMs: DAEMON_COLD_START_TIMEOUT_MS });
|
|
@@ -34,6 +34,10 @@ import {
|
|
|
34
34
|
describeCodexTrustConfirmation
|
|
35
35
|
} from "../chunk-RVGYXUOC.js";
|
|
36
36
|
import "../chunk-L5RSLIBQ.js";
|
|
37
|
+
import {
|
|
38
|
+
keepLinuxDaemonAfterLogout,
|
|
39
|
+
lingerReportOf
|
|
40
|
+
} from "../chunk-AIZYJJGD.js";
|
|
37
41
|
import {
|
|
38
42
|
waitForMachineReadiness
|
|
39
43
|
} from "../chunk-FJST6ZXC.js";
|
|
@@ -347,6 +351,8 @@ var finishUpgrade = async () => {
|
|
|
347
351
|
var startBackgroundService = async (apiKey, providers) => {
|
|
348
352
|
const service = await replaceDaemonService();
|
|
349
353
|
if (service.kind !== "ok") return "did_not_start";
|
|
354
|
+
const linger = lingerReportOf(keepLinuxDaemonAfterLogout());
|
|
355
|
+
if (linger) console.log(linger.tone === "warning" ? `! ${linger.text}` : linger.text);
|
|
350
356
|
const daemon = await ensureDaemonRunning({ spawnDaemon: () => {
|
|
351
357
|
}, waitTimeoutMs: DAEMON_COLD_START_TIMEOUT_MS });
|
|
352
358
|
if (daemon.kind !== "running" && daemon.kind !== "started") return "did_not_start";
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// src/linux-linger.ts
|
|
2
|
+
import { execFileSync } from "child_process";
|
|
3
|
+
import { userInfo } from "os";
|
|
4
|
+
var LOGINCTL_TIMEOUT_MS = 5e3;
|
|
5
|
+
var DESKTOP_SESSION_TYPES = /* @__PURE__ */ new Set(["x11", "wayland", "mir"]);
|
|
6
|
+
var STOPS_AT_LOGOUT = "stops when your last SSH session closes, and does not start after a reboot until you log in";
|
|
7
|
+
var NOT_NEEDED = { kind: "not-needed" };
|
|
8
|
+
var runLoginctl = (args) => execFileSync("loginctl", [...args], {
|
|
9
|
+
encoding: "utf8",
|
|
10
|
+
timeout: LOGINCTL_TIMEOUT_MS,
|
|
11
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
12
|
+
});
|
|
13
|
+
var propertyOf = (line) => {
|
|
14
|
+
const separator = line.indexOf("=");
|
|
15
|
+
return separator < 0 ? [line.trim(), ""] : [line.slice(0, separator).trim(), line.slice(separator + 1).trim()];
|
|
16
|
+
};
|
|
17
|
+
var propertiesOf = (output) => new Map(output.split("\n").map(propertyOf));
|
|
18
|
+
var lingerMayMatter = (platform, env) => platform === "linux" && !env.WSL_DISTRO_NAME;
|
|
19
|
+
var CLOSING_SESSION_STATE = "closing";
|
|
20
|
+
var sessionIsDesktop = (session, run) => {
|
|
21
|
+
if (session === "") return false;
|
|
22
|
+
try {
|
|
23
|
+
const properties = propertiesOf(run(["show-session", session, "--property=Type", "--property=State"]));
|
|
24
|
+
return DESKTOP_SESSION_TYPES.has(properties.get("Type") ?? "") && properties.get("State") !== CLOSING_SESSION_STATE;
|
|
25
|
+
} catch {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
var daemonStopsAtLogout = (facts) => !facts.linger && !facts.desktopSession;
|
|
30
|
+
var readLingerFacts = (user, run) => {
|
|
31
|
+
try {
|
|
32
|
+
const properties = propertiesOf(run(["show-user", user, "--property=Linger", "--property=Display"]));
|
|
33
|
+
const linger = properties.get("Linger");
|
|
34
|
+
if (linger !== "yes" && linger !== "no") return null;
|
|
35
|
+
return { linger: linger === "yes", desktopSession: sessionIsDesktop(properties.get("Display") ?? "", run) };
|
|
36
|
+
} catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
var userOf = (options) => {
|
|
41
|
+
try {
|
|
42
|
+
return options.user ?? userInfo().username;
|
|
43
|
+
} catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
var readLinuxLinger = (options = {}) => {
|
|
48
|
+
if (!lingerMayMatter(options.platform ?? process.platform, options.env ?? process.env)) return null;
|
|
49
|
+
const user = userOf(options);
|
|
50
|
+
return user === null ? null : readLingerFacts(user, options.run ?? runLoginctl);
|
|
51
|
+
};
|
|
52
|
+
var keepLinuxDaemonAfterLogout = (options = {}) => {
|
|
53
|
+
if (!lingerMayMatter(options.platform ?? process.platform, options.env ?? process.env)) return NOT_NEEDED;
|
|
54
|
+
const user = userOf(options);
|
|
55
|
+
if (user === null) return NOT_NEEDED;
|
|
56
|
+
const run = options.run ?? runLoginctl;
|
|
57
|
+
const facts = readLingerFacts(user, run);
|
|
58
|
+
if (!facts || !daemonStopsAtLogout(facts)) return NOT_NEEDED;
|
|
59
|
+
try {
|
|
60
|
+
run(["--no-ask-password", "enable-linger", user]);
|
|
61
|
+
} catch {
|
|
62
|
+
return { kind: "needs-admin", user };
|
|
63
|
+
}
|
|
64
|
+
return readLingerFacts(user, run)?.linger === true ? { kind: "enabled", user } : { kind: "needs-admin", user };
|
|
65
|
+
};
|
|
66
|
+
var lingerReportOf = (outcome) => {
|
|
67
|
+
if (outcome.kind === "enabled") {
|
|
68
|
+
return {
|
|
69
|
+
tone: "note",
|
|
70
|
+
text: `Phone-start keeps running after you log out of this machine: turned on systemd lingering for ${outcome.user}. Undo with \`loginctl disable-linger\`.`
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
if (outcome.kind === "needs-admin") {
|
|
74
|
+
return {
|
|
75
|
+
tone: "warning",
|
|
76
|
+
text: `Phone-start ${STOPS_AT_LOGOUT}. To keep it running: \`sudo loginctl enable-linger ${outcome.user}\``
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
return null;
|
|
80
|
+
};
|
|
81
|
+
var lingerDoctorDetail = (facts) => {
|
|
82
|
+
if (daemonStopsAtLogout(facts)) {
|
|
83
|
+
return `no: it ${STOPS_AT_LOGOUT}; run \`pushary daemon install\`, or \`sudo loginctl enable-linger $USER\``;
|
|
84
|
+
}
|
|
85
|
+
if (facts.linger) return "systemd lingering is on";
|
|
86
|
+
return "while you are signed in to the desktop";
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export {
|
|
90
|
+
daemonStopsAtLogout,
|
|
91
|
+
readLinuxLinger,
|
|
92
|
+
keepLinuxDaemonAfterLogout,
|
|
93
|
+
lingerReportOf,
|
|
94
|
+
lingerDoctorDetail
|
|
95
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pushary",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.134",
|
|
4
4
|
"description": "Human-in-the-loop (HITL) for AI agents: ask a person for approvals or answers by push notification. One command connects Claude Code, Codex, Cursor, Gemini CLI and more.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pushary",
|