@alwaysai/device-agent 0.0.3 → 0.0.4-1

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 (189) hide show
  1. package/lib/application-control/backup.d.ts +8 -0
  2. package/lib/application-control/backup.d.ts.map +1 -0
  3. package/lib/application-control/backup.js +34 -0
  4. package/lib/application-control/backup.js.map +1 -0
  5. package/lib/application-control/environment-variables.d.ts +9 -0
  6. package/lib/application-control/environment-variables.d.ts.map +1 -0
  7. package/lib/application-control/environment-variables.js +82 -0
  8. package/lib/application-control/environment-variables.js.map +1 -0
  9. package/lib/application-control/index.d.ts +9 -0
  10. package/lib/application-control/index.d.ts.map +1 -0
  11. package/lib/application-control/index.js +27 -0
  12. package/lib/application-control/index.js.map +1 -0
  13. package/lib/application-control/install.d.ts +16 -0
  14. package/lib/application-control/install.d.ts.map +1 -0
  15. package/lib/application-control/install.js +117 -0
  16. package/lib/application-control/install.js.map +1 -0
  17. package/lib/application-control/models.d.ts +8 -0
  18. package/lib/application-control/models.d.ts.map +1 -1
  19. package/lib/application-control/models.js +44 -11
  20. package/lib/application-control/models.js.map +1 -1
  21. package/lib/application-control/status.d.ts +26 -0
  22. package/lib/application-control/status.d.ts.map +1 -0
  23. package/lib/application-control/status.js +138 -0
  24. package/lib/application-control/status.js.map +1 -0
  25. package/lib/application-control/types.d.ts +5 -0
  26. package/lib/application-control/types.d.ts.map +1 -0
  27. package/lib/{util/spawner → application-control}/types.js +0 -0
  28. package/lib/{util/spawner → application-control}/types.js.map +1 -1
  29. package/lib/application-control/utils.d.ts +2 -9
  30. package/lib/application-control/utils.d.ts.map +1 -1
  31. package/lib/application-control/utils.js +14 -29
  32. package/lib/application-control/utils.js.map +1 -1
  33. package/lib/cloud-connection/device-agent-cloud-connection.d.ts +15 -1
  34. package/lib/cloud-connection/device-agent-cloud-connection.d.ts.map +1 -1
  35. package/lib/cloud-connection/device-agent-cloud-connection.js +227 -9
  36. package/lib/cloud-connection/device-agent-cloud-connection.js.map +1 -1
  37. package/lib/docker/docker-cmd.js +2 -2
  38. package/lib/docker/docker-cmd.js.map +1 -1
  39. package/lib/docker/docker-compose-cmd.js +2 -2
  40. package/lib/docker/docker-compose-cmd.js.map +1 -1
  41. package/lib/environment.d.ts +2 -0
  42. package/lib/environment.d.ts.map +1 -1
  43. package/lib/environment.js +3 -1
  44. package/lib/environment.js.map +1 -1
  45. package/lib/index.js +10 -8
  46. package/lib/index.js.map +1 -1
  47. package/lib/infrastructure/agent-config.d.ts +73 -0
  48. package/lib/infrastructure/agent-config.d.ts.map +1 -0
  49. package/lib/infrastructure/agent-config.js +186 -0
  50. package/lib/infrastructure/agent-config.js.map +1 -0
  51. package/lib/infrastructure/agent-config.test.d.ts +2 -0
  52. package/lib/infrastructure/agent-config.test.d.ts.map +1 -0
  53. package/lib/infrastructure/agent-config.test.js +135 -0
  54. package/lib/infrastructure/agent-config.test.js.map +1 -0
  55. package/lib/infrastructure/certificates-and-tokens.d.ts +4 -0
  56. package/lib/infrastructure/certificates-and-tokens.d.ts.map +1 -0
  57. package/lib/infrastructure/certificates-and-tokens.js +58 -0
  58. package/lib/infrastructure/certificates-and-tokens.js.map +1 -0
  59. package/lib/{util → infrastructure}/urls.d.ts +0 -0
  60. package/lib/infrastructure/urls.d.ts.map +1 -0
  61. package/lib/{util → infrastructure}/urls.js +3 -3
  62. package/lib/infrastructure/urls.js.map +1 -0
  63. package/lib/root.js +3 -3
  64. package/lib/root.js.map +1 -1
  65. package/lib/subcommands/app/app.d.ts +31 -14
  66. package/lib/subcommands/app/app.d.ts.map +1 -1
  67. package/lib/subcommands/app/app.js +117 -62
  68. package/lib/subcommands/app/app.js.map +1 -1
  69. package/lib/subcommands/app/index.d.ts.map +1 -1
  70. package/lib/subcommands/app/index.js +6 -1
  71. package/lib/subcommands/app/index.js.map +1 -1
  72. package/lib/subcommands/index.d.ts.map +1 -1
  73. package/lib/subcommands/index.js +1 -7
  74. package/lib/subcommands/index.js.map +1 -1
  75. package/lib/subcommands/login.d.ts +3 -2
  76. package/lib/subcommands/login.d.ts.map +1 -1
  77. package/lib/subcommands/login.js +11 -4
  78. package/lib/subcommands/login.js.map +1 -1
  79. package/lib/util/copy-dir.js +3 -3
  80. package/lib/util/copy-dir.js.map +1 -1
  81. package/lib/util/directories.d.ts +1 -1
  82. package/lib/util/directories.d.ts.map +1 -1
  83. package/lib/util/directories.js +9 -14
  84. package/lib/util/directories.js.map +1 -1
  85. package/lib/util/get-device-id.d.ts +2 -0
  86. package/lib/util/get-device-id.d.ts.map +1 -0
  87. package/lib/util/get-device-id.js +24 -0
  88. package/lib/util/get-device-id.js.map +1 -0
  89. package/package.json +19 -14
  90. package/readme.md +149 -72
  91. package/src/application-control/backup.ts +32 -0
  92. package/src/application-control/environment-variables.ts +81 -0
  93. package/src/application-control/index.ts +40 -0
  94. package/src/application-control/install.ts +126 -0
  95. package/src/application-control/models.ts +51 -11
  96. package/src/application-control/status.ts +156 -0
  97. package/src/application-control/types.ts +1 -0
  98. package/src/application-control/utils.ts +12 -27
  99. package/src/cloud-connection/device-agent-cloud-connection.ts +265 -12
  100. package/src/docker/docker-cmd.ts +1 -1
  101. package/src/docker/docker-compose-cmd.ts +1 -1
  102. package/src/environment.ts +2 -0
  103. package/src/index.ts +10 -7
  104. package/src/infrastructure/agent-config.test.ts +143 -0
  105. package/src/infrastructure/agent-config.ts +217 -0
  106. package/src/infrastructure/certificates-and-tokens.ts +60 -0
  107. package/src/{util → infrastructure}/urls.ts +1 -1
  108. package/src/root.ts +3 -3
  109. package/src/subcommands/app/app.ts +135 -62
  110. package/src/subcommands/app/index.ts +11 -1
  111. package/src/subcommands/index.ts +1 -7
  112. package/src/subcommands/login.ts +11 -4
  113. package/src/util/copy-dir.ts +1 -1
  114. package/src/util/directories.ts +12 -17
  115. package/src/util/get-device-id.ts +22 -0
  116. package/lib/application-control/application-control.d.ts +0 -46
  117. package/lib/application-control/application-control.d.ts.map +0 -1
  118. package/lib/application-control/application-control.js +0 -234
  119. package/lib/application-control/application-control.js.map +0 -1
  120. package/lib/constants.d.ts +0 -17
  121. package/lib/constants.d.ts.map +0 -1
  122. package/lib/constants.js +0 -24
  123. package/lib/constants.js.map +0 -1
  124. package/lib/subcommands/test-app.d.ts +0 -2
  125. package/lib/subcommands/test-app.d.ts.map +0 -1
  126. package/lib/subcommands/test-app.js +0 -29
  127. package/lib/subcommands/test-app.js.map +0 -1
  128. package/lib/util/spawner/gnu-spawner.d.ts +0 -9
  129. package/lib/util/spawner/gnu-spawner.d.ts.map +0 -1
  130. package/lib/util/spawner/gnu-spawner.js +0 -102
  131. package/lib/util/spawner/gnu-spawner.js.map +0 -1
  132. package/lib/util/spawner/js-spawner.d.ts +0 -5
  133. package/lib/util/spawner/js-spawner.d.ts.map +0 -1
  134. package/lib/util/spawner/js-spawner.js +0 -89
  135. package/lib/util/spawner/js-spawner.js.map +0 -1
  136. package/lib/util/spawner/types.d.ts +0 -28
  137. package/lib/util/spawner/types.d.ts.map +0 -1
  138. package/lib/util/spawner-base/index.d.ts +0 -17
  139. package/lib/util/spawner-base/index.d.ts.map +0 -1
  140. package/lib/util/spawner-base/index.js +0 -30
  141. package/lib/util/spawner-base/index.js.map +0 -1
  142. package/lib/util/spawner-base/run-foreground-sync.d.ts +0 -3
  143. package/lib/util/spawner-base/run-foreground-sync.d.ts.map +0 -1
  144. package/lib/util/spawner-base/run-foreground-sync.js +0 -18
  145. package/lib/util/spawner-base/run-foreground-sync.js.map +0 -1
  146. package/lib/util/spawner-base/run-foreground.d.ts +0 -3
  147. package/lib/util/spawner-base/run-foreground.d.ts.map +0 -1
  148. package/lib/util/spawner-base/run-foreground.js +0 -49
  149. package/lib/util/spawner-base/run-foreground.js.map +0 -1
  150. package/lib/util/spawner-base/run-streaming.d.ts +0 -4
  151. package/lib/util/spawner-base/run-streaming.d.ts.map +0 -1
  152. package/lib/util/spawner-base/run-streaming.js +0 -35
  153. package/lib/util/spawner-base/run-streaming.js.map +0 -1
  154. package/lib/util/spawner-base/run.d.ts +0 -4
  155. package/lib/util/spawner-base/run.d.ts.map +0 -1
  156. package/lib/util/spawner-base/run.js +0 -56
  157. package/lib/util/spawner-base/run.js.map +0 -1
  158. package/lib/util/urls.d.ts.map +0 -1
  159. package/lib/util/urls.js.map +0 -1
  160. package/lib/web/index.html +0 -229
  161. package/lib/web/static/Karla.css +0 -18
  162. package/lib/web/static/bootstrap-4.3.1.min.css +0 -7
  163. package/lib/web/static/bootstrap-4.3.1.min.js +0 -7
  164. package/lib/web/static/favicon.ico +0 -0
  165. package/lib/web/static/jquery-3.3.1.slim.min.js +0 -2
  166. package/lib/web/static/popper-1.14.7.min.js +0 -5
  167. package/lib/web/web-interface.d.ts +0 -2
  168. package/lib/web/web-interface.d.ts.map +0 -1
  169. package/lib/web/web-interface.js +0 -74
  170. package/lib/web/web-interface.js.map +0 -1
  171. package/src/application-control/application-control.ts +0 -273
  172. package/src/constants.ts +0 -32
  173. package/src/subcommands/test-app.ts +0 -30
  174. package/src/util/spawner/gnu-spawner.ts +0 -114
  175. package/src/util/spawner/js-spawner.ts +0 -110
  176. package/src/util/spawner/types.ts +0 -28
  177. package/src/util/spawner-base/index.ts +0 -28
  178. package/src/util/spawner-base/run-foreground-sync.ts +0 -16
  179. package/src/util/spawner-base/run-foreground.ts +0 -49
  180. package/src/util/spawner-base/run-streaming.ts +0 -40
  181. package/src/util/spawner-base/run.ts +0 -60
  182. package/src/web/index.html +0 -229
  183. package/src/web/static/Karla.css +0 -18
  184. package/src/web/static/bootstrap-4.3.1.min.css +0 -7
  185. package/src/web/static/bootstrap-4.3.1.min.js +0 -7
  186. package/src/web/static/favicon.ico +0 -0
  187. package/src/web/static/jquery-3.3.1.slim.min.js +0 -2
  188. package/src/web/static/popper-1.14.7.min.js +0 -5
  189. package/src/web/web-interface.ts +0 -89
