Skip to content

Commit 9a3639a

Browse files
authored
automatically upload adapter on start if the upload is missing for some reason (#2902)
1 parent fad2daa commit 9a3639a

File tree

4 files changed

+85
-93
lines changed

4 files changed

+85
-93
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
* (@foxriver76) enhanced translations for the `diskSpaceIssues` notification category
1010
* (@foxriver76) extend the time to wait until controller is stopped on controller UI upgrade
1111
* (@foxriver76) improved backup/restore process to work for arbitrary large installations
12+
* (@GermanBluefox/@foxriver76) implemented automatic upload on adapter start if version mismatch is detected
1213

1314
## 6.0.11 (2024-08-21) - Kiera
1415
* (foxriver76) only generate `packageUpdates` notification, if new updates detected

packages/cli/src/lib/setup/setupInstall.ts

Lines changed: 6 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -814,14 +814,14 @@ export class Install {
814814
let obj;
815815
let err;
816816
try {
817-
obj = await this.objects.getObjectAsync(`system.adapter.${adapter}`);
817+
obj = await this.objects.getObject(`system.adapter.${adapter}`);
818818
} catch (_err) {
819819
err = _err;
820820
}
821821
// Adapter is not installed - install it now
822822
if (err || !obj || !obj.common.installedVersion) {
823823
await this.installAdapter(adapter);
824-
obj = await this.objects.getObjectAsync(`system.adapter.${adapter}`);
824+
obj = await this.objects.getObject(`system.adapter.${adapter}`);
825825
}
826826

827827
if (!obj) {
@@ -837,7 +837,7 @@ export class Install {
837837
startkey: `${SYSTEM_ADAPTER_PREFIX}${adapter}.`,
838838
endkey: `${SYSTEM_ADAPTER_PREFIX}${adapter}.\u9999`
839839
});
840-
const systemConfig = await this.objects.getObjectAsync('system.config');
840+
const systemConfig = await this.objects.getObject('system.config');
841841
const defaultLogLevel = systemConfig?.common?.defaultLogLevel;
842842
if (!res) {
843843
console.error(`host.${hostname} error: view instanceStats`);
@@ -1845,34 +1845,9 @@ export class Install {
18451845
return;
18461846
}
18471847

1848-
const { installDir } = res;
1849-
1850-
if (name) {
1851-
await this.upload.uploadAdapter(name, true, true);
1852-
await this.upload.uploadAdapter(name, false, true);
1853-
await this.upload.upgradeAdapterObjects(name);
1854-
} else {
1855-
// Try to find io-package.json with the newest date
1856-
const dirs = fs.readdirSync(installDir);
1857-
let date = null;
1858-
let dir = null;
1859-
for (const _dir of dirs) {
1860-
if (fs.existsSync(`${installDir}/${_dir}/io-package.json`)) {
1861-
const stat = fs.statSync(`${installDir}/${_dir}/io-package.json`);
1862-
if (!date || stat.mtime.getTime() > date.getTime()) {
1863-
dir = _dir;
1864-
date = stat.mtime;
1865-
}
1866-
}
1867-
}
1868-
// if modify time is not older than one hour
1869-
if (dir && date && Date.now() - date.getTime() < 3600000) {
1870-
name = dir.substring(tools.appName.length + 1);
1871-
await this.upload.uploadAdapter(name, true, true);
1872-
await this.upload.uploadAdapter(name, false, true);
1873-
await this.upload.upgradeAdapterObjects(name);
1874-
}
1875-
}
1848+
await this.upload.uploadAdapter(name, true, true);
1849+
await this.upload.uploadAdapter(name, false, true);
1850+
await this.upload.upgradeAdapterObjects(name);
18761851

18771852
// re-enable stopped instances
18781853
await this.enableInstances(stoppedList, true);

packages/cli/src/lib/setup/setupUpload.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ interface Logger extends InternalLogger {
3434
log(message: string): void;
3535
}
3636

37+
/** Logger without noisy levels */
38+
type MinimalLogger = Omit<Logger, 'info' | 'silly' | 'debug'>;
39+
3740
export class Upload {
3841
private readonly states: StatesRedisClient;
3942
private readonly objects: ObjectsRedisClient;
@@ -324,7 +327,7 @@ export class Upload {
324327
return `${adapter}/${target}`;
325328
}
326329

327-
async eraseFiles(files: any[], logger: Logger | typeof console): Promise<void> {
330+
async eraseFiles(files: any[], logger: MinimalLogger | typeof console): Promise<void> {
328331
if (files && files.length) {
329332
for (const file of files) {
330333
try {
@@ -346,7 +349,7 @@ export class Upload {
346349
async collectExistingFilesToDelete(
347350
adapter: string,
348351
path: string,
349-
logger: Logger | typeof console
352+
logger: MinimalLogger | typeof console
350353
): Promise<{ filesToDelete: File[]; dirs: File[] }> {
351354
let _files: File[] = [];
352355
let _dirs: File[] = [];
@@ -392,11 +395,11 @@ export class Upload {
392395
isAdmin: boolean,
393396
files: string[],
394397
id: string,
395-
logger: Logger | typeof console
398+
logger: MinimalLogger | typeof console
396399
): Promise<string> {
397400
const uploadID = `system.adapter.${adapter}.upload`;
398401

399-
await this.states.setStateAsync(uploadID, { val: 0, ack: true });
402+
await this.states.setState(uploadID, { val: 0, ack: true });
400403

401404
for (let f = 0; f < files.length; f++) {
402405
const file = files[f];
@@ -494,7 +497,7 @@ export class Upload {
494497
isAdmin: boolean,
495498
forceUpload: boolean,
496499
subTree?: string,
497-
_logger?: Logger
500+
_logger?: MinimalLogger
498501
): Promise<string> {
499502
const id = adapter + (isAdmin ? '.admin' : '');
500503
const adapterDir = tools.getAdapterDir(adapter);
@@ -708,7 +711,7 @@ export class Upload {
708711
name: string,
709712
ioPack: ioBroker.AdapterObject,
710713
hostname: string,
711-
logger: Logger | typeof console
714+
logger: MinimalLogger | typeof console
712715
): Promise<string> {
713716
// Update all instances of this host
714717
const res = await this.objects.getObjectViewAsync('system', 'instance', {
@@ -805,7 +808,7 @@ export class Upload {
805808
async upgradeAdapterObjects(
806809
name: string,
807810
ioPack?: ioBroker.AdapterObject,
808-
logger: Logger | typeof console = console
811+
logger: MinimalLogger | typeof console = console
809812
): Promise<string> {
810813
const adapterDir = tools.getAdapterDir(name);
811814
let ioPackFile;

packages/controller/src/main.ts

Lines changed: 68 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ interface GetLogFilesResult {
7575
}
7676
interface UploadTask {
7777
adapter: string;
78-
msg: ioBroker.SendableMessage;
78+
msg?: ioBroker.SendableMessage;
7979
}
8080

8181
interface RebuildArgs {
@@ -224,8 +224,6 @@ let diskWarningLevel = DEFAULT_DISK_WARNING_LEVEL;
224224
let updateIPsTimer: NodeJS.Timeout | null = null;
225225
let lastDiagSend: null | number = null;
226226

227-
const uploadTasks: UploadTask[] = [];
228-
229227
const config = getConfig();
230228

231229
/**
@@ -1924,23 +1922,21 @@ async function getVersionFromHost(hostId: ioBroker.ObjectIDs.Host): Promise<Host
19241922
}
19251923

19261924
/**
1927-
* Upload all adapters which are currently in `uploadTasks` queue
1925+
* Upload given adapter
1926+
*
1927+
* @param task The upload task information containing name and an optional message
19281928
*/
1929-
async function startAdapterUpload(): Promise<void> {
1930-
if (!uploadTasks.length) {
1931-
return;
1932-
}
1933-
1929+
async function uploadAdapter(task: UploadTask): Promise<void> {
19341930
if (!upload) {
19351931
upload = new Upload({
19361932
states: states!,
19371933
objects: objects!
19381934
});
19391935
}
19401936

1941-
const msg = uploadTasks[0].msg;
1937+
const msg = task.msg;
19421938

1943-
const logger = msg.from
1939+
const logger = msg?.from
19441940
? {
19451941
log: (text: string) =>
19461942
// @ts-expect-error formally text is not allowed in Message, why not wrapped in message payload property?
@@ -1954,18 +1950,13 @@ async function startAdapterUpload(): Promise<void> {
19541950
}
19551951
: undefined;
19561952

1957-
// @ts-expect-error yes the logger is missing some levels
1958-
await upload.uploadAdapter(uploadTasks[0].adapter, true, true, '', logger);
1959-
// @ts-expect-error the logger is missing some levels
1960-
await upload.upgradeAdapterObjects(uploadTasks[0].adapter, undefined, logger);
1961-
// @ts-expect-error yes the logger is missing some levels
1962-
await upload.uploadAdapter(uploadTasks[0].adapter, false, true, '', logger);
1953+
await upload.uploadAdapter(task.adapter, true, true, '', logger);
1954+
await upload.upgradeAdapterObjects(task.adapter, undefined, logger);
1955+
await upload.uploadAdapter(task.adapter, false, true, '', logger);
19631956
// send response to requester
1964-
msg.callback && msg.from && sendTo(msg.from, msg.command, { result: 'done' }, msg.callback);
1965-
1966-
uploadTasks.shift();
1967-
1968-
setImmediate(startAdapterUpload);
1957+
if (msg?.callback && msg.from) {
1958+
sendTo(msg.from, msg.command, { result: 'done' }, msg.callback);
1959+
}
19691960
}
19701961

19711962
/**
@@ -2803,9 +2794,7 @@ async function processMessage(msg: ioBroker.SendableMessage): Promise<null | voi
28032794

28042795
case 'upload': {
28052796
if (msg.message) {
2806-
uploadTasks.push({ adapter: msg.message, msg });
2807-
// start upload if no tasks running
2808-
uploadTasks.length === 1 && startAdapterUpload();
2797+
uploadAdapter({ adapter: msg.message, msg });
28092798
} else {
28102799
logger.error(`${hostLogPrefix} No adapter name is specified for upload command from ${msg.from}`);
28112800
}
@@ -3797,26 +3786,8 @@ async function startInstance(id: ioBroker.ObjectIDs.Instance, wakeUp = false): P
37973786
}
37983787
}
37993788

3800-
const isBlocked = await blocklistManager.isAdapterVersionBlocked({
3801-
version: instance.common.version,
3802-
adapterName: instance.common.name
3803-
});
3804-
3805-
if (isBlocked) {
3806-
const message = `Do not start instance "${id}", because the version "${instance.common.version}" has been blocked by the developer`;
3807-
logger.error(`${hostLogPrefix} ${message}`);
3808-
3809-
await notificationHandler.addMessage({
3810-
scope: 'system',
3811-
category: 'blockedVersions',
3812-
message,
3813-
instance: SYSTEM_HOST_PREFIX + hostname
3814-
});
3815-
return;
3816-
}
3817-
38183789
const adapterDir = tools.getAdapterDir(name);
3819-
if (!fs.existsSync(adapterDir!)) {
3790+
if (adapterDir === null || !fs.existsSync(adapterDir)) {
38203791
proc.downloadRetry = proc.downloadRetry || 0;
38213792
logger.debug(`${hostLogPrefix} startInstance Queue ${id} for installation`);
38223793
installQueue.push({
@@ -3859,6 +3830,50 @@ async function startInstance(id: ioBroker.ObjectIDs.Instance, wakeUp = false): P
38593830
}
38603831
}
38613832

3833+
try {
3834+
// check if the io-package content is uploaded to the database
3835+
const ioPack = fs.readJSONSync(path.join(adapterDir, 'io-package.json'));
3836+
3837+
if (ioPack.common.version !== instance.common.version) {
3838+
logger.warn(`${hostLogPrefix} Detected missing upload of adapter "${name}" - starting upload now.`);
3839+
await uploadAdapter({ adapter: name });
3840+
return;
3841+
}
3842+
} catch (e) {
3843+
logger.error(
3844+
`${hostLogPrefix} startInstance ${name}.${instanceNo}: Error while ensuring adapter is uploaded: ${e.message}`
3845+
);
3846+
}
3847+
3848+
const isBlocked = await blocklistManager.isAdapterVersionBlocked({
3849+
version: instance.common.version,
3850+
adapterName: instance.common.name
3851+
});
3852+
3853+
if (isBlocked) {
3854+
const message = `Do not start instance "${id}", because the version "${instance.common.version}" has been blocked by the developer`;
3855+
logger.error(`${hostLogPrefix} ${message}`);
3856+
3857+
await notificationHandler.addMessage({
3858+
scope: 'system',
3859+
category: 'blockedVersions',
3860+
message,
3861+
instance: SYSTEM_HOST_PREFIX + hostname
3862+
});
3863+
return;
3864+
}
3865+
3866+
// Check if all required adapters installed and have a valid version
3867+
if (instance.common.dependencies || instance.common.globalDependencies) {
3868+
try {
3869+
await checkVersions(id, instance.common.dependencies, instance.common.globalDependencies);
3870+
} catch (e) {
3871+
logger.error(`${hostLogPrefix} startInstance ${id} ${e.message}`);
3872+
// Do not start this instance
3873+
return;
3874+
}
3875+
}
3876+
38623877
// workaround for old vis
38633878
if (instance.common.onlyWWW && name === 'vis') {
38643879
instance.common.onlyWWW = false;
@@ -3886,7 +3901,7 @@ async function startInstance(id: ioBroker.ObjectIDs.Instance, wakeUp = false): P
38863901
// read node.js engine requirements
38873902
try {
38883903
// read directly from disk and not via require to allow "on the fly" updates of adapters.
3889-
const packJSON = fs.readJSONSync(`${adapterDir}/package.json`);
3904+
const packJSON = fs.readJSONSync(path.join(adapterDir, 'package.json'));
38903905
proc.engine = packJSON?.engines?.node;
38913906
} catch {
38923907
logger.error(
@@ -3901,16 +3916,14 @@ async function startInstance(id: ioBroker.ObjectIDs.Instance, wakeUp = false): P
39013916
`${hostLogPrefix} startInstance ${name}.${instanceNo}: required Node.js version ${proc.engine}, actual version ${process.version}`
39023917
);
39033918
// disable instance
3904-
objects!.getObject(id, (err, obj) => {
3905-
if (obj && obj.common && obj.common.enabled) {
3906-
obj.common.enabled = false;
3907-
objects!.setObject(obj._id, obj, _err =>
3908-
logger.warn(
3909-
`${hostLogPrefix} startInstance ${name}.${instanceNo}: instance disabled because of Node.js version mismatch`
3910-
)
3911-
);
3912-
}
3913-
});
3919+
const obj = await objects!.getObject(id);
3920+
if (obj?.common?.enabled) {
3921+
obj.common.enabled = false;
3922+
await objects!.setObject(obj._id, obj);
3923+
logger.warn(
3924+
`${hostLogPrefix} startInstance ${name}.${instanceNo}: instance disabled because of Node.js version mismatch`
3925+
);
3926+
}
39143927
return;
39153928
}
39163929
}

0 commit comments

Comments
 (0)