signalk-server
Version:
An implementation of a [Signal K](http://signalk.org) server for boats.
423 lines (422 loc) • 15.6 kB
JavaScript
/* eslint-disable @typescript-eslint/no-require-imports */
/* eslint-disable @typescript-eslint/no-explicit-any */
/*
* Copyright 2014-2015 Fabian Tollenaar <fabian@starting-point.nl>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.load = load;
exports.readDefaultsFile = readDefaultsFile;
exports.sendBaseDeltas = sendBaseDeltas;
exports.writeDefaultsFile = writeDefaultsFile;
exports.writeBaseDeltasFileSync = writeBaseDeltasFileSync;
exports.writeBaseDeltasFile = writeBaseDeltasFile;
exports.writeSettingsFile = writeSettingsFile;
const fs_1 = __importDefault(require("fs"));
const node_os_1 = __importDefault(require("node:os"));
const lodash_1 = __importDefault(require("lodash"));
const path_1 = __importDefault(require("path"));
const semver_1 = __importDefault(require("semver"));
const uuid_1 = require("uuid");
const debug_1 = require("../debug");
const deltaeditor_1 = __importDefault(require("../deltaeditor"));
const ports_1 = require("../ports");
const debug = (0, debug_1.createDebug)('signalk-server:config');
let disableWriteSettings = false;
// use dynamic path so that ts compiler does not detect this
// json file, as ts compile needs to copy all (other) used
// json files under /dist
// tslint:disable-next-line
const packageJson = require('../../' + 'package.json');
function load(app) {
app.argv = require('minimist')(process.argv.slice(2));
const config = (app.config = app.config || {});
const env = (app.env = process.env);
config.getExternalHostname = getExternalHostname.bind(config, config);
config.getExternalPort = ports_1.getExternalPort.bind(config, app);
config.appPath = config.appPath || path_1.default.normalize(__dirname + '/../../');
debug('appPath:' + config.appPath);
try {
config.name = packageJson.name;
config.author = packageJson.author;
config.contributors = packageJson.contributors;
config.version = packageJson.version;
config.description = packageJson.description;
//if dependencies are installed from tarballs like in
//master docker build the version will be like
//file:signalk-server-admin-ui-1.44.1.tgz
if (!process.env.SKIP_ADMINUI_VERSION_CHECK) {
checkPackageVersion('@signalk/server-admin-ui', packageJson, app.config.appPath);
}
}
catch (err) {
console.error('error parsing package.json', err);
process.exit(1);
}
setConfigDirectory(app);
app.config.baseDeltaEditor = new deltaeditor_1.default();
if (lodash_1.default.isObject(app.config.settings)) {
debug('Using settings from constructor call, not reading defaults');
disableWriteSettings = true;
if (config.defaults) {
convertOldDefaultsToDeltas(app.config.baseDeltaEditor, config.defaults);
}
}
else {
readSettingsFile(app);
if (!setBaseDeltas(app)) {
const defaults = getFullDefaults(app);
if (defaults) {
convertOldDefaultsToDeltas(app.config.baseDeltaEditor, defaults);
if (typeof app.config.settings.useBaseDeltas === 'undefined' ||
app.config.settings.useBaseDeltas) {
writeBaseDeltasFileSync(app);
}
else {
app.config.hasOldDefaults = true;
}
}
}
}
setSelfSettings(app);
if (app.argv['sample-nmea0183-data']) {
const sample = path_1.default.join(app.config.appPath, 'samples/plaka.log');
console.log(`Using sample data from ${sample}`);
app.config.settings.pipedProviders.push({
id: 'nmea0183-sample-data',
pipeElements: [
{
type: 'providers/simple',
options: {
logging: false,
type: 'FileStream',
subOptions: {
dataType: 'NMEA0183',
filename: sample
}
}
}
],
enabled: true
});
}
if (app.argv['sample-n2k-data']) {
const sample = path_1.default.join(app.config.appPath, 'samples/aava-n2k.data');
console.log(`Using sample data from ${sample}`);
app.config.settings.pipedProviders.push({
id: 'n2k-sample-data',
pipeElements: [
{
type: 'providers/simple',
options: {
logging: false,
type: 'FileStream',
subOptions: {
dataType: 'NMEA2000JS',
filename: sample
}
}
}
],
enabled: true
});
}
if (app.argv['override-timestamps']) {
app.config.overrideTimestampWithNow = true;
}
if (app.argv.securityenabled && !app.config.security) {
app.config.settings.security = { strategy: './tokensecurity' };
}
if (env.SSLPORT) {
config.settings.ssl = true;
}
if (!lodash_1.default.isUndefined(app.env.WSCOMPRESSION)) {
config.settings.wsCompression =
app.env.WSCOMPRESSION.toLowerCase() === 'true';
}
if (config.settings.landingPage &&
config.settings.landingPage.charAt(0) !== '/') {
console.error(`invalid rootUri: ${config.settings.landingPage}`);
process.exit(1);
}
require('./development')(app);
require('./production')(app);
}
function checkPackageVersion(name, pkg, appPath) {
const expected = pkg.dependencies[name];
let modulePackageJsonPath = path_1.default.join(appPath, 'node_modules', name, 'package.json');
if (!fs_1.default.existsSync(modulePackageJsonPath)) {
modulePackageJsonPath = path_1.default.join(appPath, '..', name, 'package.json');
}
const installed = require(modulePackageJsonPath);
if (!semver_1.default.satisfies(installed.version, expected)) {
console.error(`invalid version of the ${name} package is installed ${installed.version} !== ${expected}`);
process.exit(1);
}
}
// Establish what the config directory path is.
function getConfigDirectory({ argv, config, env }) {
// Possible paths in order of priority.
const configPaths = [
env.SIGNALK_NODE_CONDFIG_DIR,
env.SIGNALK_NODE_CONFIG_DIR,
config.configPath,
argv.c,
argv.s && config.appPath,
env.HOME && path_1.default.join(env.HOME, '.signalk'),
config.appPath
];
// Find first config directory path that has a truthy value.
const configPath = path_1.default.resolve(lodash_1.default.find(configPaths));
debug('configDirPath: ' + configPath);
return configPath;
}
// Create directories and set app.config.configPath.
function setConfigDirectory(app) {
app.config.configPath = getConfigDirectory(app);
if (app.config.configPath !== app.config.appPath) {
if (!fs_1.default.existsSync(app.config.configPath)) {
fs_1.default.mkdirSync(app.config.configPath);
debug(`configDir Created: ${app.config.configPath}`);
}
const configPackage = path_1.default.join(app.config.configPath, 'package.json');
if (!fs_1.default.existsSync(configPackage)) {
fs_1.default.writeFileSync(configPackage, JSON.stringify(pluginsPackageJsonTemplate, null, 2));
}
const npmrcPath = path_1.default.join(app.config.configPath, '.npmrc');
if (!fs_1.default.existsSync(npmrcPath)) {
fs_1.default.writeFileSync(npmrcPath, 'package-lock=false\n');
}
else {
const contents = fs_1.default.readFileSync(npmrcPath);
if (contents.indexOf('package-lock=') === -1) {
fs_1.default.appendFileSync(npmrcPath, '\npackage-lock=false\n');
}
}
}
}
function getDefaultsPath(app) {
const defaultsFile = app.config.configPath !== app.config.appPath
? 'defaults.json'
: 'settings/defaults.json';
return path_1.default.join(app.config.configPath, defaultsFile);
}
function getBaseDeltasPath(app) {
const defaultsFile = app.config.configPath !== app.config.appPath
? 'baseDeltas.json'
: 'settings/baseDeltas.json';
return path_1.default.join(app.config.configPath, defaultsFile);
}
function readDefaultsFile(app) {
const defaultsPath = getDefaultsPath(app);
const data = fs_1.default.readFileSync(defaultsPath);
return JSON.parse(data.toString());
}
function getFullDefaults(app) {
const defaultsPath = getDefaultsPath(app);
try {
const defaults = readDefaultsFile(app);
debug(`Found defaults at ${defaultsPath.toString()}`);
return defaults;
}
catch (e) {
if (e?.code === 'ENOENT') {
return undefined;
}
else {
console.error(`unable to parse ${defaultsPath.toString()}`);
console.error(e);
process.exit(1);
}
}
return undefined;
}
function setBaseDeltas(app) {
const defaultsPath = getBaseDeltasPath(app);
try {
app.config.baseDeltaEditor.load(defaultsPath);
debug(`Found default deltas at ${defaultsPath.toString()}`);
}
catch (e) {
if (e?.code === 'ENOENT') {
debug(`No default deltas found at ${defaultsPath.toString()}`);
return;
}
else {
console.log(e);
}
}
return true;
}
function sendBaseDeltas(app) {
const copy = JSON.parse(JSON.stringify(app.config.baseDeltaEditor.deltas));
copy.forEach((delta) => {
app.handleMessage('defaults', delta);
});
}
function writeDefaultsFile(app, defaults, cb) {
fs_1.default.writeFile(getDefaultsPath(app), JSON.stringify(defaults, null, 2), cb);
}
function writeBaseDeltasFileSync(app) {
app.config.baseDeltaEditor.saveSync(getBaseDeltasPath(app));
}
function writeBaseDeltasFile(app) {
return app.config.baseDeltaEditor.save(getBaseDeltasPath(app));
}
function setSelfSettings(app) {
const name = app.config.baseDeltaEditor.getSelfValue('name');
const mmsi = app.config.baseDeltaEditor.getSelfValue('mmsi');
let uuid = app.config.baseDeltaEditor.getSelfValue('uuid');
if (mmsi && !lodash_1.default.isString(mmsi)) {
throw new Error(`invalid mmsi: ${mmsi}`);
}
if (uuid && !lodash_1.default.isString(uuid)) {
throw new Error(`invalid uuid: ${uuid}`);
}
if (mmsi === null && uuid === null) {
uuid = 'urn:mrn:signalk:uuid:' + (0, uuid_1.v4)();
app.config.baseDeltaEditor.setSelfValue('uuid', uuid);
}
app.config.vesselName = name;
if (mmsi) {
app.selfType = 'mmsi';
app.selfId = 'urn:mrn:imo:mmsi:' + mmsi;
app.config.vesselMMSI = mmsi;
}
else if (uuid) {
app.selfType = 'uuid';
app.selfId = uuid;
app.config.vesselUUID = uuid;
}
if (app.selfType) {
debug(app.selfType.toUpperCase() + ': ' + app.selfId);
}
app.selfContext = 'vessels.' + app.selfId;
}
function readSettingsFile(app) {
const settings = getSettingsFilename(app);
if (!app.argv.s && !fs_1.default.existsSync(settings)) {
console.log('Settings file does not exist, using empty settings');
app.config.settings = {
pipedProviders: []
};
}
else {
debug('Using settings file: ' + settings);
try {
app.config.settings = require(settings);
}
catch (_e) {
console.error(`Error reading settings file ${settings}, using empty settings`);
app.config.settings = {
pipedProviders: []
};
}
}
if (lodash_1.default.isUndefined(app.config.settings.pipedProviders)) {
app.config.settings.pipedProviders = [];
}
if (lodash_1.default.isUndefined(app.config.settings.interfaces)) {
app.config.settings.interfaces = {};
}
}
function writeSettingsFile(app, settings, cb) {
if (!disableWriteSettings) {
const settingsPath = getSettingsFilename(app);
fs_1.default.writeFile(settingsPath, JSON.stringify(settings, null, 2), cb);
}
else {
cb();
}
}
function getSettingsFilename(app) {
if (process.env.SIGNALK_NODE_SETTINGS) {
debug('Settings filename was set in environment SIGNALK_NODE_SETTINGS, overriding all other options');
return path_1.default.resolve(process.env.SIGNALK_NODE_SETTINGS);
}
const settingsFile = app.argv.s || 'settings.json';
return path_1.default.join(app.config.configPath, settingsFile);
}
function getExternalHostname(config) {
if (process.env.EXTERNALHOST) {
return process.env.EXTERNALHOST;
}
if (config.settings.proxy_host) {
return config.settings.proxy_host;
}
else if (config.settings.hostname) {
return config.settings.hostname;
}
try {
return node_os_1.default.hostname();
}
catch (_ex) {
return 'hostname_not_available';
}
}
function scanDefaults(deltaEditor, vpath, item) {
lodash_1.default.keys(item).forEach((key) => {
const value = item[key];
if (key === 'meta') {
deltaEditor.setMeta('vessels.self', vpath, value);
}
else if (key === 'value') {
deltaEditor.setSelfValue(vpath, value);
}
else if (lodash_1.default.isObject(value)) {
const childPath = vpath.length > 0 ? `${vpath}.${key}` : key;
scanDefaults(deltaEditor, childPath, value);
}
});
}
function convertOldDefaultsToDeltas(deltaEditor, defaults) {
const deltas = [];
const self = lodash_1.default.get(defaults, 'vessels.self');
if (self) {
lodash_1.default.keys(self).forEach((key) => {
const value = self[key];
if (!lodash_1.default.isString(value)) {
scanDefaults(deltaEditor, key, value);
}
else {
deltaEditor.setSelfValue(key, value);
}
});
if (self.communication) {
deltaEditor.setSelfValue('communication', self.communication);
}
}
return deltas;
}
const pluginsPackageJsonTemplate = {
name: 'signalk-server-config',
version: '0.0.1',
description: 'This file is here to track your plugin and webapp installs.',
repository: {},
license: 'Apache-2.0'
};
module.exports = {
load,
getConfigDirectory,
writeSettingsFile,
writeDefaultsFile,
readDefaultsFile,
sendBaseDeltas,
writeBaseDeltasFile,
package: packageJson
};
//# sourceMappingURL=config.js.map