package/package.json CHANGED
@@ -1,9 +1,13 @@
1
1
  {
2
2
  "name": "@alwaysai/device-agent",
3
3
  "description": "The alwaysAI Device Agent",
4
- "version": "0.0.3",
5
- "main": "lib/exports.js",
6
- "types": "lib/exports.d.ts",
4
+ "version": "0.0.4-1",
5
+ "main": "lib/index.js",
6
+ "types": "lib/index.d.ts",
7
+ "publishConfig": {
8
+ "access": "public",
9
+ "tag": "next"
10
+ },
7
11
  "bin": {
8
12
  "aai-agent": "lib/index.js"
9
13
  },
@@ -12,34 +16,35 @@
12
16
  "node": ">=16.0.0"
13
17
  },
14
18
  "scripts": {
15
- "build": "tsc --project tsconfig.build.json && copyfiles -u 1 src/web/*.html src/web/static/* lib/",
19
+ "build": "tsc --project tsconfig.build.json",
16
20
  "clean": "rimraf lib packages",
17
21
  "build:clean": "npm run clean && npm run build",
18
22
  "build:watch": "tsc --project . --watch",
19
23
  "lint": "tslint --project tsconfig.build.json",
20
24
  "lint:fix": "npm run lint -- --fix",
21
- "test": "npm run lint",
25
+ "test": "npm run test:unit",
22
26
  "test:unit": "jest --coverage src",
23
27
  "test:watch": "npm run test:unit -- --watch",
24
28
  "preversion": "npm test && npm run build:clean",
25
29
  "postversion": "npm publish"
26
30
  },
