| @@ -340,11 +340,24 @@ export interface SystemdUnitOptions { · 339 unmodified lines |
| 340 | readonly stderrPath: string; | 340 | readonly stderrPath: string; |
| 341 | } | 341 | } |
| 342 | | 342 | |
| | 343 | const SYSTEMD_BARE_VALUE = /^[A-Za-z0-9_@%+=:,./-]+$/; |
| | 344 | |
| | 345 | const systemdQuote = (value: string): string => { |
| | 346 | if (SYSTEMD_BARE_VALUE.test(value)) return value; |
| | 347 | const escaped = value |
| | 348 | .replaceAll("\\", "\\\\") |
| | 349 | .replaceAll('"', '\\"') |
| | 350 | .replaceAll("\n", "\\n") |
| | 351 | .replaceAll("\r", "\\r") |
| | 352 | .replaceAll("\t", "\\t"); |
| | 353 | return `"${escaped}"`; |
| | 354 | }; |
| | 355 | |
| 343 | /** Render a systemd --user unit. Pure (snapshot-tested). */ | 356 | /** Render a systemd --user unit. Pure (snapshot-tested). */ |
| 344 | export const generateSystemdUnit = (options: SystemdUnitOptions): string => { | 357 | export const generateSystemdUnit = (options: SystemdUnitOptions): string => { |
| 345 | const execStart = options.execStart.map((arg) => (/\s/.test(arg) ? `"${arg}"` : arg)).join(" "); | 358 | const execStart = options.execStart.map(systemdQuote).join(" "); |
| 346 | const env = Object.entries(options.environment) | 359 | const env = Object.entries(options.environment) |
| 347 | .map(([key, value]) => `Environment=${key}=${value}`) | 360 | .map(([key, value]) => `Environment=${systemdQuote(`${key}=${value}`)}`) |
| 348 | .join("\n"); | 361 | .join("\n"); |
| 349 | return `[Unit] | 362 | return `[Unit] |
| 350 | Description=Executor supervised daemon | 363 | Description=Executor supervised daemon |
| @@ -354,9 +367,9 @@ After=default.target · 3 unmodified lines |
| 354 | Type=simple | 367 | Type=simple |
| 355 | ExecStart=${execStart} | 368 | ExecStart=${execStart} |
| 356 | ${env} | 369 | ${env} |
| 357 | WorkingDirectory=${options.workingDirectory} | 370 | WorkingDirectory=${systemdQuote(options.workingDirectory)} |
| 358 | StandardOutput=append:${options.stdoutPath} | 371 | StandardOutput=${systemdQuote(`append:${options.stdoutPath}`)} |
| 359 | StandardError=append:${options.stderrPath} | 372 | StandardError=${systemdQuote(`append:${options.stderrPath}`)} |
| 360 | Restart=on-failure | 373 | Restart=on-failure |
| 361 | RestartSec=5s | 374 | RestartSec=5s |
| 362 | | 375 | |