@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.
Files changed (87) hide show
  1. package/lib/application-control/environment-variables.d.ts +1 -1
  2. package/lib/application-control/environment-variables.d.ts.map +1 -1
  3. package/lib/cloud-connection/device-agent-cloud-connection.d.ts +3 -2
  4. package/lib/cloud-connection/device-agent-cloud-connection.d.ts.map +1 -1
  5. package/lib/cloud-connection/device-agent-cloud-connection.js +81 -59
  6. package/lib/cloud-connection/device-agent-cloud-connection.js.map +1 -1
  7. package/lib/cloud-connection/live-updates-handler.d.ts.map +1 -1
  8. package/lib/cloud-connection/live-updates-handler.js +2 -4
  9. package/lib/cloud-connection/live-updates-handler.js.map +1 -1
  10. package/lib/cloud-connection/live-updates-handler.test.js +1 -1
  11. package/lib/cloud-connection/live-updates-handler.test.js.map +1 -1
  12. package/lib/cloud-connection/messages.js +6 -6
  13. package/lib/cloud-connection/messages.js.map +1 -1
  14. package/lib/cloud-connection/passthrough-handler.d.ts.map +1 -1
  15. package/lib/cloud-connection/passthrough-handler.js +37 -11
  16. package/lib/cloud-connection/passthrough-handler.js.map +1 -1
  17. package/lib/cloud-connection/publisher.d.ts +5 -4
  18. package/lib/cloud-connection/publisher.d.ts.map +1 -1
  19. package/lib/cloud-connection/publisher.js +9 -8
  20. package/lib/cloud-connection/publisher.js.map +1 -1
  21. package/lib/cloud-connection/shadow-handler.d.ts +5 -0
  22. package/lib/cloud-connection/shadow-handler.d.ts.map +1 -1
  23. package/lib/cloud-connection/shadow-handler.js +20 -5
  24. package/lib/cloud-connection/shadow-handler.js.map +1 -1
  25. package/lib/cloud-connection/shadow-handler.test.js +9 -0
  26. package/lib/cloud-connection/shadow-handler.test.js.map +1 -1
  27. package/lib/cloud-connection/transaction-manager.d.ts +10 -0
  28. package/lib/cloud-connection/transaction-manager.d.ts.map +1 -0
  29. package/lib/cloud-connection/transaction-manager.js +41 -0
  30. package/lib/cloud-connection/transaction-manager.js.map +1 -0
  31. package/lib/cloud-connection/transaction-manager.test.d.ts +2 -0
  32. package/lib/cloud-connection/transaction-manager.test.d.ts.map +1 -0
  33. package/lib/cloud-connection/transaction-manager.test.js +63 -0
  34. package/lib/cloud-connection/transaction-manager.test.js.map +1 -0
  35. package/lib/device-control/device-control.d.ts +58 -3
  36. package/lib/device-control/device-control.d.ts.map +1 -1
  37. package/lib/device-control/device-control.js +141 -14
  38. package/lib/device-control/device-control.js.map +1 -1
  39. package/lib/subcommands/app/env-vars.d.ts.map +1 -1
  40. package/lib/subcommands/app/env-vars.js +15 -9
  41. package/lib/subcommands/app/env-vars.js.map +1 -1
  42. package/lib/subcommands/device/clean.d.ts.map +1 -1
  43. package/lib/subcommands/device/clean.js +14 -12
  44. package/lib/subcommands/device/clean.js.map +1 -1
  45. package/lib/subcommands/device/device.d.ts +1 -0
  46. package/lib/subcommands/device/device.d.ts.map +1 -1
  47. package/lib/subcommands/device/device.js +26 -7
  48. package/lib/subcommands/device/device.js.map +1 -1
  49. package/lib/subcommands/device/index.js +1 -1
  50. package/lib/subcommands/device/index.js.map +1 -1
  51. package/lib/util/logger.d.ts.map +1 -1
  52. package/lib/util/logger.js +1 -1
  53. package/lib/util/logger.js.map +1 -1
  54. package/lib/util/safe-rimraf.d.ts +2 -0
  55. package/lib/util/safe-rimraf.d.ts.map +1 -0
  56. package/lib/util/safe-rimraf.js +16 -0
  57. package/lib/util/safe-rimraf.js.map +1 -0
  58. package/package.json +4 -2
  59. package/readme.md +1 -0
  60. package/src/application-control/environment-variables.ts +1 -1
  61. package/src/cloud-connection/device-agent-cloud-connection.ts +125 -80
  62. package/src/cloud-connection/live-updates-handler.test.ts +2 -2
  63. package/src/cloud-connection/live-updates-handler.ts +2 -8
  64. package/src/cloud-connection/messages.ts +9 -9
  65. package/src/cloud-connection/passthrough-handler.ts +43 -10
  66. package/src/cloud-connection/publisher.ts +27 -10
  67. package/src/cloud-connection/shadow-handler.test.ts +9 -0
  68. package/src/cloud-connection/shadow-handler.ts +25 -11
  69. package/src/cloud-connection/transaction-manager.test.ts +73 -0
  70. package/src/cloud-connection/transaction-manager.ts +43 -0
  71. package/src/device-control/device-control.ts +142 -9
  72. package/src/subcommands/app/env-vars.ts +17 -10
  73. package/src/subcommands/device/clean.ts +22 -13
  74. package/src/subcommands/device/device.ts +36 -9
  75. package/src/subcommands/device/index.ts +2 -2
  76. package/src/util/logger.ts +15 -10
  77. package/src/util/safe-rimraf.ts +14 -0
  78. package/lib/cloud-connection/transaction-queue.d.ts +0 -12
  79. package/lib/cloud-connection/transaction-queue.d.ts.map +0 -1
  80. package/lib/cloud-connection/transaction-queue.js +0 -38
  81. package/lib/cloud-connection/transaction-queue.js.map +0 -1
  82. package/lib/cloud-connection/transaction-queue.test.d.ts +0 -2
  83. package/lib/cloud-connection/transaction-queue.test.d.ts.map +0 -1
  84. package/lib/cloud-connection/transaction-queue.test.js +0 -46
  85. package/lib/cloud-connection/transaction-queue.test.js.map +0 -1
  86. package/src/cloud-connection/transaction-queue.test.ts +0 -55
  87. 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
