@anytowork/cli 0.12.27 → 0.12.28
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/package.json +1 -1
- package/src/listener/commands.js +2 -2
- package/src/listener/install.js +26 -15
package/package.json
CHANGED
package/src/listener/commands.js
CHANGED
|
@@ -41,7 +41,7 @@ export async function runListenInstall(p) {
|
|
|
41
41
|
} else {
|
|
42
42
|
try {
|
|
43
43
|
result = await (deps.installAutostart || installAutostart)({
|
|
44
|
-
profile, configDir: env.ANYTOWORK_CONFIG_DIR || null, logFile: `${dir}/listener.log`,
|
|
44
|
+
profile, configDir: profile ? null : (env.ANYTOWORK_CONFIG_DIR || env.LINGER_CONFIG_DIR || null), logFile: `${dir}/listener.log`,
|
|
45
45
|
platform: deps.platform, home: deps.home, runCmd: deps.runCmd, node: deps.node, entry: deps.entry, pathEnv: deps.pathEnv,
|
|
46
46
|
startNow: deps.startNow,
|
|
47
47
|
});
|
|
@@ -64,7 +64,7 @@ export async function runListenUninstall(p) {
|
|
|
64
64
|
const { flags, credOpts, baseUrl, fetchImpl, log, deps = {} } = p;
|
|
65
65
|
const env = deps.env || process.env;
|
|
66
66
|
try {
|
|
67
|
-
await (deps.uninstallAutostart || uninstallAutostart)({ profile: flags.profile || null, platform: deps.platform, home: deps.home, runCmd: deps.runCmd });
|
|
67
|
+
await (deps.uninstallAutostart || uninstallAutostart)({ profile: flags.profile || null, configDir: flags.profile ? null : (env.ANYTOWORK_CONFIG_DIR || env.LINGER_CONFIG_DIR || null), platform: deps.platform, home: deps.home, runCmd: deps.runCmd });
|
|
68
68
|
} catch { /* nothing to undo */ }
|
|
69
69
|
for (const f of [configPath(credOpts), cursorFile(credOpts), statePath(credOpts)]) { try { fs.unlinkSync(f); } catch { /* none */ } }
|
|
70
70
|
try {
|
package/src/listener/install.js
CHANGED
|
@@ -8,9 +8,16 @@
|
|
|
8
8
|
import fs from 'node:fs';
|
|
9
9
|
import os from 'node:os';
|
|
10
10
|
import path from 'node:path';
|
|
11
|
+
import { createHash } from 'node:crypto';
|
|
11
12
|
import { execFile } from 'node:child_process';
|
|
12
13
|
import { fileURLToPath } from 'node:url';
|
|
13
14
|
|
|
15
|
+
/** Name part that tells identities apart on one computer: the profile, else a short hash of the config dir, else "default". */
|
|
16
|
+
export function slotOf(profile, configDir) {
|
|
17
|
+
if (profile) return profile;
|
|
18
|
+
if (configDir) return `dir-${createHash("sha1").update(String(configDir)).digest("hex").slice(0, 8)}`;
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
14
21
|
export function labelOf(profile) { return `work.anytowork.listen.${profile || 'default'}`; }
|
|
15
22
|
export function unitNameOf(profile) { return `anytowork-listen-${profile || 'default'}.service`; }
|
|
16
23
|
export function taskNameOf(profile) { return `AnytoWork Listen ${profile || 'default'}`; }
|
|
@@ -79,13 +86,15 @@ function shSingle(s) { return `'${String(s).replace(/'/g, `'\\''`)}'`; }
|
|
|
79
86
|
export function cronLine({ node, entry, profile, configDir }) {
|
|
80
87
|
const cmd = [shSingle(node), shSingle(entry), ...runArgs(profile).map(shSingle)].join(' ');
|
|
81
88
|
const envPart = configDir ? `ANYTOWORK_CONFIG_DIR=${shSingle(configDir)} ` : '';
|
|
82
|
-
return `@reboot ${envPart}${cmd} >/dev/null 2>&1 ${cronMarkOf(profile)}`;
|
|
89
|
+
return `@reboot ${envPart}${cmd} >/dev/null 2>&1 ${cronMarkOf(slotOf(profile, configDir))}`;
|
|
83
90
|
}
|
|
84
91
|
|
|
85
92
|
/** Windows: the arguments of `schtasks /Create` (run at the user's logon, no administrator needed). */
|
|
86
|
-
export function schtasksCreateArgs({ node, entry, profile }) {
|
|
87
|
-
const
|
|
88
|
-
|
|
93
|
+
export function schtasksCreateArgs({ node, entry, profile, configDir }) {
|
|
94
|
+
const run = [`"${node}"`, `"${entry}"`, ...runArgs(profile)].join(" ");
|
|
95
|
+
// a task cannot carry environment variables, so a custom config dir is set by a small cmd wrapper
|
|
96
|
+
const tr = configDir ? `cmd /c "set ANYTOWORK_CONFIG_DIR=${configDir}&& ${run}"` : run;
|
|
97
|
+
return ["/Create", "/TN", taskNameOf(slotOf(profile, configDir)), "/SC", "ONLOGON", "/TR", tr, "/F"];
|
|
89
98
|
}
|
|
90
99
|
|
|
91
100
|
/** Default way to run a system command; resolves {code, stdout, stderr}, never rejects. */
|
|
@@ -117,11 +126,12 @@ export async function installAutostart(o) {
|
|
|
117
126
|
const node = o.node || process.execPath;
|
|
118
127
|
const entry = o.entry || defaultEntryPath();
|
|
119
128
|
const pathEnv = o.pathEnv != null ? o.pathEnv : process.env.PATH;
|
|
129
|
+
const slot = slotOf(o.profile, o.configDir);
|
|
120
130
|
const common = { node, entry, profile: o.profile || null, pathEnv, configDir: o.configDir || null };
|
|
121
131
|
const notes = [];
|
|
122
132
|
|
|
123
133
|
if (platform === 'darwin') {
|
|
124
|
-
const label = labelOf(
|
|
134
|
+
const label = labelOf(slot);
|
|
125
135
|
const dir = path.join(home, 'Library', 'LaunchAgents');
|
|
126
136
|
const file = path.join(dir, `${label}.plist`);
|
|
127
137
|
fs.mkdirSync(dir, { recursive: true });
|
|
@@ -136,7 +146,7 @@ export async function installAutostart(o) {
|
|
|
136
146
|
if (platform === 'win32') {
|
|
137
147
|
const create = await runCmd('schtasks', schtasksCreateArgs(common));
|
|
138
148
|
if (create.code !== 0) throw new Error(`schtasks: ${(create.stderr || create.stdout || '').trim().slice(0, 200)}`);
|
|
139
|
-
await runCmd('schtasks', ['/Run', '/TN', taskNameOf(
|
|
149
|
+
await runCmd('schtasks', ['/Run', '/TN', taskNameOf(slot)]);
|
|
140
150
|
return { kind: 'schtasks', notes };
|
|
141
151
|
}
|
|
142
152
|
|
|
@@ -144,17 +154,17 @@ export async function installAutostart(o) {
|
|
|
144
154
|
const sd = await runCmd('systemctl', ['--user', '--version']);
|
|
145
155
|
if (sd.code === 0 && !sd.missing) {
|
|
146
156
|
const dir = path.join(home, '.config', 'systemd', 'user');
|
|
147
|
-
const file = path.join(dir, unitNameOf(
|
|
157
|
+
const file = path.join(dir, unitNameOf(slot));
|
|
148
158
|
fs.mkdirSync(dir, { recursive: true });
|
|
149
159
|
fs.writeFileSync(file, systemdUnit(common), { mode: 0o644 });
|
|
150
160
|
await runCmd('systemctl', ['--user', 'daemon-reload']);
|
|
151
|
-
const r = await runCmd('systemctl', ['--user', 'enable', '--now', unitNameOf(
|
|
161
|
+
const r = await runCmd('systemctl', ['--user', 'enable', '--now', unitNameOf(slot)]);
|
|
152
162
|
if (r.code === 0) { notes.push('linger'); return { kind: 'systemd', file, notes }; }
|
|
153
163
|
// The user manager is not reachable (container, ssh without a session): fall through to cron.
|
|
154
164
|
}
|
|
155
165
|
const ct = await runCmd('crontab', ['-l']);
|
|
156
166
|
if (!ct.missing) {
|
|
157
|
-
const mark = cronMarkOf(
|
|
167
|
+
const mark = cronMarkOf(slot);
|
|
158
168
|
const kept = (ct.code === 0 ? ct.stdout : '').split('\n').filter((l) => l && !l.includes(mark));
|
|
159
169
|
kept.push(cronLine(common));
|
|
160
170
|
const w = await runCmd('crontab', ['-'], { input: `${kept.join('\n')}\n` });
|
|
@@ -168,30 +178,31 @@ export async function installAutostart(o) {
|
|
|
168
178
|
|
|
169
179
|
/** Undo installAutostart; unknown or missing pieces are skipped. */
|
|
170
180
|
export async function uninstallAutostart(o) {
|
|
181
|
+
const slot = slotOf(o.profile, o.configDir);
|
|
171
182
|
const platform = o.platform || process.platform;
|
|
172
183
|
const runCmd = o.runCmd || defaultRunCmd;
|
|
173
184
|
const home = o.home || os.homedir();
|
|
174
185
|
if (platform === 'darwin') {
|
|
175
|
-
const label = labelOf(
|
|
186
|
+
const label = labelOf(slot);
|
|
176
187
|
const uid = String(o.uid != null ? o.uid : (process.getuid ? process.getuid() : 501));
|
|
177
188
|
await runCmd('launchctl', ['bootout', `gui/${uid}/${label}`]);
|
|
178
189
|
try { fs.unlinkSync(path.join(home, 'Library', 'LaunchAgents', `${label}.plist`)); } catch { /* none */ }
|
|
179
190
|
return;
|
|
180
191
|
}
|
|
181
192
|
if (platform === 'win32') {
|
|
182
|
-
await runCmd('schtasks', ['/End', '/TN', taskNameOf(
|
|
183
|
-
await runCmd('schtasks', ['/Delete', '/TN', taskNameOf(
|
|
193
|
+
await runCmd('schtasks', ['/End', '/TN', taskNameOf(slot)]);
|
|
194
|
+
await runCmd('schtasks', ['/Delete', '/TN', taskNameOf(slot), '/F']);
|
|
184
195
|
return;
|
|
185
196
|
}
|
|
186
|
-
const unit = path.join(home, '.config', 'systemd', 'user', unitNameOf(
|
|
197
|
+
const unit = path.join(home, '.config', 'systemd', 'user', unitNameOf(slot));
|
|
187
198
|
if (fs.existsSync(unit)) {
|
|
188
|
-
await runCmd('systemctl', ['--user', 'disable', '--now', unitNameOf(
|
|
199
|
+
await runCmd('systemctl', ['--user', 'disable', '--now', unitNameOf(slot)]);
|
|
189
200
|
try { fs.unlinkSync(unit); } catch { /* none */ }
|
|
190
201
|
await runCmd('systemctl', ['--user', 'daemon-reload']);
|
|
191
202
|
}
|
|
192
203
|
const ct = await runCmd('crontab', ['-l']);
|
|
193
204
|
if (!ct.missing && ct.code === 0) {
|
|
194
|
-
const mark = cronMarkOf(
|
|
205
|
+
const mark = cronMarkOf(slot);
|
|
195
206
|
if (ct.stdout.includes(mark)) {
|
|
196
207
|
const kept = ct.stdout.split('\n').filter((l) => l && !l.includes(mark));
|
|
197
208
|
await runCmd('crontab', ['-'], { input: kept.length ? `${kept.join('\n')}\n` : '' });
|