feat(systemd): support configurable service environment variables
This commit is contained in:
@@ -1,5 +1,15 @@
|
||||
# Changelog
|
||||
|
||||
## Pending
|
||||
|
||||
### Features
|
||||
|
||||
- support configurable service environment variables (systemd)
|
||||
- Add optional environment configuration to SmartDaemon service options.
|
||||
- Render validated and escaped systemd Environment entries from service definitions.
|
||||
- Remove the hard-coded NODE_OPTIONS memory setting from generated unit files.
|
||||
- Update development tooling dependencies.
|
||||
|
||||
## 2026-05-01 - 2.1.1 - fix(systemdmanager)
|
||||
migrate systemd file operations to smartfs and tighten TypeScript safety
|
||||
|
||||
|
||||
+4
-4
@@ -32,10 +32,10 @@
|
||||
},
|
||||
"homepage": "https://code.foss.global/push.rocks/smartdaemon",
|
||||
"devDependencies": {
|
||||
"@git.zone/tsbuild": "^4.4.0",
|
||||
"@git.zone/tsrun": "^2.0.3",
|
||||
"@git.zone/tstest": "^3.6.3",
|
||||
"@types/node": "^25.6.0"
|
||||
"@git.zone/tsbuild": "^4.4.2",
|
||||
"@git.zone/tsrun": "^2.0.4",
|
||||
"@git.zone/tstest": "^3.6.6",
|
||||
"@types/node": "^25.9.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@push.rocks/lik": "^6.4.1",
|
||||
|
||||
Generated
+1385
-2962
File diff suppressed because it is too large
Load Diff
@@ -54,6 +54,29 @@ tap.test('should generate systemd unit file with User/Group directives', async (
|
||||
expect(unitFileContent).toInclude('Group=www-data');
|
||||
expect(unitFileContent).toInclude('# user: www-data');
|
||||
expect(unitFileContent).toInclude('# group: www-data');
|
||||
expect(unitFileContent).not.toInclude('max_old_space_size=100');
|
||||
});
|
||||
|
||||
tap.test('should generate systemd unit file with configured environment', async () => {
|
||||
const testService = await smartdaemon.SmartDaemonService.createFromOptions(
|
||||
testSmartdaemon,
|
||||
{
|
||||
name: 'test-service-env',
|
||||
description: 'A test service with environment',
|
||||
command: 'node test.js',
|
||||
workingDir: '/tmp',
|
||||
version: '1.0.0',
|
||||
environment: {
|
||||
NODE_OPTIONS: '--max_old_space_size=1024',
|
||||
CUSTOM_VALUE: 'quoted "value"',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const unitFileContent = testSmartdaemon.templateManager.generateUnitFileForService(testService);
|
||||
|
||||
expect(unitFileContent).toInclude('Environment="NODE_OPTIONS=--max_old_space_size=1024"');
|
||||
expect(unitFileContent).toInclude('Environment="CUSTOM_VALUE=quoted \\"value\\""');
|
||||
});
|
||||
|
||||
tap.test('should handle services without user/group', async () => {
|
||||
|
||||
@@ -10,6 +10,7 @@ export interface ISmartDaemonServiceConstructorOptions {
|
||||
version: string;
|
||||
user?: string;
|
||||
group?: string;
|
||||
environment?: Record<string, string>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -36,6 +37,7 @@ export class SmartDaemonService implements ISmartDaemonServiceConstructorOptions
|
||||
public description!: string;
|
||||
public user?: string;
|
||||
public group?: string;
|
||||
public environment?: Record<string, string>;
|
||||
|
||||
public smartdaemonRef: SmartDaemon;
|
||||
|
||||
|
||||
@@ -8,7 +8,21 @@ export class SmartDaemonTemplateManager {
|
||||
this.smartdaemonRef = smartdaemonRefArg;
|
||||
}
|
||||
|
||||
private generateEnvironmentLines(serviceArg: SmartDaemonService): string {
|
||||
const environment = serviceArg.environment || {};
|
||||
return Object.entries(environment)
|
||||
.map(([key, value]) => {
|
||||
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
|
||||
throw new Error(`Invalid systemd environment key: ${key}`);
|
||||
}
|
||||
const escapedValue = value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
||||
return `Environment="${key}=${escapedValue}"`;
|
||||
})
|
||||
.join('\n');
|
||||
}
|
||||
|
||||
public generateUnitFileForService = (serviceArg: SmartDaemonService) => {
|
||||
const environmentLines = this.generateEnvironmentLines(serviceArg);
|
||||
return `# ---
|
||||
# name: ${serviceArg.name}
|
||||
# version: ${serviceArg.version}
|
||||
@@ -26,8 +40,8 @@ After=network.target
|
||||
[Service]
|
||||
Type=simple${serviceArg.user ? `
|
||||
User=${serviceArg.user}` : ''}${serviceArg.group ? `
|
||||
Group=${serviceArg.group}` : ''}
|
||||
Environment=NODE_OPTIONS="--max_old_space_size=100"
|
||||
Group=${serviceArg.group}` : ''}${environmentLines ? `
|
||||
${environmentLines}` : ''}
|
||||
ExecStart=/bin/bash -c "cd ${serviceArg.workingDir} && ${serviceArg.command}"
|
||||
WorkingDirectory=${serviceArg.workingDir}
|
||||
Restart=on-failure
|
||||
|
||||
Reference in New Issue
Block a user