iobroker.js-controller 7.0.4 → 7.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/cjs/lib/upgradeManager.js +17 -11
- package/build/cjs/lib/upgradeManager.js.map +2 -2
- package/build/cjs/main.js +1 -1
- package/build/cjs/main.js.map +2 -2
- package/build/esm/lib/upgradeManager.js +18 -11
- package/build/esm/lib/upgradeManager.js.map +1 -1
- package/build/esm/main.js +1 -1
- package/build/esm/main.js.map +1 -1
- package/build/tsconfig.build.tsbuildinfo +1 -1
- package/io-package.json +2 -2
- package/package.json +12 -12
|
@@ -67,7 +67,6 @@ class UpgradeManager {
|
|
|
67
67
|
this.logger = this.setupLogger();
|
|
68
68
|
this.gid = args.gid;
|
|
69
69
|
this.uid = args.uid;
|
|
70
|
-
this.applyUser();
|
|
71
70
|
}
|
|
72
71
|
/**
|
|
73
72
|
* To prevent commands (including npm) running as root, we apply the passed in gid and uid
|
|
@@ -183,12 +182,12 @@ class UpgradeManager {
|
|
|
183
182
|
*
|
|
184
183
|
* @param params Web server configuration
|
|
185
184
|
*/
|
|
186
|
-
startWebServer(params) {
|
|
185
|
+
async startWebServer(params) {
|
|
187
186
|
const { useHttps } = params;
|
|
188
187
|
if (useHttps) {
|
|
189
|
-
this.startSecureWebServer(params);
|
|
188
|
+
await this.startSecureWebServer(params);
|
|
190
189
|
} else {
|
|
191
|
-
this.startInsecureWebServer(params);
|
|
190
|
+
await this.startInsecureWebServer(params);
|
|
192
191
|
}
|
|
193
192
|
}
|
|
194
193
|
/**
|
|
@@ -237,30 +236,36 @@ class UpgradeManager {
|
|
|
237
236
|
*
|
|
238
237
|
* @param params Web server configuration
|
|
239
238
|
*/
|
|
240
|
-
startInsecureWebServer(params) {
|
|
239
|
+
async startInsecureWebServer(params) {
|
|
241
240
|
const { port } = params;
|
|
242
241
|
this.server = import_node_http.default.createServer((_req, res) => {
|
|
243
242
|
this.webServerCallback(res);
|
|
244
243
|
});
|
|
245
244
|
this.monitorSockets(this.server);
|
|
246
|
-
|
|
247
|
-
this.
|
|
245
|
+
await new Promise((resolve) => {
|
|
246
|
+
this.server.listen(port, () => {
|
|
247
|
+
resolve();
|
|
248
|
+
});
|
|
248
249
|
});
|
|
250
|
+
this.log(`Server is running on http://localhost:${port}`);
|
|
249
251
|
}
|
|
250
252
|
/**
|
|
251
253
|
* Start a secure web server for admin communication
|
|
252
254
|
*
|
|
253
255
|
* @param params Web server configuration
|
|
254
256
|
*/
|
|
255
|
-
startSecureWebServer(params) {
|
|
257
|
+
async startSecureWebServer(params) {
|
|
256
258
|
const { port, certPublic, certPrivate } = params;
|
|
257
259
|
this.server = import_node_https.default.createServer({ key: certPrivate, cert: certPublic }, (_req, res) => {
|
|
258
260
|
this.webServerCallback(res);
|
|
259
261
|
});
|
|
260
262
|
this.monitorSockets(this.server);
|
|
261
|
-
|
|
262
|
-
this.
|
|
263
|
+
await new Promise((resolve) => {
|
|
264
|
+
this.server.listen(port, () => {
|
|
265
|
+
resolve();
|
|
266
|
+
});
|
|
263
267
|
});
|
|
268
|
+
this.log(`Server is running on https://localhost:${port}`);
|
|
264
269
|
}
|
|
265
270
|
/**
|
|
266
271
|
* Keep track of all existing sockets
|
|
@@ -349,7 +354,8 @@ async function main() {
|
|
|
349
354
|
upgradeManager.log("Stopping controller");
|
|
350
355
|
await upgradeManager.stopController();
|
|
351
356
|
upgradeManager.log("Successfully stopped js-controller");
|
|
352
|
-
upgradeManager.startWebServer(webServerParameters);
|
|
357
|
+
await upgradeManager.startWebServer(webServerParameters);
|
|
358
|
+
upgradeManager.applyUser();
|
|
353
359
|
try {
|
|
354
360
|
await upgradeManager.npmInstall();
|
|
355
361
|
} catch (e) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../node_modules/@alcalzone/esm2cjs/shims/import.meta.url/shim.js", "../../../src/lib/upgradeManager.ts"],
|
|
4
|
-
"sourcesContent": ["export const __import_meta_url =\n typeof document === 'undefined' ? new (require('url'.replace('', '')).URL)('file:' + __filename).href :\n (document.currentScript && document.currentScript.src || new URL('main.js', document.baseURI).href)\n", "import { type ChildProcessPromise, exec as execAsync } from 'promisify-child-process';\nimport { tools, logger } from '@iobroker/js-controller-common';\nimport { valid } from 'semver';\nimport { dbConnectAsync } from '@iobroker/js-controller-cli';\nimport http from 'node:http';\nimport https from 'node:https';\nimport type { Client as ObjectsClient } from '@iobroker/db-objects-redis';\nimport { setTimeout as wait } from 'node:timers/promises';\nimport type { Logger } from 'winston';\nimport fs from 'fs-extra';\nimport type { Socket } from 'node:net';\nimport type { Duplex } from 'node:stream';\nimport url from 'node:url';\nimport process from 'node:process';\n\n/** The upgrade arguments provided to the constructor of the UpgradeManager */\nexport interface UpgradeArguments {\n /** Version of controller to upgrade too */\n version: string;\n /** Admin instance which triggered the upgrade */\n adminInstance: number;\n /** User id the process should run as */\n uid: number;\n /** Group id the process should run as */\n gid: number;\n}\n\ninterface Certificates {\n /** Public certificate */\n certPublic: string;\n /** Private certificate */\n certPrivate: string;\n}\n\ninterface InsecureWebServerParameters {\n /** if https should be used for the webserver */\n useHttps: false;\n /** port of the web server */\n port: number;\n}\n\ntype SecureWebServerParameters = Omit<InsecureWebServerParameters, 'useHttps'> & { useHttps: true } & Certificates;\ntype WebServerParameters = InsecureWebServerParameters | SecureWebServerParameters;\n\ninterface GetCertificatesParams {\n /** The objects DB */\n objects: ObjectsClient;\n /** Name of the public certificate */\n certPublicName: string;\n /** Name of the private certificate */\n certPrivateName: string;\n}\n\ninterface ServerResponse {\n /** If the update is still running */\n running: boolean;\n stderr: string[];\n stdout: string[];\n /** if installation process succeeded */\n success?: boolean;\n}\n\nclass UpgradeManager {\n /** Wait ms until controller is stopped */\n private readonly STOP_TIMEOUT_MS = 5_000;\n /** Wait ms for delivery of final response */\n private readonly SHUTDOWN_TIMEOUT = 10_000;\n /** Instance of admin to get information from */\n private readonly adminInstance: number;\n /** Desired controller version */\n private readonly version: string;\n /** Group id the process should run as */\n private readonly gid: number;\n /** User id the process should run as */\n private readonly uid: number;\n /** Response send by webserver */\n private readonly response: ServerResponse = {\n running: true,\n stderr: [],\n stdout: [],\n };\n /** Used to stop the stop shutdown timeout */\n private shutdownAbortController?: AbortController;\n /** Logger to log to file and other transports */\n private readonly logger: Logger;\n\n /** The server used for communicating upgrade status */\n private server?: https.Server | http.Server;\n /** All socket connections of the webserver */\n private sockets = new Set<Socket | Duplex>();\n /** Name of the host for logging purposes */\n private readonly hostname = tools.getHostName();\n\n constructor(args: UpgradeArguments) {\n this.adminInstance = args.adminInstance;\n this.version = args.version;\n this.logger = this.setupLogger();\n this.gid = args.gid;\n this.uid = args.uid;\n\n this.applyUser();\n }\n\n /**\n * To prevent commands (including npm) running as root, we apply the passed in gid and uid\n */\n private applyUser(): void {\n if (!process.setuid || !process.setgid) {\n const errMessage = 'Cannot ensure user and group ids on this system, because no POSIX platform';\n this.log(errMessage, true);\n throw new Error(errMessage);\n }\n\n try {\n process.setgid(this.gid);\n process.setuid(this.uid);\n } catch (e) {\n const errMessage = `Could not ensure user and group ids on this system: ${e.message}`;\n this.log(errMessage, true);\n throw new Error(errMessage);\n }\n }\n\n /**\n * Set up the logger, to stream to file and other configured transports\n */\n private setupLogger(): Logger {\n const config = fs.readJSONSync(tools.getConfigFileName());\n return logger({ ...config.log, noStdout: false });\n }\n\n /**\n * Parse the commands from the cli\n */\n static parseCliCommands(): UpgradeArguments {\n const additionalArgs = process.argv.slice(2);\n\n const version = additionalArgs[0];\n const adminInstance = parseInt(additionalArgs[1]);\n const uid = parseInt(additionalArgs[2]);\n const gid = parseInt(additionalArgs[3]);\n\n const isValid = !!valid(version);\n\n if (!isValid) {\n UpgradeManager.printUsage();\n throw new Error('The provided version is not valid');\n }\n\n if (isNaN(adminInstance)) {\n UpgradeManager.printUsage();\n throw new Error('Please provide a valid admin instance');\n }\n\n if (isNaN(uid)) {\n UpgradeManager.printUsage();\n throw new Error('Please provide a valid uid');\n }\n\n if (isNaN(gid)) {\n UpgradeManager.printUsage();\n throw new Error('Please provide a valid gid');\n }\n\n return { version, adminInstance, uid, gid };\n }\n\n /**\n * Log via console and provide the logs for the server too\n *\n * @param message the message which will be logged\n * @param error if it is an error\n */\n log(message: string, error = false): void {\n if (error) {\n this.logger.error(`host.${this.hostname} [CONTROLLER_AUTO_UPGRADE] ${message}`);\n this.response.stderr.push(message);\n return;\n }\n\n this.logger.info(`host.${this.hostname} [CONTROLLER_AUTO_UPGRADE] ${message}`);\n this.response.stdout.push(message);\n }\n\n /**\n * Stops the js-controller via cli call\n */\n async stopController(): Promise<void> {\n if (tools.isDocker()) {\n await execAsync('/opt/scripts/maintenance.sh on -kbn');\n } else {\n await execAsync(`${tools.appNameLowerCase} stop`);\n }\n await wait(this.STOP_TIMEOUT_MS);\n }\n\n /**\n * Starts the js-controller via cli\n */\n startController(): ChildProcessPromise {\n if (tools.isDocker()) {\n return execAsync('/opt/scripts/maintenance.sh off -y');\n }\n\n return execAsync(`${tools.appNameLowerCase} start`);\n }\n\n /**\n * Print how the module should be used\n */\n static printUsage(): void {\n console.info('Example usage: \"node upgradeManager.js <version> <adminInstance> <uid> <gid>\"');\n }\n\n /**\n * Install given version of js-controller\n */\n async npmInstall(): Promise<void> {\n const res = await tools.installNodeModule(`iobroker.js-controller@${this.version}`, {\n cwd: '/opt/iobroker',\n debug: true,\n });\n\n this.response.stderr.push(...res.stderr.split('\\n'));\n this.response.stdout.push(...res.stdout.split('\\n'));\n\n this.response.success = res.success;\n\n if (!res.success) {\n throw new Error(`Could not install js-controller@${this.version}`);\n }\n }\n\n /**\n * Starts the web server for admin communication either secure or insecure\n *\n * @param params Web server configuration\n */\n startWebServer(params: WebServerParameters): void {\n const { useHttps } = params;\n if (useHttps) {\n this.startSecureWebServer(params);\n } else {\n this.startInsecureWebServer(params);\n }\n }\n\n /**\n * Shuts down the server, restarts the controller and exits the program\n */\n shutdownApp(): void {\n if (this.shutdownAbortController) {\n this.shutdownAbortController.abort();\n }\n\n if (!this.server) {\n process.exit();\n }\n\n this.destroySockets();\n\n this.server.close(async () => {\n await this.startController();\n this.log('Successfully started js-controller');\n\n process.exit();\n });\n }\n\n /**\n * Destroy all sockets, to prevent requests from keeping server alive\n */\n destroySockets(): void {\n for (const socket of this.sockets) {\n socket.destroy();\n this.sockets.delete(socket);\n }\n }\n\n /**\n * This function is called when the webserver receives a message\n *\n * @param res server response\n */\n webServerCallback(res: http.ServerResponse): void {\n res.writeHead(200, {\n 'Access-Control-Allow-Origin': '*',\n });\n\n res.end(JSON.stringify(this.response));\n\n if (!this.response.running) {\n this.log('Final information delivered');\n this.shutdownApp();\n }\n }\n\n /**\n * Start an insecure web server for admin communication\n *\n * @param params Web server configuration\n */\n startInsecureWebServer(params: InsecureWebServerParameters): void {\n const { port } = params;\n\n this.server = http.createServer((_req, res) => {\n this.webServerCallback(res);\n });\n\n this.monitorSockets(this.server);\n\n this.server.listen(port, () => {\n this.log(`Server is running on http://localhost:${port}`);\n });\n }\n\n /**\n * Start a secure web server for admin communication\n *\n * @param params Web server configuration\n */\n startSecureWebServer(params: SecureWebServerParameters): void {\n const { port, certPublic, certPrivate } = params;\n\n this.server = https.createServer({ key: certPrivate, cert: certPublic }, (_req, res) => {\n this.webServerCallback(res);\n });\n\n this.monitorSockets(this.server);\n\n this.server.listen(port, () => {\n this.log(`Server is running on https://localhost:${port}`);\n });\n }\n\n /**\n * Keep track of all existing sockets\n *\n * @param server the webserver\n */\n monitorSockets(server: http.Server | https.Server): void {\n server.on('connection', socket => {\n this.sockets.add(socket);\n\n server.once('close', () => {\n this.sockets.delete(socket);\n });\n });\n }\n\n /**\n * Get certificates from the DB\n *\n * @param params certificate information\n */\n async getCertificates(params: GetCertificatesParams): Promise<Certificates> {\n const { objects, certPublicName, certPrivateName } = params;\n\n const obj = await objects.getObjectAsync('system.certificates');\n\n if (!obj) {\n throw new Error('No certificates found');\n }\n\n const certs = obj.native.certificates;\n\n return { certPrivate: certs[certPrivateName], certPublic: certs[certPublicName] };\n }\n\n /**\n * Collect parameters for webserver from admin instance\n */\n async collectWebServerParameters(): Promise<WebServerParameters> {\n const { objects } = await dbConnectAsync(false);\n\n const obj = await objects.getObjectAsync(`system.adapter.admin.${this.adminInstance}`);\n\n if (!obj) {\n UpgradeManager.printUsage();\n throw new Error('Please provide a valid admin instance');\n }\n\n if (obj.native.secure) {\n const { certPublic: certPublicName, certPrivate: certPrivateName } = obj.native;\n const { certPublic, certPrivate } = await this.getCertificates({\n objects,\n certPublicName,\n certPrivateName,\n });\n\n return {\n useHttps: obj.native.secure,\n port: obj.native.port,\n certPublic,\n certPrivate,\n };\n }\n\n return {\n useHttps: false,\n port: obj.native.port,\n };\n }\n\n /**\n * Tells the upgrade manager, that server can be shut down on next response or on timeout\n */\n async setFinished(): Promise<void> {\n this.response.running = false;\n\n await this.startShutdownTimeout();\n }\n\n /**\n * Start a timeout which starts controller and shuts down the app if expired\n */\n async startShutdownTimeout(): Promise<void> {\n this.shutdownAbortController = new AbortController();\n try {\n await wait(this.SHUTDOWN_TIMEOUT, null, { signal: this.shutdownAbortController.signal });\n\n this.log('Timeout expired, initializing shutdown');\n this.shutdownApp();\n } catch (e) {\n if (e.code !== 'ABORT_ERR') {\n this.log(e.message, true);\n }\n }\n }\n}\n\n/**\n * Main logic\n */\nasync function main(): Promise<void> {\n const upgradeArguments = UpgradeManager.parseCliCommands();\n const upgradeManager = new UpgradeManager(upgradeArguments);\n registerErrorHandlers(upgradeManager);\n\n const webServerParameters = await upgradeManager.collectWebServerParameters();\n\n upgradeManager.log('Stopping controller');\n await upgradeManager.stopController();\n upgradeManager.log('Successfully stopped js-controller');\n\n upgradeManager.startWebServer(webServerParameters);\n\n try {\n await upgradeManager.npmInstall();\n } catch (e) {\n upgradeManager.log(e.message, true);\n }\n\n await upgradeManager.setFinished();\n}\n\n/**\n * Stream unhandled errors to the log files\n *\n * @param upgradeManager the instance of Upgrade Manager\n */\nfunction registerErrorHandlers(upgradeManager: UpgradeManager): void {\n process.on('uncaughtException', e => {\n upgradeManager.log(`Uncaught Exception: ${e.stack}`, true);\n });\n\n process.on('unhandledRejection', rej => {\n upgradeManager.log(`Unhandled rejection: ${rej instanceof Error ? rej.stack : JSON.stringify(rej)}`, true);\n });\n}\n\n/**\n * This file always needs to be executed in a process different from js-controller\n * else it will be canceled when the file itself stops the controller\n */\n// eslint-disable-next-line unicorn/prefer-module\nconst modulePath = url.fileURLToPath(import.meta.url || `file://${__filename}`);\nif (process.argv[1] === modulePath) {\n main();\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;AAAO,IAAM,oBACX,OAAO,aAAa,cAAc,KAAK,QAAQ,MAAM,QAAQ,IAAI,EAAE,CAAC,GAAE,IAAK,UAAU,UAAU,EAAE,OAC9F,SAAS,iBAAiB,SAAS,cAAc,OAAO,IAAI,IAAI,WAAW,SAAS,OAAO,EAAE;ACFlG,qCAA4D;AAC5D,kCAA8B;AAC9B,oBAAsB;AACtB,+BAA+B;AAC/B,uBAAiB;AACjB,wBAAkB;AAElB,sBAAmC;AAEnC,sBAAe;AAGf,sBAAgB;AAChB,0BAAoB;AAiDpB,MAAM,eAAc;;EAEC,kBAAkB;;EAElB,mBAAmB;;EAEnB;;EAEA;;EAEA;;EAEA;;EAEA,WAA2B;IACxC,SAAS;IACT,QAAQ,CAAA;IACR,QAAQ,CAAA;;;EAGJ;;EAES;;EAGT;;EAEA,UAAU,oBAAI,IAAG;;EAER,WAAW,kCAAM,YAAW;EAE7C,YAAY,MAAsB;AAC9B,SAAK,gBAAgB,KAAK;AAC1B,SAAK,UAAU,KAAK;AACpB,SAAK,SAAS,KAAK,YAAW;AAC9B,SAAK,MAAM,KAAK;AAChB,SAAK,MAAM,KAAK;
|
|
4
|
+
"sourcesContent": ["export const __import_meta_url =\n typeof document === 'undefined' ? new (require('url'.replace('', '')).URL)('file:' + __filename).href :\n (document.currentScript && document.currentScript.src || new URL('main.js', document.baseURI).href)\n", "import { type ChildProcessPromise, exec as execAsync } from 'promisify-child-process';\nimport { tools, logger } from '@iobroker/js-controller-common';\nimport { valid } from 'semver';\nimport { dbConnectAsync } from '@iobroker/js-controller-cli';\nimport http from 'node:http';\nimport https from 'node:https';\nimport type { Client as ObjectsClient } from '@iobroker/db-objects-redis';\nimport { setTimeout as wait } from 'node:timers/promises';\nimport type { Logger } from 'winston';\nimport fs from 'fs-extra';\nimport type { Socket } from 'node:net';\nimport type { Duplex } from 'node:stream';\nimport url from 'node:url';\nimport process from 'node:process';\n\n/** The upgrade arguments provided to the constructor of the UpgradeManager */\nexport interface UpgradeArguments {\n /** Version of controller to upgrade too */\n version: string;\n /** Admin instance which triggered the upgrade */\n adminInstance: number;\n /** User id the process should run as */\n uid: number;\n /** Group id the process should run as */\n gid: number;\n}\n\ninterface Certificates {\n /** Public certificate */\n certPublic: string;\n /** Private certificate */\n certPrivate: string;\n}\n\ninterface InsecureWebServerParameters {\n /** if https should be used for the webserver */\n useHttps: false;\n /** port of the web server */\n port: number;\n}\n\ntype SecureWebServerParameters = Omit<InsecureWebServerParameters, 'useHttps'> & { useHttps: true } & Certificates;\ntype WebServerParameters = InsecureWebServerParameters | SecureWebServerParameters;\n\ninterface GetCertificatesParams {\n /** The objects DB */\n objects: ObjectsClient;\n /** Name of the public certificate */\n certPublicName: string;\n /** Name of the private certificate */\n certPrivateName: string;\n}\n\ninterface ServerResponse {\n /** If the update is still running */\n running: boolean;\n stderr: string[];\n stdout: string[];\n /** if installation process succeeded */\n success?: boolean;\n}\n\nclass UpgradeManager {\n /** Wait ms until controller is stopped */\n private readonly STOP_TIMEOUT_MS = 5_000;\n /** Wait ms for delivery of final response */\n private readonly SHUTDOWN_TIMEOUT = 10_000;\n /** Instance of admin to get information from */\n private readonly adminInstance: number;\n /** Desired controller version */\n private readonly version: string;\n /** Group id the process should run as */\n private readonly gid: number;\n /** User id the process should run as */\n private readonly uid: number;\n /** Response send by webserver */\n private readonly response: ServerResponse = {\n running: true,\n stderr: [],\n stdout: [],\n };\n /** Used to stop the stop shutdown timeout */\n private shutdownAbortController?: AbortController;\n /** Logger to log to file and other transports */\n private readonly logger: Logger;\n\n /** The server used for communicating upgrade status */\n private server?: https.Server | http.Server;\n /** All socket connections of the webserver */\n private sockets = new Set<Socket | Duplex>();\n /** Name of the host for logging purposes */\n private readonly hostname = tools.getHostName();\n\n constructor(args: UpgradeArguments) {\n this.adminInstance = args.adminInstance;\n this.version = args.version;\n this.logger = this.setupLogger();\n this.gid = args.gid;\n this.uid = args.uid;\n }\n\n /**\n * To prevent commands (including npm) running as root, we apply the passed in gid and uid\n */\n applyUser(): void {\n if (!process.setuid || !process.setgid) {\n const errMessage = 'Cannot ensure user and group ids on this system, because no POSIX platform';\n this.log(errMessage, true);\n throw new Error(errMessage);\n }\n\n try {\n process.setgid(this.gid);\n process.setuid(this.uid);\n } catch (e) {\n const errMessage = `Could not ensure user and group ids on this system: ${e.message}`;\n this.log(errMessage, true);\n throw new Error(errMessage);\n }\n }\n\n /**\n * Set up the logger, to stream to file and other configured transports\n */\n private setupLogger(): Logger {\n const config = fs.readJSONSync(tools.getConfigFileName());\n return logger({ ...config.log, noStdout: false });\n }\n\n /**\n * Parse the commands from the cli\n */\n static parseCliCommands(): UpgradeArguments {\n const additionalArgs = process.argv.slice(2);\n\n const version = additionalArgs[0];\n const adminInstance = parseInt(additionalArgs[1]);\n const uid = parseInt(additionalArgs[2]);\n const gid = parseInt(additionalArgs[3]);\n\n const isValid = !!valid(version);\n\n if (!isValid) {\n UpgradeManager.printUsage();\n throw new Error('The provided version is not valid');\n }\n\n if (isNaN(adminInstance)) {\n UpgradeManager.printUsage();\n throw new Error('Please provide a valid admin instance');\n }\n\n if (isNaN(uid)) {\n UpgradeManager.printUsage();\n throw new Error('Please provide a valid uid');\n }\n\n if (isNaN(gid)) {\n UpgradeManager.printUsage();\n throw new Error('Please provide a valid gid');\n }\n\n return { version, adminInstance, uid, gid };\n }\n\n /**\n * Log via console and provide the logs for the server too\n *\n * @param message the message which will be logged\n * @param error if it is an error\n */\n log(message: string, error = false): void {\n if (error) {\n this.logger.error(`host.${this.hostname} [CONTROLLER_AUTO_UPGRADE] ${message}`);\n this.response.stderr.push(message);\n return;\n }\n\n this.logger.info(`host.${this.hostname} [CONTROLLER_AUTO_UPGRADE] ${message}`);\n this.response.stdout.push(message);\n }\n\n /**\n * Stops the js-controller via cli call\n */\n async stopController(): Promise<void> {\n if (tools.isDocker()) {\n await execAsync('/opt/scripts/maintenance.sh on -kbn');\n } else {\n await execAsync(`${tools.appNameLowerCase} stop`);\n }\n await wait(this.STOP_TIMEOUT_MS);\n }\n\n /**\n * Starts the js-controller via cli\n */\n startController(): ChildProcessPromise {\n if (tools.isDocker()) {\n return execAsync('/opt/scripts/maintenance.sh off -y');\n }\n\n return execAsync(`${tools.appNameLowerCase} start`);\n }\n\n /**\n * Print how the module should be used\n */\n static printUsage(): void {\n console.info('Example usage: \"node upgradeManager.js <version> <adminInstance> <uid> <gid>\"');\n }\n\n /**\n * Install given version of js-controller\n */\n async npmInstall(): Promise<void> {\n const res = await tools.installNodeModule(`iobroker.js-controller@${this.version}`, {\n cwd: '/opt/iobroker',\n debug: true,\n });\n\n this.response.stderr.push(...res.stderr.split('\\n'));\n this.response.stdout.push(...res.stdout.split('\\n'));\n\n this.response.success = res.success;\n\n if (!res.success) {\n throw new Error(`Could not install js-controller@${this.version}`);\n }\n }\n\n /**\n * Starts the web server for admin communication either secure or insecure\n *\n * @param params Web server configuration\n */\n async startWebServer(params: WebServerParameters): Promise<void> {\n const { useHttps } = params;\n if (useHttps) {\n await this.startSecureWebServer(params);\n } else {\n await this.startInsecureWebServer(params);\n }\n }\n\n /**\n * Shuts down the server, restarts the controller and exits the program\n */\n shutdownApp(): void {\n if (this.shutdownAbortController) {\n this.shutdownAbortController.abort();\n }\n\n if (!this.server) {\n process.exit();\n }\n\n this.destroySockets();\n\n this.server.close(async () => {\n await this.startController();\n this.log('Successfully started js-controller');\n\n process.exit();\n });\n }\n\n /**\n * Destroy all sockets, to prevent requests from keeping server alive\n */\n destroySockets(): void {\n for (const socket of this.sockets) {\n socket.destroy();\n this.sockets.delete(socket);\n }\n }\n\n /**\n * This function is called when the webserver receives a message\n *\n * @param res server response\n */\n webServerCallback(res: http.ServerResponse): void {\n res.writeHead(200, {\n 'Access-Control-Allow-Origin': '*',\n });\n\n res.end(JSON.stringify(this.response));\n\n if (!this.response.running) {\n this.log('Final information delivered');\n this.shutdownApp();\n }\n }\n\n /**\n * Start an insecure web server for admin communication\n *\n * @param params Web server configuration\n */\n async startInsecureWebServer(params: InsecureWebServerParameters): Promise<void> {\n const { port } = params;\n\n this.server = http.createServer((_req, res) => {\n this.webServerCallback(res);\n });\n\n this.monitorSockets(this.server);\n\n await new Promise<void>(resolve => {\n this.server!.listen(port, () => {\n resolve();\n });\n });\n\n this.log(`Server is running on http://localhost:${port}`);\n }\n\n /**\n * Start a secure web server for admin communication\n *\n * @param params Web server configuration\n */\n async startSecureWebServer(params: SecureWebServerParameters): Promise<void> {\n const { port, certPublic, certPrivate } = params;\n\n this.server = https.createServer({ key: certPrivate, cert: certPublic }, (_req, res) => {\n this.webServerCallback(res);\n });\n\n this.monitorSockets(this.server);\n\n await new Promise<void>(resolve => {\n this.server!.listen(port, () => {\n resolve();\n });\n });\n\n this.log(`Server is running on https://localhost:${port}`);\n }\n\n /**\n * Keep track of all existing sockets\n *\n * @param server the webserver\n */\n monitorSockets(server: http.Server | https.Server): void {\n server.on('connection', socket => {\n this.sockets.add(socket);\n\n server.once('close', () => {\n this.sockets.delete(socket);\n });\n });\n }\n\n /**\n * Get certificates from the DB\n *\n * @param params certificate information\n */\n async getCertificates(params: GetCertificatesParams): Promise<Certificates> {\n const { objects, certPublicName, certPrivateName } = params;\n\n const obj = await objects.getObjectAsync('system.certificates');\n\n if (!obj) {\n throw new Error('No certificates found');\n }\n\n const certs = obj.native.certificates;\n\n return { certPrivate: certs[certPrivateName], certPublic: certs[certPublicName] };\n }\n\n /**\n * Collect parameters for webserver from admin instance\n */\n async collectWebServerParameters(): Promise<WebServerParameters> {\n const { objects } = await dbConnectAsync(false);\n\n const obj = await objects.getObjectAsync(`system.adapter.admin.${this.adminInstance}`);\n\n if (!obj) {\n UpgradeManager.printUsage();\n throw new Error('Please provide a valid admin instance');\n }\n\n if (obj.native.secure) {\n const { certPublic: certPublicName, certPrivate: certPrivateName } = obj.native;\n const { certPublic, certPrivate } = await this.getCertificates({\n objects,\n certPublicName,\n certPrivateName,\n });\n\n return {\n useHttps: obj.native.secure,\n port: obj.native.port,\n certPublic,\n certPrivate,\n };\n }\n\n return {\n useHttps: false,\n port: obj.native.port,\n };\n }\n\n /**\n * Tells the upgrade manager, that server can be shut down on next response or on timeout\n */\n async setFinished(): Promise<void> {\n this.response.running = false;\n\n await this.startShutdownTimeout();\n }\n\n /**\n * Start a timeout which starts controller and shuts down the app if expired\n */\n async startShutdownTimeout(): Promise<void> {\n this.shutdownAbortController = new AbortController();\n try {\n await wait(this.SHUTDOWN_TIMEOUT, null, { signal: this.shutdownAbortController.signal });\n\n this.log('Timeout expired, initializing shutdown');\n this.shutdownApp();\n } catch (e) {\n if (e.code !== 'ABORT_ERR') {\n this.log(e.message, true);\n }\n }\n }\n}\n\n/**\n * Main logic\n */\nasync function main(): Promise<void> {\n const upgradeArguments = UpgradeManager.parseCliCommands();\n const upgradeManager = new UpgradeManager(upgradeArguments);\n registerErrorHandlers(upgradeManager);\n\n const webServerParameters = await upgradeManager.collectWebServerParameters();\n\n upgradeManager.log('Stopping controller');\n await upgradeManager.stopController();\n upgradeManager.log('Successfully stopped js-controller');\n\n await upgradeManager.startWebServer(webServerParameters);\n // do this after web server is started, else we cannot bind on privileged ports after using setgid\n upgradeManager.applyUser();\n\n try {\n await upgradeManager.npmInstall();\n } catch (e) {\n upgradeManager.log(e.message, true);\n }\n\n await upgradeManager.setFinished();\n}\n\n/**\n * Stream unhandled errors to the log files\n *\n * @param upgradeManager the instance of Upgrade Manager\n */\nfunction registerErrorHandlers(upgradeManager: UpgradeManager): void {\n process.on('uncaughtException', e => {\n upgradeManager.log(`Uncaught Exception: ${e.stack}`, true);\n });\n\n process.on('unhandledRejection', rej => {\n upgradeManager.log(`Unhandled rejection: ${rej instanceof Error ? rej.stack : JSON.stringify(rej)}`, true);\n });\n}\n\n/**\n * This file always needs to be executed in a process different from js-controller\n * else it will be canceled when the file itself stops the controller\n */\n// eslint-disable-next-line unicorn/prefer-module\nconst modulePath = url.fileURLToPath(import.meta.url || `file://${__filename}`);\nif (process.argv[1] === modulePath) {\n main();\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["process", "fs", "execAsync", "wait", "http", "https", "url"]
|
|
7
7
|
}
|
package/build/cjs/main.js
CHANGED
|
@@ -4110,7 +4110,7 @@ async function startUpgradeManager(options) {
|
|
|
4110
4110
|
stdio: "ignore"
|
|
4111
4111
|
});
|
|
4112
4112
|
} else {
|
|
4113
|
-
upgradeProcess = (0, import_node_child_process.spawn)(process.execPath, [upgradeProcessPath, version2, adminInstance.toString()], {
|
|
4113
|
+
upgradeProcess = (0, import_node_child_process.spawn)(process.execPath, [upgradeProcessPath, version2, adminInstance.toString(), uid.toString(), gid.toString()], {
|
|
4114
4114
|
detached: true,
|
|
4115
4115
|
stdio: "ignore"
|
|
4116
4116
|
});
|