Compare commits
32 Commits
Author | SHA1 | Date | |
---|---|---|---|
119f20915e | |||
ce1fa6640b | |||
8957e03445 | |||
3df86bee10 | |||
65326710ab | |||
2e174c9f55 | |||
9e42910456 | |||
ff85cee528 | |||
2a9fff0185 | |||
67689d79bd | |||
f8c851de97 | |||
09e9d8c190 | |||
80f5df3317 | |||
6cf3ff6e83 | |||
ceb30c7ac2 | |||
0df90eec5d | |||
261031a49c | |||
615cc6aa7c | |||
c9aa7fed48 | |||
44d30fc4d6 | |||
dd5e1a978d | |||
692602b463 | |||
382b694027 | |||
de831b086f | |||
01c7d2e482 | |||
49ebf991a2 | |||
513337355f | |||
5e5a679f99 | |||
9ef366eee9 | |||
10562afea1 | |||
c4e3f628cd | |||
40e6a7abe4 |
1
license
1
license
@ -1,4 +1,5 @@
|
|||||||
Copyright (c) 2019 Lossless GmbH (hello@lossless.com)
|
Copyright (c) 2019 Lossless GmbH (hello@lossless.com)
|
||||||
|
Copyright (c) 2017-2019, braces lab
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
524
package-lock.json
generated
524
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
16
package.json
16
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@pushrocks/smartdaemon",
|
"name": "@pushrocks/smartdaemon",
|
||||||
"version": "1.0.2",
|
"version": "1.0.18",
|
||||||
"private": false,
|
"private": false,
|
||||||
"description": "start scripts as long running daemons and manage them",
|
"description": "start scripts as long running daemons and manage them",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
@ -13,14 +13,22 @@
|
|||||||
"format": "(gitzone format)"
|
"format": "(gitzone format)"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@gitzone/tsbuild": "^2.0.22",
|
"@gitzone/tsbuild": "^2.1.17",
|
||||||
"@gitzone/tstest": "^1.0.15",
|
"@gitzone/tstest": "^1.0.15",
|
||||||
"@pushrocks/tapbundle": "^3.0.7",
|
"@pushrocks/tapbundle": "^3.0.13",
|
||||||
"@types/node": "^10.11.7",
|
"@types/node": "^10.11.7",
|
||||||
"tslint": "^5.11.0",
|
"tslint": "^5.11.0",
|
||||||
"tslint-config-prettier": "^1.15.0"
|
"tslint-config-prettier": "^1.15.0"
|
||||||
},
|
},
|
||||||
"dependencies": {},
|
"dependencies": {
|
||||||
|
"@pushrocks/lik": "^3.0.11",
|
||||||
|
"@pushrocks/smartfile": "^7.0.4",
|
||||||
|
"@pushrocks/smartfm": "^2.0.4",
|
||||||
|
"@pushrocks/smartlog": "^2.0.19",
|
||||||
|
"@pushrocks/smartlog-destination-local": "^8.0.2",
|
||||||
|
"@pushrocks/smartshell": "^2.0.25",
|
||||||
|
"@pushrocks/smartsystem": "^2.0.8"
|
||||||
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"ts/*",
|
"ts/*",
|
||||||
"ts_web/*",
|
"ts_web/*",
|
||||||
|
17
test/test.ts
17
test/test.ts
@ -1,8 +1,21 @@
|
|||||||
import { expect, tap } from '@pushrocks/tapbundle';
|
import { expect, tap } from '@pushrocks/tapbundle';
|
||||||
import * as smartdaemon from '../ts/index';
|
import * as smartdaemon from '../ts/index';
|
||||||
|
|
||||||
tap.test('first test', async () => {
|
let testSmartdaemon: smartdaemon.SmartDaemon;
|
||||||
console.log(smartdaemon.standardExport);
|
|
||||||
|
tap.test('should create an instance of smartdaemon', async () => {
|
||||||
|
testSmartdaemon = new smartdaemon.SmartDaemon();
|
||||||
|
expect(testSmartdaemon).to.be.instanceOf(smartdaemon.SmartDaemon);
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should create a service', async () => {
|
||||||
|
testSmartdaemon.addService({
|
||||||
|
name: 'npmversion',
|
||||||
|
version: 'x.x.x',
|
||||||
|
command: 'npm -v',
|
||||||
|
description: 'displays the npm version',
|
||||||
|
workingDir: __dirname
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.start();
|
tap.start();
|
||||||
|
@ -1,3 +1 @@
|
|||||||
import * as plugins from './smartdaemon.plugins';
|
export * from './smartdaemon.classes.smartdaemon';
|
||||||
|
|
||||||
export let standardExport = 'Hi there! :) This is an exported string';
|
|
||||||
|
84
ts/smartdaemon.classes.service.ts
Normal file
84
ts/smartdaemon.classes.service.ts
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
import * as plugins from './smartdaemon.plugins';
|
||||||
|
import * as paths from './smartdaemon.paths';
|
||||||
|
import { SmartDaemon } from './smartdaemon.classes.smartdaemon';
|
||||||
|
|
||||||
|
export interface ISmartDaemonServiceConstructorOptions {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
command: string;
|
||||||
|
workingDir: string;
|
||||||
|
version: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* represents a service that is being spawned by SmartDaemon
|
||||||
|
*/
|
||||||
|
export class SmartDaemonService implements ISmartDaemonServiceConstructorOptions {
|
||||||
|
public static async createFromOptions(smartdaemonRef: SmartDaemon, optionsArg: ISmartDaemonServiceConstructorOptions) {
|
||||||
|
const service = new SmartDaemonService(smartdaemonRef);
|
||||||
|
for (const key of Object.keys(optionsArg)) {
|
||||||
|
service[key] = optionsArg[key];
|
||||||
|
}
|
||||||
|
return service;
|
||||||
|
}
|
||||||
|
|
||||||
|
public options: ISmartDaemonServiceConstructorOptions;
|
||||||
|
public alreadyExists = false;
|
||||||
|
|
||||||
|
public name: string;
|
||||||
|
public version: string;
|
||||||
|
public command: string;
|
||||||
|
public workingDir: string;
|
||||||
|
public description: string;
|
||||||
|
|
||||||
|
public smartdaemonRef: SmartDaemon;
|
||||||
|
|
||||||
|
constructor(smartdaemonRegfArg: SmartDaemon) {
|
||||||
|
this.smartdaemonRef = smartdaemonRegfArg;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* enables the service
|
||||||
|
*/
|
||||||
|
public async enable() {
|
||||||
|
await this.smartdaemonRef.systemdManager.enableService(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* disables the service
|
||||||
|
*/
|
||||||
|
public async disable() {
|
||||||
|
await this.smartdaemonRef.systemdManager.disableService(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* starts a service
|
||||||
|
*/
|
||||||
|
public async start() {
|
||||||
|
await this.smartdaemonRef.systemdManager.startService(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* stops a service
|
||||||
|
*/
|
||||||
|
public async stop() {
|
||||||
|
await this.smartdaemonRef.systemdManager.stopService(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Save and Delete
|
||||||
|
public async save() {
|
||||||
|
await this.smartdaemonRef.systemdManager.saveService(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* deletes the service
|
||||||
|
*/
|
||||||
|
public async delete() {
|
||||||
|
await this.smartdaemonRef.systemdManager.deleteService(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async reload() {
|
||||||
|
await this.smartdaemonRef.systemdManager.reload();
|
||||||
|
}
|
||||||
|
}
|
38
ts/smartdaemon.classes.smartdaemon.ts
Normal file
38
ts/smartdaemon.classes.smartdaemon.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import * as plugins from './smartdaemon.plugins';
|
||||||
|
import { SmartDaemonTemplateManager } from './smartdaemon.classes.templatemanager';
|
||||||
|
import { SmartDaemonService, ISmartDaemonServiceConstructorOptions } from './smartdaemon.classes.service';
|
||||||
|
import { SmartDaemonSystemdManager } from './smartdaemon.classes.systemdmanager';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export class SmartDaemon {
|
||||||
|
public templateManager: SmartDaemonTemplateManager;
|
||||||
|
public systemdManager: SmartDaemonSystemdManager;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.templateManager = new SmartDaemonTemplateManager(this);
|
||||||
|
this.systemdManager = new SmartDaemonSystemdManager(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* adds a service
|
||||||
|
* @param nameArg
|
||||||
|
* @param commandArg
|
||||||
|
* @param workingDirectoryArg
|
||||||
|
*/
|
||||||
|
public async addService(optionsArg: ISmartDaemonServiceConstructorOptions): Promise<SmartDaemonService> {
|
||||||
|
let serviceToAdd: SmartDaemonService;
|
||||||
|
const existingServices = await this.systemdManager.getServices();
|
||||||
|
const existingService = existingServices.find(serviceArg => {
|
||||||
|
return serviceArg.name === optionsArg.name;
|
||||||
|
});
|
||||||
|
if (!existingService) {
|
||||||
|
serviceToAdd = await SmartDaemonService.createFromOptions(this, optionsArg);
|
||||||
|
} else {
|
||||||
|
serviceToAdd = existingService;
|
||||||
|
Object.assign(serviceToAdd, optionsArg);
|
||||||
|
}
|
||||||
|
await serviceToAdd.save();
|
||||||
|
return serviceToAdd;
|
||||||
|
}
|
||||||
|
}
|
138
ts/smartdaemon.classes.systemdmanager.ts
Normal file
138
ts/smartdaemon.classes.systemdmanager.ts
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
import * as plugins from './smartdaemon.plugins';
|
||||||
|
import * as paths from './smartdaemon.paths';
|
||||||
|
import { SmartDaemon } from './smartdaemon.classes.smartdaemon';
|
||||||
|
import { ISmartDaemonServiceConstructorOptions, SmartDaemonService } from './smartdaemon.classes.service';
|
||||||
|
|
||||||
|
export class SmartDaemonSystemdManager {
|
||||||
|
// STATIC
|
||||||
|
private static smartDaemonNamespace = 'smartdaemon';
|
||||||
|
|
||||||
|
public static createServiceNameFromServiceName (serviceNameArg: string) {
|
||||||
|
return `${SmartDaemonSystemdManager.smartDaemonNamespace}_${serviceNameArg}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static createFileNameFromServiceName (serviceNameArg: string) {
|
||||||
|
return `${SmartDaemonSystemdManager.createServiceNameFromServiceName(serviceNameArg)}.service`;
|
||||||
|
};
|
||||||
|
|
||||||
|
public static createFilePathFromServiceName (serviceNameArg: string) {
|
||||||
|
return plugins.path.join(
|
||||||
|
paths.systemdDir,
|
||||||
|
SmartDaemonSystemdManager.createFileNameFromServiceName(serviceNameArg)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// INSTANCE
|
||||||
|
public smartdaemonRef: SmartDaemon;
|
||||||
|
public smartshellInstance: plugins.smartshell.Smartshell;
|
||||||
|
public smartsystem: plugins.smartsystem.Smartsystem;
|
||||||
|
|
||||||
|
public shouldExecute: boolean = false;
|
||||||
|
|
||||||
|
constructor(smartdaemonRefArg: SmartDaemon) {
|
||||||
|
this.smartdaemonRef = smartdaemonRefArg;
|
||||||
|
this.smartshellInstance = new plugins.smartshell.Smartshell({
|
||||||
|
executor: 'bash'
|
||||||
|
});
|
||||||
|
this.smartsystem = new plugins.smartsystem.Smartsystem();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async checkElegibility() {
|
||||||
|
if (await this.smartsystem.env.isLinuxAsync()) {
|
||||||
|
this.shouldExecute = true;
|
||||||
|
} else {
|
||||||
|
console.log('Smartdaemon can only be used on Linux systems! Refusing to set up a service.');
|
||||||
|
this.shouldExecute = false;
|
||||||
|
}
|
||||||
|
return this.shouldExecute;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async execute(commandArg: string) {
|
||||||
|
if (await this.checkElegibility()) {
|
||||||
|
await this.smartshellInstance.exec(commandArg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gets all services that are already present
|
||||||
|
*/
|
||||||
|
public async getServices() {
|
||||||
|
const existingServices: SmartDaemonService[] = [];
|
||||||
|
if (await this.checkElegibility()) {
|
||||||
|
const smartfmInstance = new plugins.smartfm.Smartfm({
|
||||||
|
fmType: 'yaml'
|
||||||
|
});
|
||||||
|
const availableServices = await plugins.smartfile.fs.fileTreeToObject(
|
||||||
|
paths.systemdDir,
|
||||||
|
'smartdaemon_*.service'
|
||||||
|
);
|
||||||
|
for (const serviceFile of availableServices) {
|
||||||
|
const data = smartfmInstance.parseFromComments('# ', serviceFile.contentBuffer.toString())
|
||||||
|
.data as ISmartDaemonServiceConstructorOptions;
|
||||||
|
const service = await SmartDaemonService.createFromOptions(this.smartdaemonRef, data);
|
||||||
|
service.alreadyExists = true;
|
||||||
|
existingServices.push(service);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return existingServices;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async startService(serviceArg: SmartDaemonService) {
|
||||||
|
if (await this.checkElegibility()) {
|
||||||
|
await this.execute(
|
||||||
|
`systemctl start ${SmartDaemonSystemdManager.createServiceNameFromServiceName(serviceArg.name)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public async stopService(serviceArg: SmartDaemonService) {
|
||||||
|
if (await this.checkElegibility()) {
|
||||||
|
await this.execute(
|
||||||
|
`systemctl stop ${SmartDaemonSystemdManager.createServiceNameFromServiceName(serviceArg.name)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async saveService(serviceArg: SmartDaemonService) {
|
||||||
|
if (await this.checkElegibility()) {
|
||||||
|
await plugins.smartfile.memory.toFs(
|
||||||
|
this.smartdaemonRef.templateManager.generateUnitFileForService(serviceArg),
|
||||||
|
SmartDaemonSystemdManager.createFilePathFromServiceName(serviceArg.name)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async deleteService(serviceArg: SmartDaemonService) {
|
||||||
|
if (await this.checkElegibility()) {
|
||||||
|
await plugins.smartfile.fs.remove(
|
||||||
|
SmartDaemonSystemdManager.createServiceNameFromServiceName(serviceArg.name)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async enableService(serviceArg: SmartDaemonService) {
|
||||||
|
if (await this.checkElegibility()) {
|
||||||
|
await this.saveService(serviceArg);
|
||||||
|
if (serviceArg.alreadyExists) {
|
||||||
|
await this.execute(`systemctl daemon-reload`);
|
||||||
|
}
|
||||||
|
await this.execute(
|
||||||
|
`systemctl enable ${SmartDaemonSystemdManager.createServiceNameFromServiceName(serviceArg.name)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public async disableService(serviceArg: SmartDaemonService) {
|
||||||
|
if (await this.checkElegibility()) {
|
||||||
|
await this.execute(
|
||||||
|
`systemctl disable ${SmartDaemonSystemdManager.createServiceNameFromServiceName(serviceArg.name)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async reload() {
|
||||||
|
if (await this.checkElegibility()) {
|
||||||
|
await this.execute(
|
||||||
|
`systemctl daemon-reload`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
40
ts/smartdaemon.classes.templatemanager.ts
Normal file
40
ts/smartdaemon.classes.templatemanager.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import * as plugins from './smartdaemon.plugins';
|
||||||
|
import { SmartDaemon } from './smartdaemon.classes.smartdaemon';
|
||||||
|
import { SmartDaemonService } from './smartdaemon.classes.service';
|
||||||
|
|
||||||
|
export class SmartDaemonTemplateManager {
|
||||||
|
public smartdaemonRef: SmartDaemon;
|
||||||
|
|
||||||
|
constructor(smartdaemonRefArg: SmartDaemon) {
|
||||||
|
this.smartdaemonRef = smartdaemonRefArg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public generateUnitFileForService = (serviceArg: SmartDaemonService) => {
|
||||||
|
return `# ---
|
||||||
|
# name: ${serviceArg.name}
|
||||||
|
# version: ${serviceArg.version}
|
||||||
|
# description: ${serviceArg.description}
|
||||||
|
# command: ${serviceArg.command}
|
||||||
|
# workingDir: ${serviceArg.workingDir}
|
||||||
|
# ---
|
||||||
|
[Unit]
|
||||||
|
Description=${serviceArg.description}
|
||||||
|
Requires=network.target
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
ExecStart=/bin/bash -c "cd ${serviceArg.workingDir} && ${serviceArg.command}"
|
||||||
|
WorkingDirectory=${serviceArg.workingDir}
|
||||||
|
Restart=on-failure
|
||||||
|
LimitNOFILE=infinity
|
||||||
|
LimitCORE=infinity
|
||||||
|
StandardInput=null
|
||||||
|
StandardOutput=syslog
|
||||||
|
StandardError=syslog
|
||||||
|
Restart=always
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
4
ts/smartdaemon.paths.ts
Normal file
4
ts/smartdaemon.paths.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import * as plugins from './smartdaemon.plugins';
|
||||||
|
|
||||||
|
export const packageDir = plugins.path.join(__dirname, '../');
|
||||||
|
export const systemdDir = plugins.path.join('/lib/systemd/system/');
|
@ -1,2 +1,28 @@
|
|||||||
const removeme = {};
|
// node native scope
|
||||||
export { removeme };
|
import * as path from 'path';
|
||||||
|
|
||||||
|
|
||||||
|
export {
|
||||||
|
path
|
||||||
|
};
|
||||||
|
|
||||||
|
// @pushrocks scope
|
||||||
|
import * as lik from '@pushrocks/lik';
|
||||||
|
import * as smartfile from '@pushrocks/smartfile';
|
||||||
|
import * as smartfm from '@pushrocks/smartfm';
|
||||||
|
import * as smartlog from '@pushrocks/smartlog';
|
||||||
|
import * as smartlogDestinationLocal from '@pushrocks/smartlog-destination-local';
|
||||||
|
import * as smartshell from '@pushrocks/smartshell';
|
||||||
|
import * as smartsystem from '@pushrocks/smartsystem';
|
||||||
|
|
||||||
|
export {
|
||||||
|
lik,
|
||||||
|
smartfile,
|
||||||
|
smartfm,
|
||||||
|
smartlog,
|
||||||
|
smartlogDestinationLocal,
|
||||||
|
smartshell,
|
||||||
|
smartsystem
|
||||||
|
};
|
||||||
|
|
||||||
|
// third party
|
||||||
|
15
ts/smartdamon.logging.ts
Normal file
15
ts/smartdamon.logging.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import * as plugins from './smartdaemon.plugins';
|
||||||
|
|
||||||
|
export const logger = new plugins.smartlog.Smartlog({
|
||||||
|
logContext: {
|
||||||
|
company: 'Some Company',
|
||||||
|
companyunit: 'Some CompanyUnit',
|
||||||
|
containerName: 'Some Containername',
|
||||||
|
environment: 'local',
|
||||||
|
runtime: 'node',
|
||||||
|
zone: 'gitzone'
|
||||||
|
},
|
||||||
|
minimumLogLevel: 'silly'
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.addLogDestination(new plugins.smartlogDestinationLocal.DestinationLocal());
|
Reference in New Issue
Block a user