Migrate codebase from CommonJS to ES Modules
- Convert all require()/module.exports to import/export across 260+ files - Add "type": "module" to package.json to enable ESM by default - Add migrations/package.json with "type": "commonjs" to keep db-migrate compatible - Convert eslint.config.js to ESM with sourceType: "module" - Replace __dirname/__filename with import.meta.dirname/import.meta.filename - Replace require.main === module with process.argv[1] === import.meta.filename - Remove 'use strict' directives (implicit in ESM) - Convert dynamic require() in switch statements to static import lookup maps (dns.js, domains.js, backupformats.js, backupsites.js, network.js) - Extract self-referencing exports.CONSTANT patterns into standalone const declarations (apps.js, services.js, locks.js, users.js, mail.js, etc.) - Lazify SERVICES object in services.js to avoid circular dependency TDZ issues - Add clearMailQueue() to mailer.js for ESM-safe queue clearing in tests - Add _setMockApp() to ldapserver.js for ESM-safe test mocking - Add _setMockResolve() wrapper to dig.js for ESM-safe DNS mocking in tests - Convert backupupload.js to use dynamic imports so --check exits before loading the module graph (which requires BOX_ENV) - Update check-install to use ESM import for infra_version.js - Convert scripts/ (hotfix, release, remote_hotfix.js, find-unused-translations) - All 1315 tests passing Migration stats (AI-assisted using Cursor with Claude): - Wall clock time: ~3-4 hours - Assistant completions: ~80-100 - Estimated token usage: ~1-2M tokens Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+159
-147
@@ -1,6 +1,45 @@
|
||||
'use strict';
|
||||
import * as addonConfigs from './addonconfigs.js';
|
||||
import apps from './apps.js';
|
||||
import assert from 'node:assert';
|
||||
import blobs from './blobs.js';
|
||||
import BoxError from './boxerror.js';
|
||||
import * as branding from './branding.js';
|
||||
import constants from './constants.js';
|
||||
import crypto from 'node:crypto';
|
||||
import * as dashboard from './dashboard.js';
|
||||
import debugModule from 'debug';
|
||||
import * as dig from './dig.js';
|
||||
import * as docker from './docker.js';
|
||||
import eventlog from './eventlog.js';
|
||||
import fs from 'node:fs';
|
||||
import hat from './hat.js';
|
||||
import http from 'http';
|
||||
import infra from './infra_version.js';
|
||||
import * as logs from './logs.js';
|
||||
import * as mail from './mail.js';
|
||||
import * as mailServer from './mailserver.js';
|
||||
import oidcClients from './oidcclients.js';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import paths from './paths.js';
|
||||
import { pipeFileToRequest, pipeRequestToFile } from '@cloudron/pipework';
|
||||
import promiseRetry from './promise-retry.js';
|
||||
import safe from 'safetydance';
|
||||
import semver from 'semver';
|
||||
import * as settings from './settings.js';
|
||||
import * as sftp from './sftp.js';
|
||||
import shellModule from './shell.js';
|
||||
import superagent from '@cloudron/superagent';
|
||||
|
||||
exports = module.exports = {
|
||||
const debug = debugModule('box:services');
|
||||
const shell = shellModule('services');
|
||||
|
||||
const SERVICE_STATUS_STARTING = 'starting';
|
||||
const SERVICE_STATUS_ACTIVE = 'active';
|
||||
const SERVICE_STATUS_STOPPED = 'stopped';
|
||||
const SERVICE_STATUS_DISABLED = 'disabled';
|
||||
|
||||
export default {
|
||||
getServiceConfig,
|
||||
|
||||
listServices,
|
||||
@@ -33,52 +72,19 @@ exports = module.exports = {
|
||||
// exported only for apptask.js to update immich pgvectors extension - can be removed later
|
||||
_postgreSqlNames: postgreSqlNames,
|
||||
|
||||
SERVICE_STATUS_STARTING: 'starting', // container up, waiting for healthcheck
|
||||
SERVICE_STATUS_ACTIVE: 'active',
|
||||
SERVICE_STATUS_STOPPED: 'stopped',
|
||||
SERVICE_STATUS_DISABLED: 'disabled', // feature not supported
|
||||
SERVICE_STATUS_STARTING,
|
||||
SERVICE_STATUS_ACTIVE,
|
||||
SERVICE_STATUS_STOPPED,
|
||||
SERVICE_STATUS_DISABLED,
|
||||
};
|
||||
|
||||
const addonConfigs = require('./addonconfigs.js'),
|
||||
apps = require('./apps.js'),
|
||||
assert = require('node:assert'),
|
||||
blobs = require('./blobs.js'),
|
||||
BoxError = require('./boxerror.js'),
|
||||
branding = require('./branding.js'),
|
||||
constants = require('./constants.js'),
|
||||
crypto = require('node:crypto'),
|
||||
dashboard = require('./dashboard.js'),
|
||||
debug = require('debug')('box:services'),
|
||||
dig = require('./dig.js'),
|
||||
docker = require('./docker.js'),
|
||||
eventlog = require('./eventlog.js'),
|
||||
fs = require('node:fs'),
|
||||
hat = require('./hat.js'),
|
||||
http = require('http'),
|
||||
infra = require('./infra_version.js'),
|
||||
logs = require('./logs.js'),
|
||||
mail = require('./mail.js'),
|
||||
mailServer = require('./mailserver.js'),
|
||||
oidcClients = require('./oidcclients.js'),
|
||||
os = require('node:os'),
|
||||
path = require('node:path'),
|
||||
paths = require('./paths.js'),
|
||||
{ pipeFileToRequest, pipeRequestToFile } = require('@cloudron/pipework'),
|
||||
promiseRetry = require('./promise-retry.js'),
|
||||
safe = require('safetydance'),
|
||||
semver = require('semver'),
|
||||
settings = require('./settings.js'),
|
||||
sftp = require('./sftp.js'),
|
||||
shell = require('./shell.js')('services'),
|
||||
superagent = require('@cloudron/superagent');
|
||||
|
||||
const NOOP = async function (/*app, options*/) {};
|
||||
const RMADDONDIR_CMD = path.join(__dirname, 'scripts/rmaddondir.sh');
|
||||
const RESTART_SERVICE_CMD = path.join(__dirname, 'scripts/restartservice.sh');
|
||||
const CLEARVOLUME_CMD = path.join(__dirname, 'scripts/clearvolume.sh');
|
||||
const RMVOLUME_CMD = path.join(__dirname, 'scripts/rmvolume.sh');
|
||||
const SETUPVOLUME_CMD = path.join(__dirname, 'scripts/setupvolume.sh');
|
||||
const MV_VOLUME_CMD = path.join(__dirname, 'scripts/mvvolume.sh');
|
||||
const RMADDONDIR_CMD = path.join(import.meta.dirname, 'scripts/rmaddondir.sh');
|
||||
const RESTART_SERVICE_CMD = path.join(import.meta.dirname, 'scripts/restartservice.sh');
|
||||
const CLEARVOLUME_CMD = path.join(import.meta.dirname, 'scripts/clearvolume.sh');
|
||||
const RMVOLUME_CMD = path.join(import.meta.dirname, 'scripts/rmvolume.sh');
|
||||
const SETUPVOLUME_CMD = path.join(import.meta.dirname, 'scripts/setupvolume.sh');
|
||||
const MV_VOLUME_CMD = path.join(import.meta.dirname, 'scripts/mvvolume.sh');
|
||||
|
||||
// setup can be called multiple times for the same app (configure crash restart) and existing data must not be lost
|
||||
// teardown is destructive. app data stored with the addon is lost
|
||||
@@ -214,69 +220,75 @@ const ADDONS = {
|
||||
}
|
||||
};
|
||||
|
||||
// services are actual containers that are running. addons are the concepts requested by app
|
||||
const SERVICES = {
|
||||
turn: {
|
||||
name: 'TURN',
|
||||
status: statusTurn,
|
||||
restart: docker.restartContainer.bind(null, 'turn'),
|
||||
defaultMemoryLimit: 256 * 1024 * 1024
|
||||
},
|
||||
mail: {
|
||||
name: 'Mail',
|
||||
status: containerStatus.bind(null, 'mail'),
|
||||
restart: mailServer.restart,
|
||||
defaultMemoryLimit: mailServer.DEFAULT_MEMORY_LIMIT
|
||||
},
|
||||
mongodb: {
|
||||
name: 'MongoDB',
|
||||
status: statusMongodb,
|
||||
restart: restartMongodb,
|
||||
defaultMemoryLimit: (1 + Math.round(os.totalmem()/(1024*1024*1024)/4)) * 256 * 1024 * 1024
|
||||
},
|
||||
mysql: {
|
||||
name: 'MySQL',
|
||||
status: containerStatus.bind(null, 'mysql'),
|
||||
restart: docker.restartContainer.bind(null, 'mysql'),
|
||||
defaultMemoryLimit: (1 + Math.round(os.totalmem()/(1024*1024*1024)/4)) * 256 * 1024 * 1024
|
||||
},
|
||||
postgresql: {
|
||||
name: 'PostgreSQL',
|
||||
status: containerStatus.bind(null, 'postgresql'),
|
||||
restart: docker.restartContainer.bind(null, 'postgresql'),
|
||||
defaultMemoryLimit: (1 + Math.round(os.totalmem()/(1024*1024*1024)/4)) * 256 * 1024 * 1024
|
||||
},
|
||||
docker: {
|
||||
name: 'Docker',
|
||||
status: statusDocker,
|
||||
restart: restartDocker,
|
||||
defaultMemoryLimit: 0
|
||||
},
|
||||
unbound: {
|
||||
name: 'Unbound',
|
||||
status: statusUnbound,
|
||||
restart: restartUnbound,
|
||||
defaultMemoryLimit: 0
|
||||
},
|
||||
sftp: {
|
||||
name: 'Filemanager',
|
||||
status: containerStatus.bind(null, 'sftp'),
|
||||
restart: docker.restartContainer.bind(null, 'sftp'),
|
||||
defaultMemoryLimit: sftp.DEFAULT_MEMORY_LIMIT
|
||||
},
|
||||
graphite: {
|
||||
name: 'Metrics',
|
||||
status: statusGraphite,
|
||||
restart: restartGraphite,
|
||||
defaultMemoryLimit: 256 * 1024 * 1024
|
||||
},
|
||||
nginx: {
|
||||
name: 'Nginx',
|
||||
status: statusNginx,
|
||||
restart: restartNginx,
|
||||
defaultMemoryLimit: 0
|
||||
}
|
||||
};
|
||||
// Lazily initialized to avoid circular dependency TDZ issues at module load time
|
||||
// (docker, mailServer, sftp may not be fully initialized when this module first evaluates)
|
||||
let _services;
|
||||
function SERVICES() {
|
||||
if (_services) return _services;
|
||||
_services = {
|
||||
turn: {
|
||||
name: 'TURN',
|
||||
status: statusTurn,
|
||||
restart: docker.restartContainer.bind(null, 'turn'),
|
||||
defaultMemoryLimit: 256 * 1024 * 1024
|
||||
},
|
||||
mail: {
|
||||
name: 'Mail',
|
||||
status: containerStatus.bind(null, 'mail'),
|
||||
restart: mailServer.restart,
|
||||
defaultMemoryLimit: mailServer.DEFAULT_MEMORY_LIMIT
|
||||
},
|
||||
mongodb: {
|
||||
name: 'MongoDB',
|
||||
status: statusMongodb,
|
||||
restart: restartMongodb,
|
||||
defaultMemoryLimit: (1 + Math.round(os.totalmem()/(1024*1024*1024)/4)) * 256 * 1024 * 1024
|
||||
},
|
||||
mysql: {
|
||||
name: 'MySQL',
|
||||
status: containerStatus.bind(null, 'mysql'),
|
||||
restart: docker.restartContainer.bind(null, 'mysql'),
|
||||
defaultMemoryLimit: (1 + Math.round(os.totalmem()/(1024*1024*1024)/4)) * 256 * 1024 * 1024
|
||||
},
|
||||
postgresql: {
|
||||
name: 'PostgreSQL',
|
||||
status: containerStatus.bind(null, 'postgresql'),
|
||||
restart: docker.restartContainer.bind(null, 'postgresql'),
|
||||
defaultMemoryLimit: (1 + Math.round(os.totalmem()/(1024*1024*1024)/4)) * 256 * 1024 * 1024
|
||||
},
|
||||
docker: {
|
||||
name: 'Docker',
|
||||
status: statusDocker,
|
||||
restart: restartDocker,
|
||||
defaultMemoryLimit: 0
|
||||
},
|
||||
unbound: {
|
||||
name: 'Unbound',
|
||||
status: statusUnbound,
|
||||
restart: restartUnbound,
|
||||
defaultMemoryLimit: 0
|
||||
},
|
||||
sftp: {
|
||||
name: 'Filemanager',
|
||||
status: containerStatus.bind(null, 'sftp'),
|
||||
restart: docker.restartContainer.bind(null, 'sftp'),
|
||||
defaultMemoryLimit: sftp.DEFAULT_MEMORY_LIMIT
|
||||
},
|
||||
graphite: {
|
||||
name: 'Metrics',
|
||||
status: statusGraphite,
|
||||
restart: restartGraphite,
|
||||
defaultMemoryLimit: 256 * 1024 * 1024
|
||||
},
|
||||
nginx: {
|
||||
name: 'Nginx',
|
||||
status: statusNginx,
|
||||
restart: restartNginx,
|
||||
defaultMemoryLimit: 0
|
||||
}
|
||||
};
|
||||
return _services;
|
||||
}
|
||||
|
||||
const APP_SERVICES = {
|
||||
redis: {
|
||||
@@ -338,25 +350,25 @@ async function containerStatus(containerName) {
|
||||
assert.strictEqual(typeof containerName, 'string');
|
||||
|
||||
const [error, container] = await safe(docker.inspect(containerName));
|
||||
if (error && error.reason === BoxError.NOT_FOUND) return { status: exports.SERVICE_STATUS_STOPPED };
|
||||
if (error && error.reason === BoxError.NOT_FOUND) return { status: SERVICE_STATUS_STOPPED };
|
||||
if (error) throw error;
|
||||
|
||||
const ip = safe.query(container, 'NetworkSettings.Networks.cloudron.IPAddress', null);
|
||||
if (!ip) return { status: exports.SERVICE_STATUS_STOPPED };
|
||||
if (!ip) return { status: SERVICE_STATUS_STOPPED };
|
||||
const isRunning = container.State?.Running;
|
||||
|
||||
const [networkError, response] = await safe(superagent.get(`http://${ip}:3000/healthcheck`)
|
||||
.timeout(20000)
|
||||
.ok(() => true));
|
||||
|
||||
if (networkError) return { status: exports.SERVICE_STATUS_STARTING, error: `Error waiting for ${containerName}: ${networkError.message}` };
|
||||
if (response.status !== 200 || !response.body.status) return { status: exports.SERVICE_STATUS_STARTING, error: `Error waiting for ${containerName}. Status code: ${response.status} message: ${response.body.message}` };
|
||||
if (networkError) return { status: SERVICE_STATUS_STARTING, error: `Error waiting for ${containerName}: ${networkError.message}` };
|
||||
if (response.status !== 200 || !response.body.status) return { status: SERVICE_STATUS_STARTING, error: `Error waiting for ${containerName}. Status code: ${response.status} message: ${response.body.message}` };
|
||||
|
||||
const result = await docker.getStats(containerName, { stream: false });
|
||||
const stats = result.memory_stats || { usage: 0, limit: 1 };
|
||||
|
||||
return {
|
||||
status: isRunning ? exports.SERVICE_STATUS_ACTIVE : exports.SERVICE_STATUS_STOPPED,
|
||||
status: isRunning ? SERVICE_STATUS_ACTIVE : SERVICE_STATUS_STOPPED,
|
||||
memoryUsed: stats.usage,
|
||||
memoryPercent: parseInt(100 * stats.usage / stats.limit),
|
||||
healthcheck: response.body
|
||||
@@ -364,7 +376,7 @@ async function containerStatus(containerName) {
|
||||
}
|
||||
|
||||
async function listServices() {
|
||||
const serviceIds = Object.keys(SERVICES).map(k => { return { id: k, name: SERVICES[k].name }; });
|
||||
const serviceIds = Object.keys(SERVICES()).map(k => { return { id: k, name: SERVICES()[k].name }; });
|
||||
|
||||
const result = await apps.list();
|
||||
for (const app of result) {
|
||||
@@ -400,11 +412,11 @@ async function getServiceStatus(id) {
|
||||
let containerStatusFunc, service;
|
||||
|
||||
if (instance) {
|
||||
service = APP_SERVICES[name];
|
||||
service = APP_SERVICES()[name];
|
||||
if (!service) throw new BoxError(BoxError.NOT_FOUND, 'Service not found');
|
||||
containerStatusFunc = service.status.bind(null, instance);
|
||||
} else if (SERVICES[name]) {
|
||||
service = SERVICES[name];
|
||||
} else if (SERVICES()[name]) {
|
||||
service = SERVICES()[name];
|
||||
containerStatusFunc = service.status;
|
||||
} else {
|
||||
throw new BoxError(BoxError.NOT_FOUND, 'Service not found');
|
||||
@@ -446,7 +458,7 @@ async function configureService(id, data, auditSource) {
|
||||
let needsRebuild = false;
|
||||
|
||||
if (instance) {
|
||||
if (!APP_SERVICES[name]) throw new BoxError(BoxError.NOT_FOUND, 'Service not found');
|
||||
if (!APP_SERVICES()[name]) throw new BoxError(BoxError.NOT_FOUND, 'Service not found');
|
||||
|
||||
const app = await apps.get(instance);
|
||||
if (!app) throw new BoxError(BoxError.NOT_FOUND, 'App not found');
|
||||
@@ -456,7 +468,7 @@ async function configureService(id, data, auditSource) {
|
||||
servicesConfig[name] = data;
|
||||
|
||||
await apps.update(instance, { servicesConfig });
|
||||
} else if (SERVICES[name]) {
|
||||
} else if (SERVICES()[name]) {
|
||||
const servicesConfig = await getConfig();
|
||||
needsRebuild = servicesConfig[name]?.recoveryMode != data.recoveryMode; // intentional != since 'recoveryMode' may or may not be there
|
||||
|
||||
@@ -486,8 +498,8 @@ async function getServiceLogs(id, options) {
|
||||
const [name, instance ] = id.split(':');
|
||||
|
||||
if (instance) {
|
||||
if (!APP_SERVICES[name]) throw new BoxError(BoxError.NOT_FOUND, 'Service not found');
|
||||
} else if (!SERVICES[name]) {
|
||||
if (!APP_SERVICES()[name]) throw new BoxError(BoxError.NOT_FOUND, 'Service not found');
|
||||
} else if (!SERVICES()[name]) {
|
||||
throw new BoxError(BoxError.NOT_FOUND, 'Service not found');
|
||||
}
|
||||
|
||||
@@ -500,7 +512,7 @@ async function getServiceLogs(id, options) {
|
||||
} else if (name === 'nginx') {
|
||||
cp = logs.tail(['/var/log/nginx/access.log', '/var/log/nginx/error.log'], { lines: options.lines, follow: options.follow });
|
||||
} else {
|
||||
const containerName = APP_SERVICES[name] ? `${name}-${instance}` : name;
|
||||
const containerName = APP_SERVICES()[name] ? `${name}-${instance}` : name;
|
||||
cp = logs.tail([path.join(paths.LOG_DIR, containerName, 'app.log')], { lines: options.lines, follow: options.follow });
|
||||
}
|
||||
|
||||
@@ -566,11 +578,11 @@ async function restartService(id, auditSource) {
|
||||
const [name, instance ] = id.split(':');
|
||||
|
||||
if (instance) {
|
||||
if (!APP_SERVICES[name]) throw new BoxError(BoxError.NOT_FOUND, 'Service not found');
|
||||
if (!APP_SERVICES()[name]) throw new BoxError(BoxError.NOT_FOUND, 'Service not found');
|
||||
|
||||
await APP_SERVICES[name].restart(instance);
|
||||
} else if (SERVICES[name]) {
|
||||
await SERVICES[name].restart();
|
||||
await APP_SERVICES()[name].restart(instance);
|
||||
} else if (SERVICES()[name]) {
|
||||
await SERVICES()[name].restart();
|
||||
} else {
|
||||
throw new BoxError(BoxError.NOT_FOUND, 'Service not found');
|
||||
}
|
||||
@@ -586,7 +598,7 @@ async function startAppServices(app) {
|
||||
for (const addon of Object.keys(app.manifest.addons || {})) {
|
||||
if (!(addon in APP_SERVICES)) continue;
|
||||
|
||||
const [error] = await safe(APP_SERVICES[addon].start(instance)); // assume addons name is service name
|
||||
const [error] = await safe(APP_SERVICES()[addon].start(instance)); // assume addons name is service name
|
||||
// error ignored because we don't want "start app" to error. use can fix it from Services
|
||||
if (error) debug(`startAppServices: ${addon}:${instance}. %o`, error);
|
||||
}
|
||||
@@ -600,7 +612,7 @@ async function stopAppServices(app) {
|
||||
for (const addon of Object.keys(app.manifest.addons || {})) {
|
||||
if (!(addon in APP_SERVICES)) continue;
|
||||
|
||||
const [error] = await safe(APP_SERVICES[addon].stop(instance)); // assume addons name is service name
|
||||
const [error] = await safe(APP_SERVICES()[addon].stop(instance)); // assume addons name is service name
|
||||
// error ignored because we don't want "start app" to error. use can fix it from Services
|
||||
if (error) debug(`stopAppServices: ${addon}:${instance}. %o`, error);
|
||||
}
|
||||
@@ -780,18 +792,18 @@ async function applyMemoryLimit(id) {
|
||||
const serviceConfig = await getServiceConfig(id);
|
||||
|
||||
if (instance) {
|
||||
if (!APP_SERVICES[name]) throw new BoxError(BoxError.NOT_FOUND, 'Service not found');
|
||||
if (!APP_SERVICES()[name]) throw new BoxError(BoxError.NOT_FOUND, 'Service not found');
|
||||
|
||||
containerName = `${name}-${instance}`;
|
||||
memoryLimit = serviceConfig && serviceConfig.memoryLimit ? serviceConfig.memoryLimit : APP_SERVICES[name].defaultMemoryLimit;
|
||||
} else if (SERVICES[name]) {
|
||||
memoryLimit = serviceConfig && serviceConfig.memoryLimit ? serviceConfig.memoryLimit : APP_SERVICES()[name].defaultMemoryLimit;
|
||||
} else if (SERVICES()[name]) {
|
||||
if (name === 'mongodb' && !await hasAVX()) {
|
||||
debug('applyMemoryLimit: skipping mongodb because CPU does not have AVX');
|
||||
return;
|
||||
}
|
||||
|
||||
containerName = name;
|
||||
memoryLimit = serviceConfig && serviceConfig.memoryLimit ? serviceConfig.memoryLimit : SERVICES[name].defaultMemoryLimit;
|
||||
memoryLimit = serviceConfig && serviceConfig.memoryLimit ? serviceConfig.memoryLimit : SERVICES()[name].defaultMemoryLimit;
|
||||
} else {
|
||||
throw new BoxError(BoxError.NOT_FOUND, 'No such service');
|
||||
}
|
||||
@@ -1003,7 +1015,7 @@ async function startTurn(existingInfra) {
|
||||
|
||||
const serviceConfig = await getServiceConfig('turn');
|
||||
const image = infra.images.turn;
|
||||
const memoryLimit = serviceConfig.memoryLimit || SERVICES['turn'].defaultMemoryLimit;
|
||||
const memoryLimit = serviceConfig.memoryLimit || SERVICES()['turn'].defaultMemoryLimit;
|
||||
const { fqdn:realm } = await dashboard.getLocation();
|
||||
|
||||
let turnSecret = await blobs.getString(blobs.ADDON_TURN_SECRET);
|
||||
@@ -1705,7 +1717,7 @@ async function restoreMongoDb(app, options) {
|
||||
}
|
||||
|
||||
async function statusMongodb() {
|
||||
if (!await hasAVX()) return { status: exports.SERVICE_STATUS_DISABLED };
|
||||
if (!await hasAVX()) return { status: SERVICE_STATUS_DISABLED };
|
||||
return await containerStatus('mongodb');
|
||||
}
|
||||
|
||||
@@ -1862,7 +1874,7 @@ async function setupRedis(app, options) {
|
||||
const redisServiceToken = hat(4 * 48);
|
||||
|
||||
// Compute redis memory limit based on app's memory limit (this is arbitrary)
|
||||
const memoryLimit = app.servicesConfig['redis']?.memoryLimit || APP_SERVICES['redis'].defaultMemoryLimit;
|
||||
const memoryLimit = app.servicesConfig['redis']?.memoryLimit || APP_SERVICES()['redis'].defaultMemoryLimit;
|
||||
|
||||
const recoveryMode = app.servicesConfig['redis']?.recoveryMode || false;
|
||||
const readOnly = !recoveryMode ? '--read-only' : '';
|
||||
@@ -1990,14 +2002,14 @@ async function teardownTls(app, options) {
|
||||
|
||||
async function statusTurn() {
|
||||
const [error, container] = await safe(docker.inspect('turn'));
|
||||
if (error && error.reason === BoxError.NOT_FOUND) return { status: exports.SERVICE_STATUS_STOPPED };
|
||||
if (error && error.reason === BoxError.NOT_FOUND) return { status: SERVICE_STATUS_STOPPED };
|
||||
if (error) throw error;
|
||||
|
||||
const result = await docker.getStats(container.Id, { stream: false });
|
||||
|
||||
const status = container.State.Running
|
||||
? (container.HostConfig.ReadonlyRootfs ? exports.SERVICE_STATUS_ACTIVE : exports.SERVICE_STATUS_STARTING)
|
||||
: exports.SERVICE_STATUS_STOPPED;
|
||||
? (container.HostConfig.ReadonlyRootfs ? SERVICE_STATUS_ACTIVE : SERVICE_STATUS_STARTING)
|
||||
: SERVICE_STATUS_STOPPED;
|
||||
const stats = result.memory_stats || { usage: 0, limit: 1 };
|
||||
|
||||
return {
|
||||
@@ -2009,7 +2021,7 @@ async function statusTurn() {
|
||||
|
||||
async function statusDocker() {
|
||||
const [error] = await safe(docker.ping());
|
||||
return { status: error ? exports.SERVICE_STATUS_STOPPED: exports.SERVICE_STATUS_ACTIVE };
|
||||
return { status: error ? SERVICE_STATUS_STOPPED: SERVICE_STATUS_ACTIVE };
|
||||
}
|
||||
|
||||
async function restartDocker() {
|
||||
@@ -2019,13 +2031,13 @@ async function restartDocker() {
|
||||
|
||||
async function statusUnbound() {
|
||||
const [error] = await safe(shell.spawn('systemctl', ['is-active', 'unbound'], { encoding: 'utf8' }));
|
||||
if (error) return { status: exports.SERVICE_STATUS_STOPPED };
|
||||
if (error) return { status: SERVICE_STATUS_STOPPED };
|
||||
|
||||
const [digError, digResult] = await safe(dig.resolve('ipv4.api.cloudron.io', 'A', { timeout: 10000 }));
|
||||
if (!digError && Array.isArray(digResult) && digResult.length !== 0) return { status: exports.SERVICE_STATUS_ACTIVE };
|
||||
if (!digError && Array.isArray(digResult) && digResult.length !== 0) return { status: SERVICE_STATUS_ACTIVE };
|
||||
|
||||
debug('statusUnbound: unbound is up, but failed to resolve ipv4.api.cloudron.io . %o %j', digError, digResult);
|
||||
return { status: exports.SERVICE_STATUS_STARTING };
|
||||
return { status: SERVICE_STATUS_STARTING };
|
||||
}
|
||||
|
||||
async function restartUnbound() {
|
||||
@@ -2035,7 +2047,7 @@ async function restartUnbound() {
|
||||
|
||||
async function statusNginx() {
|
||||
const [error] = await safe(shell.spawn('systemctl', ['is-active', 'nginx'], { encoding: 'utf8' }));
|
||||
return { status: error ? exports.SERVICE_STATUS_STOPPED : exports.SERVICE_STATUS_ACTIVE };
|
||||
return { status: error ? SERVICE_STATUS_STOPPED : SERVICE_STATUS_ACTIVE };
|
||||
}
|
||||
|
||||
async function restartNginx() {
|
||||
@@ -2045,7 +2057,7 @@ async function restartNginx() {
|
||||
|
||||
async function statusGraphite() {
|
||||
const [error, container] = await safe(docker.inspect('graphite'));
|
||||
if (error && error.reason === BoxError.NOT_FOUND) return { status: exports.SERVICE_STATUS_STOPPED };
|
||||
if (error && error.reason === BoxError.NOT_FOUND) return { status: SERVICE_STATUS_STOPPED };
|
||||
if (error) throw error;
|
||||
|
||||
const ip = safe.query(container, 'NetworkSettings.Networks.cloudron.IPAddress', null);
|
||||
@@ -2055,14 +2067,14 @@ async function statusGraphite() {
|
||||
.timeout(20000)
|
||||
.ok(() => true));
|
||||
|
||||
if (networkError) return { status: exports.SERVICE_STATUS_STARTING, error: `Error waiting for graphite: ${networkError.message}` };
|
||||
if (response.status !== 200) return { status: exports.SERVICE_STATUS_STARTING, error: `Error waiting for graphite. Status code: ${response.status} message: ${response.body.message}` };
|
||||
if (networkError) return { status: SERVICE_STATUS_STARTING, error: `Error waiting for graphite: ${networkError.message}` };
|
||||
if (response.status !== 200) return { status: SERVICE_STATUS_STARTING, error: `Error waiting for graphite. Status code: ${response.status} message: ${response.body.message}` };
|
||||
|
||||
const result = await docker.getStats('graphite', { stream: false });
|
||||
const stats = result.memory_stats || { usage: 0, limit: 1 };
|
||||
|
||||
return {
|
||||
status: container.State.Running ? exports.SERVICE_STATUS_ACTIVE : exports.SERVICE_STATUS_STOPPED,
|
||||
status: container.State.Running ? SERVICE_STATUS_ACTIVE : SERVICE_STATUS_STOPPED,
|
||||
memoryUsed: stats.usage,
|
||||
memoryPercent: parseInt(100 * stats.usage / stats.limit)
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user