feat(systemd): support configurable service environment variables
This commit is contained in:
@@ -1,5 +1,15 @@
|
|||||||
# Changelog
|
# 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)
|
## 2026-05-01 - 2.1.1 - fix(systemdmanager)
|
||||||
migrate systemd file operations to smartfs and tighten TypeScript safety
|
migrate systemd file operations to smartfs and tighten TypeScript safety
|
||||||
|
|
||||||
|
|||||||
+4
-4
@@ -32,10 +32,10 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://code.foss.global/push.rocks/smartdaemon",
|
"homepage": "https://code.foss.global/push.rocks/smartdaemon",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@git.zone/tsbuild": "^4.4.0",
|
"@git.zone/tsbuild": "^4.4.2",
|
||||||
"@git.zone/tsrun": "^2.0.3",
|
"@git.zone/tsrun": "^2.0.4",
|
||||||
"@git.zone/tstest": "^3.6.3",
|
"@git.zone/tstest": "^3.6.6",
|
||||||
"@types/node": "^25.6.0"
|
"@types/node": "^25.9.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@push.rocks/lik": "^6.4.1",
|
"@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('Group=www-data');
|
||||||
expect(unitFileContent).toInclude('# user: www-data');
|
expect(unitFileContent).toInclude('# user: www-data');
|
||||||
expect(unitFileContent).toInclude('# group: 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 () => {
|
tap.test('should handle services without user/group', async () => {
|
||||||
@@ -82,4 +105,4 @@ tap.test('should create smartdaemon with sudo password option', async () => {
|
|||||||
expect(daemonWithPassword.systemdManager.sudoPassword).toEqual('test-password');
|
expect(daemonWithPassword.systemdManager.sudoPassword).toEqual('test-password');
|
||||||
});
|
});
|
||||||
|
|
||||||
export default tap.start();
|
export default tap.start();
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export interface ISmartDaemonServiceConstructorOptions {
|
|||||||
version: string;
|
version: string;
|
||||||
user?: string;
|
user?: string;
|
||||||
group?: string;
|
group?: string;
|
||||||
|
environment?: Record<string, string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -36,6 +37,7 @@ export class SmartDaemonService implements ISmartDaemonServiceConstructorOptions
|
|||||||
public description!: string;
|
public description!: string;
|
||||||
public user?: string;
|
public user?: string;
|
||||||
public group?: string;
|
public group?: string;
|
||||||
|
public environment?: Record<string, string>;
|
||||||
|
|
||||||
public smartdaemonRef: SmartDaemon;
|
public smartdaemonRef: SmartDaemon;
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,21 @@ export class SmartDaemonTemplateManager {
|
|||||||
this.smartdaemonRef = smartdaemonRefArg;
|
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) => {
|
public generateUnitFileForService = (serviceArg: SmartDaemonService) => {
|
||||||
|
const environmentLines = this.generateEnvironmentLines(serviceArg);
|
||||||
return `# ---
|
return `# ---
|
||||||
# name: ${serviceArg.name}
|
# name: ${serviceArg.name}
|
||||||
# version: ${serviceArg.version}
|
# version: ${serviceArg.version}
|
||||||
@@ -26,8 +40,8 @@ After=network.target
|
|||||||
[Service]
|
[Service]
|
||||||
Type=simple${serviceArg.user ? `
|
Type=simple${serviceArg.user ? `
|
||||||
User=${serviceArg.user}` : ''}${serviceArg.group ? `
|
User=${serviceArg.user}` : ''}${serviceArg.group ? `
|
||||||
Group=${serviceArg.group}` : ''}
|
Group=${serviceArg.group}` : ''}${environmentLines ? `
|
||||||
Environment=NODE_OPTIONS="--max_old_space_size=100"
|
${environmentLines}` : ''}
|
||||||
ExecStart=/bin/bash -c "cd ${serviceArg.workingDir} && ${serviceArg.command}"
|
ExecStart=/bin/bash -c "cd ${serviceArg.workingDir} && ${serviceArg.command}"
|
||||||
WorkingDirectory=${serviceArg.workingDir}
|
WorkingDirectory=${serviceArg.workingDir}
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
|
|||||||
Reference in New Issue
Block a user