feat: add Generate Systemd Unit operation and tests

This commit is contained in:
Niall 2026-04-01 08:15:58 +00:00
parent 80286f1e6f
commit de0ad1d4bf
4 changed files with 228 additions and 0 deletions

View File

@ -329,6 +329,7 @@
"Find / Replace",
"Regular expression",
"Fuzzy Match",
"Generate Systemd Unit",
"Offset checker",
"Hamming Distance",
"Levenshtein Distance",

View File

@ -0,0 +1,126 @@
/**
* @author 0xff1ce [github.com/0xff1ce]
* @copyright Crown Copyright 2026
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
const RESTART_OPTIONS = ["no", "on-sucess", "on-failure", "on-abnormal", "on-watchdog", "on-abort", "always"];
const TYPE_OPTIONS = ["simple", "exec", "forking", "oneshot", "dbus", "notify", "idle"];
/**
* GenerateSystemdUnit operation
*/
class GenerateSystemdUnit extends Operation {
/**
* GenerateSystemdUnit constructor
*/
constructor() {
super();
this.name = "Generate Systemd Unit";
this.module = "Default";
this.description = "Generates a systemd unit file based on provided inputs";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
"name": "Description",
"type": "string",
"value": "",
},
{
"name": "After",
"type": "string",
"value": "network.target",
},
{
"name": "Wants",
"type": "string",
"value": "network-online.target",
},
{
"name": "Restart",
"type": "option",
"value": RESTART_OPTIONS,
},
{
"name": "Type",
"type": "option",
"value": TYPE_OPTIONS,
},
{
"name": "ExecStart",
"type": "string",
"value": "",
},
{
"name": "Environment",
"type": "string",
"value": "KEY=VALUE",
},
{
"name": "User",
"type": "string",
"value": "",
},
{
"name": "WorkingDirectory",
"type": "string",
"value": "",
},
{
"name": "WantedBy",
"type": "string",
"value": "multi-user.target",
},
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(_, args) {
const description = args[0];
const after = args[1];
const wants = args[2];
const restart = args[3];
const type = args[4];
const execStart = args[5];
const environment = args[6];
const user = args[7];
const workingDirectory = args[8];
const wantedBy = args[9];
const lines = [];
// [Unit] section
lines.push("[Unit]");
if (description) lines.push(`Description=${description}`);
if (after) lines.push(`After=${after}`);
if (wants) lines.push(`Wants=${wants}`);
// [Service] section
lines.push("");
lines.push("[Service]");
if (restart) lines.push(`Restart=${restart}`);
if (type) lines.push(`Type=${type}`);
if (execStart) lines.push(`ExecStart=${execStart}`);
if (environment) lines.push(`Environment=${environment}`);
if (user) lines.push(`User=${user}`);
if (workingDirectory) lines.push(`WorkingDirectory=${workingDirectory}`);
// [Install] section
lines.push("");
lines.push("[Install]");
if (wantedBy) lines.push(`WantedBy=${wantedBy}`);
return lines.join("\n");
}
}
export default GenerateSystemdUnit;

View File

@ -80,6 +80,7 @@ import "./tests/GenerateAllChecksums.mjs";
import "./tests/GenerateAllHashes.mjs";
import "./tests/GenerateDeBruijnSequence.mjs";
import "./tests/GenerateQRCode.mjs";
import "./tests/GenerateSystemdUnit.mjs";
import "./tests/GetAllCasings.mjs";
import "./tests/GOST.mjs";
import "./tests/Gunzip.mjs";

View File

@ -0,0 +1,100 @@
/**
* GenerateSystemdUnit tests.
*
* @author 0xff1ce
* @copyright Crown Copyright 2026
* @license Apache-2.0
*/
import TestRegister from "../../lib/TestRegister.mjs";
TestRegister.addTests([
{
name: "Generate minimal systemd unit with required defaults",
input: "",
expectedMatch: /\[Unit\]\nAfter=network\.target\nWants=network-online\.target\n\n\[Service\]\nEnvironment=KEY=VALUE\n\n\[Install\]\nWantedBy=multi-user\.target/,
recipeConfig: [
{
op: "Generate Systemd Unit",
args: [
"", // Description
"network.target", // After
"network-online.target", // Wants
"", // Restart
"", // Type
"", // ExecStart
"KEY=VALUE", // Environment
"", // User
"", // WorkingDirectory
"multi-user.target" // WantedBy
]
},
],
},
{
name: "Generate full systemd unit with all fields populated",
input: "",
expectedMatch: /\[Unit\]\nDescription=My Service\nAfter=network\.target\nWants=network-online\.target\n\n\[Service\]\nRestart=always\nType=simple\nExecStart=\/usr\/bin\/node app\.js\nEnvironment=NODE_ENV=production\nUser=www-data\nWorkingDirectory=\/var\/www\n\n\[Install\]\nWantedBy=multi-user\.target/,
recipeConfig: [
{
op: "Generate Systemd Unit",
args: [
"My Service",
"network.target",
"network-online.target",
"always",
"simple",
"/usr/bin/node app.js",
"NODE_ENV=production",
"www-data",
"/var/www",
"multi-user.target"
]
},
],
},
{
name: "Generate unit without optional Service fields",
input: "",
expectedMatch: /\[Unit\]\nDescription=Test\nAfter=network\.target\nWants=network-online\.target\n\n\[Service\]\nEnvironment=KEY=VALUE\n\n\[Install\]\nWantedBy=multi-user\.target/,
recipeConfig: [
{
op: "Generate Systemd Unit",
args: [
"Test",
"network.target",
"network-online.target",
"", // Restart
"", // Type
"", // ExecStart
"KEY=VALUE",
"", // User
"", // WorkingDirectory
"multi-user.target"
]
},
],
},
{
name: "Generate unit with custom dependencies and install target",
input: "",
expectedMatch: /\[Unit\]\nDescription=Custom\nAfter=docker\.service\nWants=network\.target\n\n\[Service\]\nRestart=on-failure\nType=exec\nExecStart=\/bin\/bash start\.sh\nEnvironment=ENV=dev\n\n\[Install\]\nWantedBy=graphical\.target/,
recipeConfig: [
{
op: "Generate Systemd Unit",
args: [
"Custom",
"docker.service",
"network.target",
"on-failure",
"exec",
"/bin/bash start.sh",
"ENV=dev",
"", // User
"", // WorkingDirectory
"graphical.target"
]
},
],
}
]);