mirror of
https://github.com/balena-os/balena-supervisor.git
synced 2025-01-31 08:25:36 +00:00
Merge pull request #857 from balena-io/typescript
Further typescript conversions
This commit is contained in:
commit
3f743b7477
33
package-lock.json
generated
33
package-lock.json
generated
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "balena-supervisor",
|
"name": "balena-supervisor",
|
||||||
"version": "9.2.3",
|
"version": "9.2.6",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -234,6 +234,15 @@
|
|||||||
"integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==",
|
"integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"@types/mkdirp": {
|
||||||
|
"version": "0.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/mkdirp/-/mkdirp-0.5.2.tgz",
|
||||||
|
"integrity": "sha512-U5icWpv7YnZYGsN4/cmh3WD2onMY0aJIiTE6+51TwJCttdHvtCYmkBNOobHlXwrJRL0nkH9jH4kD+1FAdMN4Tg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/mz": {
|
"@types/mz": {
|
||||||
"version": "0.0.32",
|
"version": "0.0.32",
|
||||||
"resolved": "https://registry.npmjs.org/@types/mz/-/mz-0.0.32.tgz",
|
"resolved": "https://registry.npmjs.org/@types/mz/-/mz-0.0.32.tgz",
|
||||||
@ -1554,7 +1563,7 @@
|
|||||||
},
|
},
|
||||||
"cacache": {
|
"cacache": {
|
||||||
"version": "10.0.4",
|
"version": "10.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/cacache/-/cacache-10.0.4.tgz",
|
"resolved": "http://registry.npmjs.org/cacache/-/cacache-10.0.4.tgz",
|
||||||
"integrity": "sha512-Dph0MzuH+rTQzGPNT9fAnrPmMmjKfST6trxJeK7NQuHRaVw24VzPRWTmg9MpcwOVQZO0E1FBICUlFeNaKPIfHA==",
|
"integrity": "sha512-Dph0MzuH+rTQzGPNT9fAnrPmMmjKfST6trxJeK7NQuHRaVw24VzPRWTmg9MpcwOVQZO0E1FBICUlFeNaKPIfHA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
@ -2199,7 +2208,7 @@
|
|||||||
},
|
},
|
||||||
"dbus-native": {
|
"dbus-native": {
|
||||||
"version": "0.2.5",
|
"version": "0.2.5",
|
||||||
"resolved": "https://registry.npmjs.org/dbus-native/-/dbus-native-0.2.5.tgz",
|
"resolved": "http://registry.npmjs.org/dbus-native/-/dbus-native-0.2.5.tgz",
|
||||||
"integrity": "sha512-ocxMKCV7QdiNhzhFSeEMhj258OGtvpANSb3oWGiotmI5h1ZIse0TMPcSLiXSpqvbYvQz2Y5RsYPMNYLWhg9eBw==",
|
"integrity": "sha512-ocxMKCV7QdiNhzhFSeEMhj258OGtvpANSb3oWGiotmI5h1ZIse0TMPcSLiXSpqvbYvQz2Y5RsYPMNYLWhg9eBw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
@ -2354,7 +2363,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"globby": {
|
"globby": {
|
||||||
"version": "6.1.0",
|
"version": "6.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz",
|
"resolved": "http://registry.npmjs.org/globby/-/globby-6.1.0.tgz",
|
||||||
"integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=",
|
"integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
@ -2617,7 +2626,7 @@
|
|||||||
},
|
},
|
||||||
"string_decoder": {
|
"string_decoder": {
|
||||||
"version": "0.10.31",
|
"version": "0.10.31",
|
||||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
|
"resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
|
||||||
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=",
|
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=",
|
||||||
"dev": true
|
"dev": true
|
||||||
}
|
}
|
||||||
@ -6803,7 +6812,7 @@
|
|||||||
},
|
},
|
||||||
"next-tick": {
|
"next-tick": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
|
"resolved": "http://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
|
||||||
"integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=",
|
"integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
@ -7153,7 +7162,7 @@
|
|||||||
},
|
},
|
||||||
"os-homedir": {
|
"os-homedir": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
|
"resolved": "http://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
|
||||||
"integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M="
|
"integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M="
|
||||||
},
|
},
|
||||||
"os-locale": {
|
"os-locale": {
|
||||||
@ -7203,7 +7212,7 @@
|
|||||||
},
|
},
|
||||||
"os-tmpdir": {
|
"os-tmpdir": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
|
"resolved": "http://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
|
||||||
"integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ="
|
"integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ="
|
||||||
},
|
},
|
||||||
"osenv": {
|
"osenv": {
|
||||||
@ -7352,7 +7361,7 @@
|
|||||||
},
|
},
|
||||||
"path-is-absolute": {
|
"path-is-absolute": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
"resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||||
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
|
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
|
||||||
},
|
},
|
||||||
"path-is-inside": {
|
"path-is-inside": {
|
||||||
@ -8725,7 +8734,7 @@
|
|||||||
},
|
},
|
||||||
"source-map": {
|
"source-map": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz",
|
"resolved": "http://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz",
|
||||||
"integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=",
|
"integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true,
|
"optional": true,
|
||||||
@ -8937,7 +8946,7 @@
|
|||||||
},
|
},
|
||||||
"stream-browserify": {
|
"stream-browserify": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz",
|
"resolved": "http://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz",
|
||||||
"integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=",
|
"integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
@ -9017,7 +9026,7 @@
|
|||||||
},
|
},
|
||||||
"string_decoder": {
|
"string_decoder": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
"resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"safe-buffer": "~5.1.0"
|
"safe-buffer": "~5.1.0"
|
||||||
|
@ -30,6 +30,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bluebird": "^3.5.25",
|
"@types/bluebird": "^3.5.25",
|
||||||
|
"@types/common-tags": "^1.8.0",
|
||||||
"@types/dockerode": "^2.5.10",
|
"@types/dockerode": "^2.5.10",
|
||||||
"@types/event-stream": "^3.3.34",
|
"@types/event-stream": "^3.3.34",
|
||||||
"@types/express": "^4.11.1",
|
"@types/express": "^4.11.1",
|
||||||
@ -37,6 +38,7 @@
|
|||||||
"@types/lockfile": "^1.0.0",
|
"@types/lockfile": "^1.0.0",
|
||||||
"@types/lodash": "^4.14.119",
|
"@types/lodash": "^4.14.119",
|
||||||
"@types/memoizee": "^0.4.2",
|
"@types/memoizee": "^0.4.2",
|
||||||
|
"@types/mkdirp": "^0.5.2",
|
||||||
"@types/mz": "0.0.32",
|
"@types/mz": "0.0.32",
|
||||||
"@types/node": "^10.12.17",
|
"@types/node": "^10.12.17",
|
||||||
"@types/request": "^2.48.1",
|
"@types/request": "^2.48.1",
|
||||||
@ -50,6 +52,7 @@
|
|||||||
"chai-events": "0.0.1",
|
"chai-events": "0.0.1",
|
||||||
"coffee-loader": "^0.9.0",
|
"coffee-loader": "^0.9.0",
|
||||||
"coffeescript": "^1.12.7",
|
"coffeescript": "^1.12.7",
|
||||||
|
"common-tags": "^1.8.0",
|
||||||
"copy-webpack-plugin": "^4.6.0",
|
"copy-webpack-plugin": "^4.6.0",
|
||||||
"dbus-native": "^0.2.5",
|
"dbus-native": "^0.2.5",
|
||||||
"deep-object-diff": "^1.1.0",
|
"deep-object-diff": "^1.1.0",
|
||||||
|
@ -329,13 +329,13 @@ module.exports = class APIBinder
|
|||||||
# Creates the necessary config vars in the API to match the current device state,
|
# Creates the necessary config vars in the API to match the current device state,
|
||||||
# without overwriting any variables that are already set.
|
# without overwriting any variables that are already set.
|
||||||
_reportInitialEnv: (apiEndpoint) =>
|
_reportInitialEnv: (apiEndpoint) =>
|
||||||
|
defaultConfig = @deviceState.deviceConfig.getDefaults()
|
||||||
Promise.join(
|
Promise.join(
|
||||||
@deviceState.getCurrentForComparison()
|
@deviceState.getCurrentForComparison()
|
||||||
@getTargetState().then (targetState) =>
|
@getTargetState().then (targetState) =>
|
||||||
@deviceState.deviceConfig.formatConfigKeys(targetState.local.config)
|
@deviceState.deviceConfig.formatConfigKeys(targetState.local.config)
|
||||||
@deviceState.deviceConfig.getDefaults()
|
|
||||||
@config.get('deviceId')
|
@config.get('deviceId')
|
||||||
(currentState, targetConfig, defaultConfig, deviceId) =>
|
(currentState, targetConfig, deviceId) =>
|
||||||
currentConfig = currentState.local.config
|
currentConfig = currentState.local.config
|
||||||
Promise.mapSeries _.toPairs(currentConfig), ([ key, value ]) =>
|
Promise.mapSeries _.toPairs(currentConfig), ([ key, value ]) =>
|
||||||
# We want to disable local mode when joining a cloud
|
# We want to disable local mode when joining a cloud
|
||||||
|
6
src/application-manager.d.ts
vendored
6
src/application-manager.d.ts
vendored
@ -8,7 +8,7 @@ import { EventTracker } from './event-tracker';
|
|||||||
|
|
||||||
import Images = require('./compose/images');
|
import Images = require('./compose/images');
|
||||||
import ServiceManager = require('./compose/service-manager');
|
import ServiceManager = require('./compose/service-manager');
|
||||||
import DB = require('./db');
|
import DB from './db';
|
||||||
|
|
||||||
import { Service } from './compose/service';
|
import { Service } from './compose/service';
|
||||||
|
|
||||||
@ -51,7 +51,9 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
opts: Options,
|
opts: Options,
|
||||||
): Bluebird<void>;
|
): Bluebird<void>;
|
||||||
|
|
||||||
public getStatus(): Promise<DeviceApplicationState>;
|
// FIXME: Type this properly as it's some mutant state between
|
||||||
|
// the state endpoint and the ApplicationManager internals
|
||||||
|
public getStatus(): Promise<Dictionay<any>>;
|
||||||
|
|
||||||
public serviceNameFromId(serviceId: number): Bluebird<string>;
|
public serviceNameFromId(serviceId: number): Bluebird<string>;
|
||||||
}
|
}
|
||||||
|
@ -6,11 +6,11 @@ import { generateUniqueKey } from 'resin-register-device';
|
|||||||
|
|
||||||
import ConfigJsonConfigBackend from './configJson';
|
import ConfigJsonConfigBackend from './configJson';
|
||||||
|
|
||||||
import { ConfigProviderFunctions, createProviderFunctions } from './functions';
|
|
||||||
import * as constants from '../lib/constants';
|
import * as constants from '../lib/constants';
|
||||||
import { ConfigMap, ConfigSchema, ConfigValue } from '../lib/types';
|
import { ConfigMap, ConfigSchema, ConfigValue } from '../lib/types';
|
||||||
|
import { ConfigProviderFunctions, createProviderFunctions } from './functions';
|
||||||
|
|
||||||
import DB = require('../db');
|
import DB from '../db';
|
||||||
|
|
||||||
interface ConfigOpts {
|
interface ConfigOpts {
|
||||||
db: DB;
|
db: DB;
|
||||||
@ -215,7 +215,7 @@ class Config extends EventEmitter {
|
|||||||
return setValuesInTransaction(trx).return();
|
return setValuesInTransaction(trx).return();
|
||||||
} else {
|
} else {
|
||||||
return this.db
|
return this.db
|
||||||
.transaction(tx => {
|
.transaction((tx: Transaction) => {
|
||||||
return setValuesInTransaction(tx);
|
return setValuesInTransaction(tx);
|
||||||
})
|
})
|
||||||
.return();
|
.return();
|
||||||
|
@ -10,7 +10,9 @@ interface DBOpts {
|
|||||||
|
|
||||||
type DBTransactionCallback = (trx: Knex.Transaction) => void;
|
type DBTransactionCallback = (trx: Knex.Transaction) => void;
|
||||||
|
|
||||||
class DB {
|
export type Transaction = Knex.Transaction;
|
||||||
|
|
||||||
|
export class DB {
|
||||||
private databasePath: string;
|
private databasePath: string;
|
||||||
private knex: Knex;
|
private knex: Knex;
|
||||||
|
|
||||||
@ -65,4 +67,4 @@ class DB {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export = DB;
|
export default DB;
|
||||||
|
@ -1,235 +0,0 @@
|
|||||||
Promise = require 'bluebird'
|
|
||||||
_ = require 'lodash'
|
|
||||||
|
|
||||||
systemd = require './lib/systemd'
|
|
||||||
{ checkTruthy, checkInt } = require './lib/validation'
|
|
||||||
{ UnitNotLoadedError } = require './lib/errors'
|
|
||||||
configUtils = require './config/utils'
|
|
||||||
|
|
||||||
vpnServiceName = 'openvpn-resin'
|
|
||||||
|
|
||||||
module.exports = class DeviceConfig
|
|
||||||
constructor: ({ @db, @config, @logger }) ->
|
|
||||||
@rebootRequired = false
|
|
||||||
@configKeys = {
|
|
||||||
appUpdatePollInterval: { envVarName: 'SUPERVISOR_POLL_INTERVAL', varType: 'int', defaultValue: '60000' }
|
|
||||||
localMode: { envVarName: 'SUPERVISOR_LOCAL_MODE', varType: 'bool', defaultValue: 'false' }
|
|
||||||
connectivityCheckEnabled: { envVarName: 'SUPERVISOR_CONNECTIVITY_CHECK', varType: 'bool', defaultValue: 'true' }
|
|
||||||
loggingEnabled: { envVarName: 'SUPERVISOR_LOG_CONTROL', varType: 'bool', defaultValue: 'true' }
|
|
||||||
delta: { envVarName: 'SUPERVISOR_DELTA', varType: 'bool', defaultValue: 'false' }
|
|
||||||
deltaRequestTimeout: { envVarName: 'SUPERVISOR_DELTA_REQUEST_TIMEOUT', varType: 'int', defaultValue: '30000' }
|
|
||||||
deltaApplyTimeout: { envVarName: 'SUPERVISOR_DELTA_APPLY_TIMEOUT', varType: 'int', defaultValue: '' }
|
|
||||||
deltaRetryCount: { envVarName: 'SUPERVISOR_DELTA_RETRY_COUNT', varType: 'int', defaultValue: '30' }
|
|
||||||
deltaRetryInterval: { envVarName: 'SUPERVISOR_DELTA_RETRY_INTERVAL', varType: 'int', defaultValue: '10000' }
|
|
||||||
deltaVersion: { envVarName: 'SUPERVISOR_DELTA_VERSION', varType: 'int', defaultValue: '2' }
|
|
||||||
lockOverride: { envVarName: 'SUPERVISOR_OVERRIDE_LOCK', varType: 'bool', defaultValue: 'false' }
|
|
||||||
persistentLogging: { envVarName: 'SUPERVISOR_PERSISTENT_LOGGING', varType: 'bool', defaultValue: 'false', rebootRequired: true }
|
|
||||||
}
|
|
||||||
@validKeys = [
|
|
||||||
'SUPERVISOR_VPN_CONTROL',
|
|
||||||
'OVERRIDE_LOCK',
|
|
||||||
].concat(_.map(@configKeys, 'envVarName'))
|
|
||||||
@actionExecutors = {
|
|
||||||
changeConfig: (step) =>
|
|
||||||
@logger.logConfigChange(step.humanReadableTarget)
|
|
||||||
@config.set(step.target)
|
|
||||||
.then =>
|
|
||||||
@logger.logConfigChange(step.humanReadableTarget, { success: true })
|
|
||||||
if step.rebootRequired
|
|
||||||
@rebootRequired = true
|
|
||||||
.tapCatch (err) =>
|
|
||||||
@logger.logConfigChange(step.humanReadableTarget, { err })
|
|
||||||
setVPNEnabled: (step, { initial = false } = {}) =>
|
|
||||||
logValue = { SUPERVISOR_VPN_CONTROL: step.target }
|
|
||||||
if !initial
|
|
||||||
@logger.logConfigChange(logValue)
|
|
||||||
@setVPNEnabled(step.target)
|
|
||||||
.then =>
|
|
||||||
if !initial
|
|
||||||
@logger.logConfigChange(logValue, { success: true })
|
|
||||||
.tapCatch (err) =>
|
|
||||||
@logger.logConfigChange(logValue, { err })
|
|
||||||
setBootConfig: (step) =>
|
|
||||||
@getConfigBackend()
|
|
||||||
.then (configBackend ) =>
|
|
||||||
@setBootConfig(configBackend, step.target)
|
|
||||||
}
|
|
||||||
@validActions = _.keys(@actionExecutors)
|
|
||||||
@configBackend = null
|
|
||||||
|
|
||||||
getConfigBackend: =>
|
|
||||||
if @configBackend?
|
|
||||||
Promise.resolve(@configBackend)
|
|
||||||
else
|
|
||||||
@config.get('deviceType').then (deviceType) =>
|
|
||||||
@configBackend = configUtils.getConfigBackend(deviceType)
|
|
||||||
return @configBackend
|
|
||||||
|
|
||||||
setTarget: (target, trx) =>
|
|
||||||
db = trx ? @db.models.bind(@db)
|
|
||||||
@formatConfigKeys(target)
|
|
||||||
.then (formattedTarget) ->
|
|
||||||
confToUpdate = {
|
|
||||||
targetValues: JSON.stringify(formattedTarget)
|
|
||||||
}
|
|
||||||
db('deviceConfig').update(confToUpdate)
|
|
||||||
|
|
||||||
getTarget: ({ initial = false } = {}) =>
|
|
||||||
Promise.all([
|
|
||||||
@config.get('unmanaged')
|
|
||||||
@db.models('deviceConfig').select('targetValues')
|
|
||||||
])
|
|
||||||
.then ([unmanaged, [ devConfig ]]) =>
|
|
||||||
conf = JSON.parse(devConfig.targetValues)
|
|
||||||
if initial or !conf.SUPERVISOR_VPN_CONTROL?
|
|
||||||
conf.SUPERVISOR_VPN_CONTROL = 'true'
|
|
||||||
if unmanaged and !conf.SUPERVISOR_LOCAL_MODE?
|
|
||||||
conf.SUPERVISOR_LOCAL_MODE = 'true'
|
|
||||||
for own k, { envVarName, defaultValue } of @configKeys
|
|
||||||
conf[envVarName] ?= defaultValue
|
|
||||||
return conf
|
|
||||||
|
|
||||||
getCurrent: =>
|
|
||||||
Promise.all [
|
|
||||||
@config.getMany([ 'deviceType' ].concat(_.keys(@configKeys)))
|
|
||||||
@getConfigBackend()
|
|
||||||
]
|
|
||||||
.then ([ conf, configBackend ]) =>
|
|
||||||
Promise.join(
|
|
||||||
@getVPNEnabled()
|
|
||||||
@getBootConfig(configBackend)
|
|
||||||
(vpnStatus, bootConfig) =>
|
|
||||||
currentConf = {
|
|
||||||
SUPERVISOR_VPN_CONTROL: (vpnStatus ? 'true').toString()
|
|
||||||
}
|
|
||||||
for own key, { envVarName } of @configKeys
|
|
||||||
currentConf[envVarName] = (conf[key] ? '').toString()
|
|
||||||
return _.assign(currentConf, bootConfig)
|
|
||||||
)
|
|
||||||
|
|
||||||
formatConfigKeys: (conf) =>
|
|
||||||
@getConfigBackend()
|
|
||||||
.then (configBackend) =>
|
|
||||||
configUtils.formatConfigKeys(configBackend, @validKeys, conf)
|
|
||||||
|
|
||||||
getDefaults: =>
|
|
||||||
Promise.try =>
|
|
||||||
return _.extend({
|
|
||||||
SUPERVISOR_VPN_CONTROL: 'true'
|
|
||||||
}, _.mapValues(_.mapKeys(@configKeys, 'envVarName'), 'defaultValue'))
|
|
||||||
|
|
||||||
bootConfigChangeRequired: (configBackend, current, target) =>
|
|
||||||
targetBootConfig = configUtils.envToBootConfig(configBackend, target)
|
|
||||||
currentBootConfig = configUtils.envToBootConfig(configBackend, current)
|
|
||||||
|
|
||||||
if !_.isEqual(currentBootConfig, targetBootConfig)
|
|
||||||
_.each targetBootConfig, (value, key) =>
|
|
||||||
if not configBackend.isSupportedConfig(key)
|
|
||||||
if currentBootConfig[key] != value
|
|
||||||
err = "Attempt to change blacklisted config value #{key}"
|
|
||||||
@logger.logSystemMessage(err, { error: err }, 'Apply boot config error')
|
|
||||||
throw new Error(err)
|
|
||||||
return true
|
|
||||||
return false
|
|
||||||
|
|
||||||
getRequiredSteps: (currentState, targetState) =>
|
|
||||||
current = _.clone(currentState.local?.config ? {})
|
|
||||||
target = _.clone(targetState.local?.config ? {})
|
|
||||||
steps = []
|
|
||||||
Promise.all [
|
|
||||||
@config.getMany([ 'deviceType', 'unmanaged' ])
|
|
||||||
@getConfigBackend()
|
|
||||||
]
|
|
||||||
.then ([{ deviceType, unmanaged }, configBackend ]) =>
|
|
||||||
configChanges = {}
|
|
||||||
humanReadableConfigChanges = {}
|
|
||||||
match = {
|
|
||||||
'bool': (a, b) ->
|
|
||||||
checkTruthy(a) == checkTruthy(b)
|
|
||||||
'int': (a, b) ->
|
|
||||||
checkInt(a) == checkInt(b)
|
|
||||||
}
|
|
||||||
# If the legacy lock override is used, place it as the new variable
|
|
||||||
if checkTruthy(target['OVERRIDE_LOCK'])
|
|
||||||
target['SUPERVISOR_OVERRIDE_LOCK'] = target['OVERRIDE_LOCK']
|
|
||||||
reboot = false
|
|
||||||
for own key, { envVarName, varType, rebootRequired } of @configKeys
|
|
||||||
if !match[varType](current[envVarName], target[envVarName])
|
|
||||||
configChanges[key] = target[envVarName]
|
|
||||||
humanReadableConfigChanges[envVarName] = target[envVarName]
|
|
||||||
reboot = reboot || (rebootRequired ? false)
|
|
||||||
if !_.isEmpty(configChanges)
|
|
||||||
steps.push({
|
|
||||||
action: 'changeConfig'
|
|
||||||
target: configChanges
|
|
||||||
humanReadableTarget: humanReadableConfigChanges
|
|
||||||
rebootRequired: reboot
|
|
||||||
})
|
|
||||||
return
|
|
||||||
|
|
||||||
# Check if we need to perform special case actions for the VPN
|
|
||||||
if !checkTruthy(unmanaged) &&
|
|
||||||
!_.isEmpty(target['SUPERVISOR_VPN_CONTROL']) &&
|
|
||||||
@checkBoolChanged(current, target, 'SUPERVISOR_VPN_CONTROL')
|
|
||||||
steps.push({
|
|
||||||
action: 'setVPNEnabled'
|
|
||||||
target: target['SUPERVISOR_VPN_CONTROL']
|
|
||||||
})
|
|
||||||
|
|
||||||
# Do we need to change the boot config?
|
|
||||||
if @bootConfigChangeRequired(configBackend, current, target)
|
|
||||||
steps.push({
|
|
||||||
action: 'setBootConfig'
|
|
||||||
target
|
|
||||||
})
|
|
||||||
|
|
||||||
if !_.isEmpty(steps)
|
|
||||||
return
|
|
||||||
if @rebootRequired
|
|
||||||
steps.push({
|
|
||||||
action: 'reboot'
|
|
||||||
})
|
|
||||||
return
|
|
||||||
.return(steps)
|
|
||||||
|
|
||||||
executeStepAction: (step, opts) =>
|
|
||||||
@actionExecutors[step.action](step, opts)
|
|
||||||
|
|
||||||
getBootConfig: (configBackend) ->
|
|
||||||
Promise.try ->
|
|
||||||
if !configBackend?
|
|
||||||
return {}
|
|
||||||
configBackend.getBootConfig()
|
|
||||||
.then (config) ->
|
|
||||||
return configUtils.bootConfigToEnv(configBackend, config)
|
|
||||||
|
|
||||||
setBootConfig: (configBackend, target) =>
|
|
||||||
Promise.try =>
|
|
||||||
if !configBackend?
|
|
||||||
return false
|
|
||||||
conf = configUtils.envToBootConfig(configBackend, target)
|
|
||||||
@logger.logSystemMessage("Applying boot config: #{JSON.stringify(conf)}", {}, 'Apply boot config in progress')
|
|
||||||
|
|
||||||
configBackend.setBootConfig(conf)
|
|
||||||
.then =>
|
|
||||||
@logger.logSystemMessage("Applied boot config: #{JSON.stringify(conf)}", {}, 'Apply boot config success')
|
|
||||||
@rebootRequired = true
|
|
||||||
return true
|
|
||||||
.tapCatch (err) =>
|
|
||||||
@logger.logSystemMessage("Error setting boot config: #{err}", { error: err }, 'Apply boot config error')
|
|
||||||
|
|
||||||
getVPNEnabled: ->
|
|
||||||
systemd.serviceActiveState(vpnServiceName)
|
|
||||||
.then (activeState) ->
|
|
||||||
return activeState not in [ 'inactive', 'deactivating' ]
|
|
||||||
.catchReturn(UnitNotLoadedError, null)
|
|
||||||
|
|
||||||
setVPNEnabled: (val) ->
|
|
||||||
enable = checkTruthy(val) ? true
|
|
||||||
if enable
|
|
||||||
systemd.startService(vpnServiceName)
|
|
||||||
else
|
|
||||||
systemd.stopService(vpnServiceName)
|
|
||||||
|
|
||||||
checkBoolChanged: (current, target, key) ->
|
|
||||||
checkTruthy(current[key]) != checkTruthy(target[key])
|
|
518
src/device-config.ts
Normal file
518
src/device-config.ts
Normal file
@ -0,0 +1,518 @@
|
|||||||
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
|
import Config = require('./config');
|
||||||
|
import Database, { Transaction } from './db';
|
||||||
|
import Logger from './logger';
|
||||||
|
|
||||||
|
import { DeviceConfigBackend } from './config/backend';
|
||||||
|
import * as configUtils from './config/utils';
|
||||||
|
import { UnitNotLoadedError } from './lib/errors';
|
||||||
|
import * as systemd from './lib/systemd';
|
||||||
|
import { EnvVarObject } from './lib/types';
|
||||||
|
import { checkInt, checkTruthy } from './lib/validation';
|
||||||
|
import { DeviceApplicationState } from './types/state';
|
||||||
|
|
||||||
|
const vpnServiceName = 'openvpn-resin';
|
||||||
|
|
||||||
|
interface DeviceConfigConstructOpts {
|
||||||
|
db: Database;
|
||||||
|
config: Config;
|
||||||
|
logger: Logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ConfigOption {
|
||||||
|
envVarName: string;
|
||||||
|
varType: string;
|
||||||
|
defaultValue: string;
|
||||||
|
rebootRequired?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ConfigStep {
|
||||||
|
// TODO: This is a bit of a mess, the DeviceConfig class shouldn't
|
||||||
|
// know that the reboot action exists as it is implemented by
|
||||||
|
// DeviceState. Fix this weird circular dependency
|
||||||
|
action: keyof DeviceActionExecutors | 'reboot';
|
||||||
|
humanReadableTarget?: Dictionary<string>;
|
||||||
|
target?: string | Dictionary<string>;
|
||||||
|
rebootRequired?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DeviceActionExecutorOpts {
|
||||||
|
initial?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeviceActionExecutorFn = (
|
||||||
|
step: ConfigStep,
|
||||||
|
opts?: DeviceActionExecutorOpts,
|
||||||
|
) => Promise<void>;
|
||||||
|
|
||||||
|
interface DeviceActionExecutors {
|
||||||
|
changeConfig: DeviceActionExecutorFn;
|
||||||
|
setVPNEnabled: DeviceActionExecutorFn;
|
||||||
|
setBootConfig: DeviceActionExecutorFn;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DeviceConfig {
|
||||||
|
private db: Database;
|
||||||
|
private config: Config;
|
||||||
|
private logger: Logger;
|
||||||
|
private rebootRequired = false;
|
||||||
|
private actionExecutors: DeviceActionExecutors;
|
||||||
|
private configBackend: DeviceConfigBackend | null = null;
|
||||||
|
|
||||||
|
private static configKeys: Dictionary<ConfigOption> = {
|
||||||
|
appUpdatePollInterval: {
|
||||||
|
envVarName: 'SUPERVISOR_POLL_INTERVAL',
|
||||||
|
varType: 'int',
|
||||||
|
defaultValue: '60000',
|
||||||
|
},
|
||||||
|
localMode: {
|
||||||
|
envVarName: 'SUPERVISOR_LOCAL_MODE',
|
||||||
|
varType: 'bool',
|
||||||
|
defaultValue: 'false',
|
||||||
|
},
|
||||||
|
connectivityCheckEnabled: {
|
||||||
|
envVarName: 'SUPERVISOR_CONNECTIVITY_CHECK',
|
||||||
|
varType: 'bool',
|
||||||
|
defaultValue: 'true',
|
||||||
|
},
|
||||||
|
loggingEnabled: {
|
||||||
|
envVarName: 'SUPERVISOR_LOG_CONTROL',
|
||||||
|
varType: 'bool',
|
||||||
|
defaultValue: 'true',
|
||||||
|
},
|
||||||
|
delta: {
|
||||||
|
envVarName: 'SUPERVISOR_DELTA',
|
||||||
|
varType: 'bool',
|
||||||
|
defaultValue: 'false',
|
||||||
|
},
|
||||||
|
deltaRequestTimeout: {
|
||||||
|
envVarName: 'SUPERVISOR_DELTA_REQUEST_TIMEOUT',
|
||||||
|
varType: 'int',
|
||||||
|
defaultValue: '30000',
|
||||||
|
},
|
||||||
|
deltaApplyTimeout: {
|
||||||
|
envVarName: 'SUPERVISOR_DELTA_APPLY_TIMEOUT',
|
||||||
|
varType: 'int',
|
||||||
|
defaultValue: '',
|
||||||
|
},
|
||||||
|
deltaRetryCount: {
|
||||||
|
envVarName: 'SUPERVISOR_DELTA_RETRY_COUNT',
|
||||||
|
varType: 'int',
|
||||||
|
defaultValue: '30',
|
||||||
|
},
|
||||||
|
deltaRetryInterval: {
|
||||||
|
envVarName: 'SUPERVISOR_DELTA_RETRY_INTERVAL',
|
||||||
|
varType: 'int',
|
||||||
|
defaultValue: '10000',
|
||||||
|
},
|
||||||
|
deltaVersion: {
|
||||||
|
envVarName: 'SUPERVISOR_DELTA_VERSION',
|
||||||
|
varType: 'int',
|
||||||
|
defaultValue: '2',
|
||||||
|
},
|
||||||
|
lockOverride: {
|
||||||
|
envVarName: 'SUPERVISOR_OVERRIDE_LOCK',
|
||||||
|
varType: 'bool',
|
||||||
|
defaultValue: 'false',
|
||||||
|
},
|
||||||
|
persistentLogging: {
|
||||||
|
envVarName: 'SUPERVISOR_PERSISTENT_LOGGING',
|
||||||
|
varType: 'bool',
|
||||||
|
defaultValue: 'false',
|
||||||
|
rebootRequired: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
static validKeys = [
|
||||||
|
'SUPERVISOR_VPN_CONTROL',
|
||||||
|
'OVERRRIDE_LOCK',
|
||||||
|
..._.map(DeviceConfig.configKeys, 'envVarName'),
|
||||||
|
];
|
||||||
|
|
||||||
|
public constructor({ db, config, logger }: DeviceConfigConstructOpts) {
|
||||||
|
this.db = db;
|
||||||
|
this.config = config;
|
||||||
|
this.logger = logger;
|
||||||
|
|
||||||
|
this.actionExecutors = {
|
||||||
|
changeConfig: async step => {
|
||||||
|
try {
|
||||||
|
if (step.humanReadableTarget) {
|
||||||
|
this.logger.logConfigChange(step.humanReadableTarget);
|
||||||
|
}
|
||||||
|
if (!_.isObject(step.target)) {
|
||||||
|
throw new Error('Non-dictionary value passed to changeConfig');
|
||||||
|
}
|
||||||
|
await this.config.set(step.target as Dictionary<string>);
|
||||||
|
if (step.humanReadableTarget) {
|
||||||
|
this.logger.logConfigChange(step.humanReadableTarget, {
|
||||||
|
success: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (step.rebootRequired) {
|
||||||
|
this.rebootRequired = true;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (step.humanReadableTarget) {
|
||||||
|
this.logger.logConfigChange(step.humanReadableTarget, {
|
||||||
|
err,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setVPNEnabled: async (step, opts = {}) => {
|
||||||
|
const { initial = false } = opts;
|
||||||
|
if (!_.isString(step.target)) {
|
||||||
|
throw new Error('Non-string value passed to setVPNEnabled');
|
||||||
|
}
|
||||||
|
const logValue = { SUPERVISOR_VPN_CONTROL: step.target };
|
||||||
|
if (!initial) {
|
||||||
|
this.logger.logConfigChange(logValue);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await this.setVPNEnabled(step.target);
|
||||||
|
if (!initial) {
|
||||||
|
this.logger.logConfigChange(logValue, { success: true });
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
this.logger.logConfigChange(logValue, { err });
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setBootConfig: async step => {
|
||||||
|
const configBackend = await this.getConfigBackend();
|
||||||
|
if (!_.isObject(step.target)) {
|
||||||
|
throw new Error(
|
||||||
|
'Non-dictionary passed to DeviceConfig.setBootConfig',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
await this.setBootConfig(configBackend, step.target as Dictionary<
|
||||||
|
string
|
||||||
|
>);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getConfigBackend() {
|
||||||
|
if (this.configBackend != null) {
|
||||||
|
return this.configBackend;
|
||||||
|
}
|
||||||
|
const dt = await this.config.get('deviceType');
|
||||||
|
if (!_.isString(dt)) {
|
||||||
|
throw new Error('Could not detect device type');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.configBackend = configUtils.getConfigBackend(dt) || null;
|
||||||
|
|
||||||
|
return this.configBackend;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async setTarget(
|
||||||
|
target: Dictionary<string>,
|
||||||
|
trx?: Transaction,
|
||||||
|
): Promise<void> {
|
||||||
|
const db = trx != null ? trx : this.db.models.bind(this.db);
|
||||||
|
|
||||||
|
const formatted = await this.formatConfigKeys(target);
|
||||||
|
const confToUpdate = {
|
||||||
|
targetValues: JSON.stringify(formatted),
|
||||||
|
};
|
||||||
|
await db('deviceConfig').update(confToUpdate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getTarget({ initial = false }: { initial?: boolean } = {}) {
|
||||||
|
const [unmanaged, [devConfig]] = await Promise.all([
|
||||||
|
this.config.get('unmanaged'),
|
||||||
|
this.db.models('deviceConfig').select('targetValues'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
let conf: Dictionary<string>;
|
||||||
|
try {
|
||||||
|
conf = JSON.parse(devConfig.targetValues);
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error(`Corrupted supervisor database! Error: ${e.message}`);
|
||||||
|
}
|
||||||
|
if (initial || conf.SUPERVISOR_VPN_CONTROL == null) {
|
||||||
|
conf.SUPERVISOR_VPN_CONTROL = 'true';
|
||||||
|
}
|
||||||
|
if (unmanaged && conf.SUPERVISOR_LOCAL_MODE == null) {
|
||||||
|
conf.SUPERVISOR_LOCAL_MODE = 'true';
|
||||||
|
}
|
||||||
|
|
||||||
|
_.defaults(
|
||||||
|
conf,
|
||||||
|
_(DeviceConfig.configKeys)
|
||||||
|
.mapKeys('envVarName')
|
||||||
|
.mapValues('defaultValue')
|
||||||
|
.value(),
|
||||||
|
);
|
||||||
|
|
||||||
|
return conf;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getCurrent() {
|
||||||
|
const conf = await this.config.getMany(
|
||||||
|
['deviceType'].concat(_.keys(DeviceConfig.configKeys)),
|
||||||
|
);
|
||||||
|
|
||||||
|
const configBackend = await this.getConfigBackend();
|
||||||
|
|
||||||
|
const [vpnStatus, bootConfig] = await Promise.all([
|
||||||
|
this.getVPNEnabled(),
|
||||||
|
this.getBootConfig(configBackend),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const currentConf: Dictionary<string> = {
|
||||||
|
// TODO: Fix this mess of half strings half boolean values everywhere
|
||||||
|
SUPERVISOR_VPN_CONTROL: vpnStatus != null ? vpnStatus.toString() : 'true',
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const key in DeviceConfig.configKeys) {
|
||||||
|
const { envVarName } = DeviceConfig.configKeys[key];
|
||||||
|
const confValue = conf[key];
|
||||||
|
currentConf[envVarName] = confValue != null ? confValue.toString() : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return _.assign(currentConf, bootConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async formatConfigKeys(
|
||||||
|
conf: Dictionary<string>,
|
||||||
|
): Promise<Dictionary<any>> {
|
||||||
|
const backend = await this.getConfigBackend();
|
||||||
|
return await configUtils.formatConfigKeys(
|
||||||
|
backend,
|
||||||
|
DeviceConfig.validKeys,
|
||||||
|
conf,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getDefaults() {
|
||||||
|
return _.extend(
|
||||||
|
{
|
||||||
|
SUPERVISOR_VPN_CONTROL: 'true',
|
||||||
|
},
|
||||||
|
_.mapValues(
|
||||||
|
_.mapKeys(DeviceConfig.configKeys, 'envVarName'),
|
||||||
|
'defaultValues',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bootConfigChangeRequired(
|
||||||
|
configBackend: DeviceConfigBackend | null,
|
||||||
|
current: Dictionary<string>,
|
||||||
|
target: Dictionary<string>,
|
||||||
|
): boolean {
|
||||||
|
const targetBootConfig = configUtils.envToBootConfig(configBackend, target);
|
||||||
|
const currentBootConfig = configUtils.envToBootConfig(
|
||||||
|
configBackend,
|
||||||
|
current,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!_.isEqual(currentBootConfig, targetBootConfig)) {
|
||||||
|
_.each(targetBootConfig, (value, key) => {
|
||||||
|
// Ignore null check because we can't get here if configBackend is null
|
||||||
|
if (!configBackend!.isSupportedConfig(key)) {
|
||||||
|
if (currentBootConfig[key] !== value) {
|
||||||
|
const err = `Attempt to change blacklisted config value ${key}`;
|
||||||
|
this.logger.logSystemMessage(
|
||||||
|
err,
|
||||||
|
{ error: err },
|
||||||
|
'Apply boot config error',
|
||||||
|
);
|
||||||
|
throw new Error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getRequiredSteps(
|
||||||
|
currentState: DeviceApplicationState,
|
||||||
|
targetState: DeviceApplicationState,
|
||||||
|
): Promise<ConfigStep[]> {
|
||||||
|
const current: Dictionary<string> = _.get(
|
||||||
|
currentState,
|
||||||
|
['local', 'config'],
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
const target: Dictionary<string> = _.get(
|
||||||
|
targetState,
|
||||||
|
['local', 'config'],
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
|
||||||
|
const steps: ConfigStep[] = [];
|
||||||
|
|
||||||
|
const unmanaged = await this.config.get('unmanaged');
|
||||||
|
const backend = await this.getConfigBackend();
|
||||||
|
|
||||||
|
const configChanges: Dictionary<string> = {};
|
||||||
|
const humanReadableConfigChanges: Dictionary<string> = {};
|
||||||
|
let reboot = false;
|
||||||
|
|
||||||
|
// If the legacy lock override is used, place it as the new variable
|
||||||
|
if (checkTruthy(target['OVERRIDE_LOCK'])) {
|
||||||
|
target['SUPERVISOR_OVERRIDE_LOCK'] = target['OVERRIDE_LOCK'];
|
||||||
|
}
|
||||||
|
|
||||||
|
_.each(
|
||||||
|
DeviceConfig.configKeys,
|
||||||
|
({ envVarName, varType, rebootRequired }, key) => {
|
||||||
|
// Test if the key is different
|
||||||
|
if (
|
||||||
|
!DeviceConfig.configTest(
|
||||||
|
varType,
|
||||||
|
current[envVarName],
|
||||||
|
target[envVarName],
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
// Save the change if it is
|
||||||
|
configChanges[key] = target[envVarName];
|
||||||
|
humanReadableConfigChanges[envVarName] = target[envVarName];
|
||||||
|
reboot = rebootRequired || reboot;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!_.isEmpty(configChanges)) {
|
||||||
|
steps.push({
|
||||||
|
action: 'changeConfig',
|
||||||
|
target: configChanges,
|
||||||
|
humanReadableTarget: humanReadableConfigChanges,
|
||||||
|
rebootRequired: reboot,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for special case actions for the VPN
|
||||||
|
if (
|
||||||
|
!checkTruthy(unmanaged || false) &&
|
||||||
|
!_.isEmpty(target['SUPERVISOR_VPN_CONTROL']) &&
|
||||||
|
DeviceConfig.checkBoolChanged(current, target, 'SUPERVISOR_VPN_CONTROL')
|
||||||
|
) {
|
||||||
|
steps.push({
|
||||||
|
action: 'setVPNEnabled',
|
||||||
|
target: target['SUPERVISOR_VPN_CONTROL'],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do we need to change the boot config?
|
||||||
|
if (this.bootConfigChangeRequired(backend, current, target)) {
|
||||||
|
steps.push({
|
||||||
|
action: 'setBootConfig',
|
||||||
|
target,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_.isEmpty(steps) && this.rebootRequired) {
|
||||||
|
steps.push({
|
||||||
|
action: 'reboot',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return steps;
|
||||||
|
}
|
||||||
|
|
||||||
|
public executeStepAction(step: ConfigStep, opts: DeviceActionExecutorOpts) {
|
||||||
|
if (step.action !== 'reboot') {
|
||||||
|
return this.actionExecutors[step.action](step, opts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public isValidAction(action: string): boolean {
|
||||||
|
return _.includes(_.keys(this.actionExecutors), action);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getBootConfig(
|
||||||
|
backend: DeviceConfigBackend | null,
|
||||||
|
): Promise<EnvVarObject> {
|
||||||
|
if (backend == null) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const conf = await backend.getBootConfig();
|
||||||
|
return configUtils.bootConfigToEnv(backend, conf);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async setBootConfig(
|
||||||
|
backend: DeviceConfigBackend | null,
|
||||||
|
target: Dictionary<string>,
|
||||||
|
) {
|
||||||
|
if (backend == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const conf = configUtils.envToBootConfig(backend, target);
|
||||||
|
this.logger.logSystemMessage(
|
||||||
|
`Applying boot config: ${JSON.stringify(conf)}`,
|
||||||
|
{},
|
||||||
|
'Apply boot config in progress',
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await backend.setBootConfig(conf);
|
||||||
|
this.logger.logSystemMessage(
|
||||||
|
`Applied boot config: ${JSON.stringify(conf)}`,
|
||||||
|
{},
|
||||||
|
'Apply boot config success',
|
||||||
|
);
|
||||||
|
this.rebootRequired = true;
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
this.logger.logSystemMessage(
|
||||||
|
`Error setting boot config: ${err}`,
|
||||||
|
{ error: err },
|
||||||
|
'Apply boot config error',
|
||||||
|
);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getVPNEnabled(): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const activeState = await systemd.serviceActiveState(vpnServiceName);
|
||||||
|
return !_.includes(['inactive', 'deactivating'], activeState);
|
||||||
|
} catch (e) {
|
||||||
|
if (UnitNotLoadedError(e)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async setVPNEnabled(value?: string | boolean) {
|
||||||
|
const v = checkTruthy(value || true);
|
||||||
|
const enable = v != null ? v : true;
|
||||||
|
|
||||||
|
if (enable) {
|
||||||
|
await systemd.startService(vpnServiceName);
|
||||||
|
} else {
|
||||||
|
await systemd.stopService(vpnServiceName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static configTest(method: string, a: string, b: string): boolean {
|
||||||
|
switch (method) {
|
||||||
|
case 'bool':
|
||||||
|
return checkTruthy(a) === checkTruthy(b);
|
||||||
|
case 'int':
|
||||||
|
return checkInt(a) === checkInt(b);
|
||||||
|
default:
|
||||||
|
throw new Error('Incorrect datatype passed to DeviceConfig.configTest');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static checkBoolChanged(
|
||||||
|
current: Dictionary<string>,
|
||||||
|
target: Dictionary<string>,
|
||||||
|
key: string,
|
||||||
|
): boolean {
|
||||||
|
return checkTruthy(current[key]) !== checkTruthy(target[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DeviceConfig;
|
@ -19,7 +19,7 @@ updateLock = require './lib/update-lock'
|
|||||||
{ singleToMulticontainerApp } = require './lib/migration'
|
{ singleToMulticontainerApp } = require './lib/migration'
|
||||||
{ ENOENT, EISDIR, NotFoundError, UpdatesLockedError } = require './lib/errors'
|
{ ENOENT, EISDIR, NotFoundError, UpdatesLockedError } = require './lib/errors'
|
||||||
|
|
||||||
DeviceConfig = require './device-config'
|
{ DeviceConfig } = require './device-config'
|
||||||
ApplicationManager = require './application-manager'
|
ApplicationManager = require './application-manager'
|
||||||
|
|
||||||
validateLocalState = (state) ->
|
validateLocalState = (state) ->
|
||||||
@ -534,7 +534,7 @@ module.exports = class DeviceState extends EventEmitter
|
|||||||
|
|
||||||
executeStepAction: (step, { force, initial, skipLock }) =>
|
executeStepAction: (step, { force, initial, skipLock }) =>
|
||||||
Promise.try =>
|
Promise.try =>
|
||||||
if _.includes(@deviceConfig.validActions, step.action)
|
if @deviceConfig.isValidAction(step.action)
|
||||||
@deviceConfig.executeStepAction(step, { initial })
|
@deviceConfig.executeStepAction(step, { initial })
|
||||||
else if _.includes(@applications.validActions, step.action)
|
else if _.includes(@applications.validActions, step.action)
|
||||||
@applications.executeStepAction(step, { force, skipLock })
|
@applications.executeStepAction(step, { force, skipLock })
|
||||||
|
@ -1,127 +0,0 @@
|
|||||||
Promise = require 'bluebird'
|
|
||||||
_ = require 'lodash'
|
|
||||||
systemd = require './lib/systemd'
|
|
||||||
path = require 'path'
|
|
||||||
constants = require './lib/constants'
|
|
||||||
fs = Promise.promisifyAll(require('fs'))
|
|
||||||
{ writeFileAtomic } = require './lib/fs-utils'
|
|
||||||
mkdirp = Promise.promisify(require('mkdirp'))
|
|
||||||
|
|
||||||
ENOENT = (err) -> err.code is 'ENOENT'
|
|
||||||
|
|
||||||
redsocksHeader = '''
|
|
||||||
base {
|
|
||||||
log_debug = off;
|
|
||||||
log_info = on;
|
|
||||||
log = stderr;
|
|
||||||
daemon = off;
|
|
||||||
redirector = iptables;
|
|
||||||
}
|
|
||||||
|
|
||||||
redsocks {
|
|
||||||
local_ip = 127.0.0.1;
|
|
||||||
local_port = 12345;
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
redsocksFooter = '}\n'
|
|
||||||
|
|
||||||
proxyFields = [ 'type', 'ip', 'port', 'login', 'password' ]
|
|
||||||
|
|
||||||
proxyBasePath = path.join(constants.rootMountPoint, constants.bootMountPoint, 'system-proxy')
|
|
||||||
redsocksConfPath = path.join(proxyBasePath, 'redsocks.conf')
|
|
||||||
noProxyPath = path.join(proxyBasePath, 'no_proxy')
|
|
||||||
|
|
||||||
readProxy = ->
|
|
||||||
fs.readFileAsync(redsocksConfPath)
|
|
||||||
.then (redsocksConf) ->
|
|
||||||
lines = new String(redsocksConf).split('\n')
|
|
||||||
conf = {}
|
|
||||||
for line in lines
|
|
||||||
for proxyField in proxyFields
|
|
||||||
if proxyField in [ 'login', 'password' ]
|
|
||||||
m = line.match(new RegExp(proxyField + '\\s*=\\s*\"(.*)\"\\s*;'))
|
|
||||||
else
|
|
||||||
m = line.match(new RegExp(proxyField + '\\s*=\\s*([^;\\s]*)\\s*;'))
|
|
||||||
if m?
|
|
||||||
conf[proxyField] = m[1]
|
|
||||||
return conf
|
|
||||||
.catch ENOENT, ->
|
|
||||||
return null
|
|
||||||
.then (conf) ->
|
|
||||||
if !conf?
|
|
||||||
return null
|
|
||||||
else
|
|
||||||
fs.readFileAsync(noProxyPath)
|
|
||||||
.then (noProxy) ->
|
|
||||||
conf.noProxy = new String(noProxy).split('\n')
|
|
||||||
return conf
|
|
||||||
.catch ENOENT, ->
|
|
||||||
return conf
|
|
||||||
|
|
||||||
generateRedsocksConfEntries = (conf) ->
|
|
||||||
val = ''
|
|
||||||
for field in proxyFields
|
|
||||||
if conf[field]?
|
|
||||||
v = conf[field]
|
|
||||||
if field in [ 'login', 'password' ]
|
|
||||||
v = "\"#{v}\""
|
|
||||||
val += "\t#{field} = #{v};\n"
|
|
||||||
return val
|
|
||||||
|
|
||||||
setProxy = (conf) ->
|
|
||||||
Promise.try ->
|
|
||||||
if _.isEmpty(conf)
|
|
||||||
fs.unlinkAsync(redsocksConfPath)
|
|
||||||
.catch(ENOENT, _.noop)
|
|
||||||
.then ->
|
|
||||||
fs.unlinkAsync(noProxyPath)
|
|
||||||
.catch(ENOENT, _.noop)
|
|
||||||
else
|
|
||||||
mkdirp(proxyBasePath)
|
|
||||||
.then ->
|
|
||||||
if _.isArray(conf.noProxy)
|
|
||||||
writeFileAtomic(noProxyPath, conf.noProxy.join('\n'))
|
|
||||||
.then ->
|
|
||||||
redsocksConf = ''
|
|
||||||
redsocksConf += redsocksHeader
|
|
||||||
redsocksConf += generateRedsocksConfEntries(conf)
|
|
||||||
redsocksConf += redsocksFooter
|
|
||||||
writeFileAtomic(redsocksConfPath, redsocksConf)
|
|
||||||
.then ->
|
|
||||||
systemd.restartService('resin-proxy-config')
|
|
||||||
.then ->
|
|
||||||
systemd.restartService('redsocks')
|
|
||||||
|
|
||||||
hostnamePath = path.join(constants.rootMountPoint, '/etc/hostname')
|
|
||||||
readHostname = ->
|
|
||||||
fs.readFileAsync(hostnamePath)
|
|
||||||
.then (hostnameData) ->
|
|
||||||
return _.trim(new String(hostnameData))
|
|
||||||
|
|
||||||
setHostname = (val, configModel) ->
|
|
||||||
configModel.set(hostname: val)
|
|
||||||
.then ->
|
|
||||||
systemd.restartService('resin-hostname')
|
|
||||||
|
|
||||||
|
|
||||||
exports.get = ->
|
|
||||||
Promise.join(
|
|
||||||
readProxy()
|
|
||||||
readHostname()
|
|
||||||
(proxy, hostname) ->
|
|
||||||
return {
|
|
||||||
network: {
|
|
||||||
proxy
|
|
||||||
hostname
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
exports.patch = (conf, configModel) ->
|
|
||||||
Promise.try ->
|
|
||||||
if !_.isUndefined(conf?.network?.proxy)
|
|
||||||
setProxy(conf.network.proxy)
|
|
||||||
.then ->
|
|
||||||
if !_.isUndefined(conf?.network?.hostname)
|
|
||||||
setHostname(conf.network.hostname, configModel)
|
|
182
src/host-config.ts
Normal file
182
src/host-config.ts
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
import * as Bluebird from 'bluebird';
|
||||||
|
import { stripIndent } from 'common-tags';
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
import * as mkdirCb from 'mkdirp';
|
||||||
|
import { fs } from 'mz';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
|
import Config = require('./config');
|
||||||
|
import * as constants from './lib/constants';
|
||||||
|
import { ENOENT } from './lib/errors';
|
||||||
|
import { writeFileAtomic } from './lib/fs-utils';
|
||||||
|
import * as systemd from './lib/systemd';
|
||||||
|
|
||||||
|
const mkdirp = Bluebird.promisify(mkdirCb) as (
|
||||||
|
path: string,
|
||||||
|
opts?: any,
|
||||||
|
) => Bluebird<mkdirCb.Made>;
|
||||||
|
|
||||||
|
const redsocksHeader = stripIndent`
|
||||||
|
base {
|
||||||
|
log_debug = off;
|
||||||
|
log_info = on;
|
||||||
|
log = stderr;
|
||||||
|
daemon = off;
|
||||||
|
redirector = iptables;
|
||||||
|
}
|
||||||
|
|
||||||
|
redsocks {
|
||||||
|
local_ip = 127.0.0.1;
|
||||||
|
local_port = 12345;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const redsocksFooter = '}\n';
|
||||||
|
|
||||||
|
const proxyFields = ['type', 'ip', 'port', 'login', 'password'];
|
||||||
|
|
||||||
|
const proxyBasePath = path.join(
|
||||||
|
constants.rootMountPoint,
|
||||||
|
constants.bootMountPoint,
|
||||||
|
'system-proxy',
|
||||||
|
);
|
||||||
|
const redsocksConfPath = path.join(proxyBasePath, 'redsocks.conf');
|
||||||
|
const noProxyPath = path.join(proxyBasePath, 'no_proxy');
|
||||||
|
|
||||||
|
interface ProxyConfig {
|
||||||
|
[key: string]: string | string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface HostConfig {
|
||||||
|
network: {
|
||||||
|
proxy?: ProxyConfig;
|
||||||
|
hostname?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const isAuthField = (field: string): boolean =>
|
||||||
|
_.includes(['login', 'password'], field);
|
||||||
|
|
||||||
|
const memoizedAuthRegex = _.memoize(
|
||||||
|
(proxyField: string) => new RegExp(proxyField + '\\s*=\\s*"(.*)"\\s*;'),
|
||||||
|
);
|
||||||
|
|
||||||
|
const memoizedRegex = _.memoize(
|
||||||
|
proxyField => new RegExp(proxyField + '\\s*=\\s*([^;\\s]*)\\s*;'),
|
||||||
|
);
|
||||||
|
|
||||||
|
async function readProxy(): Promise<ProxyConfig | undefined> {
|
||||||
|
const conf: ProxyConfig = {};
|
||||||
|
let redsocksConf: string;
|
||||||
|
try {
|
||||||
|
redsocksConf = await fs.readFile(redsocksConfPath, 'utf-8');
|
||||||
|
} catch (e) {
|
||||||
|
if (!ENOENT(e)) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const lines = redsocksConf.split('\n');
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
for (const proxyField of proxyFields) {
|
||||||
|
let match: string[] | null = null;
|
||||||
|
if (isAuthField(proxyField)) {
|
||||||
|
match = line.match(memoizedAuthRegex(proxyField));
|
||||||
|
} else {
|
||||||
|
match = line.match(memoizedRegex(proxyField));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (match != null) {
|
||||||
|
conf[proxyField] = match[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const noProxy = await fs.readFile(noProxyPath, 'utf-8');
|
||||||
|
conf.noProxy = noProxy.split('\n');
|
||||||
|
} catch (e) {
|
||||||
|
if (!ENOENT(e)) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return conf;
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateRedsocksConfEntries(conf: ProxyConfig): string {
|
||||||
|
let val = '';
|
||||||
|
for (const field of proxyFields) {
|
||||||
|
let v = conf[field];
|
||||||
|
if (v != null) {
|
||||||
|
if (isAuthField(field)) {
|
||||||
|
v = `"${v}"`;
|
||||||
|
}
|
||||||
|
val += `\t${field} = ${v};\n`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setProxy(maybeConf: ProxyConfig | null): Promise<void> {
|
||||||
|
if (_.isEmpty(maybeConf)) {
|
||||||
|
try {
|
||||||
|
await Promise.all([fs.unlink(redsocksConfPath), fs.unlink(noProxyPath)]);
|
||||||
|
} catch (e) {
|
||||||
|
if (!ENOENT(e)) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// We know that maybeConf is not null due to the _.isEmpty check above,
|
||||||
|
// but the compiler doesn't
|
||||||
|
const conf = maybeConf as ProxyConfig;
|
||||||
|
await mkdirp(proxyBasePath);
|
||||||
|
if (_.isArray(conf.noProxy)) {
|
||||||
|
await writeFileAtomic(noProxyPath, conf.noProxy.join('\n'));
|
||||||
|
}
|
||||||
|
const redsocksConf = `${redsocksHeader}${generateRedsocksConfEntries(
|
||||||
|
conf,
|
||||||
|
)}${redsocksFooter}`;
|
||||||
|
await writeFileAtomic(redsocksConfPath, redsocksConf);
|
||||||
|
}
|
||||||
|
|
||||||
|
await systemd.restartService('resin-proxy-config');
|
||||||
|
await systemd.restartService('redsocks');
|
||||||
|
}
|
||||||
|
|
||||||
|
const hostnamePath = path.join(constants.rootMountPoint, '/etc/hostname');
|
||||||
|
async function readHostname() {
|
||||||
|
const hostnameData = await fs.readFile(hostnamePath, 'utf-8');
|
||||||
|
return _.trim(hostnameData);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setHostname(val: string, configModel: Config) {
|
||||||
|
await configModel.set({ hostname: val });
|
||||||
|
await systemd.restartService('resin-hostname');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't use async/await here to maintain the bluebird
|
||||||
|
// promises being returned
|
||||||
|
export function get(): Bluebird<HostConfig> {
|
||||||
|
return Bluebird.join(readProxy(), readHostname(), (proxy, hostname) => {
|
||||||
|
return {
|
||||||
|
network: {
|
||||||
|
proxy,
|
||||||
|
hostname,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function patch(conf: HostConfig, configModel: Config): Bluebird<void> {
|
||||||
|
const promises = [];
|
||||||
|
if (conf != null && conf.network != null) {
|
||||||
|
if (conf.network.proxy != null) {
|
||||||
|
promises.push(setProxy(conf.network.proxy));
|
||||||
|
}
|
||||||
|
if (conf.network.hostname != null) {
|
||||||
|
promises.push(setHostname(conf.network.hostname, configModel));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Bluebird.all(promises).return();
|
||||||
|
}
|
@ -3,7 +3,7 @@ import * as Docker from 'dockerode';
|
|||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
import Config = require('./config');
|
import Config = require('./config');
|
||||||
import Database = require('./db');
|
import Database from './db';
|
||||||
import { checkTruthy } from './lib/validation';
|
import { checkTruthy } from './lib/validation';
|
||||||
import { Logger } from './logger';
|
import { Logger } from './logger';
|
||||||
|
|
||||||
|
@ -315,3 +315,5 @@ export class Logger {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default Logger;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
EventEmitter = require 'events'
|
EventEmitter = require 'events'
|
||||||
|
|
||||||
{ EventTracker } = require './event-tracker'
|
{ EventTracker } = require './event-tracker'
|
||||||
DB = require './db'
|
{ DB } = require './db'
|
||||||
Config = require './config'
|
Config = require './config'
|
||||||
APIBinder = require './api-binder'
|
APIBinder = require './api-binder'
|
||||||
DeviceState = require './device-state'
|
DeviceState = require './device-state'
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
export interface DeviceApplicationState {
|
export interface DeviceApplicationState {
|
||||||
local: {
|
local?: {
|
||||||
[appId: string]: {
|
config?: Dictionary<string>;
|
||||||
services: {
|
apps?: {
|
||||||
[serviceId: string]: {
|
[appId: string]: {
|
||||||
status: string;
|
services: {
|
||||||
releaseId: number;
|
[serviceId: string]: {
|
||||||
download_progress: number | null;
|
status: string;
|
||||||
|
releaseId: number;
|
||||||
|
download_progress: number | null;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -4,7 +4,7 @@ m = require 'mochainon'
|
|||||||
{ expect } = m.chai
|
{ expect } = m.chai
|
||||||
fs = Promise.promisifyAll(require('fs'))
|
fs = Promise.promisifyAll(require('fs'))
|
||||||
Knex = require('knex')
|
Knex = require('knex')
|
||||||
DB = require('../src/db')
|
{ DB } = require('../src/db')
|
||||||
|
|
||||||
createOldDatabase = (path) ->
|
createOldDatabase = (path) ->
|
||||||
knex = new Knex(
|
knex = new Knex(
|
||||||
|
@ -5,7 +5,7 @@ m = require 'mochainon'
|
|||||||
fs = Promise.promisifyAll(require('fs'))
|
fs = Promise.promisifyAll(require('fs'))
|
||||||
m.chai.use(require('chai-events'))
|
m.chai.use(require('chai-events'))
|
||||||
|
|
||||||
DB = require('../src/db')
|
{ DB } = require('../src/db')
|
||||||
Config = require('../src/config')
|
Config = require('../src/config')
|
||||||
constants = require('../src/lib/constants')
|
constants = require('../src/lib/constants')
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ m.chai.use(require('chai-events'))
|
|||||||
|
|
||||||
prepare = require './lib/prepare'
|
prepare = require './lib/prepare'
|
||||||
DeviceState = require '../src/device-state'
|
DeviceState = require '../src/device-state'
|
||||||
DB = require('../src/db')
|
{ DB } = require('../src/db')
|
||||||
Config = require('../src/config')
|
Config = require('../src/config')
|
||||||
{ RPiConfigBackend } = require('../src/config/backend')
|
{ RPiConfigBackend } = require('../src/config/backend')
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ m = require 'mochainon'
|
|||||||
{ expect } = m.chai
|
{ expect } = m.chai
|
||||||
{ stub, spy } = m.sinon
|
{ stub, spy } = m.sinon
|
||||||
|
|
||||||
DB = require('../src/db')
|
{ DB } = require('../src/db')
|
||||||
Config = require('../src/config')
|
Config = require('../src/config')
|
||||||
DeviceState = require('../src/device-state')
|
DeviceState = require('../src/device-state')
|
||||||
APIBinder = require('../src/api-binder')
|
APIBinder = require('../src/api-binder')
|
||||||
|
@ -8,7 +8,7 @@ m = require 'mochainon'
|
|||||||
prepare = require './lib/prepare'
|
prepare = require './lib/prepare'
|
||||||
fsUtils = require '../src/lib/fs-utils'
|
fsUtils = require '../src/lib/fs-utils'
|
||||||
|
|
||||||
DeviceConfig = require '../src/device-config'
|
{ DeviceConfig } = require '../src/device-config'
|
||||||
{ ExtlinuxConfigBackend, RPiConfigBackend } = require '../src/config/backend'
|
{ ExtlinuxConfigBackend, RPiConfigBackend } = require '../src/config/backend'
|
||||||
|
|
||||||
extlinuxBackend = new ExtlinuxConfigBackend()
|
extlinuxBackend = new ExtlinuxConfigBackend()
|
||||||
|
@ -8,7 +8,7 @@ m.chai.use(require('chai-events'))
|
|||||||
|
|
||||||
prepare = require './lib/prepare'
|
prepare = require './lib/prepare'
|
||||||
DeviceState = require '../src/device-state'
|
DeviceState = require '../src/device-state'
|
||||||
DB = require('../src/db')
|
{ DB } = require('../src/db')
|
||||||
Config = require('../src/config')
|
Config = require('../src/config')
|
||||||
{ Service } = require '../src/compose/service'
|
{ Service } = require '../src/compose/service'
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user