@alwaysai/device-agent 0.0.3 → 0.0.4
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/lib/application-control/backup.d.ts +8 -0
- package/lib/application-control/backup.d.ts.map +1 -0
- package/lib/application-control/backup.js +30 -0
- package/lib/application-control/backup.js.map +1 -0
- package/lib/application-control/install.d.ts +10 -0
- package/lib/application-control/install.d.ts.map +1 -0
- package/lib/application-control/install.js +87 -0
- package/lib/application-control/install.js.map +1 -0
- package/lib/application-control/models.d.ts +8 -0
- package/lib/application-control/models.d.ts.map +1 -1
- package/lib/application-control/models.js +45 -2
- package/lib/application-control/models.js.map +1 -1
- package/lib/application-control/{application-control.d.ts → status.d.ts} +3 -23
- package/lib/application-control/status.d.ts.map +1 -0
- package/lib/application-control/{application-control.js → status.js} +6 -101
- package/lib/application-control/status.js.map +1 -0
- package/lib/application-control/types.d.ts +18 -0
- package/lib/application-control/types.d.ts.map +1 -0
- package/lib/application-control/types.js +3 -0
- package/lib/application-control/types.js.map +1 -0
- package/lib/application-control/utils.d.ts.map +1 -1
- package/lib/application-control/utils.js +3 -3
- package/lib/application-control/utils.js.map +1 -1
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts +2 -0
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts.map +1 -1
- package/lib/cloud-connection/device-agent-cloud-connection.js +59 -2
- package/lib/cloud-connection/device-agent-cloud-connection.js.map +1 -1
- package/lib/constants.d.ts +2 -6
- package/lib/constants.d.ts.map +1 -1
- package/lib/constants.js +3 -9
- package/lib/constants.js.map +1 -1
- package/lib/subcommands/app/app.d.ts +24 -14
- package/lib/subcommands/app/app.d.ts.map +1 -1
- package/lib/subcommands/app/app.js +89 -68
- package/lib/subcommands/app/app.js.map +1 -1
- package/lib/subcommands/app/index.d.ts.map +1 -1
- package/lib/subcommands/app/index.js +4 -1
- package/lib/subcommands/app/index.js.map +1 -1
- package/lib/subcommands/index.d.ts.map +1 -1
- package/lib/subcommands/index.js +1 -7
- package/lib/subcommands/index.js.map +1 -1
- package/lib/util/directories.d.ts +1 -1
- package/lib/util/directories.d.ts.map +1 -1
- package/lib/util/directories.js +8 -13
- package/lib/util/directories.js.map +1 -1
- package/lib/util/get-device-id.d.ts +2 -0
- package/lib/util/get-device-id.d.ts.map +1 -0
- package/lib/util/get-device-id.js +24 -0
- package/lib/util/get-device-id.js.map +1 -0
- package/lib/web/web-interface.d.ts.map +1 -1
- package/lib/web/web-interface.js +8 -7
- package/lib/web/web-interface.js.map +1 -1
- package/package.json +2 -2
- package/readme.md +15 -8
- package/src/application-control/backup.ts +28 -0
- package/src/application-control/install.ts +102 -0
- package/src/application-control/models.ts +46 -1
- package/src/application-control/{application-control.ts → status.ts} +5 -124
- package/src/application-control/types.ts +5 -0
- package/src/application-control/utils.ts +3 -3
- package/src/cloud-connection/device-agent-cloud-connection.ts +66 -2
- package/src/constants.ts +2 -12
- package/src/subcommands/app/app.ts +98 -63
- package/src/subcommands/app/index.ts +7 -1
- package/src/subcommands/index.ts +1 -7
- package/src/util/directories.ts +10 -15
- package/src/util/get-device-id.ts +22 -0
- package/src/web/web-interface.ts +2 -6
- package/lib/application-control/application-control.d.ts.map +0 -1
- package/lib/application-control/application-control.js.map +0 -1
- package/lib/subcommands/test-app.d.ts +0 -2
- package/lib/subcommands/test-app.d.ts.map +0 -1
- package/lib/subcommands/test-app.js +0 -29
- package/lib/subcommands/test-app.js.map +0 -1
- package/src/subcommands/test-app.ts +0 -30
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getDeviceId = void 0;
|
|
4
|
+
const directories_1 = require("../util/directories");
|
|
5
|
+
const alwayscli_1 = require("@alwaysai/alwayscli");
|
|
6
|
+
const config_nodejs_1 = require("@alwaysai/config-nodejs");
|
|
7
|
+
const t = require("io-ts");
|
|
8
|
+
function getDeviceId() {
|
|
9
|
+
const path = (0, directories_1.getCredentialsFilePath)();
|
|
10
|
+
const codec = t.type({
|
|
11
|
+
deviceId: t.union([t.string, t.undefined]),
|
|
12
|
+
});
|
|
13
|
+
const credentialsJsonFile = (0, config_nodejs_1.ConfigFile)({
|
|
14
|
+
path,
|
|
15
|
+
codec,
|
|
16
|
+
});
|
|
17
|
+
const config = credentialsJsonFile.readIfExists();
|
|
18
|
+
if (!config || !config.deviceId) {
|
|
19
|
+
throw new alwayscli_1.CliTerseError('Invalid device credentials! Please ensure you are using the device agent on a valid device.');
|
|
20
|
+
}
|
|
21
|
+
return config.deviceId;
|
|
22
|
+
}
|
|
23
|
+
exports.getDeviceId = getDeviceId;
|
|
24
|
+
//# sourceMappingURL=get-device-id.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-device-id.js","sourceRoot":"","sources":["../../src/util/get-device-id.ts"],"names":[],"mappings":";;;AAAA,qDAA6D;AAC7D,mDAAoD;AACpD,2DAAqD;AACrD,2BAA2B;AAE3B,SAAgB,WAAW;IACzB,MAAM,IAAI,GAAG,IAAA,oCAAsB,GAAE,CAAC;IACtC,MAAM,KAAK,GAAG,CAAC,CAAC,IAAI,CAAC;QACnB,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC;KAC3C,CAAC,CAAC;IACH,MAAM,mBAAmB,GAAG,IAAA,0BAAU,EAAC;QACrC,IAAI;QACJ,KAAK;KACN,CAAC,CAAC;IACH,MAAM,MAAM,GAAG,mBAAmB,CAAC,YAAY,EAAE,CAAC;IAClD,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE;QAC/B,MAAM,IAAI,yBAAa,CACrB,6FAA6F,CAC9F,CAAC;KACH;IACD,OAAO,MAAM,CAAC,QAAQ,CAAC;AACzB,CAAC;AAhBD,kCAgBC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"web-interface.d.ts","sourceRoot":"","sources":["../../src/web/web-interface.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"web-interface.d.ts","sourceRoot":"","sources":["../../src/web/web-interface.ts"],"names":[],"mappings":"AAYA,wBAAsB,eAAe,kBA8CpC"}
|
package/lib/web/web-interface.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.runWebInterface = void 0;
|
|
4
|
-
const
|
|
4
|
+
const install_1 = require("../application-control/install");
|
|
5
|
+
const status_1 = require("../application-control/status");
|
|
5
6
|
const device_control_1 = require("../device-control/device-control");
|
|
6
7
|
const express = require('express');
|
|
7
8
|
const app = express();
|
|
@@ -31,14 +32,14 @@ async function runWebInterface() {
|
|
|
31
32
|
socket.emit('apps-info-rsp', appsInfoRsp);
|
|
32
33
|
});
|
|
33
34
|
socket.on('start-app', async (msg) => {
|
|
34
|
-
(0,
|
|
35
|
+
(0, status_1.startApp)({ projectId: msg.projectId });
|
|
35
36
|
});
|
|
36
37
|
socket.on('stop-app', async (msg) => {
|
|
37
|
-
(0,
|
|
38
|
+
(0, status_1.stopApp)({ projectId: msg.projectId });
|
|
38
39
|
});
|
|
39
40
|
socket.on('restart-app', async (msg) => {
|
|
40
|
-
await (0,
|
|
41
|
-
setTimeout(() => (0,
|
|
41
|
+
await (0, status_1.stopApp)({ projectId: msg.projectId });
|
|
42
|
+
setTimeout(() => (0, status_1.startApp)({ projectId: msg.projectId }), 2000);
|
|
42
43
|
});
|
|
43
44
|
const appsInfoRsp = await buildAppsInfoRsp();
|
|
44
45
|
socket.emit('apps-info-rsp', appsInfoRsp);
|
|
@@ -59,10 +60,10 @@ async function buildDeviceInfoRsp() {
|
|
|
59
60
|
}
|
|
60
61
|
async function buildAppsInfoRsp() {
|
|
61
62
|
const apps = [];
|
|
62
|
-
const installedApps = await (0,
|
|
63
|
+
const installedApps = await (0, install_1.getInstalledApps)();
|
|
63
64
|
console.log(installedApps);
|
|
64
65
|
for (const appDetails of installedApps) {
|
|
65
|
-
const app = await (0,
|
|
66
|
+
const app = await (0, status_1.getAppStatus)({ projectId: appDetails.projectId });
|
|
66
67
|
console.log(app);
|
|
67
68
|
// FIXME: only reads first service
|
|
68
69
|
apps.push({ projectId: appDetails.projectId, status: app.services[0].state });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"web-interface.js","sourceRoot":"","sources":["../../src/web/web-interface.ts"],"names":[],"mappings":";;;AAAA,
|
|
1
|
+
{"version":3,"file":"web-interface.js","sourceRoot":"","sources":["../../src/web/web-interface.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alwaysai/device-agent",
|
|
3
3
|
"description": "The alwaysAI Device Agent",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.4",
|
|
5
5
|
"main": "lib/exports.js",
|
|
6
6
|
"types": "lib/exports.d.ts",
|
|
7
7
|
"bin": {
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"@types/signal-exit": "3.0.1",
|
|
35
35
|
"@types/tar": "6.1.1",
|
|
36
36
|
"aws-iot-device-sdk": "2.2.12",
|
|
37
|
-
"alwaysai": "1.
|
|
37
|
+
"alwaysai": "1.6.0",
|
|
38
38
|
"docker-compose": "0.23.17",
|
|
39
39
|
"express": "4.17.3",
|
|
40
40
|
"fp-ts": "2.11.5",
|
package/readme.md
CHANGED
|
@@ -47,14 +47,21 @@ Usage: aai-agent <subcommand> ...
|
|
|
47
47
|
|
|
48
48
|
Subcommands:
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
50
|
+
login : Login to alwaysAI (this is meant for scripted environments)
|
|
51
|
+
app list : List all installed apps
|
|
52
|
+
app list-releases : List all releases for a given app
|
|
53
|
+
app list-latest-release : List the latest release hash for a given app
|
|
54
|
+
app install : Install an alwaysAI app from a project
|
|
55
|
+
app status : Get the status of an installed alwaysAI app
|
|
56
|
+
app start : Start an installed alwaysAI app
|
|
57
|
+
app logs : Get logs for an application
|
|
58
|
+
app stop : Stop a running alwaysAI app
|
|
59
|
+
app uninstall : Remove an alwaysAI app
|
|
60
|
+
app rollback : Rollback an alwaysAI app to the previous version
|
|
61
|
+
app add-model : Add a model to an alwaysAI app
|
|
62
|
+
app remove-model : Remove a model from an alwaysAI app
|
|
63
|
+
app update-models : Update all models for an alwaysAI app
|
|
64
|
+
get-device-info : Get device info
|
|
58
65
|
```
|
|
59
66
|
|
|
60
67
|
To install a test app, run:
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import * as rimraf from 'rimraf';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
|
|
4
|
+
import { copyDir } from '../util/copy-dir';
|
|
5
|
+
import { buildApp, getAppDir } from './utils';
|
|
6
|
+
|
|
7
|
+
export const BACKUP_EXT = '.bak';
|
|
8
|
+
|
|
9
|
+
export async function createAppBackup(props: { projectId: string }) {
|
|
10
|
+
const { projectId } = props;
|
|
11
|
+
const appDir = getAppDir(projectId);
|
|
12
|
+
const backupAppDir = `${appDir}${BACKUP_EXT}`;
|
|
13
|
+
await copyDir({ srcPath: appDir, destPath: backupAppDir });
|
|
14
|
+
console.log(`Backed up app ${projectId} to ${backupAppDir}`);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function rollbackApp(props: { projectId: string }) {
|
|
18
|
+
const { projectId } = props;
|
|
19
|
+
const appDir = getAppDir(projectId);
|
|
20
|
+
const backupAppDir = `${appDir}${BACKUP_EXT}`;
|
|
21
|
+
if (!fs.existsSync(backupAppDir)) {
|
|
22
|
+
throw new Error(`Backup doesn't exist for ${projectId}`);
|
|
23
|
+
}
|
|
24
|
+
rimraf.sync(appDir);
|
|
25
|
+
await copyDir({ srcPath: `${appDir}${BACKUP_EXT}`, destPath: appDir });
|
|
26
|
+
await buildApp({ appDir });
|
|
27
|
+
console.log(`Rolled back app ${projectId} to previous version`);
|
|
28
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import * as rimraf from 'rimraf';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
|
|
5
|
+
import { runCliCmd } from '../util/run-cli-cmd';
|
|
6
|
+
import { JsSpawner } from '../util/spawner/js-spawner';
|
|
7
|
+
import {
|
|
8
|
+
APP_ROOT,
|
|
9
|
+
buildApp,
|
|
10
|
+
getAppDir,
|
|
11
|
+
getAppVersion,
|
|
12
|
+
isAppInstalled,
|
|
13
|
+
setAppVersion,
|
|
14
|
+
} from './utils';
|
|
15
|
+
import { AppDetails } from './types';
|
|
16
|
+
import { BACKUP_EXT, createAppBackup } from './backup';
|
|
17
|
+
import { stopApp } from './status';
|
|
18
|
+
|
|
19
|
+
export async function getInstalledApps(): Promise<AppDetails[]> {
|
|
20
|
+
if (!fs.existsSync(APP_ROOT)) {
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const projectIds = fs.readdirSync(APP_ROOT).filter(file => {
|
|
25
|
+
const isDir = fs.statSync(`${APP_ROOT}/${file}`).isDirectory();
|
|
26
|
+
const isBak = `${APP_ROOT}/${file}`.includes(BACKUP_EXT);
|
|
27
|
+
return isDir && !isBak;
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const appDetails: AppDetails[] = [];
|
|
31
|
+
for (const projectId of projectIds) {
|
|
32
|
+
const appDir = getAppDir(projectId);
|
|
33
|
+
const version = await getAppVersion({ appDir });
|
|
34
|
+
appDetails.push({ projectId, version });
|
|
35
|
+
}
|
|
36
|
+
return appDetails;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function installApp(props: {
|
|
40
|
+
projectId: string;
|
|
41
|
+
releaseHash: string;
|
|
42
|
+
}): Promise<void> {
|
|
43
|
+
const { projectId, releaseHash } = props;
|
|
44
|
+
console.log(`Installing ${projectId}`);
|
|
45
|
+
const appDir = getAppDir(projectId);
|
|
46
|
+
const TEMP_DIR = join(APP_ROOT, 'temp');
|
|
47
|
+
const spawner = JsSpawner();
|
|
48
|
+
await spawner.mkdirp(TEMP_DIR);
|
|
49
|
+
// TODO Check valid projectId
|
|
50
|
+
// Check if app is installed
|
|
51
|
+
if (await isAppInstalled({ appDir })) {
|
|
52
|
+
console.log('Application is already installed, updating');
|
|
53
|
+
await createAppBackup({ projectId });
|
|
54
|
+
await spawner.rimraf(appDir);
|
|
55
|
+
}
|
|
56
|
+
// Create app directory
|
|
57
|
+
await spawner.mkdirp(appDir);
|
|
58
|
+
|
|
59
|
+
// download the application
|
|
60
|
+
await runCliCmd({
|
|
61
|
+
cmd: [
|
|
62
|
+
'release',
|
|
63
|
+
'pull',
|
|
64
|
+
'--yes',
|
|
65
|
+
'--project',
|
|
66
|
+
projectId,
|
|
67
|
+
'--releaseHash',
|
|
68
|
+
releaseHash,
|
|
69
|
+
],
|
|
70
|
+
cwd: TEMP_DIR,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// Unpack app package and move to install dir
|
|
74
|
+
await spawner.untar(fs.createReadStream(join(TEMP_DIR, `${releaseHash}.tgz`)), appDir);
|
|
75
|
+
console.log(`Unpacked application to ${appDir}`);
|
|
76
|
+
|
|
77
|
+
await setAppVersion({ appDir, version: releaseHash });
|
|
78
|
+
|
|
79
|
+
await buildApp({ appDir });
|
|
80
|
+
|
|
81
|
+
await spawner.rimraf(TEMP_DIR);
|
|
82
|
+
|
|
83
|
+
console.log(`Installed ${projectId} to ${appDir}`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export async function uninstallApp(props: { projectId: string }): Promise<void> {
|
|
87
|
+
const { projectId } = props;
|
|
88
|
+
const appDir = getAppDir(projectId);
|
|
89
|
+
if (!(await isAppInstalled({ appDir }))) {
|
|
90
|
+
console.log(`Application ${projectId} not installed`);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
await stopApp({ projectId });
|
|
95
|
+
} catch {
|
|
96
|
+
console.log('Failed to stop app, may be left running...');
|
|
97
|
+
}
|
|
98
|
+
// Delete application directory and backup
|
|
99
|
+
rimraf.sync(appDir);
|
|
100
|
+
rimraf.sync(`${appDir}${BACKUP_EXT}`);
|
|
101
|
+
console.log('Uninstalled', projectId);
|
|
102
|
+
}
|
|
@@ -1,6 +1,32 @@
|
|
|
1
|
+
import { existsSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
|
|
1
4
|
import { runCliCmd } from '../util/run-cli-cmd';
|
|
5
|
+
import { JsSpawner } from '../util/spawner/js-spawner';
|
|
6
|
+
import { ModelDetails } from './types';
|
|
2
7
|
import { buildApp, getAppDir, isAppInstalled } from './utils';
|
|
3
8
|
|
|
9
|
+
export async function getAppModels(props: { projectId: string }) {
|
|
10
|
+
const { projectId } = props;
|
|
11
|
+
const appDir = getAppDir(projectId);
|
|
12
|
+
if (!(await isAppInstalled({ appDir }))) {
|
|
13
|
+
throw new Error('Application is not installed');
|
|
14
|
+
}
|
|
15
|
+
const appCfgPath = join(appDir, 'alwaysai.app.json');
|
|
16
|
+
if (!existsSync(appCfgPath)) {
|
|
17
|
+
throw new Error('Application config not found!');
|
|
18
|
+
}
|
|
19
|
+
const appCfg = JSON.parse(await JsSpawner().readFile(appCfgPath));
|
|
20
|
+
const modelDetails: ModelDetails[] = [];
|
|
21
|
+
if (!('models' in appCfg)) {
|
|
22
|
+
return modelDetails;
|
|
23
|
+
}
|
|
24
|
+
for (const model in appCfg['models']) {
|
|
25
|
+
modelDetails.push({ modelId: model, version: appCfg['models'][model] });
|
|
26
|
+
}
|
|
27
|
+
return modelDetails;
|
|
28
|
+
}
|
|
29
|
+
|
|
4
30
|
export async function addModel(props: { projectId: string; modelId: string }) {
|
|
5
31
|
const { projectId, modelId } = props;
|
|
6
32
|
const appDir = getAppDir(projectId);
|
|
@@ -21,11 +47,30 @@ export async function removeModel(props: { projectId: string; modelId: string })
|
|
|
21
47
|
throw new Error('Application is not installed');
|
|
22
48
|
}
|
|
23
49
|
await runCliCmd({
|
|
24
|
-
cmd: ['app', 'models', 'remove', modelId],
|
|
50
|
+
cmd: ['app', 'models', 'remove', modelId, '--purge'],
|
|
25
51
|
cwd: appDir,
|
|
26
52
|
});
|
|
27
53
|
}
|
|
28
54
|
|
|
55
|
+
export async function replaceModels(props: { projectId: string; modelIds: string[] }) {
|
|
56
|
+
const { projectId, modelIds } = props;
|
|
57
|
+
const appDir = getAppDir(projectId);
|
|
58
|
+
if (!(await isAppInstalled({ appDir }))) {
|
|
59
|
+
throw new Error('Application is not installed');
|
|
60
|
+
}
|
|
61
|
+
await runCliCmd({
|
|
62
|
+
cmd: ['app', 'models', 'remove-all', '--purge'],
|
|
63
|
+
cwd: appDir,
|
|
64
|
+
});
|
|
65
|
+
for (const modelId of modelIds) {
|
|
66
|
+
await runCliCmd({
|
|
67
|
+
cmd: ['app', 'models', 'add', modelId],
|
|
68
|
+
cwd: appDir,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
await buildApp({ appDir });
|
|
72
|
+
}
|
|
73
|
+
|
|
29
74
|
export async function updateModels(props: { projectId: string }) {
|
|
30
75
|
const { projectId } = props;
|
|
31
76
|
const appDir = getAppDir(projectId);
|
|
@@ -1,66 +1,10 @@
|
|
|
1
1
|
import compose from 'docker-compose';
|
|
2
|
-
import * as rimraf from 'rimraf';
|
|
3
|
-
import * as fs from 'fs';
|
|
4
2
|
import { fetchAppReleaseHistory } from 'alwaysai';
|
|
5
|
-
import { join } from 'path';
|
|
6
3
|
|
|
7
|
-
import { runCliCmd } from '../util/run-cli-cmd';
|
|
8
4
|
import { runDockerLogin } from '../docker/docker-cmd';
|
|
9
5
|
import { JsSpawner } from '../util/spawner/js-spawner';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
APP_ROOT,
|
|
13
|
-
buildApp,
|
|
14
|
-
getAppDir,
|
|
15
|
-
getAppVersion,
|
|
16
|
-
isAppInstalled,
|
|
17
|
-
setAppVersion,
|
|
18
|
-
} from './utils';
|
|
19
|
-
|
|
20
|
-
const BACKUP_EXT = '.bak';
|
|
21
|
-
|
|
22
|
-
export type AppDetails = { projectId: string; version: string };
|
|
23
|
-
|
|
24
|
-
export async function getInstalledApps(): Promise<AppDetails[]> {
|
|
25
|
-
if (!fs.existsSync(APP_ROOT)) {
|
|
26
|
-
return [];
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const projectIds = fs.readdirSync(APP_ROOT).filter(file => {
|
|
30
|
-
const isDir = fs.statSync(`${APP_ROOT}/${file}`).isDirectory();
|
|
31
|
-
const isBak = `${APP_ROOT}/${file}`.includes(BACKUP_EXT);
|
|
32
|
-
return isDir && !isBak;
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
const appDetails: AppDetails[] = [];
|
|
36
|
-
for (const projectId of projectIds) {
|
|
37
|
-
const appDir = getAppDir(projectId);
|
|
38
|
-
const version = await getAppVersion({ appDir });
|
|
39
|
-
appDetails.push({ projectId, version });
|
|
40
|
-
}
|
|
41
|
-
return appDetails;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
async function createAppBackup(props: { projectId: string }) {
|
|
45
|
-
const { projectId } = props;
|
|
46
|
-
const appDir = getAppDir(projectId);
|
|
47
|
-
const backupAppDir = `${appDir}${BACKUP_EXT}`;
|
|
48
|
-
await copyDir({ srcPath: appDir, destPath: backupAppDir });
|
|
49
|
-
console.log(`Backed up app ${projectId} to ${backupAppDir}`);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export async function rollbackApp(props: { projectId: string }) {
|
|
53
|
-
const { projectId } = props;
|
|
54
|
-
const appDir = getAppDir(projectId);
|
|
55
|
-
const backupAppDir = `${appDir}${BACKUP_EXT}`;
|
|
56
|
-
if (!fs.existsSync(backupAppDir)) {
|
|
57
|
-
throw new Error(`Backup doesn't exist for ${projectId}`);
|
|
58
|
-
}
|
|
59
|
-
rimraf.sync(appDir);
|
|
60
|
-
await copyDir({ srcPath: `${appDir}${BACKUP_EXT}`, destPath: appDir });
|
|
61
|
-
await buildApp({ appDir });
|
|
62
|
-
console.log(`Rolled back app ${projectId} to previous version`);
|
|
63
|
-
}
|
|
6
|
+
import { getAppDir, getAppVersion, isAppInstalled } from './utils';
|
|
7
|
+
import { AppStatus, ServiceState, ServiceStatus } from './types';
|
|
64
8
|
|
|
65
9
|
export async function listAppReleases(props: { projectId: string }) {
|
|
66
10
|
const { projectId } = props;
|
|
@@ -77,57 +21,6 @@ export async function listAppLatestRelease(props: { projectId: string }) {
|
|
|
77
21
|
return undefined;
|
|
78
22
|
}
|
|
79
23
|
|
|
80
|
-
export async function installApp(props: {
|
|
81
|
-
projectId: string;
|
|
82
|
-
releaseHash: string;
|
|
83
|
-
}): Promise<void> {
|
|
84
|
-
const { projectId, releaseHash } = props;
|
|
85
|
-
console.log(`Installing ${projectId}`);
|
|
86
|
-
const appDir = getAppDir(projectId);
|
|
87
|
-
const TEMP_DIR = join(APP_ROOT, 'temp');
|
|
88
|
-
const spawner = JsSpawner();
|
|
89
|
-
await spawner.mkdirp(TEMP_DIR);
|
|
90
|
-
// TODO Check valid projectId
|
|
91
|
-
// Check if app is installed
|
|
92
|
-
if (await isAppInstalled({ appDir })) {
|
|
93
|
-
console.log('Application is already installed, updating');
|
|
94
|
-
await createAppBackup({ projectId });
|
|
95
|
-
await spawner.rimraf(appDir);
|
|
96
|
-
}
|
|
97
|
-
// Create app directory
|
|
98
|
-
await spawner.mkdirp(appDir);
|
|
99
|
-
|
|
100
|
-
// download the application
|
|
101
|
-
await runCliCmd({
|
|
102
|
-
cmd: [
|
|
103
|
-
'release',
|
|
104
|
-
'pull',
|
|
105
|
-
'--yes',
|
|
106
|
-
'--project',
|
|
107
|
-
projectId,
|
|
108
|
-
'--releaseHash',
|
|
109
|
-
releaseHash,
|
|
110
|
-
],
|
|
111
|
-
cwd: TEMP_DIR,
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
// Unpack app package and move to install dir
|
|
115
|
-
await spawner.untar(fs.createReadStream(join(TEMP_DIR, `${releaseHash}.tgz`)), appDir);
|
|
116
|
-
console.log(`Unpacked application to ${appDir}`);
|
|
117
|
-
|
|
118
|
-
await setAppVersion({ appDir, version: releaseHash });
|
|
119
|
-
|
|
120
|
-
await buildApp({ appDir });
|
|
121
|
-
|
|
122
|
-
await spawner.rimraf(TEMP_DIR);
|
|
123
|
-
|
|
124
|
-
console.log(`Installed ${projectId} to ${appDir}`);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
export type ServiceState = 'Stopped' | 'Up' | 'Restarting';
|
|
128
|
-
export type ServiceStatus = { name: string; state: ServiceState };
|
|
129
|
-
export type AppStatus = { appDetails: AppDetails; services: ServiceStatus[] };
|
|
130
|
-
|
|
131
24
|
export async function getAppStatus(props: { projectId: string }): Promise<AppStatus> {
|
|
132
25
|
const { projectId } = props;
|
|
133
26
|
const appDir = getAppDir(projectId);
|
|
@@ -254,20 +147,8 @@ export async function stopApp(props: { projectId: string }): Promise<void> {
|
|
|
254
147
|
console.log('Stopped', projectId);
|
|
255
148
|
}
|
|
256
149
|
|
|
257
|
-
export async function
|
|
150
|
+
export async function restartApp(props: { projectId: string }): Promise<void> {
|
|
258
151
|
const { projectId } = props;
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
console.log(`Application ${projectId} not installed`);
|
|
262
|
-
return;
|
|
263
|
-
}
|
|
264
|
-
try {
|
|
265
|
-
await stopApp({ projectId });
|
|
266
|
-
} catch {
|
|
267
|
-
console.log('Failed to stop app, may be left running...');
|
|
268
|
-
}
|
|
269
|
-
// Delete application directory and backup
|
|
270
|
-
rimraf.sync(appDir);
|
|
271
|
-
rimraf.sync(`${appDir}${BACKUP_EXT}`);
|
|
272
|
-
console.log('Uninstalled', projectId);
|
|
152
|
+
await stopApp({ projectId });
|
|
153
|
+
await startApp({ projectId });
|
|
273
154
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export type AppDetails = { projectId: string; version: string };
|
|
2
|
+
export type ServiceState = 'Stopped' | 'Up' | 'Restarting';
|
|
3
|
+
export type ServiceStatus = { name: string; state: ServiceState };
|
|
4
|
+
export type AppStatus = { appDetails: AppDetails; services: ServiceStatus[] };
|
|
5
|
+
export type ModelDetails = { modelId: string; version: number };
|
|
@@ -3,10 +3,10 @@ import * as path from 'path';
|
|
|
3
3
|
import * as fs from 'fs';
|
|
4
4
|
|
|
5
5
|
import { JsSpawner } from '../util/spawner/js-spawner';
|
|
6
|
-
import {
|
|
6
|
+
import { AAI_DIR } from 'alwaysai';
|
|
7
7
|
import { runCliCmd } from '../util/run-cli-cmd';
|
|
8
8
|
|
|
9
|
-
export const APP_ROOT = path.join(
|
|
9
|
+
export const APP_ROOT = path.join(AAI_DIR, 'applications');
|
|
10
10
|
const RELEASE_FILENAME = 'alwaysai.release.json';
|
|
11
11
|
const DOCKER_COMPOSE_FILENAME = 'docker-compose.yaml';
|
|
12
12
|
|
|
@@ -41,7 +41,7 @@ export async function getAppVersion(props: { appDir: string }) {
|
|
|
41
41
|
export async function buildApp(props: { appDir: string }) {
|
|
42
42
|
const { appDir } = props;
|
|
43
43
|
await runCliCmd({
|
|
44
|
-
cmd: ['app', 'configure', '--yes', '--protocol', 'docker:'],
|
|
44
|
+
cmd: ['app', 'configure', '--yes', '--protocol', 'docker:', '--skip-model-sync'],
|
|
45
45
|
cwd: appDir,
|
|
46
46
|
});
|
|
47
47
|
await runCliCmd({ cmd: ['app', 'install', '--yes', '--clean', '--pull'], cwd: appDir });
|
|
@@ -5,11 +5,17 @@ import {
|
|
|
5
5
|
getCertificateFilePath,
|
|
6
6
|
getRootCertificateFilePath,
|
|
7
7
|
} from '../util/directories';
|
|
8
|
+
import { getAppStatus } from '../application-control/status';
|
|
9
|
+
import { getInstalledApps } from '../application-control/install';
|
|
10
|
+
import { AppStatus } from '../application-control/types';
|
|
11
|
+
import { getCpuUtil, getDiskUtil, getMemUtil } from '../device-control/device-control';
|
|
12
|
+
import { getDeviceId } from '../util/get-device-id';
|
|
8
13
|
|
|
9
14
|
export class DeviceAgentCloudConnection {
|
|
10
15
|
public device = awsIot.device;
|
|
11
|
-
private clientId =
|
|
16
|
+
private clientId = getDeviceId();
|
|
12
17
|
private host = getIoTCoreEndpointUrl();
|
|
18
|
+
private publishInterval = 1000;
|
|
13
19
|
|
|
14
20
|
constructor() {
|
|
15
21
|
this.device = awsIot.device({
|
|
@@ -22,6 +28,11 @@ export class DeviceAgentCloudConnection {
|
|
|
22
28
|
|
|
23
29
|
// TODO: subscribe to topics here
|
|
24
30
|
this.device.subscribe('command');
|
|
31
|
+
this.publishInterval = this.publishInterval;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public getPublishInterval() {
|
|
35
|
+
return this.publishInterval;
|
|
25
36
|
}
|
|
26
37
|
|
|
27
38
|
public getClientId() {
|
|
@@ -30,13 +41,66 @@ export class DeviceAgentCloudConnection {
|
|
|
30
41
|
|
|
31
42
|
public publishMessage(topic: string, message: string) {
|
|
32
43
|
console.log('publishing message ', message);
|
|
33
|
-
this.device.publish(topic,
|
|
44
|
+
this.device.publish(topic, message);
|
|
34
45
|
}
|
|
35
46
|
}
|
|
36
47
|
|
|
37
48
|
export function runDeviceAgentCloudInterface() {
|
|
38
49
|
const deviceAgent = new DeviceAgentCloudConnection();
|
|
39
50
|
|
|
51
|
+
async function getAppStateMessage() {
|
|
52
|
+
const appStateMessage: AppStatus[] = [];
|
|
53
|
+
const installedApps = await getInstalledApps();
|
|
54
|
+
for (const app of installedApps) {
|
|
55
|
+
const projectId = app['projectId'];
|
|
56
|
+
const status = await getAppStatus({ projectId });
|
|
57
|
+
appStateMessage.push(status);
|
|
58
|
+
}
|
|
59
|
+
const applicationStatePackage = {
|
|
60
|
+
applicationState: appStateMessage,
|
|
61
|
+
};
|
|
62
|
+
return applicationStatePackage;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const publishAppState = setInterval(async function() {
|
|
66
|
+
const topic = `device/${deviceAgent.getClientId()}/topic/application-management`;
|
|
67
|
+
const appStateMessage = await getAppStateMessage();
|
|
68
|
+
const appStatePackage = {
|
|
69
|
+
timestamp: new Date().toUTCString(),
|
|
70
|
+
deviceId: deviceAgent.getClientId(),
|
|
71
|
+
topic,
|
|
72
|
+
payload: appStateMessage,
|
|
73
|
+
};
|
|
74
|
+
deviceAgent.publishMessage(topic, JSON.stringify({ appStatePackage }));
|
|
75
|
+
}, deviceAgent.getPublishInterval());
|
|
76
|
+
|
|
77
|
+
async function getDeviceStatsMessage() {
|
|
78
|
+
const cpuUsage = await getCpuUtil();
|
|
79
|
+
const diskUtil = await getDiskUtil();
|
|
80
|
+
const memUtil = await getMemUtil();
|
|
81
|
+
|
|
82
|
+
const deviceStatsMessage = {
|
|
83
|
+
deviceStats: {
|
|
84
|
+
cpuUsage,
|
|
85
|
+
diskUtil,
|
|
86
|
+
freeMemoryPercentage: memUtil,
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
return deviceStatsMessage;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const publishDeviceStats = setInterval(async function() {
|
|
93
|
+
const topic = `device/${deviceAgent.getClientId()}/topic/device-management`;
|
|
94
|
+
const deviceStatsMessage = await getDeviceStatsMessage();
|
|
95
|
+
const deviceStatsPackage = {
|
|
96
|
+
timestamp: new Date().toUTCString(),
|
|
97
|
+
deviceId: deviceAgent.getClientId(),
|
|
98
|
+
topic,
|
|
99
|
+
payload: deviceStatsMessage,
|
|
100
|
+
};
|
|
101
|
+
deviceAgent.publishMessage(topic, JSON.stringify({ deviceStatsPackage }));
|
|
102
|
+
}, deviceAgent.getPublishInterval());
|
|
103
|
+
|
|
40
104
|
deviceAgent.device.on('connect', function() {
|
|
41
105
|
deviceAgent.publishMessage('connection', deviceAgent.getClientId());
|
|
42
106
|
console.log('Device Agent has connected to the cloud');
|
package/src/constants.ts
CHANGED
|
@@ -1,23 +1,13 @@
|
|
|
1
1
|
import { join, posix } from 'path';
|
|
2
2
|
import { homedir } from 'os';
|
|
3
|
-
const os = require('os');
|
|
4
|
-
const path = require('path');
|
|
5
3
|
|
|
6
4
|
export const ALWAYSAI_CLI_EXECUTABLE_NAME = 'aai-agent';
|
|
7
|
-
export const DOT_ALWAYSAI_DIR = join(homedir(), '.alwaysai');
|
|
8
|
-
export const AAI_DIR = path.join(os.homedir(), '.alwaysai');
|
|
9
5
|
|
|
10
6
|
export const DEVICE_TOKEN_FILE_NAME = 'alwaysai.credentials.json';
|
|
11
7
|
export const DEVICE_CONFIG_FILE_NAME = 'alwaysai.config.json';
|
|
12
8
|
|
|
13
|
-
export const
|
|
14
|
-
export const
|
|
15
|
-
|
|
16
|
-
export const DEV_HOST_CERT_AND_KEY_DIR = join(DEV_HOST_TOKEN_DIR, 'certificates');
|
|
17
|
-
export const DEVICE_CERT_AND_KEY_DIR_LINUX = posix.join(
|
|
18
|
-
DEVICE_TOKEN_DIR_LINUX,
|
|
19
|
-
'certificates',
|
|
20
|
-
);
|
|
9
|
+
export const TOKEN_DIR = join(homedir(), '.config', 'alwaysai');
|
|
10
|
+
export const CERT_AND_KEY_DIR = join(TOKEN_DIR, 'certificates');
|
|
21
11
|
|
|
22
12
|
export const DEVICE_CERTIFICATE_FILE_NAME = 'alwaysai.certificate.pem.crt';
|
|
23
13
|
export const DEVICE_PRIVATE_KEY_FILE_NAME = 'alwaysai.private.pem.key';
|