27
31
  "dependencies": {
28
- "@alwaysai/alwayscli": "0.3.0",
29
- "@alwaysai/config-nodejs": "0.0.2",
32
+ "@alwaysai/alwayscli": "0.3.1",
33
+ "@alwaysai/config-nodejs": "0.1.0",
34
+ "@alwaysai/device-agent-schemas": "0.0.1",
30
35
  "@carnesen/coded-error": "0.4.0",
31
36
  "@types/mkdirp": "1.0.2",
32
37
  "@types/pump": "1.1.1",
33
38
  "@types/rimraf": "3.0.2",
34
39
  "@types/signal-exit": "3.0.1",
35
40
  "@types/tar": "6.1.1",
41
+ "ajv": "8.11.0",
42
+ "alwaysai": "1.8.0",
36
43
  "aws-iot-device-sdk": "2.2.12",
37
- "alwaysai": "1.5.1",
38
44
  "docker-compose": "0.23.17",
39
45
  "express": "4.17.3",
40
- "fp-ts": "2.11.5",
41
- "io-ts": "2.2.16",
42
46
  "mkdirp": "1.0.4",
47
+ "node-fetch": "2.6.1",
43
48
  "node-os-utils": "1.3.6",
44
49
  "pump": "3.0.0",
45
50
  "rimraf": "3.0.2",
@@ -48,17 +53,17 @@
48
53
  "tar": "6.1.11",
49
54
  "tree-kill": "1.2.2",
50
55
  "winston": "3.3.3",
51
- "winston-daily-rotate-file": "4.5.5"
56
+ "winston-daily-rotate-file": "4.5.5",
57
+ "yaml": "2.1.1"
52
58
  },
