Initial Commit

This commit is contained in:
garrettmills
2020-02-21 00:36:55 -06:00
commit e2016069f2
40 changed files with 4902 additions and 0 deletions

View File

@@ -0,0 +1,111 @@
const { Injectable } = require('flitter-di')
class ServiceConfig extends Injectable {
static get services() {
return [...super.services, 'utility']
}
_data = {
after: ['syslog.target', 'network.target'],
type: 'simple',
restart: 'always',
restart_after: '2s',
install_to: 'multi-user.target',
}
name(set) { return this._get_or_set('name', set) }
description(set) { return this._get_or_set('description', set) }
after(set) { return this._get_or_set('after', set, true) }
restart(set) { return this._get_or_set('restart', set) }
restart_after(set) { return this._get_or_set('restart_after', set) }
type(set) { return this._get_or_set('type', set) }
user(set) { return this._get_or_set('user', set) }
group(set) { return this._get_or_set('group', set) }
directory(set) { return this._get_or_set('directory', set) }
execute(set) { return this._get_or_set('execute', set) }
env(set) { return this._get_or_set('env', set) }
install_to(set) { return this._get_or_set('install_to', set) }
_export() {
return this.utility.deep_copy(this._data)
}
_build() {
this._validate()
const lines = []
lines.push('[Unit]')
if ( this.description() ) lines.push(`Description=${this.description()}`)
if ( this.after() ) {
for ( const item of this.after() ) {
lines.push(`After=${item}`)
}
}
lines.push('\n[Service]')
if ( this.restart() ) {
lines.push(`Restart=${this.restart()}`)
if ( this.restart_after() ) {
lines.push(`RestartSec=${this.restart_after()}`)
}
}
if ( this.user() ) {
lines.push(`User=${this.user()}`)
}
if ( this.group() ) {
lines.push(`Group=${this.group()}`)
}
if ( this.directory() ) {
lines.push(`WorkingDirectory=${this.directory()}`)
}
if ( this.execute() ) {
lines.push(`ExecStart=${this.execute()}`)
}
if ( this.env() ) {
lines.push(`Environment=${this.env()}`)
}
if ( this.install_to() ) {
lines.push('\n[Install]')
lines.push(`WantedBy=${this.install_to()}`)
}
return lines.join('\n')
}
_validate() {
const required_fields = ['name', 'description', 'after', 'type', 'user', 'group', 'directory', 'execute']
for ( const field of required_fields ) {
if ( !this[field]() ) throw new Error(`Missing required service config field: ${field}`)
}
}
_get_or_set(field, value, arr = false) {
if ( value ) {
if ( arr ) {
if ( !this._data[field] ) this._data[field] = []
this._data[field].push(value)
} else this._data[field] = value
return this
}
return this._data[field]
}
}
module.exports = exports = ServiceConfig

View File

@@ -0,0 +1,79 @@
const { Injectable } = require('flitter-di')
const ImplementationError = require('libflitter/errors/ImplementationError')
const ServiceConfig = require('./ServiceConfig')
class ServiceManager extends Injectable {
static SERVICE_STATE_STOPPED = 'stopped'
static SERVICE_STATE_ERROR = 'error'
static SERVICE_STATE_RUNNING = 'running'
_command_restart_service = '%%SERVICE%%'
_command_start_service = '%%SERVICE%%'
_command_stop_service = '%%SERVICE%%'
_command_reload_service = '%%SERVICE%%'
_command_enable_service = '%%SERVICE%%'
_command_disable_service = '%%SERVICE%%'
_command_daemon_reload = ''
_group_services_by = ' '
constructor(host) {
super()
this._host = host
}
async status(...services) {
throw new ImplementationError()
}
async install(config) {
throw new ImplementationError()
}
new_config() {
return new ServiceConfig()
}
async start(...services) {
await this._standard_format_execution(services, this._command_start_service)
}
async stop(...services) {
await this._standard_format_execution(services, this._command_stop_service)
}
async restart(...services) {
await this._standard_format_execution(services, this._command_restart_service)
}
async reload(...services) {
await this._standard_format_execution(services, this._command_reload_service)
}
async enable(...services) {
await this._standard_format_execution(services, this._command_enable_service)
}
async disable(...services) {
await this._standard_format_execution(services, this._command_disable_service)
}
async daemon_reload() {
await this._host.run_line_result(this._command_daemon_reload)
}
async _standard_format_execution(items, command, replace = '%%SERVICE%%') {
if ( this._group_services_by ) {
items = [items.join(this._group_services_by)]
}
for ( const item of items ) {
const result = await this._host.execute(command.replace(replace, item))
if ( result.exit_code !== 0 ) {
throw new Error('Error encountered while executing command: '+result.stderr.join('\n'))
}
}
}
}
module.exports = exports = ServiceManager

View File

@@ -0,0 +1,55 @@
const ServiceManager = require('./ServiceManager')
class SystemDManager extends ServiceManager {
_command_restart_service = 'systemctl restart %%SERVICE%%'
_command_start_service = 'systemctl start %%SERVICE%%'
_command_stop_service = 'systemctl stop %%SERVICE%%'
_command_reload_service = 'systemctl reload %%SERVICE%%'
_command_enable_service = 'systemctl enable %%SERVICE%%'
_command_disable_service = 'systemctl disable %%SERVICE%%'
_command_daemon_reload = 'systemctl daemon-reload'
async status(...services) {
const statuses = []
for ( const service_name of services ) {
const result = await this._host.execute(`systemctl status -l --value --no-pager ${service_name}`)
if ( result.exit_code !== 0 ) {
throw new Error(`Unable to determine service state: ${result.stderr.join('\n')}`)
}
const status = result.clean_out.map(x => x.trim().toLowerCase())
.filter(x => x.startsWith('active:'))[0]
.split(':')[1]
.trim()
statuses.push({
name: service_name,
status: this._determine_status_from_string(status)
})
}
return statuses.length === 1 ? statuses[0] : statuses
}
async install(config) {
if ( !config._build || !config.name ) throw new Error('Config must be instance of classes/logical/services/ServiceConfig')
const file_contents = config._build()
const name = config.name()
const service_file = await this._host.get_path(`/etc/systemd/system/${name}.service`)
await service_file.echo(file_contents)
}
_determine_status_from_string(str) {
if ( str.startsWith('active') ) {
return this.constructor.SERVICE_STATE_RUNNING
} else if ( str.startsWith('fail') ) {
return this.constructor.SERVICE_STATE_ERROR
} else {
return this.constructor.SERVICE_STATE_STOPPED
}
}
}
module.exports = exports = SystemDManager