arisa 5.1.8 → 5.1.9
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/runtime/slave-service.js +18 -4
- package/test/slave-cli.test.js +12 -0
package/package.json
CHANGED
|
@@ -65,7 +65,21 @@ export async function selectSlaveServiceAccount({
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
function quoteSystemd(value) {
|
|
68
|
-
return `"${String(value).replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
|
|
68
|
+
return `"${String(value).replaceAll("\\", "\\\\").replaceAll('"', '\\"').replaceAll("%", "%%")}"`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function escapeSystemdPath(value) {
|
|
72
|
+
const text = String(value);
|
|
73
|
+
if (text.includes("\0")) throw new Error("Systemd paths cannot contain NUL bytes");
|
|
74
|
+
return [...text].map((character) => {
|
|
75
|
+
if (character === "\\") return "\\\\";
|
|
76
|
+
if (character === "%") return "%%";
|
|
77
|
+
const codePoint = character.codePointAt(0);
|
|
78
|
+
if (codePoint <= 0x20 || codePoint === 0x7f || character === '"' || character === "'") {
|
|
79
|
+
return `\\x${codePoint.toString(16).padStart(2, "0")}`;
|
|
80
|
+
}
|
|
81
|
+
return character;
|
|
82
|
+
}).join("");
|
|
69
83
|
}
|
|
70
84
|
|
|
71
85
|
export function buildSlaveSystemdUnit({ account, slaveHome, entryFile, nodePath = process.execPath }) {
|
|
@@ -84,10 +98,10 @@ export function buildSlaveSystemdUnit({ account, slaveHome, entryFile, nodePath
|
|
|
84
98
|
userDirective.trimEnd(),
|
|
85
99
|
`Environment=${quoteSystemd(`ARISA_HOME=${paths.home}`)}`,
|
|
86
100
|
`Environment=${quoteSystemd(`ARISA_SLAVE_HOME=${paths.home}`)}`,
|
|
87
|
-
`WorkingDirectory=${
|
|
101
|
+
`WorkingDirectory=${escapeSystemdPath(paths.home)}`,
|
|
88
102
|
`ExecStart=${quoteSystemd(nodePath)} ${quoteSystemd(entryFile)} slave --service-runner`,
|
|
89
|
-
`StandardOutput=append:${paths.logFile}`,
|
|
90
|
-
`StandardError=append:${paths.logFile}`,
|
|
103
|
+
`StandardOutput=append:${escapeSystemdPath(paths.logFile)}`,
|
|
104
|
+
`StandardError=append:${escapeSystemdPath(paths.logFile)}`,
|
|
91
105
|
"Restart=on-failure",
|
|
92
106
|
"RestartSec=2",
|
|
93
107
|
"",
|
package/test/slave-cli.test.js
CHANGED
|
@@ -82,11 +82,23 @@ test("builds a dedicated headless systemd service with isolated state", () => {
|
|
|
82
82
|
});
|
|
83
83
|
assert.match(unit, /User=arisa-slave/);
|
|
84
84
|
assert.match(unit, /Environment="ARISA_HOME=\/var\/lib\/arisa-slave"/);
|
|
85
|
+
assert.match(unit, /^WorkingDirectory=\/var\/lib\/arisa-slave$/m);
|
|
85
86
|
assert.match(unit, /ExecStart="\/usr\/bin\/node" "\/opt\/arisa\/src\/index\.js" slave --service-runner/);
|
|
86
87
|
assert.match(unit, /StandardOutput=append:\/var\/lib\/arisa-slave\/state\/arisa-slave\.log/);
|
|
87
88
|
assert.doesNotMatch(unit, /Telegram|Pi Agent/);
|
|
88
89
|
});
|
|
89
90
|
|
|
91
|
+
test("escapes systemd WorkingDirectory paths without quoting the entire value", () => {
|
|
92
|
+
const unit = buildSlaveSystemdUnit({
|
|
93
|
+
account: { scope: "user", user: "tester" },
|
|
94
|
+
slaveHome: "/srv/arisa slave",
|
|
95
|
+
entryFile: "/opt/arisa/src/index.js",
|
|
96
|
+
nodePath: "/usr/bin/node"
|
|
97
|
+
});
|
|
98
|
+
assert.match(unit, /^WorkingDirectory=\/srv\/arisa\\x20slave$/m);
|
|
99
|
+
assert.match(unit, /^StandardOutput=append:\/srv\/arisa\\x20slave\/state\/arisa-slave\.log$/m);
|
|
100
|
+
});
|
|
101
|
+
|
|
90
102
|
test("refuses to replace the PID of an active Slave host", async (t) => {
|
|
91
103
|
const home = await mkdtemp(path.join(os.tmpdir(), "arisa-slave-pid-"));
|
|
92
104
|
t.after(() => rm(home, { recursive: true, force: true }));
|