53
59
  "devDependencies": {
54
- "@alwaysai/tsconfig": "0.0.0",
55
- "@alwaysai/tslint-config": "0.0.3",
60
+ "@alwaysai/tsconfig": "0.0.1",
61
+ "@alwaysai/tslint-config": "0.0.4",
56
62
  "@carnesen/run-and-catch": "0.4.3",
57
63
  "@types/jest": "27.0.3",
58
64
  "@types/node": "16.11.12",
59
65
  "@types/sinon": "10.0.6",
60
66
  "aws-sdk": "^2.1046.0",
61
- "copyfiles": "^2.4.1",
62
67
  "cp-cli": "2.0.0",
63
68
  "get-stream": "6.0.1",
64
69
  "jest": "27.4.3",
package/readme.md CHANGED
@@ -1,115 +1,192 @@
1
1
  # alwaysAI Device Agent
2
2
 
3
+ The alwaysAI Device Agent is a tool that runs on an alwaysAI device provisioned
4
+ for production remote deployment. It enables management of one or more alwaysAI
5
+ apps on a particular device via a command line interface.
6
+
7
+ Note that the Device Agent is still in an experimental phase and these commands
8
+ are likely to change. This guide will be updated with the latest usage as things
9
+ change.
10
+
3
11
  ## System Requirements
4
12
 
5
- 1. Preferably running Linux, but can hack it to work on Mac too
6
- 2. Docker installed with user access (On Linux: `$ sudo usermod -aG docker $USER`, nothing needed for Mac)
7
- 3. Docker Compose installed
8
- 4. npm and node (for build and test)
13
+ * Recent version of Debian or Ubuntu
14
+ * `npm` >= 7.0.0
15
+ * `node` >= 16.0.0
16
+ * Recent version of `docker` with user access
17
+ * On Linux: `$ sudo usermod -aG docker $USER`
18
+ * Recent version of `docker-compose`
19
+ * `curl` installed.
20
+
21
+ ## Provision Device
22
+
23
+ ### Create an alwaysAI device
9
24
 
10
- ## Lint and Build
25
+ On any machine running the alwaysAI CLI you can create a new production device
26
+ in the alwaysAI cloud:
11
27
 
12
28
  ```
13
- $ npm run lint:fix
14
- $ npm run build
29
+ $ aai generate-production-device --name test-dev-1 --description "Device for testing alwaysAI Device Agent"
30
+ Create new production device
31
+ ✔ Write device credentials to /home/alwaysai/abc8fbbf-5d7f-4e49-92b7-8feedffeedbf
32
+ Created test-dev-1: device ID=abc8fbbf-5d7f-4e49-92b7-8feedffeedbf
15
33
  ```
16
34
 
17
- ## Usage
35
+ Note down the device ID for use with Device Agent commands. The device
36
+ credentials are also stored locally for you to keep in a safe place, but you
37
+ won't need those since the Device Agent will update them automatically for you.
18
38
 
19
- There are three modes of operation: CLI, which is great for testing new features, the device-hosted web dashboard, and the IoT Core cloud connection.
39
+ ### Provision the device
40
+
41
+ If you're system already meets all the system requirements, you can simply install the device agent via `npm`:
20
42
 
21
- To enable the cloud IoT Core connection, set the following environment variable:
22
43
  ```
23
- export ALWAYSAI_DEVICE_AGENT_MODE="cloud"
44
+ sudo npm install -g @alwaysai/device-agent@latest
24
45
  ```
25
46
 
26
- To enable the device-hosted web dashboard, set the following environment variable:
47
+ Otherwise, you can run the provision script to install the agent and its dependencies:
48
+
27
49
  ```
28
- export ALWAYSAI_DEVICE_AGENT_MODE="web"
50
+ curl -fsSL https://alwaysai-artifacts-prod.s3.us-west-1.amazonaws.com/device-agent/install-device-agent.sh | sudo -E bash -
29
51
  ```
30
52
 
31
- Any other value will run the CLI.
53
+ The Device Agent will be available in the terminal as `aai-agent`.
54
+
55
+ Next, log in to the Device Agent with the following command:
32
56
 
33
- On Mac you need to override the OS to remove some usage restrictions:
34
57
  ```
35
- export ALWAYSAI_OS_PLATFORM="linux"
58
+ aai-agent login --email <username> --password <password> --device <device_id>
36
59
  ```
37
60
 
38
- ### CLI
61
+ ## Run an alwaysAI application on your device
62
+
63
+ ### Publish an application release and install on device
64
+
65
+ First, you must publish your application to the alwaysAI cloud. From the root
66
+ of your application directory (where the `alwaysai.app.json` file is) on your
67
+ development host:
39
68
 
40
- Here are the available commands:
41
69
  ```
42
- $ node lib --help
43
- Starting alwaysAI Device Agent
44
- Usage: aai-agent <subcommand> ...
70
+ aai app configure
71
+ aai app publish
72
+ ```
45
73
 
46
- Manage your alwaysAI production device
74
+ The output of the final command will give you the release hash that was
75
+ published. You can run `aai release list --project <project_id>` to list all
76
+ release versions for the project, where project ID can be found in the
77
+ `alwaysai.project.json` file.
47
78
 
48
- Subcommands:
79
+ Now you can install the application on the device using the device agent. Run
80
+ the following on the device where the Device Agent is installed:
81
+
82
+ ```
83
+ aai-agent app install --project <project_id> [--release <release_hash>]
84
+ ```
85
+
86
+ ### Control the application
87
+
88
+ Run the following commands on the device where the Device Agent is installed:
49
89
 
50
- get-device-info : Get device info
51
- install-test-app : Install an empty test application
52
- get-installed-apps : List all installed apps
53
- install-app : Install an alwaysAI app from a project
54
- get-app-status : Get the status of an installed alwaysAI app
55
- start-app : Start an installed alwaysAI app
56
- stop-app : Stop a running alwaysAI app
57
- uninstall-app : Remove an alwaysAI app
90
+ Get application status:
91
+ ```
92
+ aai-agent app status --project <project_id>
58
93
  ```
59
94
 
60
- To install a test app, run:
95
+ Start the application:
61
96
  ```
62
- $ node lib install-test-app
97
+ aai-agent app start --project <project_id>
63
98
  ```
64
99
 
65
- The test app has the project ID 16ad11bb-8c3a-4a15-923e-49f7a8311dc6.
100
+ Show the application logs:
101
+ ```
102
+ aai-agent app logs --project <project_id>
103
+ ```
66
104
 
67
- Now you can start, check the status, and stop using the commands:
105
+ Stop the application:
68
106
  ```
69
- $ node lib start-app --project 16ad11bb-8c3a-4a15-923e-49f7a8311dc6
70
- Starting alwaysAI Device Agent
71
- Starting app...
72
- {
73
- exitCode: 0,
74
- err: 'Starting 16ad11bb-8c3a-4a15-923e-49f7a8311dc6_alwaysai_1 ... \r\n' +
75
- 'Starting 16ad11bb-8c3a-4a15-923e-49f7a8311dc6_alwaysai_1 ... done\r\n',
76
- out: ''
77
- }
78
- $ node lib get-app-status --project 16ad11bb-8c3a-4a15-923e-49f7a8311dc6
79
- Starting alwaysAI Device Agent
80
- [
81
- {
82
- name: '16ad11bb-8c3a-4a15-923e-49f7a8311dc6_alwaysai_1',
83
- command: 'tail -f /dev/null',
84
- state: 'Up',
85
- ports: []
86
- }
87
- ]
88
- $ node lib stop-app --project 16ad11bb-8c3a-4a15-923e-49f7a8311dc6
89
- Starting alwaysAI Device Agent
90
- {
91
- exitCode: 0,
92
- err: 'Stopping 16ad11bb-8c3a-4a15-923e-49f7a8311dc6_alwaysai_1 ... \r\n' +
93
- 'Stopping 16ad11bb-8c3a-4a15-923e-49f7a8311dc6_alwaysai_1 ... done\r\n' +
94
- 'Removing 16ad11bb-8c3a-4a15-923e-49f7a8311dc6_alwaysai_1 ... \r\n' +
95
- 'Removing 16ad11bb-8c3a-4a15-923e-49f7a8311dc6_alwaysai_1 ... done\r\n' +
96
- 'Removing network 16ad11bb-8c3a-4a15-923e-49f7a8311dc6_default\n',
97
- out: ''
98
- }
107
+ aai-agent app stop --project <project_id>
99
108
  ```
100
109
 
101
- ### Web Dashboard
110
+ Uninstall the application:
111
+ ```
112
+ aai-agent app uninstall --project <project_id>
113
+ ```
114
+
115
+ ### Manage application models
116
+
117
+ There are several ways to manage the models for your application.
102
118
 
103
- You can start the web dashboard by running:
119
+ #### Update models to new version of the same model ID
120
+
121
+ If a new version of a model is published for an existing model ID, and an
122
+ application is already configured to be using that model ID, updating to the
123
+ new model can simply be done with:
104
124
 
105
125
  ```
106
- node lib
126
+ aai-agent app stop --project <project_id>
127
+ aai-agent app update-models --project <project_id>
128
+ aai-agent app start --project <project_id>
107
129
  ```
108
130
 
109
- And clicking the link that comes up.
131
+ #### Replace models for an application for new ID
132
+
133
+ If you'd like to install an entirely new model to an application, replacing the
134
+ model the app was originally configured with, run the following command:
110
135
 
111
- If you are developing new functionality for the cli in tandem with the device agent, you can run
112
136
  ```
113
- npm i --save /path/to/your/cli/repo
137
+ aai-agent app stop --project <project_id>
138
+ aai-agent app replace-models --project <project_id> --models <model_id_1> [<model_id_2> ...]
139
+ aai-agent app start --project <project_id>
114
140
  ```
115
- Remember to not commit the package.json, or revert it when you are done developing a feature
141
+
142
+ If you plan on using this method, you can make your application source model ID
143
+ agnostic by providing the model ID to edgeIQ in the following way:
144
+
145
+ Select the first model in the config list:
146
+ ```
147
+ obj_detect = edgeiq.ObjectDetection(edgeiq._globals.MODEL_ID_LIST[0])
148
+ ```
149
+
150
+ ### Set and update environment variables
151
+
152
+ The Device Agent enables you to set and update environment variables for all
153
+ services or a single service of you application. For example, set teh following
154
+ environment variable for the `alwaysai` service run the following command on the
155
+ device:
156
+
157
+ ```
158
+ aai-agent app set-env TEST_ENV=1 --project <project_id> --service alwaysai
159
+ ```
160
+
161
+ ## Device Agent Commands
162
+
163
+ ```
164
+ $ aai-agent --help
165
+ Starting alwaysAI Device Agent
166
+ Usage: aai-agent <subcommand> ...
167
+
168
+ Manage your alwaysAI production device
169
+
170
+ Subcommands:
171
+
172
+ login : Login to alwaysAI (this is meant for scripted environments)
173
+ app list : List all installed apps
174
+ app list-releases : List all releases for a given app
175
+ app list-latest-release : List the latest release hash for a given app
176
+ app install : Install an alwaysAI app from a project
177
+ app status : Get the status of an installed alwaysAI app
178
+ app start : Start an installed alwaysAI app
179
+ app stop : Stop a running alwaysAI app
180
+ app restart : Restart running alwaysAI app
181
+ app logs : Get logs for an application
182
+ app uninstall : Remove an alwaysAI app
183
+ app rollback : Rollback an alwaysAI app to the previous version
184
+ app show-models : Show the application models
185
+ app add-model : Add a model to an alwaysAI app
186
+ app remove-model : Remove a model from an alwaysAI app
187
+ app replace-models : Replace all models of an alwaysAI app with new models
188
+ app update-models : Update all models for an alwaysAI app
189
+ app get-all-envs : Get environment variables for an application
190
+ app set-env : Set environment variables for a service
191
+ get-device-info : Get device info
192
+ ```
@@ -0,0 +1,32 @@
1
+ import * as rimraf from 'rimraf';
2
+
3
+ import { copyDir } from '../util/copy-dir';
4
+ import { buildApp, getAppDir } from './utils';
5
+ import { AgentConfigFile } from '../infrastructure/agent-config';
6
+
7
+ export const BACKUP_EXT = '.bak';
8
+
9
+ export async function createAppBackup(props: { projectId: string }) {
10
+ const { projectId } = props;
11
+ const appDir = getAppDir(projectId);
12
+ const backupAppDir = `${appDir}${BACKUP_EXT}`;
13
+ rimraf.sync(backupAppDir);
14
+ await copyDir({ srcPath: appDir, destPath: backupAppDir });
15
+ await AgentConfigFile().setAppBackup({ projectId });
16
+ console.log(`Backed up app ${projectId} to ${backupAppDir}`);
17
+ }
18
+
19
+ export async function rollbackApp(props: { projectId: string }) {
20
+ const { projectId } = props;
21
+ const config = await AgentConfigFile().getAppBackup({ projectId });
22
+ if (!config) {
23
+ throw new Error(`Backup doesn't exist for ${projectId}`);
24
+ }
25
+ const appDir = getAppDir(projectId);
26
+ const backupAppDir = `${appDir}${BACKUP_EXT}`;
27
+ rimraf.sync(appDir);
28
+ await copyDir({ srcPath: backupAppDir, destPath: appDir });
29
+ await buildApp({ appDir });
30
+ await AgentConfigFile().setAppInstalled({ projectId, version: config.version });
31
+ console.log(`Rolled back app ${projectId} to ${config.version}`);
32
+ }
@@ -0,0 +1,81 @@
1
+ import { JsSpawner } from 'alwaysai/lib/util';
2
+ import compose from 'docker-compose';
3
+ import { parse, stringify } from 'yaml';
4
+ import { AgentConfigFile } from '../infrastructure/agent-config';
5
+ import { getAppDir } from './utils';
6
+
7
+ export async function setEnv(props: {
8
+ projectId: string;
9
+ vars: string[];
10
+ service?: string;
11
+ }) {
12
+ const { projectId, vars } = props;
13
+ if (!AgentConfigFile().isAppReady({ projectId })) {
14
+ throw new Error(`App ${projectId} is not ready!`);
15
+ }
16
+ const appDir = getAppDir(projectId);
17
+ const spawner = JsSpawner({ path: appDir });
18
+ const composeContents = await spawner.readFile('docker-compose.yaml');
19
+ const composeParsed = parse(composeContents);
20
+ if ('services' in composeParsed) {
21
+ let services: string[] = Object.keys(composeParsed['services']);
22
+ if (props.service) {
23
+ console.log(services);
24
+ if (services.includes(props.service)) {
25
+ services = [props.service];
26
+ } else {
27
+ throw new Error(`Service ${props.service} not found in ${services}`);
28
+ }
29
+ }
30
+ for (const s of services) {
31
+ const service = composeParsed['services'][s];
32
+ // The environment field overrides the env files, so by appending to
33
+ // the environment list we can assure the value will be used
34
+ if ('environment' in service) {
35
+ const environment: string[] = service['environment'];
36
+ composeParsed['services'][s]['environment'] = environment.concat(vars);
37
+ } else {
38
+ composeParsed['services'][s]['environment'] = vars;
39
+ }
40
+ }
41
+ }
42
+ const composeOutput = stringify(composeParsed);
43
+ // Validate new contents
44
+ await compose.config({ cwd: appDir, configAsString: composeOutput });
45
+ await spawner.writeFile('docker-compose.yaml', composeOutput);
46
+ }
47
+
48
+ export async function getAllEnvs(props: { projectId: string }) {
49
+ const { projectId } = props;
50
+ if (!AgentConfigFile().isAppReady({ projectId })) {
51
+ throw new Error(`App ${projectId} is not ready!`);
52
+ }
53
+ const appDir = getAppDir(projectId);
54
+ const spawner = JsSpawner({ path: appDir });
55
+ const envVars = {};
56
+ const composeContents = await spawner.readFile('docker-compose.yaml');
57
+ const composeParsed = parse(composeContents);
58
+ if ('services' in composeParsed) {
59
+ const services = Object.keys(composeParsed['services']);
60
+ for (const s of services) {
61
+ envVars[s] = [];
62
+ const service = composeParsed['services'][s];
63
+ if ('env_file' in service) {
64
+ const envFiles: string[] = service['env_file'];
65
+ for (const ef of envFiles) {
66
+ envVars[s] = envVars[s].concat((await spawner.readFile(ef)).split('\n'));
67
+ }
68
+ }
69
+ if ('environment' in service) {
70
+ const environment: string[] = service['environment'];
71
+ envVars[s] = envVars[s].concat(environment);
72
+ }
73
+ // Filter out empty lines and comment lines
74
+ envVars[s] = envVars[s].filter((v) => {
75
+ return v !== '' && !v.includes('#');
76
+ });
77
+ }
78
+ }
79
+
80
+ return envVars;
81
+ }
@@ -0,0 +1,40 @@
1
+ import { installApp, uninstallApp } from './install';
2
+ import { rollbackApp } from './backup';
3
+ import {
4
+ listAppReleases,
5
+ listAppLatestRelease,
6
+ getAppStatus,
7
+ startApp,
8
+ getAppLogs,
9
+ stopApp,
10
+ restartApp,
11
+ } from './status';
12
+ import { ModelDetails } from './types';
13
+ import { getAllEnvs, setEnv } from './environment-variables';
14
+
15
+ export {
16
+ installApp,
17
+ uninstallApp,
18
+ rollbackApp,
19
+ listAppReleases,
20
+ listAppLatestRelease,
21
+ getAppStatus,
22
+ startApp,
23
+ getAppLogs,
24
+ stopApp,
25
+ restartApp,
26
+ ModelDetails,
27
+ getAllEnvs,
28
+ setEnv,
29
+ };
30
+
31
+ // CLI-mode only
32
+ import {
33
+ addModel,
34
+ getAppModels,
35
+ removeModel,
36
+ replaceModels,
37
+ updateModels,
38
+ } from './models';
39
+
40
+ export { addModel, getAppModels, removeModel, replaceModels, updateModels };
@@ -0,0 +1,126 @@
1
+ import * as rimraf from 'rimraf';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ import nodeFetch, { Response } from 'node-fetch';
5
+ import { JsSpawner } from 'alwaysai/lib/util';
6
+
7
+ import { runCliCmd } from '../util/run-cli-cmd';
8
+ import { buildApp, getAppDir } from './utils';
9
+ import { AppDetails } from '@alwaysai/device-agent-schemas';
10
+ import { BACKUP_EXT, createAppBackup } from './backup';
11
+ import { stopApp } from './status';
12
+ import { AgentConfigFile } from '../infrastructure/agent-config';
13
+
14
+ export async function getInstalledApps(): Promise<AppDetails[]> {
15
+ const apps = await AgentConfigFile().getApps();
16
+ const appDetails: AppDetails[] = [];
17
+ for (const app of apps) {
18
+ const { projectId, version } = app;
19
+ appDetails.push({ projectId, version });
20
+ }
21
+ return appDetails;
22
+ }
23
+
24
+ export type InstallAppProps = {
25
+ projectId: string;
26
+ releaseHash: string;
27
+ presignedAppUrl?: string;
28
+ };
29
+
30
+ export async function installAppPackage({ localDest, presignedUrl }): Promise<void> {
31
+ const response = await nodeFetch(presignedUrl);
32
+ if (response.status !== 200) {
33
+ // If the URL is invalid; I think we shouldn't get here with the new changes
34
+ throw new Error(
35
+ `Status Code: ${response.status}, ${response.statusText}. Response: ${response.body}`,
36
+ );
37
+ }
38
+
39
+ /**
40
+ * Write the app package to the hash directory
41
+ */
42
+ const stream = response.body.pipe(fs.createWriteStream(localDest));
43
+ await new Promise((resolve, reject) => {
44
+ stream.on('finish', resolve);
45
+ stream.on('error', reject);
46
+ });
47
+ }
48
+
49
+ export async function installApp(props: InstallAppProps): Promise<void> {
50
+ const { projectId, releaseHash, presignedAppUrl } = props;
51
+ const appDir = getAppDir(projectId);
52
+ const spawner = JsSpawner();
53
+ if (await AgentConfigFile().isAppPresent({ projectId })) {
54
+ if (!(await AgentConfigFile().isAppReady({ projectId }))) {
55
+ throw new Error('Application already has installation in progress!');
56
+ }
57
+ await AgentConfigFile().setAppInstalling({ projectId, version: releaseHash });
58
+ console.log('Application is already installed, updating');
59
+ await createAppBackup({ projectId });
60
+ await spawner.rimraf(appDir);
61
+ } else {
62
+ await AgentConfigFile().setAppInstalling({ projectId, version: releaseHash });
63
+ }
64
+
65
+ /**
66
+ * Prep directory and fetch the app package
67
+ */
68
+ await spawner.mkdirp(appDir);
69
+ const localDest = path.join(appDir, `${path.basename(releaseHash)}.tgz`);
70
+ if (!presignedAppUrl) {
71
+ // Download the application using the CLI
72
+ await runCliCmd({
73
+ cmd: [
74
+ 'release',
75
+ 'pull',
76
+ '--yes',
77
+ '--project',
78
+ projectId,
79
+ '--releaseHash',
80
+ releaseHash,
81
+ ],
82
+ cwd: appDir,
83
+ });
84
+ } else {
85
+ await installAppPackage({ localDest, presignedUrl: presignedAppUrl });
86
+ }
87
+
88
+ /**
89
+ * Unpack app package
90
+ */
91
+ await spawner.untar(fs.createReadStream(localDest), appDir);
92
+ console.log(`Unpacked application to ${appDir}`);
93
+
94
+ /**
95
+ * Build app
96
+ * ToDo: migrate the model pull to use MQTT workflow
97
+ */
98
+ await buildApp({ appDir });
99
+
100
+ /**
101
+ * Remove tar file
102
+ */
103
+ await spawner.rimraf(localDest);
104
+
105
+ await AgentConfigFile().setAppInstalled({ projectId, version: releaseHash });
106
+ console.log(`Installed ${projectId} to ${appDir}`);
107
+ }
108
+
109
+ export async function uninstallApp(props: { projectId: string }): Promise<void> {
110
+ const { projectId } = props;
111
+ if (!(await AgentConfigFile().isAppPresent({ projectId }))) {
112
+ console.log(`Application ${projectId} not installed`);
113
+ return;
114
+ }
115
+ try {
116
+ await stopApp({ projectId });
117
+ } catch {
118
+ console.log('Failed to stop app, may be left running...');
119
+ }
120
+ await AgentConfigFile().setAppUninstalled({ projectId });
121
+ // Delete application directory and backup
122
+ const appDir = getAppDir(projectId);
123
+ rimraf.sync(appDir);
124
+ rimraf.sync(`${appDir}${BACKUP_EXT}`);
125
+ console.log('Uninstalled', projectId);
126
+ }