- const shadowUpdate: ShadowUpdate = { projectId };
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 message sent from self: ${JSON.stringify(
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
- // eslint-disable-next-line
2
- const osu = require('node-os-utils');
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
- export async function getCpuUtil(): Promise<number> {
5
- const cpuUsage = await osu.cpu.usage();
6
- return cpuUsage;
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 getDiskUtil(): Promise<number> {
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 parseFloat(driveInfo.usedPercentage);
24
+ return {
25
+ usedGb: parseFloat(driveInfo.usedGb),
26
+ freeGb: parseFloat(driveInfo.freeGb)
27
+ };
12
28
  }
13
29
 
14
- export async function getMemUtil(): Promise<number> {
30
+ export async function getMemDetails() {
15
31
  const memInfo = await osu.mem.info();
16
- return 100.0 - memInfo.freeMemPercentage;
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
- envVars[service][nameVal[0]] = nameVal[1];
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
- const topic = deviceAgent.getShadowTopics().projects.updateDelta;
58
-
59
- const message = {
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
- [project]: {
64
- envVars
63
+ desired: {
64
+ [project]: {
65
+ envVars
66
+ }
65
67
  }
66
68
  },
67
- clientToken: 'not-self'
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
- if (await checkRabbitMQContainerRunning()) {
23
- await stopRabbitMQContainer();
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
- logger.debug(`Removing ${DEVICE_AGENT_CFG_PATH}`);
26
- await rimraf(DEVICE_AGENT_CFG_PATH);
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
- logger.info('Cleaning device configuration');
34
- logger.debug(`Removing ${LOCAL_CERT_AND_KEY_DIR}`);
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
- logger.debug(`Removing ${CREDENTIALS_FILE_PATH}`);
41
- await rimraf(CREDENTIALS_FILE_PATH);
42
- logger.debug(`Removing ${APP_ROOT}`);
43
- await rimraf(APP_ROOT);
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
- getCpuUtil,
16
- getDiskUtil,
17
- getMemUtil
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 deviceInfo = [
110
- ['CPU Utilization', `${String(await getCpuUtil())} %`],
111
- ['Disk Utilization', `${String(await getDiskUtil())} %`],
112
- ['Memory Utilization', `${String(await getMemUtil())} %`]
113
- ];
114
- console.table(deviceInfo);
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
  });
@@ -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 { ALWAYSAI_LOG_LEVEL, ALWAYSAI_LOG_TO_CONSOLE } from '../environment';
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 = ALWAYSAI_LOG_TO_CONSOLE
10
- ? [new winston.transports.Console({ level: LOG_LEVEL })]
11
- : [
12
- new winston.transports.DailyRotateFile({
13
- filename: path.join(AAI_DIR, 'agent-logs', 'agent-logs.txt'),
14
- maxSize: '5m',
15
- maxFiles: '2d'
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"}