@alwaysai/device-agent 1.0.0 → 1.3.0
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/environment-variables.d.ts +1 -1
- package/lib/application-control/environment-variables.d.ts.map +1 -1
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts +3 -2
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts.map +1 -1
- package/lib/cloud-connection/device-agent-cloud-connection.js +81 -59
- package/lib/cloud-connection/device-agent-cloud-connection.js.map +1 -1
- package/lib/cloud-connection/live-updates-handler.d.ts.map +1 -1
- package/lib/cloud-connection/live-updates-handler.js +2 -4
- package/lib/cloud-connection/live-updates-handler.js.map +1 -1
- package/lib/cloud-connection/live-updates-handler.test.js +1 -1
- package/lib/cloud-connection/live-updates-handler.test.js.map +1 -1
- package/lib/cloud-connection/messages.js +6 -6
- package/lib/cloud-connection/messages.js.map +1 -1
- package/lib/cloud-connection/passthrough-handler.d.ts.map +1 -1
- package/lib/cloud-connection/passthrough-handler.js +37 -11
- package/lib/cloud-connection/passthrough-handler.js.map +1 -1
- package/lib/cloud-connection/publisher.d.ts +5 -4
- package/lib/cloud-connection/publisher.d.ts.map +1 -1
- package/lib/cloud-connection/publisher.js +9 -8
- package/lib/cloud-connection/publisher.js.map +1 -1
- package/lib/cloud-connection/shadow-handler.d.ts +5 -0
- package/lib/cloud-connection/shadow-handler.d.ts.map +1 -1
- package/lib/cloud-connection/shadow-handler.js +20 -5
- package/lib/cloud-connection/shadow-handler.js.map +1 -1
- package/lib/cloud-connection/shadow-handler.test.js +9 -0
- package/lib/cloud-connection/shadow-handler.test.js.map +1 -1
- package/lib/cloud-connection/transaction-manager.d.ts +10 -0
- package/lib/cloud-connection/transaction-manager.d.ts.map +1 -0
- package/lib/cloud-connection/transaction-manager.js +41 -0
- package/lib/cloud-connection/transaction-manager.js.map +1 -0
- package/lib/cloud-connection/transaction-manager.test.d.ts +2 -0
- package/lib/cloud-connection/transaction-manager.test.d.ts.map +1 -0
- package/lib/cloud-connection/transaction-manager.test.js +63 -0
- package/lib/cloud-connection/transaction-manager.test.js.map +1 -0
- package/lib/device-control/device-control.d.ts +58 -3
- package/lib/device-control/device-control.d.ts.map +1 -1
- package/lib/device-control/device-control.js +141 -14
- package/lib/device-control/device-control.js.map +1 -1
- package/lib/subcommands/app/env-vars.d.ts.map +1 -1
- package/lib/subcommands/app/env-vars.js +15 -9
- package/lib/subcommands/app/env-vars.js.map +1 -1
- package/lib/subcommands/device/clean.d.ts.map +1 -1
- package/lib/subcommands/device/clean.js +14 -12
- package/lib/subcommands/device/clean.js.map +1 -1
- package/lib/subcommands/device/device.d.ts +1 -0
- package/lib/subcommands/device/device.d.ts.map +1 -1
- package/lib/subcommands/device/device.js +26 -7
- package/lib/subcommands/device/device.js.map +1 -1
- package/lib/subcommands/device/index.js +1 -1
- package/lib/subcommands/device/index.js.map +1 -1
- package/lib/util/logger.d.ts.map +1 -1
- package/lib/util/logger.js +1 -1
- package/lib/util/logger.js.map +1 -1
- package/lib/util/safe-rimraf.d.ts +2 -0
- package/lib/util/safe-rimraf.d.ts.map +1 -0
- package/lib/util/safe-rimraf.js +16 -0
- package/lib/util/safe-rimraf.js.map +1 -0
- package/package.json +4 -2
- package/readme.md +1 -0
- package/src/application-control/environment-variables.ts +1 -1
- package/src/cloud-connection/device-agent-cloud-connection.ts +125 -80
- package/src/cloud-connection/live-updates-handler.test.ts +2 -2
- package/src/cloud-connection/live-updates-handler.ts +2 -8
- package/src/cloud-connection/messages.ts +9 -9
- package/src/cloud-connection/passthrough-handler.ts +43 -10
- package/src/cloud-connection/publisher.ts +27 -10
- package/src/cloud-connection/shadow-handler.test.ts +9 -0
- package/src/cloud-connection/shadow-handler.ts +25 -11
- package/src/cloud-connection/transaction-manager.test.ts +73 -0
- package/src/cloud-connection/transaction-manager.ts +43 -0
- package/src/device-control/device-control.ts +142 -9
- package/src/subcommands/app/env-vars.ts +17 -10
- package/src/subcommands/device/clean.ts +22 -13
- package/src/subcommands/device/device.ts +36 -9
- package/src/subcommands/device/index.ts +2 -2
- package/src/util/logger.ts +15 -10
- package/src/util/safe-rimraf.ts +14 -0
- package/lib/cloud-connection/transaction-queue.d.ts +0 -12
- package/lib/cloud-connection/transaction-queue.d.ts.map +0 -1
- package/lib/cloud-connection/transaction-queue.js +0 -38
- package/lib/cloud-connection/transaction-queue.js.map +0 -1
- package/lib/cloud-connection/transaction-queue.test.d.ts +0 -2
- package/lib/cloud-connection/transaction-queue.test.d.ts.map +0 -1
- package/lib/cloud-connection/transaction-queue.test.js +0 -46
- package/lib/cloud-connection/transaction-queue.test.js.map +0 -1
- package/src/cloud-connection/transaction-queue.test.ts +0 -55
- package/src/cloud-connection/transaction-queue.ts +0 -40
|
@@ -6,6 +6,8 @@ import { EnvVars, getAllEnvs, readAppCfgFile } from '../application-control';
|
|
|
6
6
|
import { logger } from '../util/logger';
|
|
7
7
|
import { Publisher } from './publisher';
|
|
8
8
|
import { AppConfigModels, getAppCfgModelsDiff } from './shadow';
|
|
9
|
+
import { getSystemInformation } from '../device-control/device-control';
|
|
10
|
+
import { generateTxId } from '@alwaysai/device-agent-schemas';
|
|
9
11
|
|
|
10
12
|
export interface ShadowTopics {
|
|
11
13
|
projects: {
|
|
@@ -18,6 +20,9 @@ export interface ShadowTopics {
|
|
|
18
20
|
updateRejected: string;
|
|
19
21
|
delete: string;
|
|
20
22
|
};
|
|
23
|
+
systemInfo: {
|
|
24
|
+
update: string;
|
|
25
|
+
};
|
|
21
26
|
}
|
|
22
27
|
|
|
23
28
|
export type AppConfigUpdate = {
|
|
@@ -31,6 +36,7 @@ export type EnvVarUpdate = {
|
|
|
31
36
|
|
|
32
37
|
export type ShadowUpdate = {
|
|
33
38
|
projectId: string;
|
|
39
|
+
txId: string;
|
|
34
40
|
appCfgUpdate?: AppConfigUpdate;
|
|
35
41
|
envVarUpdate?: EnvVarUpdate;
|
|
36
42
|
};
|
|
@@ -55,6 +61,9 @@ export class ShadowHandler {
|
|
|
55
61
|
updateAccepted: `${this.shadowPrefix}projects/update/accepted`,
|
|
56
62
|
updateRejected: `${this.shadowPrefix}projects/update/rejected`,
|
|
57
63
|
delete: `${this.shadowPrefix}projects/delete`
|
|
64
|
+
},
|
|
65
|
+
systemInfo: {
|
|
66
|
+
update: `${this.shadowPrefix}system-info/update`
|
|
58
67
|
}
|
|
59
68
|
};
|
|
60
69
|
}
|
|
@@ -70,7 +79,9 @@ export class ShadowHandler {
|
|
|
70
79
|
|
|
71
80
|
for (const projectId of deltaKeys) {
|
|
72
81
|
const projectShadow = delta[projectId];
|
|
73
|
-
|
|
82
|
+
// For incoming shadow updates, there will be no TxID, so it needs to be generated here.
|
|
83
|
+
const txId = generateTxId();
|
|
84
|
+
const shadowUpdate: ShadowUpdate = { projectId, txId };
|
|
74
85
|
|
|
75
86
|
if (projectShadow.appConfig) {
|
|
76
87
|
const newAppCfg = JSON.parse(projectShadow.appConfig);
|
|
@@ -133,7 +144,7 @@ export class ShadowHandler {
|
|
|
133
144
|
case this.shadowTopics.projects.updateDelta:
|
|
134
145
|
if (clientToken === this.clientId) {
|
|
135
146
|
logger.debug(
|
|
136
|
-
`Ignoring
|
|
147
|
+
`Ignoring delta caused by Device Agent: ${JSON.stringify(
|
|
137
148
|
{ topic, payload },
|
|
138
149
|
null,
|
|
139
150
|
2
|
|
@@ -163,6 +174,18 @@ export class ShadowHandler {
|
|
|
163
174
|
return [];
|
|
164
175
|
}
|
|
165
176
|
|
|
177
|
+
public async updateSystemInfoShadow() {
|
|
178
|
+
const systemInfo = await getSystemInformation();
|
|
179
|
+
const packet = {
|
|
180
|
+
state: {
|
|
181
|
+
reported: systemInfo
|
|
182
|
+
},
|
|
183
|
+
clientToken: this.clientId
|
|
184
|
+
};
|
|
185
|
+
const topic = this.shadowTopics.systemInfo.update;
|
|
186
|
+
this.publisher.publish(topic, JSON.stringify(packet));
|
|
187
|
+
}
|
|
188
|
+
|
|
166
189
|
public async updateProjectShadow(projectId: string) {
|
|
167
190
|
const appCfg = await readAppCfgFile({ projectId });
|
|
168
191
|
const envVars = await getAllEnvs({ projectId });
|
|
@@ -178,9 +201,6 @@ export class ShadowHandler {
|
|
|
178
201
|
clientToken: this.clientId
|
|
179
202
|
};
|
|
180
203
|
const topic = this.shadowTopics.projects.update;
|
|
181
|
-
logger.debug(
|
|
182
|
-
`Publishing message:\n${JSON.stringify({ topic, packet }, null, 2)}`
|
|
183
|
-
);
|
|
184
204
|
this.publisher.publish(topic, JSON.stringify(packet));
|
|
185
205
|
}
|
|
186
206
|
|
|
@@ -189,9 +209,6 @@ export class ShadowHandler {
|
|
|
189
209
|
const packet = {
|
|
190
210
|
clientToken: this.clientId
|
|
191
211
|
};
|
|
192
|
-
logger.debug(
|
|
193
|
-
`Publishing message:\n${JSON.stringify({ topic, packet }, null, 2)}`
|
|
194
|
-
);
|
|
195
212
|
this.publisher.publish(topic, JSON.stringify(packet));
|
|
196
213
|
}
|
|
197
214
|
|
|
@@ -210,9 +227,6 @@ export class ShadowHandler {
|
|
|
210
227
|
},
|
|
211
228
|
clientToken: this.clientId
|
|
212
229
|
};
|
|
213
|
-
logger.debug(
|
|
214
|
-
`Publishing message:\n${JSON.stringify({ topic, packet }, null, 2)}`
|
|
215
|
-
);
|
|
216
230
|
this.publisher.publish(topic, JSON.stringify(packet));
|
|
217
231
|
}
|
|
218
232
|
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { generateTxId } from '@alwaysai/device-agent-schemas';
|
|
2
|
+
import { TransactionManager } from './transaction-manager';
|
|
3
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
4
|
+
|
|
5
|
+
describe('Test Transaction Manager', () => {
|
|
6
|
+
let txnMgr: TransactionManager;
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
txnMgr = new TransactionManager();
|
|
10
|
+
jest.clearAllMocks();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
function generateRandomProjectId() {
|
|
14
|
+
return uuidv4();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
test('Add a new transaction', async () => {
|
|
18
|
+
const txId = generateTxId();
|
|
19
|
+
const projectId = generateRandomProjectId();
|
|
20
|
+
txnMgr.addTransaction(txId, projectId);
|
|
21
|
+
expect(txnMgr.getProjectFromTransaction(txId)).toEqual(projectId);
|
|
22
|
+
expect(txnMgr.getTransactionFromProject(projectId)).toEqual(txId);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('Add multiple transactions to different projects', async () => {
|
|
26
|
+
const numTransactions = 3;
|
|
27
|
+
const projectTxIdList: any = [];
|
|
28
|
+
for (let i = 0; i < numTransactions; i++) {
|
|
29
|
+
const txId = generateTxId();
|
|
30
|
+
const projectId = generateRandomProjectId();
|
|
31
|
+
txnMgr.addTransaction(txId, projectId);
|
|
32
|
+
projectTxIdList.push({ projectId, txId });
|
|
33
|
+
}
|
|
34
|
+
projectTxIdList.forEach(({ txId, projectId }) => {
|
|
35
|
+
expect(txnMgr.getProjectFromTransaction(txId)).toEqual(projectId);
|
|
36
|
+
expect(txnMgr.getTransactionFromProject(projectId)).toEqual(txId);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('Attempt to add existing transaction to a project, results in failure', async () => {
|
|
41
|
+
const txId = generateTxId();
|
|
42
|
+
const projectId = generateRandomProjectId();
|
|
43
|
+
txnMgr.addTransaction(txId, projectId);
|
|
44
|
+
expect(() => txnMgr.addTransaction(txId, projectId)).toThrow(
|
|
45
|
+
`Transaction ID ${txId} already exists for this projectId ${projectId}!`
|
|
46
|
+
);
|
|
47
|
+
expect(txnMgr.getTransactionFromProject(projectId)).toEqual(txId);
|
|
48
|
+
expect(txnMgr.getProjectFromTransaction(txId)).toEqual(projectId);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('Attempt to add a transaction to a project with an ongoing transaction, results in failure', async () => {
|
|
52
|
+
const txId = generateTxId();
|
|
53
|
+
const txId2 = generateTxId();
|
|
54
|
+
const projectId = generateRandomProjectId();
|
|
55
|
+
txnMgr.addTransaction(txId, projectId);
|
|
56
|
+
expect(() => txnMgr.addTransaction(txId2, projectId)).toThrow(
|
|
57
|
+
`This Project ID ${projectId} already has an ongoing transaction!`
|
|
58
|
+
);
|
|
59
|
+
expect(txnMgr.getTransactionFromProject(projectId)).toEqual(txId);
|
|
60
|
+
expect(txnMgr.getProjectFromTransaction(txId)).toEqual(projectId);
|
|
61
|
+
expect(txnMgr.getProjectFromTransaction(txId2)).toBeUndefined();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('Test remove transaction from queue', async () => {
|
|
65
|
+
const txId = generateTxId();
|
|
66
|
+
const projectId = generateRandomProjectId();
|
|
67
|
+
txnMgr.addTransaction(txId, projectId);
|
|
68
|
+
txnMgr.completeTransaction(txId);
|
|
69
|
+
|
|
70
|
+
expect(txnMgr.getTransactionFromProject(projectId)).toBeUndefined();
|
|
71
|
+
expect(txnMgr.getProjectFromTransaction(txId)).toBeUndefined();
|
|
72
|
+
});
|
|
73
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export class TransactionManager {
|
|
2
|
+
private txToProject: Record<string, string> = {};
|
|
3
|
+
private projectToTx: Record<string, string> = {};
|
|
4
|
+
|
|
5
|
+
constructor() {
|
|
6
|
+
this.txToProject = {};
|
|
7
|
+
this.projectToTx = {};
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
public addTransaction(txId: string, projectId: string) {
|
|
11
|
+
// Check if the Transaction already exists
|
|
12
|
+
if (this.txToProject[txId]) {
|
|
13
|
+
throw new Error(
|
|
14
|
+
`Transaction ID ${txId} already exists for this projectId ${projectId}!`
|
|
15
|
+
);
|
|
16
|
+
} else {
|
|
17
|
+
// Check if there is any ongoing Transactions for Project ID
|
|
18
|
+
if (this.projectToTx[projectId]) {
|
|
19
|
+
throw new Error(
|
|
20
|
+
`This Project ID ${projectId} already has an ongoing transaction!`
|
|
21
|
+
);
|
|
22
|
+
} else {
|
|
23
|
+
// Map the Transaction ID with Project ID
|
|
24
|
+
this.txToProject[txId] = projectId;
|
|
25
|
+
this.projectToTx[projectId] = txId;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public getTransactionFromProject(projectId: string) {
|
|
31
|
+
return this.projectToTx[projectId];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public getProjectFromTransaction(txId: string) {
|
|
35
|
+
return this.txToProject[txId];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public completeTransaction(txId: string) {
|
|
39
|
+
const projectId = this.txToProject[txId];
|
|
40
|
+
delete this.txToProject[txId];
|
|
41
|
+
delete this.projectToTx[projectId];
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -1,17 +1,150 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { SystemInformationPayload } from '@alwaysai/device-agent-schemas';
|
|
2
|
+
import { logger } from '../util/logger';
|
|
3
|
+
import * as osu from 'node-os-utils';
|
|
4
|
+
import * as si from 'systeminformation';
|
|
5
|
+
import { exec } from 'child_process';
|
|
6
|
+
import { promisify } from 'util';
|
|
3
7
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
8
|
+
const exec_promise = promisify(exec);
|
|
9
|
+
|
|
10
|
+
// Device Stats
|
|
11
|
+
export async function getCpuDetails() {
|
|
12
|
+
const cpuFree = await osu.cpu.free();
|
|
13
|
+
const cpuTemp = await si.cpuTemperature();
|
|
14
|
+
return {
|
|
15
|
+
usedPerc: 100.0 - cpuFree,
|
|
16
|
+
temperature: cpuTemp.main
|
|
17
|
+
};
|
|
7
18
|
}
|
|
8
19
|
|
|
9
|
-
export async function
|
|
20
|
+
export async function getDiskDetails() {
|
|
21
|
+
// Types incorrectly specify diskname as required instead of optional
|
|
22
|
+
// @ts-expect-error
|
|
10
23
|
const driveInfo = await osu.drive.info();
|
|
11
|
-
return
|
|
24
|
+
return {
|
|
25
|
+
usedGb: parseFloat(driveInfo.usedGb),
|
|
26
|
+
freeGb: parseFloat(driveInfo.freeGb)
|
|
27
|
+
};
|
|
12
28
|
}
|
|
13
29
|
|
|
14
|
-
export async function
|
|
30
|
+
export async function getMemDetails() {
|
|
15
31
|
const memInfo = await osu.mem.info();
|
|
16
|
-
return
|
|
32
|
+
return {
|
|
33
|
+
usedMb: memInfo.usedMemMb,
|
|
34
|
+
freeMb: memInfo.freeMemMb
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// System information
|
|
39
|
+
export async function getOsInfo() {
|
|
40
|
+
const osInfo = await si.osInfo();
|
|
41
|
+
return {
|
|
42
|
+
platform: osInfo.platform,
|
|
43
|
+
distro: osInfo.distro,
|
|
44
|
+
release: osInfo.release,
|
|
45
|
+
kernel: osInfo.kernel,
|
|
46
|
+
architecture: osInfo.arch,
|
|
47
|
+
hostname: osInfo.hostname
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export async function getCpuInfo() {
|
|
52
|
+
const cpuInfo = await si.cpu();
|
|
53
|
+
return {
|
|
54
|
+
manufacturer: cpuInfo.manufacturer,
|
|
55
|
+
brand: cpuInfo.brand,
|
|
56
|
+
vendor: cpuInfo.vendor,
|
|
57
|
+
model: cpuInfo.model,
|
|
58
|
+
cores: cpuInfo.cores,
|
|
59
|
+
physicalCores: cpuInfo.physicalCores,
|
|
60
|
+
efficiencyCores: cpuInfo.efficiencyCores,
|
|
61
|
+
processors: cpuInfo.processors
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function getDiskInfo() {
|
|
66
|
+
const diskInfo = await si.diskLayout();
|
|
67
|
+
return {
|
|
68
|
+
drives: diskInfo.map((drive) => ({
|
|
69
|
+
device: drive.device,
|
|
70
|
+
type: drive.type,
|
|
71
|
+
name: drive.name,
|
|
72
|
+
vendor: drive.vendor,
|
|
73
|
+
size: drive.size
|
|
74
|
+
}))
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export async function getDeviceInfo() {
|
|
79
|
+
const deviceInfo = await si.system();
|
|
80
|
+
return {
|
|
81
|
+
manufacturer: deviceInfo.manufacturer,
|
|
82
|
+
model: deviceInfo.model,
|
|
83
|
+
version: deviceInfo.version,
|
|
84
|
+
serial:
|
|
85
|
+
deviceInfo.serial && deviceInfo.serial !== '-'
|
|
86
|
+
? deviceInfo.serial
|
|
87
|
+
: undefined,
|
|
88
|
+
virtual: deviceInfo.virtual
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export async function getNetworkInfo() {
|
|
93
|
+
const networkInterfaces = await si.networkInterfaces();
|
|
94
|
+
|
|
95
|
+
const defaultNetworkInterface = Array.isArray(networkInterfaces)
|
|
96
|
+
? networkInterfaces.filter((iface: any) => iface.ip4 !== '127.0.0.1')[0]
|
|
97
|
+
: networkInterfaces;
|
|
98
|
+
return {
|
|
99
|
+
ipv4Address: defaultNetworkInterface.ip4,
|
|
100
|
+
ipv6Address: defaultNetworkInterface.ip6,
|
|
101
|
+
macAddress: defaultNetworkInterface.mac
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export async function getPackageVersions() {
|
|
106
|
+
// eslint-disable-next-line
|
|
107
|
+
const agentJson = require('../../package.json');
|
|
108
|
+
// eslint-disable-next-line
|
|
109
|
+
const deviceAgentSchemasJson = require('../../node_modules/@alwaysai/device-agent-schemas/package.json');
|
|
110
|
+
return {
|
|
111
|
+
agent: agentJson.version,
|
|
112
|
+
deviceAgentSchemas: deviceAgentSchemasJson.version
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export async function getSystemInformation(): Promise<SystemInformationPayload> {
|
|
117
|
+
try {
|
|
118
|
+
const systemInfo: SystemInformationPayload = {
|
|
119
|
+
os: await getOsInfo(),
|
|
120
|
+
cpu: await getCpuInfo(),
|
|
121
|
+
disk: await getDiskInfo(),
|
|
122
|
+
device: await getDeviceInfo(),
|
|
123
|
+
network: await getNetworkInfo(),
|
|
124
|
+
versions: await getPackageVersions()
|
|
125
|
+
};
|
|
126
|
+
return systemInfo;
|
|
127
|
+
} catch (e) {
|
|
128
|
+
logger.error('There was a problem getting system information: ', e);
|
|
129
|
+
}
|
|
130
|
+
return {};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export async function reboot() {
|
|
134
|
+
try {
|
|
135
|
+
// FIXME: This command must always be run with sudo. The user must enable access
|
|
136
|
+
// in the sudoers file for it to work.
|
|
137
|
+
// If passwordless access is not set, will prompt user for password. Use
|
|
138
|
+
// timeout to break out of the prompt
|
|
139
|
+
const result = await exec_promise('sudo /sbin/shutdown -r now', {
|
|
140
|
+
timeout: 5000
|
|
141
|
+
});
|
|
142
|
+
logger.info(result.stdout.trim());
|
|
143
|
+
} catch (err) {
|
|
144
|
+
throw new Error(
|
|
145
|
+
`Could not reboot device. You may need to add passwordless access to '/sbin/shutdown'. ${JSON.stringify(
|
|
146
|
+
err
|
|
147
|
+
)}`
|
|
148
|
+
);
|
|
149
|
+
}
|
|
17
150
|
}
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
import { EnvVars, getAllEnvs } from '../../application-control';
|
|
8
8
|
import { DeviceAgentCloudConnection } from '../../cloud-connection/device-agent-cloud-connection';
|
|
9
9
|
import sleep from '../../util/sleep';
|
|
10
|
+
import { logger } from '../../util/logger';
|
|
10
11
|
|
|
11
12
|
export const getAllEnvsCliLeaf = CliLeaf({
|
|
12
13
|
name: 'get-all-envs',
|
|
@@ -49,25 +50,31 @@ export const setEnvCliLeaf = CliLeaf({
|
|
|
49
50
|
if (nameVal.length !== 2) {
|
|
50
51
|
throw new CliTerseError(`Invalid argument: ${arg}`);
|
|
51
52
|
}
|
|
52
|
-
|
|
53
|
+
const value = nameVal[1] === '' ? null : nameVal[1];
|
|
54
|
+
envVars[service][nameVal[0]] = value;
|
|
53
55
|
});
|
|
54
56
|
const deviceAgent = new DeviceAgentCloudConnection();
|
|
55
57
|
await deviceAgent.setupHandlers();
|
|
56
58
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
version: 3,
|
|
61
|
-
timestamp: 0,
|
|
59
|
+
// Update the shadow as a client
|
|
60
|
+
const topic = deviceAgent.getShadowTopics().projects.update;
|
|
61
|
+
const packet = {
|
|
62
62
|
state: {
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
desired: {
|
|
64
|
+
[project]: {
|
|
65
|
+
envVars
|
|
66
|
+
}
|
|
65
67
|
}
|
|
66
68
|
},
|
|
67
|
-
clientToken: '
|
|
69
|
+
clientToken: 'client'
|
|
68
70
|
};
|
|
71
|
+
logger.debug(
|
|
72
|
+
`Publishing message:\n${JSON.stringify({ topic, packet }, null, 2)}`
|
|
73
|
+
);
|
|
74
|
+
deviceAgent.publisher.publish(topic, JSON.stringify(packet));
|
|
75
|
+
// Sleep for extra time to ensure time for shadow response
|
|
76
|
+
await sleep(10000);
|
|
69
77
|
|
|
70
|
-
await deviceAgent.handleMessage(topic, message);
|
|
71
78
|
while (deviceAgent.isCmdInProgress(project)) {
|
|
72
79
|
await sleep(1000);
|
|
73
80
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { CliLeaf } from '@alwaysai/alwayscli';
|
|
2
|
-
import { rimraf } from 'rimraf';
|
|
3
2
|
import { logger } from '../../util/logger';
|
|
4
3
|
import { AgentConfigFile } from '../../infrastructure/agent-config';
|
|
5
4
|
import { DeviceConfigFile } from 'alwaysai/lib/core/device';
|
|
6
5
|
import {
|
|
7
6
|
APP_ROOT,
|
|
8
7
|
CREDENTIALS_FILE_PATH,
|
|
9
|
-
DEVICE_AGENT_CFG_PATH
|
|
8
|
+
DEVICE_AGENT_CFG_PATH,
|
|
9
|
+
DEVICE_AGENT_DOCKER_COMPOSE_PATH
|
|
10
10
|
} from '../../util/directories';
|
|
11
11
|
import {
|
|
12
12
|
checkRabbitMQContainerRunning,
|
|
@@ -14,33 +14,42 @@ import {
|
|
|
14
14
|
} from '../../local-connection/rabbitmq-connection';
|
|
15
15
|
import { stopApp } from '../../application-control';
|
|
16
16
|
import { LOCAL_CERT_AND_KEY_DIR } from 'alwaysai/lib/paths';
|
|
17
|
+
import safeRimraf from '../../util/safe-rimraf';
|
|
17
18
|
|
|
18
19
|
export const cleanCliLeaf = CliLeaf({
|
|
19
20
|
name: 'clean',
|
|
20
21
|
description: 'Remove all provisioning files',
|
|
21
22
|
async action(_, opts) {
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
logger.info('Removing provisioning files.');
|
|
24
|
+
try {
|
|
25
|
+
if (await checkRabbitMQContainerRunning()) {
|
|
26
|
+
await stopRabbitMQContainer();
|
|
27
|
+
}
|
|
28
|
+
} catch (e) {
|
|
29
|
+
logger.error(
|
|
30
|
+
`You may need to manually stop the container by running docker-compose down in the following directory: ${DEVICE_AGENT_DOCKER_COMPOSE_PATH}`
|
|
31
|
+
);
|
|
32
|
+
logger.debug(`Error in checking / stopping RabbitMQ container: ${e}`);
|
|
24
33
|
}
|
|
25
|
-
|
|
26
|
-
await
|
|
34
|
+
|
|
35
|
+
await safeRimraf(DEVICE_AGENT_CFG_PATH);
|
|
36
|
+
|
|
27
37
|
logger.debug('Checking for alwaysAI applications still running');
|
|
28
38
|
const apps = await AgentConfigFile().getApps();
|
|
29
39
|
for (const app of apps) {
|
|
30
40
|
const { projectId } = app;
|
|
31
41
|
await stopApp({ projectId });
|
|
32
42
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
await rimraf(LOCAL_CERT_AND_KEY_DIR);
|
|
43
|
+
await safeRimraf(LOCAL_CERT_AND_KEY_DIR);
|
|
44
|
+
|
|
36
45
|
logger.debug(`Removing ${AgentConfigFile().path}`);
|
|
37
46
|
AgentConfigFile().remove();
|
|
38
47
|
logger.debug(`Removing ${DeviceConfigFile().path}`);
|
|
39
48
|
DeviceConfigFile().remove();
|
|
40
|
-
|
|
41
|
-
await
|
|
42
|
-
|
|
43
|
-
|
|
49
|
+
|
|
50
|
+
await safeRimraf(CREDENTIALS_FILE_PATH);
|
|
51
|
+
await safeRimraf(APP_ROOT);
|
|
52
|
+
|
|
44
53
|
logger.info('Device configuration cleaned');
|
|
45
54
|
}
|
|
46
55
|
});
|
|
@@ -12,9 +12,11 @@ import {
|
|
|
12
12
|
|
|
13
13
|
import { JsSpawner } from 'alwaysai/lib/util';
|
|
14
14
|
import {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
getCpuDetails,
|
|
16
|
+
getDiskDetails,
|
|
17
|
+
getMemDetails,
|
|
18
|
+
getSystemInformation,
|
|
19
|
+
reboot
|
|
18
20
|
} from '../../device-control/device-control';
|
|
19
21
|
import { writeTokenAndDeviceCfg } from '../../infrastructure/tokens-and-device-cfg';
|
|
20
22
|
import { logger } from '../../util/logger';
|
|
@@ -106,11 +108,36 @@ export const getInfoCliLeaf = CliLeaf({
|
|
|
106
108
|
description: 'Get device info',
|
|
107
109
|
namedInputs: {},
|
|
108
110
|
async action(_, opts) {
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
111
|
+
const cpuDetails = await getCpuDetails();
|
|
112
|
+
const diskDetails = await getDiskDetails();
|
|
113
|
+
const memDetails = await getMemDetails();
|
|
114
|
+
const out = {
|
|
115
|
+
'CPU Utilization': `Used ${cpuDetails.usedPerc.toFixed(
|
|
116
|
+
2
|
|
117
|
+
)}%, Temperature ${cpuDetails.temperature} °C`,
|
|
118
|
+
'Disk Utilization': `${diskDetails.usedGb} GB / ${
|
|
119
|
+
diskDetails.usedGb + diskDetails.freeGb
|
|
120
|
+
} GB`,
|
|
121
|
+
'Memory Utilization': `${memDetails.usedMb} MB / ${
|
|
122
|
+
memDetails.usedMb + memDetails.freeMb
|
|
123
|
+
} MB`
|
|
124
|
+
};
|
|
125
|
+
console.table(out);
|
|
126
|
+
const systemInfo = await getSystemInformation();
|
|
127
|
+
console.table(systemInfo.os);
|
|
128
|
+
console.table(systemInfo.cpu);
|
|
129
|
+
console.table(systemInfo.disk?.drives);
|
|
130
|
+
console.table(systemInfo.device);
|
|
131
|
+
console.table(systemInfo.network);
|
|
132
|
+
console.table(systemInfo.versions);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
export const restartCliLeaf = CliLeaf({
|
|
137
|
+
name: 'restart',
|
|
138
|
+
description: 'Restart the device',
|
|
139
|
+
namedInputs: {},
|
|
140
|
+
async action(_, opts) {
|
|
141
|
+
await reboot();
|
|
115
142
|
}
|
|
116
143
|
});
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { CliBranch } from '@alwaysai/alwayscli';
|
|
2
|
-
import { getInfoCliLeaf, initCliLeaf } from './device';
|
|
2
|
+
import { getInfoCliLeaf, initCliLeaf, restartCliLeaf } from './device';
|
|
3
3
|
import { cleanCliLeaf } from './clean';
|
|
4
4
|
|
|
5
5
|
export const deviceCliBranch = CliBranch({
|
|
6
6
|
name: 'device',
|
|
7
7
|
description: 'Manage current device',
|
|
8
|
-
subcommands: [initCliLeaf, getInfoCliLeaf, cleanCliLeaf]
|
|
8
|
+
subcommands: [initCliLeaf, getInfoCliLeaf, cleanCliLeaf, restartCliLeaf]
|
|
9
9
|
});
|
package/src/util/logger.ts
CHANGED
|
@@ -2,19 +2,24 @@ import * as winston from 'winston';
|
|
|
2
2
|
import 'winston-daily-rotate-file';
|
|
3
3
|
import * as path from 'path';
|
|
4
4
|
import { AAI_DIR } from 'alwaysai/lib/paths';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
ALWAYSAI_DEVICE_AGENT_MODE,
|
|
7
|
+
ALWAYSAI_LOG_LEVEL,
|
|
8
|
+
ALWAYSAI_LOG_TO_CONSOLE
|
|
9
|
+
} from '../environment';
|
|
6
10
|
|
|
7
11
|
const LOG_LEVEL = ALWAYSAI_LOG_LEVEL || 'info';
|
|
8
12
|
|
|
9
|
-
const transports =
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
const transports =
|
|
14
|
+
ALWAYSAI_LOG_TO_CONSOLE || ALWAYSAI_DEVICE_AGENT_MODE !== 'cloud'
|
|
15
|
+
? [new winston.transports.Console({ level: LOG_LEVEL })]
|
|
16
|
+
: [
|
|
17
|
+
new winston.transports.DailyRotateFile({
|
|
18
|
+
filename: path.join(AAI_DIR, 'agent-logs', 'agent-logs.txt'),
|
|
19
|
+
maxSize: '5m',
|
|
20
|
+
maxFiles: '2d'
|
|
21
|
+
})
|
|
22
|
+
];
|
|
18
23
|
|
|
19
24
|
export const logger = winston.createLogger({
|
|
20
25
|
level: LOG_LEVEL,
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { rimraf } from 'rimraf';
|
|
2
|
+
import { logger } from '../util/logger';
|
|
3
|
+
|
|
4
|
+
export default async function safeRimraf(path: string) {
|
|
5
|
+
logger.debug(`Removing ${path}`);
|
|
6
|
+
try {
|
|
7
|
+
await rimraf(path);
|
|
8
|
+
} catch (e) {
|
|
9
|
+
logger.error(
|
|
10
|
+
`Failed to remove ${path}. Please manually delete the file or directory.`
|
|
11
|
+
);
|
|
12
|
+
logger.debug(`Error removing ${path}: ${e}`);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
export declare class TransactionQueue {
|
|
2
|
-
private transactionList;
|
|
3
|
-
private txIdToProjectIdMap;
|
|
4
|
-
constructor();
|
|
5
|
-
addTxIdToQueue(txId: string, projectId: string): void;
|
|
6
|
-
getCurrentTxId(): string;
|
|
7
|
-
checkTxnInQueue(txId: string): boolean;
|
|
8
|
-
getProjectIdForTxnId(txId: string): string;
|
|
9
|
-
completeTxn(txId: string): void;
|
|
10
|
-
completeCurrentTxn(): void;
|
|
11
|
-
}
|
|
12
|
-
//# sourceMappingURL=transaction-queue.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"transaction-queue.d.ts","sourceRoot":"","sources":["../../src/cloud-connection/transaction-queue.ts"],"names":[],"mappings":"AAAA,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,eAAe,CAAW;IAClC,OAAO,CAAC,kBAAkB,CAAkC;;IAOrD,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;IAS9C,cAAc;IAId,eAAe,CAAC,IAAI,EAAE,MAAM;IAI5B,oBAAoB,CAAC,IAAI,EAAE,MAAM;IAIjC,WAAW,CAAC,IAAI,EAAE,MAAM;IAKxB,kBAAkB;CAI1B"}
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.TransactionQueue = void 0;
|
|
4
|
-
class TransactionQueue {
|
|
5
|
-
constructor() {
|
|
6
|
-
this.txIdToProjectIdMap = {};
|
|
7
|
-
this.transactionList = [];
|
|
8
|
-
this.txIdToProjectIdMap = {};
|
|
9
|
-
}
|
|
10
|
-
addTxIdToQueue(txId, projectId) {
|
|
11
|
-
if (!this.checkTxnInQueue(txId)) {
|
|
12
|
-
this.transactionList.push(txId);
|
|
13
|
-
this.txIdToProjectIdMap[txId] = projectId;
|
|
14
|
-
}
|
|
15
|
-
else {
|
|
16
|
-
throw new Error(`txId ${txId} is already added to the queue!`);
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
getCurrentTxId() {
|
|
20
|
-
return this.transactionList[0];
|
|
21
|
-
}
|
|
22
|
-
checkTxnInQueue(txId) {
|
|
23
|
-
return this.transactionList.includes(txId);
|
|
24
|
-
}
|
|
25
|
-
getProjectIdForTxnId(txId) {
|
|
26
|
-
return this.txIdToProjectIdMap[txId];
|
|
27
|
-
}
|
|
28
|
-
completeTxn(txId) {
|
|
29
|
-
delete this.txIdToProjectIdMap[txId];
|
|
30
|
-
this.transactionList.splice(this.transactionList.indexOf(txId), 1);
|
|
31
|
-
}
|
|
32
|
-
completeCurrentTxn() {
|
|
33
|
-
delete this.txIdToProjectIdMap[this.transactionList[0]];
|
|
34
|
-
this.transactionList.shift();
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
exports.TransactionQueue = TransactionQueue;
|
|
38
|
-
//# sourceMappingURL=transaction-queue.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"transaction-queue.js","sourceRoot":"","sources":["../../src/cloud-connection/transaction-queue.ts"],"names":[],"mappings":";;;AAAA,MAAa,gBAAgB;IAI3B;QAFQ,uBAAkB,GAA+B,EAAE,CAAC;QAG1D,IAAI,CAAC,eAAe,GAAG,EAAE,CAAC;QAC1B,IAAI,CAAC,kBAAkB,GAAG,EAAE,CAAC;IAC/B,CAAC;IAEM,cAAc,CAAC,IAAY,EAAE,SAAiB;QACnD,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,EAAE;YAC/B,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAChC,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,GAAG,SAAS,CAAC;SAC3C;aAAM;YACL,MAAM,IAAI,KAAK,CAAC,QAAQ,IAAI,iCAAiC,CAAC,CAAC;SAChE;IACH,CAAC;IAEM,cAAc;QACnB,OAAO,IAAI,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;IACjC,CAAC;IAEM,eAAe,CAAC,IAAY;QACjC,OAAO,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC7C,CAAC;IAEM,oBAAoB,CAAC,IAAY;QACtC,OAAO,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC;IACvC,CAAC;IAEM,WAAW,CAAC,IAAY;QAC7B,OAAO,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC;QACrC,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;IACrE,CAAC;IAEM,kBAAkB;QACvB,OAAO,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC;QACxD,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,CAAC;IAC/B,CAAC;CACF;AAvCD,4CAuCC"}
|