@alwaysai/device-agent 0.0.3 → 0.0.5
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 +34 -0
- package/lib/application-control/backup.js.map +1 -0
- package/lib/application-control/environment-variables.d.ts +9 -0
- package/lib/application-control/environment-variables.d.ts.map +1 -0
- package/lib/application-control/environment-variables.js +82 -0
- package/lib/application-control/environment-variables.js.map +1 -0
- package/lib/application-control/index.d.ts +9 -0
- package/lib/application-control/index.d.ts.map +1 -0
- package/lib/application-control/index.js +27 -0
- package/lib/application-control/index.js.map +1 -0
- package/lib/application-control/install.d.ts +16 -0
- package/lib/application-control/install.d.ts.map +1 -0
- package/lib/application-control/install.js +117 -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 +44 -11
- package/lib/application-control/models.js.map +1 -1
- package/lib/application-control/status.d.ts +26 -0
- package/lib/application-control/status.d.ts.map +1 -0
- package/lib/application-control/status.js +138 -0
- package/lib/application-control/status.js.map +1 -0
- package/lib/application-control/types.d.ts +5 -0
- package/lib/application-control/types.d.ts.map +1 -0
- package/lib/{util/spawner → application-control}/types.js +0 -0
- package/lib/{util/spawner → application-control}/types.js.map +1 -1
- package/lib/application-control/utils.d.ts +2 -9
- package/lib/application-control/utils.d.ts.map +1 -1
- package/lib/application-control/utils.js +14 -29
- package/lib/application-control/utils.js.map +1 -1
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts +16 -1
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts.map +1 -1
- package/lib/cloud-connection/device-agent-cloud-connection.js +239 -9
- package/lib/cloud-connection/device-agent-cloud-connection.js.map +1 -1
- package/lib/docker/docker-cmd.js +2 -2
- package/lib/docker/docker-cmd.js.map +1 -1
- package/lib/docker/docker-compose-cmd.js +2 -2
- package/lib/docker/docker-compose-cmd.js.map +1 -1
- package/lib/environment.d.ts +2 -0
- package/lib/environment.d.ts.map +1 -1
- package/lib/environment.js +3 -1
- package/lib/environment.js.map +1 -1
- package/lib/index.js +10 -8
- package/lib/index.js.map +1 -1
- package/lib/infrastructure/agent-config.d.ts +73 -0
- package/lib/infrastructure/agent-config.d.ts.map +1 -0
- package/lib/infrastructure/agent-config.js +186 -0
- package/lib/infrastructure/agent-config.js.map +1 -0
- package/lib/infrastructure/agent-config.test.d.ts +2 -0
- package/lib/infrastructure/agent-config.test.d.ts.map +1 -0
- package/lib/infrastructure/agent-config.test.js +135 -0
- package/lib/infrastructure/agent-config.test.js.map +1 -0
- package/lib/infrastructure/certificates-and-tokens.d.ts +6 -0
- package/lib/infrastructure/certificates-and-tokens.d.ts.map +1 -0
- package/lib/infrastructure/certificates-and-tokens.js +69 -0
- package/lib/infrastructure/certificates-and-tokens.js.map +1 -0
- package/lib/{util → infrastructure}/urls.d.ts +0 -0
- package/lib/infrastructure/urls.d.ts.map +1 -0
- package/lib/{util → infrastructure}/urls.js +3 -3
- package/lib/infrastructure/urls.js.map +1 -0
- package/lib/root.js +3 -3
- package/lib/root.js.map +1 -1
- package/lib/subcommands/app/app.d.ts +31 -14
- package/lib/subcommands/app/app.d.ts.map +1 -1
- package/lib/subcommands/app/app.js +117 -62
- 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 +6 -1
- package/lib/subcommands/app/index.js.map +1 -1
- package/lib/subcommands/device/device.d.ts +6 -0
- package/lib/subcommands/device/device.d.ts.map +1 -0
- package/lib/subcommands/device/device.js +62 -0
- package/lib/subcommands/device/device.js.map +1 -0
- package/lib/subcommands/device/index.d.ts +2 -0
- package/lib/subcommands/device/index.d.ts.map +1 -0
- package/lib/subcommands/device/index.js +11 -0
- package/lib/subcommands/device/index.js.map +1 -0
- package/lib/subcommands/get-model-package.d.ts +5 -0
- package/lib/subcommands/get-model-package.d.ts.map +1 -0
- package/lib/subcommands/get-model-package.js +51 -0
- package/lib/subcommands/get-model-package.js.map +1 -0
- package/lib/subcommands/index.d.ts +8 -1
- package/lib/subcommands/index.d.ts.map +1 -1
- package/lib/subcommands/index.js +6 -6
- package/lib/subcommands/index.js.map +1 -1
- package/lib/subcommands/login.d.ts +3 -2
- package/lib/subcommands/login.d.ts.map +1 -1
- package/lib/subcommands/login.js +11 -4
- package/lib/subcommands/login.js.map +1 -1
- package/lib/util/copy-dir.js +3 -3
- package/lib/util/copy-dir.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 +9 -14
- 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/package.json +19 -14
- package/readme.md +176 -72
- package/src/application-control/backup.ts +32 -0
- package/src/application-control/environment-variables.ts +81 -0
- package/src/application-control/index.ts +40 -0
- package/src/application-control/install.ts +126 -0
- package/src/application-control/models.ts +51 -11
- package/src/application-control/status.ts +156 -0
- package/src/application-control/types.ts +1 -0
- package/src/application-control/utils.ts +12 -27
- package/src/cloud-connection/device-agent-cloud-connection.ts +280 -13
- package/src/docker/docker-cmd.ts +1 -1
- package/src/docker/docker-compose-cmd.ts +1 -1
- package/src/environment.ts +2 -0
- package/src/index.ts +10 -7
- package/src/infrastructure/agent-config.test.ts +143 -0
- package/src/infrastructure/agent-config.ts +217 -0
- package/src/infrastructure/certificates-and-tokens.ts +71 -0
- package/src/{util → infrastructure}/urls.ts +1 -1
- package/src/root.ts +3 -3
- package/src/subcommands/app/app.ts +135 -62
- package/src/subcommands/app/index.ts +11 -1
- package/src/subcommands/device/device.ts +63 -0
- package/src/subcommands/device/index.ts +8 -0
- package/src/subcommands/get-model-package.ts +60 -0
- package/src/subcommands/index.ts +5 -5
- package/src/subcommands/login.ts +11 -4
- package/src/util/copy-dir.ts +1 -1
- package/src/util/directories.ts +12 -17
- package/src/util/get-device-id.ts +22 -0
- package/lib/application-control/application-control.d.ts +0 -46
- package/lib/application-control/application-control.d.ts.map +0 -1
- package/lib/application-control/application-control.js +0 -234
- package/lib/application-control/application-control.js.map +0 -1
- package/lib/constants.d.ts +0 -17
- package/lib/constants.d.ts.map +0 -1
- package/lib/constants.js +0 -24
- package/lib/constants.js.map +0 -1
- package/lib/subcommands/device-control.d.ts +0 -2
- package/lib/subcommands/device-control.d.ts.map +0 -1
- package/lib/subcommands/device-control.js +0 -19
- package/lib/subcommands/device-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/lib/util/spawner/gnu-spawner.d.ts +0 -9
- package/lib/util/spawner/gnu-spawner.d.ts.map +0 -1
- package/lib/util/spawner/gnu-spawner.js +0 -102
- package/lib/util/spawner/gnu-spawner.js.map +0 -1
- package/lib/util/spawner/js-spawner.d.ts +0 -5
- package/lib/util/spawner/js-spawner.d.ts.map +0 -1
- package/lib/util/spawner/js-spawner.js +0 -89
- package/lib/util/spawner/js-spawner.js.map +0 -1
- package/lib/util/spawner/types.d.ts +0 -28
- package/lib/util/spawner/types.d.ts.map +0 -1
- package/lib/util/spawner-base/index.d.ts +0 -17
- package/lib/util/spawner-base/index.d.ts.map +0 -1
- package/lib/util/spawner-base/index.js +0 -30
- package/lib/util/spawner-base/index.js.map +0 -1
- package/lib/util/spawner-base/run-foreground-sync.d.ts +0 -3
- package/lib/util/spawner-base/run-foreground-sync.d.ts.map +0 -1
- package/lib/util/spawner-base/run-foreground-sync.js +0 -18
- package/lib/util/spawner-base/run-foreground-sync.js.map +0 -1
- package/lib/util/spawner-base/run-foreground.d.ts +0 -3
- package/lib/util/spawner-base/run-foreground.d.ts.map +0 -1
- package/lib/util/spawner-base/run-foreground.js +0 -49
- package/lib/util/spawner-base/run-foreground.js.map +0 -1
- package/lib/util/spawner-base/run-streaming.d.ts +0 -4
- package/lib/util/spawner-base/run-streaming.d.ts.map +0 -1
- package/lib/util/spawner-base/run-streaming.js +0 -35
- package/lib/util/spawner-base/run-streaming.js.map +0 -1
- package/lib/util/spawner-base/run.d.ts +0 -4
- package/lib/util/spawner-base/run.d.ts.map +0 -1
- package/lib/util/spawner-base/run.js +0 -56
- package/lib/util/spawner-base/run.js.map +0 -1
- package/lib/util/urls.d.ts.map +0 -1
- package/lib/util/urls.js.map +0 -1
- package/lib/web/index.html +0 -229
- package/lib/web/static/Karla.css +0 -18
- package/lib/web/static/bootstrap-4.3.1.min.css +0 -7
- package/lib/web/static/bootstrap-4.3.1.min.js +0 -7
- package/lib/web/static/favicon.ico +0 -0
- package/lib/web/static/jquery-3.3.1.slim.min.js +0 -2
- package/lib/web/static/popper-1.14.7.min.js +0 -5
- package/lib/web/web-interface.d.ts +0 -2
- package/lib/web/web-interface.d.ts.map +0 -1
- package/lib/web/web-interface.js +0 -74
- package/lib/web/web-interface.js.map +0 -1
- package/src/application-control/application-control.ts +0 -273
- package/src/constants.ts +0 -32
- package/src/subcommands/device-control.ts +0 -16
- package/src/subcommands/test-app.ts +0 -30
- package/src/util/spawner/gnu-spawner.ts +0 -114
- package/src/util/spawner/js-spawner.ts +0 -110
- package/src/util/spawner/types.ts +0 -28
- package/src/util/spawner-base/index.ts +0 -28
- package/src/util/spawner-base/run-foreground-sync.ts +0 -16
- package/src/util/spawner-base/run-foreground.ts +0 -49
- package/src/util/spawner-base/run-streaming.ts +0 -40
- package/src/util/spawner-base/run.ts +0 -60
- package/src/web/index.html +0 -229
- package/src/web/static/Karla.css +0 -18
- package/src/web/static/bootstrap-4.3.1.min.css +0 -7
- package/src/web/static/bootstrap-4.3.1.min.js +0 -7
- package/src/web/static/favicon.ico +0 -0
- package/src/web/static/jquery-3.3.1.slim.min.js +0 -2
- package/src/web/static/popper-1.14.7.min.js +0 -5
- package/src/web/web-interface.ts +0 -89
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import compose from 'docker-compose';
|
|
2
|
+
import { fetchAppReleaseHistory } from 'alwaysai/lib/infrastructure';
|
|
3
|
+
import { JsSpawner } from 'alwaysai/lib/util';
|
|
4
|
+
|
|
5
|
+
import { runDockerLogin } from '../docker/docker-cmd';
|
|
6
|
+
import { getAppDir, requireAppInstalled } from './utils';
|
|
7
|
+
import { AppStateValue, ServiceStatus, AppState } from '@alwaysai/device-agent-schemas';
|
|
8
|
+
import { AgentConfigFile } from '../infrastructure/agent-config';
|
|
9
|
+
|
|
10
|
+
export async function listAppReleases(props: { projectId: string }) {
|
|
11
|
+
const { projectId } = props;
|
|
12
|
+
const releaseHistory = await fetchAppReleaseHistory(projectId);
|
|
13
|
+
return releaseHistory;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function listAppLatestRelease(props: { projectId: string }) {
|
|
17
|
+
const { projectId } = props;
|
|
18
|
+
const releaseHistory = await fetchAppReleaseHistory(projectId);
|
|
19
|
+
if (releaseHistory.length >= 1) {
|
|
20
|
+
return releaseHistory[0]['releaseHash'];
|
|
21
|
+
}
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function getAppStatus(props: { projectId: string }): Promise<AppState> {
|
|
26
|
+
const { projectId } = props;
|
|
27
|
+
if (!(await AgentConfigFile().isAppPresent({ projectId }))) {
|
|
28
|
+
throw new Error('Application is not installed');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const appDetails = {
|
|
32
|
+
projectId,
|
|
33
|
+
version: await AgentConfigFile().getAppVersion({ projectId }),
|
|
34
|
+
};
|
|
35
|
+
if (!(await AgentConfigFile().isAppReady({ projectId }))) {
|
|
36
|
+
// App is being installed or updated
|
|
37
|
+
return { appDetails, services: [] };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const appDir = getAppDir(projectId);
|
|
41
|
+
|
|
42
|
+
// Check if app is running
|
|
43
|
+
const output = await compose.ps({ cwd: appDir });
|
|
44
|
+
|
|
45
|
+
if (output.exitCode !== 0) {
|
|
46
|
+
throw new Error(
|
|
47
|
+
`Failed to get application status! stdout=${output.out} stderr=${output.err}`,
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const services: ServiceStatus[] = [];
|
|
52
|
+
if (output.data.services.length === 0) {
|
|
53
|
+
// If the app has never been started (or has an empty docker-compose.yaml)
|
|
54
|
+
// the list will be empty.
|
|
55
|
+
services.push({ name: '', state: 'Stopped' });
|
|
56
|
+
} else {
|
|
57
|
+
for (const service of output.data.services) {
|
|
58
|
+
// Container name has the format:
|
|
59
|
+
// <projectId>_name_<number>
|
|
60
|
+
const matchProjectId = new RegExp(`^${projectId}`);
|
|
61
|
+
const name = service.name
|
|
62
|
+
.replace(matchProjectId, '')
|
|
63
|
+
.replace(/\d+$/, '')
|
|
64
|
+
.replace(/_/g, '');
|
|
65
|
+
|
|
66
|
+
let state: AppStateValue;
|
|
67
|
+
if (service.state.includes('Up')) {
|
|
68
|
+
state = 'Up';
|
|
69
|
+
} else if (service.state.includes('Stopped') || service.state.includes('Exit')) {
|
|
70
|
+
state = 'Stopped';
|
|
71
|
+
} else if (service.state.includes('Restarting')) {
|
|
72
|
+
state = 'Restarting';
|
|
73
|
+
} else {
|
|
74
|
+
throw new Error(`Unknown state for service! (${service.state})`);
|
|
75
|
+
}
|
|
76
|
+
services.push({ name, state });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return { appDetails, services };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export async function getAppLogs(props: {
|
|
83
|
+
projectId: string;
|
|
84
|
+
services?: string[];
|
|
85
|
+
}): Promise<NodeJS.ReadableStream> {
|
|
86
|
+
const { projectId, services } = props;
|
|
87
|
+
await requireAppInstalled({ projectId });
|
|
88
|
+
|
|
89
|
+
const appDir = getAppDir(projectId);
|
|
90
|
+
|
|
91
|
+
const serviceList = services
|
|
92
|
+
? services
|
|
93
|
+
: await (async function () {
|
|
94
|
+
const appStatus = await getAppStatus({ projectId });
|
|
95
|
+
const services: string[] = [];
|
|
96
|
+
for (const service of appStatus.services) {
|
|
97
|
+
services.push(service.name);
|
|
98
|
+
}
|
|
99
|
+
return services;
|
|
100
|
+
})();
|
|
101
|
+
|
|
102
|
+
// Use direct command with spawner in order to get a readable stream
|
|
103
|
+
return await JsSpawner().runStreaming({
|
|
104
|
+
exe: 'docker-compose',
|
|
105
|
+
args: ['logs', '-f', ...serviceList],
|
|
106
|
+
cwd: appDir,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export async function startApp(props: {
|
|
111
|
+
projectId: string;
|
|
112
|
+
dockerLoginToken?: string;
|
|
113
|
+
}): Promise<void> {
|
|
114
|
+
const { projectId, dockerLoginToken } = props;
|
|
115
|
+
await requireAppInstalled({ projectId });
|
|
116
|
+
|
|
117
|
+
const appDir = getAppDir(projectId);
|
|
118
|
+
if (dockerLoginToken !== undefined) {
|
|
119
|
+
const result = await runDockerLogin({ token: dockerLoginToken });
|
|
120
|
+
console.log(result);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// TODO: Check if app is running
|
|
124
|
+
// Start app
|
|
125
|
+
const upOut = await compose.upAll({ cwd: appDir });
|
|
126
|
+
console.log(upOut);
|
|
127
|
+
if (upOut.exitCode !== 0) {
|
|
128
|
+
throw new Error(
|
|
129
|
+
`Failed to start application! stdout=${upOut.out} stderr=${upOut.err}`,
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
console.log('Started', projectId);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export async function stopApp(props: { projectId: string }): Promise<void> {
|
|
136
|
+
const { projectId } = props;
|
|
137
|
+
await requireAppInstalled({ projectId });
|
|
138
|
+
|
|
139
|
+
const appDir = getAppDir(projectId);
|
|
140
|
+
// TODO: Check if app is running
|
|
141
|
+
// Stop app
|
|
142
|
+
const output = await compose.down({ cwd: appDir });
|
|
143
|
+
console.log(output);
|
|
144
|
+
if (output.exitCode !== 0) {
|
|
145
|
+
throw new Error(
|
|
146
|
+
`Failed to stop application! stdout=${output.out} stderr=${output.err}`,
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
console.log('Stopped', projectId);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export async function restartApp(props: { projectId: string }): Promise<void> {
|
|
153
|
+
const { projectId } = props;
|
|
154
|
+
await stopApp({ projectId });
|
|
155
|
+
await startApp({ projectId });
|
|
156
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type ModelDetails = { modelId: string; version: number };
|
|
@@ -1,47 +1,33 @@
|
|
|
1
1
|
import compose from 'docker-compose';
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import * as fs from 'fs';
|
|
4
|
+
import { AAI_DIR } from 'alwaysai/lib/constants';
|
|
4
5
|
|
|
5
|
-
import { JsSpawner } from '../util/spawner/js-spawner';
|
|
6
|
-
import { DOT_ALWAYSAI_DIR } from '../constants';
|
|
7
6
|
import { runCliCmd } from '../util/run-cli-cmd';
|
|
7
|
+
import { AgentConfigFile } from '../infrastructure/agent-config';
|
|
8
8
|
|
|
9
|
-
export const APP_ROOT = path.join(
|
|
10
|
-
const RELEASE_FILENAME = 'alwaysai.release.json';
|
|
9
|
+
export const APP_ROOT = path.join(AAI_DIR, 'applications');
|
|
11
10
|
const DOCKER_COMPOSE_FILENAME = 'docker-compose.yaml';
|
|
12
11
|
|
|
13
12
|
export function getAppDir(projectId: string): string {
|
|
14
13
|
return path.join(APP_ROOT, projectId);
|
|
15
14
|
}
|
|
16
15
|
|
|
17
|
-
export async function
|
|
18
|
-
const {
|
|
19
|
-
//
|
|
20
|
-
if (
|
|
21
|
-
|
|
16
|
+
export async function requireAppInstalled(props: { projectId: string }) {
|
|
17
|
+
const { projectId } = props;
|
|
18
|
+
// Ensure an app that is being installed or updated is not modified
|
|
19
|
+
if (!(await AgentConfigFile().isAppPresent({ projectId }))) {
|
|
20
|
+
throw new Error('Application is not installed');
|
|
21
|
+
}
|
|
22
|
+
if (!(await AgentConfigFile().isAppReady({ projectId }))) {
|
|
23
|
+
throw new Error('Application is not done installing or updating');
|
|
22
24
|
}
|
|
23
|
-
return false;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export async function setAppVersion(props: { appDir: string; version: string }) {
|
|
27
|
-
const { appDir, version } = props;
|
|
28
|
-
// TODO: Switch to node-config
|
|
29
|
-
const versionPath = path.join(appDir, RELEASE_FILENAME);
|
|
30
|
-
const versionData = { version };
|
|
31
|
-
await JsSpawner().writeFile(versionPath, JSON.stringify(versionData, null, 2));
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export async function getAppVersion(props: { appDir: string }) {
|
|
35
|
-
const { appDir } = props;
|
|
36
|
-
const versionPath = path.join(appDir, RELEASE_FILENAME);
|
|
37
|
-
const releaseData = JSON.parse(await JsSpawner().readFile(versionPath));
|
|
38
|
-
return releaseData.version;
|
|
39
25
|
}
|
|
40
26
|
|
|
41
27
|
export async function buildApp(props: { appDir: string }) {
|
|
42
28
|
const { appDir } = props;
|
|
43
29
|
await runCliCmd({
|
|
44
|
-
cmd: ['app', 'configure', '--yes', '--protocol', 'docker:'],
|
|
30
|
+
cmd: ['app', 'configure', '--yes', '--protocol', 'docker:', '--skip-model-sync'],
|
|
45
31
|
cwd: appDir,
|
|
46
32
|
});
|
|
47
33
|
await runCliCmd({ cmd: ['app', 'install', '--yes', '--clean', '--pull'], cwd: appDir });
|
|
@@ -55,7 +41,6 @@ export async function buildApp(props: { appDir: string }) {
|
|
|
55
41
|
}
|
|
56
42
|
|
|
57
43
|
const buildOut = await compose.buildAll({ cwd: appDir });
|
|
58
|
-
console.log(buildOut);
|
|
59
44
|
if (buildOut.exitCode !== 0) {
|
|
60
45
|
throw new Error(
|
|
61
46
|
`Failed to build application! stdout=${buildOut.out} stderr=${buildOut.err}`,
|
|
@@ -1,15 +1,46 @@
|
|
|
1
1
|
const awsIot = require('aws-iot-device-sdk');
|
|
2
|
-
import { getIoTCoreEndpointUrl } from '../
|
|
2
|
+
import { getIoTCoreEndpointUrl } from '../infrastructure/urls';
|
|
3
3
|
import {
|
|
4
4
|
getPrivateKeyFilePath,
|
|
5
5
|
getCertificateFilePath,
|
|
6
6
|
getRootCertificateFilePath,
|
|
7
7
|
} from '../util/directories';
|
|
8
|
+
import {
|
|
9
|
+
startApp,
|
|
10
|
+
stopApp,
|
|
11
|
+
restartApp,
|
|
12
|
+
getAppStatus,
|
|
13
|
+
getAppLogs,
|
|
14
|
+
} from '../application-control/status';
|
|
15
|
+
import { installApp } from '../application-control/install';
|
|
16
|
+
import { getCpuUtil, getDiskUtil, getMemUtil } from '../device-control/device-control';
|
|
17
|
+
import {
|
|
18
|
+
DeviceAgentMessage,
|
|
19
|
+
DeviceStatsMessage,
|
|
20
|
+
AppState,
|
|
21
|
+
AppStateMessage,
|
|
22
|
+
ActionMessage,
|
|
23
|
+
AppLogs,
|
|
24
|
+
AppLogsMessage,
|
|
25
|
+
InstallationStatusMessage,
|
|
26
|
+
InstallationStatus,
|
|
27
|
+
InstallationStatusEnum,
|
|
28
|
+
} from '@alwaysai/device-agent-schemas';
|
|
29
|
+
import { getDeviceId } from '../util/get-device-id';
|
|
30
|
+
import { AgentConfigFile } from '../infrastructure/agent-config';
|
|
8
31
|
|
|
9
32
|
export class DeviceAgentCloudConnection {
|
|
10
|
-
|
|
11
|
-
private clientId = 'DEVICE_AGENT'; // TODO: use CLI to get either the username or the device ID? how do we determine the device ID in this case?
|
|
33
|
+
private clientId = getDeviceId();
|
|
12
34
|
private host = getIoTCoreEndpointUrl();
|
|
35
|
+
private publishable = false;
|
|
36
|
+
private readonly publishInterval = 1000;
|
|
37
|
+
private readonly installationStatusInterval = 3000;
|
|
38
|
+
private installationStatus: InstallationStatus;
|
|
39
|
+
private publishableTimeout: ReturnType<typeof setTimeout>;
|
|
40
|
+
|
|
41
|
+
public device = awsIot.device;
|
|
42
|
+
public agentTopicPrefix = `destination/agent/device/${this.clientId}/topic/`;
|
|
43
|
+
public cloudTopicPrefix = `destination/cloud/device/${this.clientId}/topic/`;
|
|
13
44
|
|
|
14
45
|
constructor() {
|
|
15
46
|
this.device = awsIot.device({
|
|
@@ -20,34 +51,270 @@ export class DeviceAgentCloudConnection {
|
|
|
20
51
|
host: this.host,
|
|
21
52
|
});
|
|
22
53
|
|
|
23
|
-
|
|
24
|
-
this.device.subscribe(
|
|
54
|
+
this.device.subscribe(`${this.agentTopicPrefix}command`);
|
|
55
|
+
this.device.subscribe(`${this.agentTopicPrefix}response`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
public setInstallationStatus(installationStatus: InstallationStatus) {
|
|
59
|
+
this.installationStatus = installationStatus;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
public getInstallationStatus(): InstallationStatus {
|
|
63
|
+
return this.installationStatus;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
public getPublishable(): boolean {
|
|
67
|
+
return this.publishable;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
public setPublishable(enabled: boolean) {
|
|
71
|
+
this.publishable = enabled;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
public restartPublishableTimeout() {
|
|
75
|
+
clearTimeout(this.publishableTimeout);
|
|
76
|
+
this.publishableTimeout = setTimeout(() => {
|
|
77
|
+
this.setPublishable(false);
|
|
78
|
+
}, 600000); // 10 min
|
|
25
79
|
}
|
|
26
80
|
|
|
27
|
-
public
|
|
81
|
+
public getPublishInterval() {
|
|
82
|
+
return this.publishInterval;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
public getInstallationStatusInterval() {
|
|
86
|
+
return this.installationStatusInterval;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
public getClientId(): string {
|
|
28
90
|
return this.clientId;
|
|
29
91
|
}
|
|
30
92
|
|
|
31
93
|
public publishMessage(topic: string, message: string) {
|
|
32
|
-
|
|
33
|
-
this.device.publish(topic, JSON.stringify({ message }));
|
|
94
|
+
this.device.publish(topic, message);
|
|
34
95
|
}
|
|
35
96
|
}
|
|
36
97
|
|
|
37
98
|
export function runDeviceAgentCloudInterface() {
|
|
38
99
|
const deviceAgent = new DeviceAgentCloudConnection();
|
|
39
100
|
|
|
40
|
-
|
|
101
|
+
async function buildMessagePacket(
|
|
102
|
+
topic: string,
|
|
103
|
+
payload:
|
|
104
|
+
| DeviceStatsMessage
|
|
105
|
+
| ActionMessage
|
|
106
|
+
| AppStateMessage
|
|
107
|
+
| AppLogsMessage
|
|
108
|
+
| InstallationStatusMessage,
|
|
109
|
+
): Promise<DeviceAgentMessage> {
|
|
110
|
+
const packet = {
|
|
111
|
+
timestamp: new Date().toUTCString(),
|
|
112
|
+
deviceId: deviceAgent.getClientId(),
|
|
113
|
+
topic,
|
|
114
|
+
payload,
|
|
115
|
+
};
|
|
116
|
+
return packet;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function getAppLogsMessage(): Promise<AppLogsMessage> {
|
|
120
|
+
const appLogsList: AppLogs[] = [];
|
|
121
|
+
const apps = await AgentConfigFile().getReadyApps();
|
|
122
|
+
for (const app of apps) {
|
|
123
|
+
const projectId = app.projectId;
|
|
124
|
+
const readable = await getAppLogs({ projectId });
|
|
125
|
+
const logs: any[] = [];
|
|
126
|
+
readable.setEncoding('utf8');
|
|
127
|
+
for await (const chunk of readable) {
|
|
128
|
+
logs.push(chunk);
|
|
129
|
+
}
|
|
130
|
+
const appLogs = {
|
|
131
|
+
projectId,
|
|
132
|
+
logs,
|
|
133
|
+
};
|
|
134
|
+
appLogsList.push(appLogs);
|
|
135
|
+
}
|
|
136
|
+
const applicationStatePackage = {
|
|
137
|
+
applicationLogs: appLogsList,
|
|
138
|
+
};
|
|
139
|
+
return applicationStatePackage;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const publishAppLogs = setInterval(async function () {
|
|
143
|
+
const appLogsMessage = await getAppLogsMessage();
|
|
144
|
+
const topic = `device/${deviceAgent.getClientId()}/topic/application-management`;
|
|
145
|
+
const appLogsPacket = await buildMessagePacket(topic, appLogsMessage);
|
|
146
|
+
|
|
147
|
+
deviceAgent.publishMessage(topic, JSON.stringify({ appLogsPacket }));
|
|
148
|
+
}, deviceAgent.getPublishInterval());
|
|
149
|
+
|
|
150
|
+
async function getAppStateMessage(): Promise<AppStateMessage> {
|
|
151
|
+
const appStateMessage: AppState[] = [];
|
|
152
|
+
const apps = await AgentConfigFile().getApps();
|
|
153
|
+
for (const app of apps) {
|
|
154
|
+
const projectId = app.projectId;
|
|
155
|
+
const status = await getAppStatus({ projectId });
|
|
156
|
+
appStateMessage.push(status);
|
|
157
|
+
}
|
|
158
|
+
const applicationStatePackage = {
|
|
159
|
+
applicationState: appStateMessage,
|
|
160
|
+
};
|
|
161
|
+
return applicationStatePackage;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const publishAppState = setInterval(async function () {
|
|
165
|
+
if (deviceAgent.getPublishable()) {
|
|
166
|
+
const topic = `${deviceAgent.cloudTopicPrefix}application-management`;
|
|
167
|
+
const appStateMessage = await getAppStateMessage();
|
|
168
|
+
const appStatePacket = await buildMessagePacket(topic, appStateMessage);
|
|
169
|
+
deviceAgent.publishMessage(topic, JSON.stringify({ appStatePacket }));
|
|
170
|
+
}
|
|
171
|
+
}, deviceAgent.getPublishInterval());
|
|
172
|
+
|
|
173
|
+
async function getDeviceStatsMessage(): Promise<DeviceStatsMessage> {
|
|
174
|
+
const cpuUsage = await getCpuUtil();
|
|
175
|
+
const diskUtil = await getDiskUtil();
|
|
176
|
+
const memUtil = await getMemUtil();
|
|
177
|
+
|
|
178
|
+
const deviceStatsMessage = {
|
|
179
|
+
deviceStats: {
|
|
180
|
+
cpuUsage,
|
|
181
|
+
diskUtil,
|
|
182
|
+
usedMemoryPercentage: memUtil,
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
return deviceStatsMessage;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const publishDeviceStats = setInterval(async function () {
|
|
189
|
+
if (deviceAgent.getPublishable()) {
|
|
190
|
+
const topic = `${deviceAgent.cloudTopicPrefix}device-management`;
|
|
191
|
+
const deviceStatsMessage = await getDeviceStatsMessage();
|
|
192
|
+
const deviceStatsPacket = await buildMessagePacket(topic, deviceStatsMessage);
|
|
193
|
+
deviceAgent.publishMessage(topic, JSON.stringify({ deviceStatsPacket }));
|
|
194
|
+
}
|
|
195
|
+
}, deviceAgent.getPublishInterval());
|
|
196
|
+
|
|
197
|
+
const publishDeviceRequest = async ({ projectId }) => {
|
|
198
|
+
const topic = `${deviceAgent.cloudTopicPrefix}request`;
|
|
199
|
+
const deviceRequestPackage = {
|
|
200
|
+
timestamp: new Date().toUTCString(),
|
|
201
|
+
deviceId: deviceAgent.getClientId(),
|
|
202
|
+
projectId,
|
|
203
|
+
releaseHash: '',
|
|
204
|
+
topic,
|
|
205
|
+
};
|
|
206
|
+
deviceAgent.publishMessage(
|
|
207
|
+
topic,
|
|
208
|
+
JSON.stringify({ device_request: deviceRequestPackage }),
|
|
209
|
+
);
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const publishInstallationStatus = async (interval: NodeJS.Timeout) => {
|
|
213
|
+
const topic = `${deviceAgent.cloudTopicPrefix}installation-status`;
|
|
214
|
+
const installationStatus = deviceAgent.getInstallationStatus();
|
|
215
|
+
const installationStatusPacket = await buildMessagePacket(topic, {
|
|
216
|
+
installationStatus,
|
|
217
|
+
});
|
|
218
|
+
deviceAgent.device.publish(topic, JSON.stringify({ installationStatusPacket }));
|
|
219
|
+
if (installationStatus.status !== InstallationStatusEnum.IN_PROGRESS) {
|
|
220
|
+
clearInterval(interval);
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
const handleMessageTopic = async ({ topic, payload }) => {
|
|
225
|
+
const action = payload['action'];
|
|
226
|
+
const actionPayload: any = payload[action];
|
|
227
|
+
const type = topic.split('/').slice(-1)[0];
|
|
228
|
+
|
|
229
|
+
if (!type) {
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
switch (action) {
|
|
234
|
+
/**
|
|
235
|
+
* Install app package based on the given project ID
|
|
236
|
+
*/
|
|
237
|
+
case 'install':
|
|
238
|
+
/**
|
|
239
|
+
{
|
|
240
|
+
"action": "install",
|
|
241
|
+
"install": {
|
|
242
|
+
"releaseHash": "7fb2a812f9e7aa193208dac353521965da50d755085162066c125592f1ed760b",
|
|
243
|
+
"projectId": "786e4686-a681-4cff-9e17-1e7d385c0fdb"
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
*/
|
|
247
|
+
if (type === 'response') {
|
|
248
|
+
deviceAgent.setInstallationStatus({
|
|
249
|
+
status: InstallationStatusEnum.IN_PROGRESS,
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
const installationStatusPing = setInterval(
|
|
253
|
+
() => publishInstallationStatus(installationStatusPing),
|
|
254
|
+
deviceAgent.getInstallationStatusInterval(),
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
// Install the app with the given url
|
|
258
|
+
await (async () => {
|
|
259
|
+
try {
|
|
260
|
+
await installApp(actionPayload);
|
|
261
|
+
deviceAgent.setInstallationStatus({
|
|
262
|
+
status: InstallationStatusEnum.SUCCESS,
|
|
263
|
+
});
|
|
264
|
+
} catch (e) {
|
|
265
|
+
const reason: string = e.message;
|
|
266
|
+
deviceAgent.setInstallationStatus({
|
|
267
|
+
status: InstallationStatusEnum.FAILURE,
|
|
268
|
+
reason,
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
})();
|
|
272
|
+
break;
|
|
273
|
+
}
|
|
274
|
+
publishDeviceRequest(actionPayload);
|
|
275
|
+
break;
|
|
276
|
+
case 'start':
|
|
277
|
+
startApp({ projectId: actionPayload.projectId });
|
|
278
|
+
break;
|
|
279
|
+
case 'stop':
|
|
280
|
+
stopApp({ projectId: actionPayload.projectId });
|
|
281
|
+
break;
|
|
282
|
+
case 'restart':
|
|
283
|
+
restartApp({ projectId: actionPayload.projectId });
|
|
284
|
+
break;
|
|
285
|
+
/**
|
|
286
|
+
* Allow/disallow publishing on this device. By default, (publishable = false)
|
|
287
|
+
*/
|
|
288
|
+
case 'publishable':
|
|
289
|
+
/**
|
|
290
|
+
{
|
|
291
|
+
"action": "publishable",
|
|
292
|
+
"publishable": true | false
|
|
293
|
+
}
|
|
294
|
+
*/
|
|
295
|
+
deviceAgent.setPublishable(actionPayload);
|
|
296
|
+
deviceAgent.restartPublishableTimeout();
|
|
297
|
+
break;
|
|
298
|
+
default:
|
|
299
|
+
break;
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
deviceAgent.device.on('connect', function () {
|
|
41
304
|
deviceAgent.publishMessage('connection', deviceAgent.getClientId());
|
|
42
305
|
console.log('Device Agent has connected to the cloud');
|
|
43
306
|
});
|
|
44
307
|
|
|
45
|
-
deviceAgent.device.on('disconnect', function() {
|
|
308
|
+
deviceAgent.device.on('disconnect', function () {
|
|
46
309
|
console.log('Device Agent has been disconnected from the cloud');
|
|
47
310
|
});
|
|
48
311
|
|
|
49
|
-
deviceAgent.device.on('message', function(topic, payload) {
|
|
50
|
-
//
|
|
51
|
-
|
|
312
|
+
deviceAgent.device.on('message', function (topic, payload) {
|
|
313
|
+
// ToDo: insert valdiation here for incoming messages. Maybe we will use a JSON schema for the message structure.
|
|
314
|
+
try {
|
|
315
|
+
handleMessageTopic({ topic, payload: JSON.parse(payload) });
|
|
316
|
+
} catch (error) {
|
|
317
|
+
console.error(error);
|
|
318
|
+
}
|
|
52
319
|
});
|
|
53
320
|
}
|
package/src/docker/docker-cmd.ts
CHANGED
package/src/environment.ts
CHANGED
|
@@ -2,6 +2,8 @@ import { platform } from 'os';
|
|
|
2
2
|
|
|
3
3
|
export const ALWAYSAI_OS_PLATFORM = parseOsPlatform(process.env.ALWAYSAI_OS_PLATFORM);
|
|
4
4
|
export const ALWAYSAI_SHOW_HIDDEN = parseBoolean(process.env.ALWAYSAI_SHOW_HIDDEN);
|
|
5
|
+
export const ALWAYSAI_DEVICE_AGENT_MODE = process.env.ALWAYSAI_DEVICE_AGENT_MODE;
|
|
6
|
+
export const ALWAYSAI_LOG_LEVEL = process.env.AAI_LOG_LEVEL;
|
|
5
7
|
|
|
6
8
|
function parseOsPlatform(str: string | undefined): NodeJS.Platform {
|
|
7
9
|
switch (str) {
|
package/src/index.ts
CHANGED
|
@@ -1,21 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
/*
|
|
4
|
+
Though this file is not executable, the above "shebang" line is necessary as an
|
|
5
|
+
indicator to npm that this file is a Node.js script, not a shell script e.g.
|
|
6
|
+
*/
|
|
6
7
|
|
|
7
8
|
import { runCliAndExit } from '@alwaysai/alwayscli';
|
|
8
9
|
import { root } from './root';
|
|
9
|
-
import { runWebInterface } from './web/web-interface';
|
|
10
10
|
import { runDeviceAgentCloudInterface } from './cloud-connection/device-agent-cloud-connection';
|
|
11
|
+
import { AgentConfigFile } from './infrastructure/agent-config';
|
|
12
|
+
import { ALWAYSAI_DEVICE_AGENT_MODE } from './environment';
|
|
11
13
|
|
|
12
14
|
if (module === require.main) {
|
|
13
15
|
console.log('Starting alwaysAI Device Agent');
|
|
16
|
+
if (!AgentConfigFile().exists()) {
|
|
17
|
+
AgentConfigFile().initialize();
|
|
18
|
+
}
|
|
14
19
|
|
|
15
|
-
if (
|
|
20
|
+
if (ALWAYSAI_DEVICE_AGENT_MODE === 'cloud') {
|
|
16
21
|
runDeviceAgentCloudInterface();
|
|
17
|
-
} else if (process.env.ALWAYSAI_DEVICE_AGENT_MODE === 'web') {
|
|
18
|
-
runWebInterface();
|
|
19
22
|
} else {
|
|
20
23
|
runCliAndExit(root, {});
|
|
21
24
|
}
|