unitup 0.0.9 → 0.0.11

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/src/systemd.js CHANGED
@@ -264,6 +264,8 @@ export async function checkUserLinger() {
264
264
  // Systemd Operations
265
265
  // ---------------------------------------------------------------------------
266
266
 
267
+ import { resolveRuntimeConfig } from './runtimes/index.js';
268
+
267
269
  export async function daemonReload() {
268
270
  const res = await runCommand('systemctl', ['--user', 'daemon-reload']);
269
271
  if (res.code !== 0) {
@@ -278,7 +280,36 @@ export async function resetFailed() {
278
280
 
279
281
  export async function addService(opts) {
280
282
  const safeName = sanitizeServiceName(opts.name);
281
- const { path: unitPath } = writeUnitFile({ ...opts, name: safeName });
283
+
284
+ if (unitFileExists(safeName)) {
285
+ try {
286
+ const show = await getServiceShow(safeName);
287
+ if (show.ActiveState === 'active') {
288
+ if (!opts.force) {
289
+ throw new Error(
290
+ `Service "${safeName}" is currently running.\n` +
291
+ `Use --force (-f) to overwrite running services, or stop it first:\n` +
292
+ ` unitup stop ${safeName}`
293
+ );
294
+ }
295
+ }
296
+ } catch (err) {
297
+ if (err.message.includes('currently running')) {
298
+ throw err;
299
+ }
300
+ }
301
+ }
302
+
303
+ const runtimeConfig = await resolveRuntimeConfig({ ...opts, name: safeName });
304
+
305
+ const { path: unitPath } = writeUnitFile({
306
+ ...opts,
307
+ name: safeName,
308
+ runtime: runtimeConfig.runtime,
309
+ command: runtimeConfig.command,
310
+ args: runtimeConfig.args,
311
+ cwd: opts.cwd
312
+ });
282
313
 
283
314
  await daemonReload();
284
315
 
@@ -365,14 +396,16 @@ export async function restartService(name) {
365
396
  return true;
366
397
  }
367
398
 
368
- export async function removeService(name) {
399
+ export async function removeService(name, opts = {}) {
400
+ const force = typeof opts === 'boolean' ? opts : !!opts?.force;
401
+
369
402
  if (name && typeof name === 'string' && name.startsWith('@')) {
370
403
  const list = await getServicesByGroup(name);
371
404
  if (list.length === 0) {
372
405
  throw new Error(`No services found in group "${name}".`);
373
406
  }
374
407
  for (const s of list) {
375
- await removeService(s);
408
+ await removeService(s, { force });
376
409
  }
377
410
  return true;
378
411
  }
@@ -382,6 +415,23 @@ export async function removeService(name) {
382
415
  throw new Error(`Service "${safeName}" does not exist.`);
383
416
  }
384
417
 
418
+ try {
419
+ const show = await getServiceShow(safeName);
420
+ if (show.ActiveState === 'active') {
421
+ if (!force) {
422
+ throw new Error(
423
+ `Service "${safeName}" is currently running.\n` +
424
+ `Use --force (-f) to remove running services, or stop it first:\n` +
425
+ ` unitup stop ${safeName}`
426
+ );
427
+ }
428
+ }
429
+ } catch (err) {
430
+ if (err.message.includes('currently running')) {
431
+ throw err;
432
+ }
433
+ }
434
+
385
435
  // Attempt disable and stop
386
436
  await runCommand('systemctl', ['--user', 'disable', '--now', getUnitFilename(safeName)]);
387
437
 
@@ -431,11 +481,12 @@ export async function getServiceStatus(name) {
431
481
  // Read unit file content for script and working dir
432
482
  let script = 'unknown';
433
483
  let cwd = 'unknown';
484
+ let parsed = {};
434
485
 
435
486
  const unitPath = getUnitPath(safeName);
436
487
  try {
437
488
  const content = fs.readFileSync(unitPath, 'utf8');
438
- const parsed = parseUnitContent(content);
489
+ parsed = parseUnitContent(content);
439
490
  if (parsed.script) script = parsed.script;
440
491
  if (parsed.cwd) cwd = parsed.cwd;
441
492
  } catch {
@@ -465,6 +516,17 @@ export async function getServiceStatus(name) {
465
516
  ? formatRelativeTime(startedRaw)
466
517
  : 'never';
467
518
 
519
+ const meta = readAppMetadata(safeName);
520
+ const command = meta?.command || parsed.command || meta?.node || process.execPath;
521
+ const argsList = meta?.args || (parsed.script ? [parsed.script] : []);
522
+
523
+ const { formatMemoryBytes } = await import('./utils.js');
524
+ const memoryCurrent = formatMemoryBytes(show.MemoryCurrent);
525
+ const memoryPeak = formatMemoryBytes(show.MemoryPeak);
526
+ const memoryHigh = formatMemoryBytes(show.MemoryHigh || meta?.resources?.memoryHigh || parsed.memoryHigh);
527
+ const memoryMax = formatMemoryBytes(show.MemoryMax || meta?.resources?.memoryMax || parsed.memoryMax);
528
+ const memorySwapMax = formatMemoryBytes(show.MemorySwapMax || meta?.resources?.memorySwapMax || parsed.memorySwapMax);
529
+
468
530
  return {
469
531
  name: safeName,
470
532
  unitFile: getUnitFilename(safeName),
@@ -475,8 +537,16 @@ export async function getServiceStatus(name) {
475
537
  restarts,
476
538
  started,
477
539
  startedRaw,
540
+ command,
541
+ arguments: argsList.join(' '),
542
+ args: argsList,
478
543
  script,
479
- cwd
544
+ cwd,
545
+ memory: memoryCurrent,
546
+ memoryPeak,
547
+ memoryHigh,
548
+ memoryMax,
549
+ memorySwapMax
480
550
  };
481
551
  }
482
552
 
@@ -504,11 +574,29 @@ export async function listServices(filterOpts = {}) {
504
574
  for (const unit of units) {
505
575
  const meta = readAppMetadata(unit.name);
506
576
  const group = meta?.group || 'default';
577
+ const runtime = meta?.runtime || 'node';
507
578
 
508
579
  if (targetGroup && group.toLowerCase() !== targetGroup) {
509
580
  continue;
510
581
  }
511
582
 
583
+ let commandSummary = '';
584
+ if (meta?.command) {
585
+ if (runtime === 'native') {
586
+ commandSummary = './' + path.basename(meta.command);
587
+ } else {
588
+ const cmdBase = path.basename(meta.command);
589
+ const argsStr = (meta.args || [])
590
+ .map(a => (a.startsWith('/') || a.startsWith('./') ? path.basename(a) : a))
591
+ .join(' ');
592
+ commandSummary = (cmdBase + (argsStr ? ' ' + argsStr : '')).trim();
593
+ }
594
+ } else if (meta?.script) {
595
+ commandSummary = `node ${path.basename(meta.script)}`;
596
+ } else {
597
+ commandSummary = 'unknown';
598
+ }
599
+
512
600
  try {
513
601
  const show = await getServiceShow(unit.name);
514
602
  const activeState = show.ActiveState || 'inactive';
@@ -536,20 +624,24 @@ export async function listServices(filterOpts = {}) {
536
624
 
537
625
  result.push({
538
626
  name: unit.name,
627
+ runtime,
539
628
  group,
540
629
  status,
541
630
  enabled,
542
631
  pid,
632
+ command: commandSummary,
543
633
  uptime,
544
634
  restarts
545
635
  });
546
636
  } catch {
547
637
  result.push({
548
638
  name: unit.name,
639
+ runtime,
549
640
  group,
550
641
  status: 'unknown',
551
642
  enabled: 'unknown',
552
643
  pid: '-',
644
+ command: commandSummary,
553
645
  uptime: 'never',
554
646
  restarts: '0'
555
647
  });
@@ -568,20 +660,33 @@ export async function inspectService(name) {
568
660
  const meta = readAppMetadata(safeName);
569
661
  const statusObj = await getServiceStatus(safeName);
570
662
 
663
+ const command = meta?.command || statusObj.command || statusObj.node || process.execPath;
664
+ const argsList = meta?.args || (statusObj.script ? [statusObj.script] : []);
665
+
571
666
  return {
572
667
  name: safeName,
573
- unit: getUnitFilename(safeName),
574
- unitPath: getUnitPath(safeName),
575
- group: meta?.group || 'default',
576
- script: meta?.script || statusObj.script,
577
- cwd: meta?.cwd || statusObj.cwd,
578
- node: meta?.node || process.execPath,
668
+ runtime: meta?.runtime || 'node',
579
669
  status: statusObj.status,
580
670
  activeState: statusObj.activeState,
581
671
  subState: statusObj.subState,
672
+ command,
673
+ arguments: argsList.join(' '),
674
+ args: argsList,
675
+ cwd: meta?.cwd || statusObj.cwd,
676
+ unit: getUnitFilename(safeName),
677
+ unitPath: getUnitPath(safeName),
678
+ group: meta?.group || 'default',
582
679
  pid: statusObj.pid,
583
680
  restarts: statusObj.restarts,
584
- started: statusObj.started
681
+ started: statusObj.started,
682
+ memory: statusObj.memory,
683
+ memoryPeak: statusObj.memoryPeak,
684
+ memoryHigh: statusObj.memoryHigh,
685
+ memoryMax: statusObj.memoryMax,
686
+ memorySwapMax: statusObj.memorySwapMax,
687
+ // Legacy fields for backward compatibility
688
+ script: meta?.script || statusObj.script,
689
+ node: meta?.node || command
585
690
  };
586
691
  }
587
692
 
@@ -610,6 +715,15 @@ export async function getServiceFailures() {
610
715
  export async function runJournalctlLogs(name, opts = {}) {
611
716
  const safeName = sanitizeServiceName(name);
612
717
  const unitFilename = getUnitFilename(safeName);
718
+
719
+ if (opts.diskUsage) {
720
+ const res = await runCommand('journalctl', ['--disk-usage']);
721
+ if (res.code !== 0 && (res.stderr.toLowerCase().includes('permission') || res.stderr.toLowerCase().includes('root'))) {
722
+ throw new Error('Journal maintenance requires additional privileges on this system.\nRun the command manually with the appropriate permissions.');
723
+ }
724
+ return res.stdout || res.stderr;
725
+ }
726
+
613
727
  const syslogId = `unitup-${safeName}`;
614
728
 
615
729
  const candidateArgSets = [
@@ -640,19 +754,27 @@ export async function runJournalctlLogs(name, opts = {}) {
640
754
  }
641
755
 
642
756
  const workingCandidate = chosenCandidate || ['--user', '-u', unitFilename];
757
+ const filterArgs = [...workingCandidate];
758
+
759
+ if (opts.since) filterArgs.push(`--since=${opts.since}`);
760
+ if (opts.until) filterArgs.push(`--until=${opts.until}`);
761
+ if (opts.priority) filterArgs.push(`--priority=${opts.priority}`);
762
+ if (opts.grep) filterArgs.push(`--grep=${opts.grep}`);
763
+ if (opts.boot) filterArgs.push('--boot');
764
+
765
+ if (opts.json) {
766
+ filterArgs.push('-o', 'json');
767
+ } else if (opts.cat || opts.output === 'cat') {
768
+ filterArgs.push('-o', 'cat');
769
+ } else if (opts.output) {
770
+ filterArgs.push('-o', opts.output);
771
+ }
643
772
 
644
773
  if (opts.follow) {
645
- const followArgs = [...workingCandidate, '-f'];
646
- if (opts.lines) {
647
- followArgs.push('-n', String(opts.lines));
648
- }
649
- if (opts.cat || opts.output === 'cat') {
650
- followArgs.push('-o', 'cat');
651
- } else if (opts.output) {
652
- followArgs.push('-o', opts.output);
653
- }
774
+ filterArgs.push('-f');
775
+ if (opts.lines) filterArgs.push('-n', String(opts.lines));
654
776
  try {
655
- const child = spawn('journalctl', followArgs, { stdio: 'inherit', shell: false });
777
+ const child = spawn('journalctl', filterArgs, { stdio: 'inherit', shell: false });
656
778
  child.on('error', () => {});
657
779
  return child;
658
780
  } catch {
@@ -660,15 +782,125 @@ export async function runJournalctlLogs(name, opts = {}) {
660
782
  }
661
783
  }
662
784
 
663
- const staticArgs = [...workingCandidate, '--no-pager'];
664
- if (opts.cat || opts.output === 'cat') {
665
- staticArgs.push('-o', 'cat');
666
- } else if (opts.output) {
667
- staticArgs.push('-o', opts.output);
668
- }
785
+ filterArgs.push('--no-pager');
669
786
  const lines = opts.lines ? String(opts.lines) : '100';
670
- staticArgs.push('-n', lines);
787
+ filterArgs.push('-n', lines);
671
788
 
672
- const res = await runCommand('journalctl', staticArgs);
789
+ const res = await runCommand('journalctl', filterArgs);
673
790
  return res.stdout || res.stderr || 'No logs found.';
674
791
  }
792
+
793
+ export async function setServiceLimits(name, options = {}) {
794
+ const safeName = sanitizeServiceName(name);
795
+ if (!unitFileExists(safeName)) {
796
+ throw new Error(`Service "${safeName}" does not exist.`);
797
+ }
798
+
799
+ const meta = readAppMetadata(safeName) || {};
800
+ const { validateMemorySize } = await import('./utils.js');
801
+
802
+ let memoryHigh = options.memoryHigh;
803
+ let memoryMax = options.memoryMax;
804
+ let memorySwapMax = options.memorySwapMax;
805
+
806
+ const resources = { ...(meta.resources || {}) };
807
+
808
+ if (options.resetMemory) {
809
+ delete resources.memoryHigh;
810
+ delete resources.memoryMax;
811
+ delete resources.memorySwapMax;
812
+ memoryHigh = undefined;
813
+ memoryMax = undefined;
814
+ memorySwapMax = undefined;
815
+ } else {
816
+ if (memoryHigh !== undefined) {
817
+ memoryHigh = validateMemorySize(memoryHigh, 'MemoryHigh');
818
+ resources.memoryHigh = memoryHigh;
819
+ }
820
+ if (memoryMax !== undefined) {
821
+ memoryMax = validateMemorySize(memoryMax, 'MemoryMax');
822
+ resources.memoryMax = memoryMax;
823
+ }
824
+ if (memorySwapMax !== undefined) {
825
+ memorySwapMax = validateMemorySize(memorySwapMax, 'MemorySwapMax');
826
+ resources.memorySwapMax = memorySwapMax;
827
+ }
828
+ }
829
+
830
+ const updatedMeta = {
831
+ ...meta,
832
+ name: safeName,
833
+ resources: Object.keys(resources).length > 0 ? resources : undefined,
834
+ memoryHigh: resources.memoryHigh,
835
+ memoryMax: resources.memoryMax,
836
+ memorySwapMax: resources.memorySwapMax
837
+ };
838
+
839
+ writeUnitFile(updatedMeta);
840
+
841
+ await runCommand('systemctl', ['--user', 'daemon-reload']);
842
+
843
+ const status = await getServiceStatus(safeName);
844
+ if (status.status === 'running') {
845
+ const setPropsArgs = ['--user', 'set-property', getUnitFilename(safeName)];
846
+ if (options.resetMemory) {
847
+ setPropsArgs.push('MemoryHigh=infinity', 'MemoryMax=infinity', 'MemorySwapMax=infinity');
848
+ } else {
849
+ if (resources.memoryHigh) setPropsArgs.push(`MemoryHigh=${resources.memoryHigh}`);
850
+ if (resources.memoryMax) setPropsArgs.push(`MemoryMax=${resources.memoryMax}`);
851
+ if (resources.memorySwapMax) setPropsArgs.push(`MemorySwapMax=${resources.memorySwapMax}`);
852
+ }
853
+
854
+ if (setPropsArgs.length > 3) {
855
+ try {
856
+ await runCommand('systemctl', setPropsArgs);
857
+ } catch {
858
+ // Graceful fallback if runtime property setting fails
859
+ }
860
+ }
861
+ }
862
+
863
+ return await inspectService(safeName);
864
+ }
865
+
866
+ export async function executeJournalctlMaintenance(action, opts = {}) {
867
+ const args = [];
868
+ if (action === 'disk-usage') {
869
+ args.push('--disk-usage');
870
+ } else if (action === 'rotate') {
871
+ args.push('--rotate');
872
+ } else if (action === 'vacuum') {
873
+ const { validateMemorySize } = await import('./utils.js');
874
+ if (opts.size) {
875
+ const validSize = validateMemorySize(opts.size, 'Vacuum size');
876
+ args.push(`--vacuum-size=${validSize}`);
877
+ } else if (opts.time) {
878
+ const timeStr = String(opts.time).trim();
879
+ if (/[;&|$`"'\n\r\t ]/.test(timeStr)) {
880
+ throw new Error(`Vacuum time contains invalid characters or shell injection: "${timeStr}".`);
881
+ }
882
+ args.push(`--vacuum-time=${timeStr}`);
883
+ } else if (opts.files) {
884
+ const filesNum = parseInt(opts.files, 10);
885
+ if (Number.isNaN(filesNum) || filesNum <= 0) {
886
+ throw new Error(`Invalid vacuum files count: "${opts.files}". Must be a positive integer.`);
887
+ }
888
+ args.push(`--vacuum-files=${filesNum}`);
889
+ } else {
890
+ throw new Error('Vacuum action requires one of --size, --time, or --files.');
891
+ }
892
+ } else {
893
+ throw new Error(`Unknown journal maintenance action: "${action}".`);
894
+ }
895
+
896
+ if (opts.dryRun) {
897
+ return `[dry-run] Would execute: journalctl ${args.join(' ')}`;
898
+ }
899
+
900
+ const res = await runCommand('journalctl', args);
901
+ if (res.code !== 0 && (res.stderr.toLowerCase().includes('permission') || res.stderr.toLowerCase().includes('root') || res.stderr.toLowerCase().includes('access denied'))) {
902
+ throw new Error('Journal maintenance requires additional privileges on this system.\nRun the command manually with the appropriate permissions.');
903
+ }
904
+
905
+ return (res.stdout || res.stderr || 'Journal maintenance completed successfully.').trim();
906
+ }
package/src/unit.js CHANGED
@@ -52,31 +52,41 @@ export function unitFileExists(name) {
52
52
  *
53
53
  * @param {Object} opts
54
54
  * @param {string} opts.name - Service name
55
- * @param {string} opts.script - Absolute path to script
55
+ * @param {string} [opts.command] - Absolute path to binary executable
56
+ * @param {Array<string>} [opts.args] - Command arguments
57
+ * @param {string} [opts.script] - Absolute path to script (legacy support)
56
58
  * @param {string} [opts.cwd] - Working directory path
57
- * @param {string} [opts.nodePath] - Absolute path to Node executable
59
+ * @param {string} [opts.nodePath] - Absolute path to Node executable (legacy support)
58
60
  * @param {Record<string, string>} [opts.env] - Environment variables object
59
61
  * @param {string} [opts.envFile] - Path to environment file
60
62
  * @param {string} [opts.restart] - Restart policy (default: 'on-failure')
61
- * @param {Array<string>} [opts.args] - Extra script arguments
62
63
  * @returns {string}
63
64
  */
64
65
  export function generateUnitContent(opts) {
65
66
  const safeName = sanitizeServiceName(opts.name);
66
- const scriptPath = resolveAbsolutePath(opts.script);
67
- const cwd = opts.cwd ? resolveAbsolutePath(opts.cwd) : path.dirname(scriptPath);
68
- const nodeExec = opts.nodePath ? resolveAbsolutePath(opts.nodePath) : process.execPath;
69
- const restartPolicy = opts.restart || 'on-failure';
67
+ let commandExec = '';
68
+ let execArgs = [];
70
69
 
71
- const execArgs = [nodeExec, scriptPath];
72
- if (Array.isArray(opts.args) && opts.args.length > 0) {
73
- execArgs.push(...opts.args);
70
+ if (opts.command) {
71
+ commandExec = resolveAbsolutePath(opts.command);
72
+ execArgs = Array.isArray(opts.args) ? [...opts.args] : [];
73
+ } else if (opts.script) {
74
+ const scriptPath = resolveAbsolutePath(opts.script);
75
+ commandExec = opts.nodePath ? resolveAbsolutePath(opts.nodePath) : process.execPath;
76
+ execArgs = [scriptPath, ...(Array.isArray(opts.args) ? opts.args : [])];
77
+ } else {
78
+ throw new Error('Either "command" or "script" must be provided to generate systemd unit file.');
74
79
  }
75
80
 
76
- const execStartLine = execArgs.map(escapeExecArg).join(' ');
81
+ const scriptOrCmdPath = opts.script ? resolveAbsolutePath(opts.script) : (execArgs[0] && (execArgs[0].startsWith('/') || execArgs[0].startsWith('./')) ? resolveAbsolutePath(execArgs[0]) : commandExec);
82
+ const cwd = opts.cwd ? resolveAbsolutePath(opts.cwd) : path.dirname(scriptOrCmdPath || commandExec);
83
+ const restartPolicy = opts.restart || 'on-failure';
84
+
85
+ const execStartTokens = [commandExec, ...execArgs];
86
+ const execStartLine = execStartTokens.map(escapeExecArg).join(' ');
77
87
 
78
- const nodeBinDir = path.dirname(nodeExec);
79
- const defaultPath = [nodeBinDir, '/usr/local/bin', '/usr/bin', '/bin']
88
+ const binDir = path.dirname(commandExec);
89
+ const defaultPath = [binDir, '/usr/local/bin', '/usr/bin', '/bin']
80
90
  .filter((p, i, self) => p && self.indexOf(p) === i)
81
91
  .join(':');
82
92
 
@@ -96,6 +106,17 @@ export function generateUnitContent(opts) {
96
106
  'StandardError=journal'
97
107
  ];
98
108
 
109
+ const memHigh = opts.memoryHigh || opts.resources?.memoryHigh;
110
+ const memMax = opts.memoryMax || opts.resources?.memoryMax;
111
+ const memSwapMax = opts.memorySwapMax || opts.resources?.memorySwapMax;
112
+
113
+ if (memHigh || memMax || memSwapMax) {
114
+ serviceLines.push('MemoryAccounting=yes');
115
+ if (memHigh) serviceLines.push(`MemoryHigh=${memHigh}`);
116
+ if (memMax) serviceLines.push(`MemoryMax=${memMax}`);
117
+ if (memSwapMax) serviceLines.push(`MemorySwapMax=${memSwapMax}`);
118
+ }
119
+
99
120
  const envObj = opts.env && typeof opts.env === 'object' ? { ...opts.env } : {};
100
121
  if (!envObj.PATH) {
101
122
  envObj.PATH = defaultPath;
@@ -136,9 +157,16 @@ export function writeUnitFile(opts) {
136
157
  saveAppMetadata({
137
158
  name: safeName,
138
159
  group: opts.group || 'default',
160
+ runtime: opts.runtime || 'node',
161
+ command: opts.command,
162
+ args: opts.args,
139
163
  script: opts.script,
140
164
  cwd: opts.cwd,
141
- node: opts.nodePath
165
+ node: opts.nodePath,
166
+ memoryHigh: opts.memoryHigh || opts.resources?.memoryHigh,
167
+ memoryMax: opts.memoryMax || opts.resources?.memoryMax,
168
+ memorySwapMax: opts.memorySwapMax || opts.resources?.memorySwapMax,
169
+ resources: opts.resources
142
170
  });
143
171
 
144
172
  return { path: unitPath, content };
@@ -190,10 +218,10 @@ export function listUnitFiles() {
190
218
  }
191
219
 
192
220
  /**
193
- * Parses basic information (Script, WorkingDirectory) from a unit file content.
221
+ * Parses basic information (Command, Arguments, Script, WorkingDirectory) from a unit file content.
194
222
  *
195
223
  * @param {string} content
196
- * @returns {{ script?: string, cwd?: string, restart?: string }}
224
+ * @returns {{ command?: string, args?: string[], script?: string, cwd?: string, restart?: string }}
197
225
  */
198
226
  export function parseUnitContent(content) {
199
227
  const result = {};
@@ -206,15 +234,25 @@ export function parseUnitContent(content) {
206
234
  result.cwd = trimmed.slice(17).trim();
207
235
  } else if (trimmed.startsWith('ExecStart=')) {
208
236
  const execVal = trimmed.slice(10).trim();
209
- // Extract script from ExecStart (usually second token if node executable is first)
210
237
  const parts = execVal.match(/(?:[^\s"]+|"[^"]*")+/g) || [];
211
- if (parts.length >= 2) {
212
- result.script = parts[1].replace(/^"|"$/g, '');
213
- } else if (parts.length === 1) {
214
- result.script = parts[0].replace(/^"|"$/g, '');
238
+ const cleanParts = parts.map(p => p.replace(/^"|"$/g, ''));
239
+ if (cleanParts.length > 0) {
240
+ result.command = cleanParts[0];
241
+ result.args = cleanParts.slice(1);
242
+ if (cleanParts.length >= 2) {
243
+ result.script = cleanParts[1];
244
+ } else {
245
+ result.script = cleanParts[0];
246
+ }
215
247
  }
216
248
  } else if (trimmed.startsWith('Restart=')) {
217
249
  result.restart = trimmed.slice(8).trim();
250
+ } else if (trimmed.startsWith('MemoryHigh=')) {
251
+ result.memoryHigh = trimmed.slice(11).trim();
252
+ } else if (trimmed.startsWith('MemoryMax=')) {
253
+ result.memoryMax = trimmed.slice(10).trim();
254
+ } else if (trimmed.startsWith('MemorySwapMax=')) {
255
+ result.memorySwapMax = trimmed.slice(14).trim();
218
256
  }
219
257
  }
220
258