@alwaysai/device-agent 0.0.3 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (210) 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 +16 -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 +239 -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 +6 -0
  56. package/lib/infrastructure/certificates-and-tokens.d.ts.map +1 -0
  57. package/lib/infrastructure/certificates-and-tokens.js +69 -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/device/device.d.ts +6 -0
  73. package/lib/subcommands/device/device.d.ts.map +1 -0
  74. package/lib/subcommands/device/device.js +62 -0
  75. package/lib/subcommands/device/device.js.map +1 -0
  76. package/lib/subcommands/device/index.d.ts +2 -0
  77. package/lib/subcommands/device/index.d.ts.map +1 -0
  78. package/lib/subcommands/device/index.js +11 -0
  79. package/lib/subcommands/device/index.js.map +1 -0
  80. package/lib/subcommands/get-model-package.d.ts +5 -0
  81. package/lib/subcommands/get-model-package.d.ts.map +1 -0
  82. package/lib/subcommands/get-model-package.js +51 -0
  83. package/lib/subcommands/get-model-package.js.map +1 -0
  84. package/lib/subcommands/index.d.ts +8 -1
  85. package/lib/subcommands/index.d.ts.map +1 -1
  86. package/lib/subcommands/index.js +6 -6
  87. package/lib/subcommands/index.js.map +1 -1
  88. package/lib/subcommands/login.d.ts +3 -2
  89. package/lib/subcommands/login.d.ts.map +1 -1
  90. package/lib/subcommands/login.js +11 -4
  91. package/lib/subcommands/login.js.map +1 -1
  92. package/lib/util/copy-dir.js +3 -3
  93. package/lib/util/copy-dir.js.map +1 -1
  94. package/lib/util/directories.d.ts +1 -1
  95. package/lib/util/directories.d.ts.map +1 -1
  96. package/lib/util/directories.js +9 -14
  97. package/lib/util/directories.js.map +1 -1
  98. package/lib/util/get-device-id.d.ts +2 -0
  99. package/lib/util/get-device-id.d.ts.map +1 -0
  100. package/lib/util/get-device-id.js +24 -0
  101. package/lib/util/get-device-id.js.map +1 -0
  102. package/package.json +19 -14
  103. package/readme.md +176 -72
  104. package/src/application-control/backup.ts +32 -0
  105. package/src/application-control/environment-variables.ts +81 -0
  106. package/src/application-control/index.ts +40 -0
  107. package/src/application-control/install.ts +126 -0
  108. package/src/application-control/models.ts +51 -11
  109. package/src/application-control/status.ts +156 -0
  110. package/src/application-control/types.ts +1 -0
  111. package/src/application-control/utils.ts +12 -27
  112. package/src/cloud-connection/device-agent-cloud-connection.ts +280 -13
  113. package/src/docker/docker-cmd.ts +1 -1
  114. package/src/docker/docker-compose-cmd.ts +1 -1
  115. package/src/environment.ts +2 -0
  116. package/src/index.ts +10 -7
  117. package/src/infrastructure/agent-config.test.ts +143 -0
  118. package/src/infrastructure/agent-config.ts +217 -0
  119. package/src/infrastructure/certificates-and-tokens.ts +71 -0
  120. package/src/{util → infrastructure}/urls.ts +1 -1
  121. package/src/root.ts +3 -3
  122. package/src/subcommands/app/app.ts +135 -62
  123. package/src/subcommands/app/index.ts +11 -1
  124. package/src/subcommands/device/device.ts +63 -0
  125. package/src/subcommands/device/index.ts +8 -0
  126. package/src/subcommands/get-model-package.ts +60 -0
  127. package/src/subcommands/index.ts +5 -5
  128. package/src/subcommands/login.ts +11 -4
  129. package/src/util/copy-dir.ts +1 -1
  130. package/src/util/directories.ts +12 -17
  131. package/src/util/get-device-id.ts +22 -0
  132. package/lib/application-control/application-control.d.ts +0 -46
  133. package/lib/application-control/application-control.d.ts.map +0 -1
  134. package/lib/application-control/application-control.js +0 -234
  135. package/lib/application-control/application-control.js.map +0 -1
  136. package/lib/constants.d.ts +0 -17
  137. package/lib/constants.d.ts.map +0 -1
  138. package/lib/constants.js +0 -24
  139. package/lib/constants.js.map +0 -1
  140. package/lib/subcommands/device-control.d.ts +0 -2
  141. package/lib/subcommands/device-control.d.ts.map +0 -1
  142. package/lib/subcommands/device-control.js +0 -19
  143. package/lib/subcommands/device-control.js.map +0 -1
  144. package/lib/subcommands/test-app.d.ts +0 -2
  145. package/lib/subcommands/test-app.d.ts.map +0 -1
  146. package/lib/subcommands/test-app.js +0 -29
  147. package/lib/subcommands/test-app.js.map +0 -1
  148. package/lib/util/spawner/gnu-spawner.d.ts +0 -9
  149. package/lib/util/spawner/gnu-spawner.d.ts.map +0 -1
  150. package/lib/util/spawner/gnu-spawner.js +0 -102
  151. package/lib/util/spawner/gnu-spawner.js.map +0 -1
  152. package/lib/util/spawner/js-spawner.d.ts +0 -5
  153. package/lib/util/spawner/js-spawner.d.ts.map +0 -1
  154. package/lib/util/spawner/js-spawner.js +0 -89
  155. package/lib/util/spawner/js-spawner.js.map +0 -1
  156. package/lib/util/spawner/types.d.ts +0 -28
  157. package/lib/util/spawner/types.d.ts.map +0 -1
  158. package/lib/util/spawner-base/index.d.ts +0 -17
  159. package/lib/util/spawner-base/index.d.ts.map +0 -1
  160. package/lib/util/spawner-base/index.js +0 -30
  161. package/lib/util/spawner-base/index.js.map +0 -1
  162. package/lib/util/spawner-base/run-foreground-sync.d.ts +0 -3
  163. package/lib/util/spawner-base/run-foreground-sync.d.ts.map +0 -1
  164. package/lib/util/spawner-base/run-foreground-sync.js +0 -18
  165. package/lib/util/spawner-base/run-foreground-sync.js.map +0 -1
  166. package/lib/util/spawner-base/run-foreground.d.ts +0 -3
  167. package/lib/util/spawner-base/run-foreground.d.ts.map +0 -1
  168. package/lib/util/spawner-base/run-foreground.js +0 -49
  169. package/lib/util/spawner-base/run-foreground.js.map +0 -1
  170. package/lib/util/spawner-base/run-streaming.d.ts +0 -4
  171. package/lib/util/spawner-base/run-streaming.d.ts.map +0 -1
  172. package/lib/util/spawner-base/run-streaming.js +0 -35
  173. package/lib/util/spawner-base/run-streaming.js.map +0 -1
  174. package/lib/util/spawner-base/run.d.ts +0 -4
  175. package/lib/util/spawner-base/run.d.ts.map +0 -1
  176. package/lib/util/spawner-base/run.js +0 -56
  177. package/lib/util/spawner-base/run.js.map +0 -1
  178. package/lib/util/urls.d.ts.map +0 -1
  179. package/lib/util/urls.js.map +0 -1
  180. package/lib/web/index.html +0 -229
  181. package/lib/web/static/Karla.css +0 -18
  182. package/lib/web/static/bootstrap-4.3.1.min.css +0 -7
  183. package/lib/web/static/bootstrap-4.3.1.min.js +0 -7
  184. package/lib/web/static/favicon.ico +0 -0
  185. package/lib/web/static/jquery-3.3.1.slim.min.js +0 -2
  186. package/lib/web/static/popper-1.14.7.min.js +0 -5
  187. package/lib/web/web-interface.d.ts +0 -2
  188. package/lib/web/web-interface.d.ts.map +0 -1
  189. package/lib/web/web-interface.js +0 -74
  190. package/lib/web/web-interface.js.map +0 -1
  191. package/src/application-control/application-control.ts +0 -273
  192. package/src/constants.ts +0 -32
  193. package/src/subcommands/device-control.ts +0 -16
  194. package/src/subcommands/test-app.ts +0 -30
  195. package/src/util/spawner/gnu-spawner.ts +0 -114
  196. package/src/util/spawner/js-spawner.ts +0 -110
  197. package/src/util/spawner/types.ts +0 -28
  198. package/src/util/spawner-base/index.ts +0 -28
  199. package/src/util/spawner-base/run-foreground-sync.ts +0 -16
  200. package/src/util/spawner-base/run-foreground.ts +0 -49
  201. package/src/util/spawner-base/run-streaming.ts +0 -40
  202. package/src/util/spawner-base/run.ts +0 -60
  203. package/src/web/index.html +0 -229
  204. package/src/web/static/Karla.css +0 -18
  205. package/src/web/static/bootstrap-4.3.1.min.css +0 -7
  206. package/src/web/static/bootstrap-4.3.1.min.js +0 -7
  207. package/src/web/static/favicon.ico +0 -0
  208. package/src/web/static/jquery-3.3.1.slim.min.js +0 -2
  209. package/src/web/static/popper-1.14.7.min.js +0 -5
  210. package/src/web/web-interface.ts +0 -89
package/readme.md CHANGED
@@ -1,115 +1,219 @@
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
9
22
 
10
- ## Lint and Build
23
+ ### Provision the device
24
+
25
+ If you're system already meets all the system requirements, you can simply install the device agent via `npm`:
11
26
 
12
27
  ```
13
- $ npm run lint:fix
14
- $ npm run build
28
+ sudo npm install -g @alwaysai/device-agent@latest
15
29
  ```
16
30
 
17
- ## Usage
31
+ Otherwise, you can run the provision script to install the agent and its dependencies:
18
32
 
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.
33
+ ```
34
+ curl -fsSL https://alwaysai-artifacts-prod.s3.us-west-1.amazonaws.com/device-agent/install-device-agent.sh | sudo -E bash -
35
+ ```
36
+
37
+ The Device Agent will be available in the terminal as `aai-agent`.
38
+
39
+ Next, log in to the Device Agent with the following command:
20
40
 
21
- To enable the cloud IoT Core connection, set the following environment variable:
22
41
  ```
23
- export ALWAYSAI_DEVICE_AGENT_MODE="cloud"
42
+ aai-agent login --email <username> --password <password> [--device <device_id>]
24
43
  ```
25
44
 
26
- To enable the device-hosted web dashboard, set the following environment variable:
45
+ Note that if you already have a device registered with the alwaysAI cloud, you
46
+ can optionally provide that device ID in the login command. If you don't yet
47
+ have a device configured, follow the next step to initialize the device.
48
+
49
+ ### Initialize the alwaysAI device
50
+
51
+ After logging in, run the following command to initialize the current device as
52
+ an alwaysAI device:
53
+
27
54
  ```
28
- export ALWAYSAI_DEVICE_AGENT_MODE="web"
55
+ $ aai-agent device init --name <name> [--description <description>]
29
56
  ```
30
57
 
31
- Any other value will run the CLI.
58
+ For example:
32
59
 
33
- On Mac you need to override the OS to remove some usage restrictions:
34
60
  ```
35
- export ALWAYSAI_OS_PLATFORM="linux"
61
+ $ aai-agent device init --name test-dev-1 --description "Device for testing alwaysAI Device Agent"
36
62
  ```
37
63
 
38
- ### CLI
64
+ ## Run an alwaysAI application on your device
65
+
66
+ ### Publish an application release and install on device
67
+
68
+ First, you must publish your application to the alwaysAI cloud. From the root
69
+ of your application directory (where the `alwaysai.app.json` file is) on your
70
+ development host:
39
71
 
40
- Here are the available commands:
41
72
  ```
42
- $ node lib --help
43
- Starting alwaysAI Device Agent
44
- Usage: aai-agent <subcommand> ...
73
+ aai app configure
74
+ aai app publish
75
+ ```
45
76
 
46
- Manage your alwaysAI production device
77
+ The output of the final command will give you the release hash that was
78
+ published. You can run `aai release list --project <project_id>` to list all
79
+ release versions for the project, where project ID can be found in the
80
+ `alwaysai.project.json` file.
47
81
 
48
- Subcommands:
82
+ Now you can install the application on the device using the device agent. Run
83
+ the following on the device where the Device Agent is installed:
49
84
 
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
58
85
  ```
86
+ aai-agent app install --project <project_id> [--release <release_hash>]
87
+ ```
88
+
89
+ ### Control the application
59
90
 
60
- To install a test app, run:
91
+ Run the following commands on the device where the Device Agent is installed:
92
+
93
+ Get application status:
61
94
  ```
62
- $ node lib install-test-app
95
+ aai-agent app status --project <project_id>
63
96
  ```
64
97
 
65
- The test app has the project ID 16ad11bb-8c3a-4a15-923e-49f7a8311dc6.
98
+ Start the application:
99
+ ```
100
+ aai-agent app start --project <project_id>
101
+ ```
66
102
 
67
- Now you can start, check the status, and stop using the commands:
103
+ Show the application logs:
68
104
  ```
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
- }
105
+ aai-agent app logs --project <project_id>
106
+ ```
107
+
108
+ Stop the application:
109
+ ```
110
+ aai-agent app stop --project <project_id>
111
+ ```
112
+
113
+ Uninstall the application:
114
+ ```
115
+ aai-agent app uninstall --project <project_id>
116
+ ```
117
+
118
+ ### Manage application models
119
+
120
+ There are several ways to manage the models for your application.
121
+
122
+ #### Update models to new version of the same model ID
123
+
124
+ If a new version of a model is published for an existing model ID, and an
125
+ application is already configured to be using that model ID, updating to the
126
+ new model can simply be done with:
127
+
128
+ ```
129
+ aai-agent app stop --project <project_id>
130
+ aai-agent app update-models --project <project_id>
131
+ aai-agent app start --project <project_id>
132
+ ```
133
+
134
+ #### Replace models for an application for new ID
135
+
136
+ If you'd like to install an entirely new model to an application, replacing the
137
+ model the app was originally configured with, run the following command:
138
+
139
+ ```
140
+ aai-agent app stop --project <project_id>
141
+ aai-agent app replace-models --project <project_id> --models <model_id_1> [<model_id_2> ...]
142
+ aai-agent app start --project <project_id>
143
+ ```
144
+
145
+ If you plan on using this method, you can make your application source model ID
146
+ agnostic by providing the model ID to edgeIQ in the following way:
147
+
148
+ Select the first model in the config list:
149
+ ```
150
+ obj_detect = edgeiq.ObjectDetection(edgeiq._globals.MODEL_ID_LIST[0])
151
+ ```
152
+
153
+ #### Manually download a model package
154
+
155
+ To download a model package from the alwaysAI cloud and unpack to a specific directory, run:
156
+
157
+ ```
158
+ aai-agent get-model-package <model ID> [--path <destination path>]
99
159
  ```
100
160
 
101
- ### Web Dashboard
161
+ For example, to download `alwaysai/yolo_v3` to `~/models` run:
162
+
163
+ ```
164
+ aai-agent get-model-package alwaysai/yolo_v3 --path ~/models
165
+ ```
102
166
 
103
- You can start the web dashboard by running:
167
+ Once the command completes, you'll see the model package in the
168
+ `~/models/alwaysai/yolo_v3` directory.
169
+
170
+ ### Set and update environment variables
171
+
172
+ The Device Agent enables you to set and update environment variables for all
173
+ services or a single service of you application.
104
174
 
105
175
  ```
106
- node lib
176
+ aai-agent app set-env <key=val> --project <project_id> [--service <service>]
107
177
  ```
108
178
 
109
- And clicking the link that comes up.
179
+ For example, to set the following environment variable for the `alwaysai`
180
+ service run the following command on the device:
110
181
 
111
- If you are developing new functionality for the cli in tandem with the device agent, you can run
112
182
  ```
113
- npm i --save /path/to/your/cli/repo
183
+ aai-agent app set-env TEST_ENV=1 --project <project_id> --service alwaysai
114
184
  ```
115
- Remember to not commit the package.json, or revert it when you are done developing a feature
185
+
186
+ ## Device Agent Commands
187
+
188
+ ```
189
+ $ aai-agent --help
190
+ Starting alwaysAI Device Agent
191
+ Usage: aai-agent <subcommand> ...
192
+
193
+ Manage your alwaysAI production device
194
+
195
+ Subcommands:
196
+
197
+ login : Login to alwaysAI (this is meant for scripted environments)
198
+ app list : List all installed apps
199
+ app list-releases : List all releases for a given app
200
+ app list-latest-release : List the latest release hash for a given app
201
+ app install : Install an alwaysAI app from a project
202
+ app status : Get the status of an installed alwaysAI app
203
+ app start : Start an installed alwaysAI app
204
+ app stop : Stop a running alwaysAI app
205
+ app restart : Restart running alwaysAI app
206
+ app logs : Get logs for an application
207
+ app uninstall : Remove an alwaysAI app
208
+ app rollback : Rollback an alwaysAI app to the previous version
209
+ app show-models : Show the application models
210
+ app add-model : Add a model to an alwaysAI app
211
+ app remove-model : Remove a model from an alwaysAI app
212
+ app replace-models : Replace all models of an alwaysAI app with new models
213
+ app update-models : Update all models for an alwaysAI app
214
+ app get-all-envs : Get environment variables for an application
215
+ app set-env : Set environment variables for a service
216
+ device init : Initialize device
217
+ device get-info : Get device info
218
+ get-model-package : Download and unpack a model package
219
+ ```
@@ -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
+ }
@@ -1,12 +1,36 @@
1
+ import { JsSpawner } from 'alwaysai/lib/util';
2
+ import { existsSync } from 'fs';
3
+ import { join } from 'path';
4
+
1
5
  import { runCliCmd } from '../util/run-cli-cmd';
2
- import { buildApp, getAppDir, isAppInstalled } from './utils';
6
+ import { ModelDetails } from './types';
7
+ import { buildApp, getAppDir, requireAppInstalled } from './utils';
8
+
9
+ export async function getAppModels(props: { projectId: string }) {
10
+ const { projectId } = props;
11
+ await requireAppInstalled({ projectId });
12
+
13
+ const appDir = getAppDir(projectId);
14
+ const appCfgPath = join(appDir, 'alwaysai.app.json');
15
+ if (!existsSync(appCfgPath)) {
16
+ throw new Error('Application config not found!');
17
+ }
18
+ const appCfg = JSON.parse(await JsSpawner().readFile(appCfgPath));
19
+ const modelDetails: ModelDetails[] = [];
20
+ if (!('models' in appCfg)) {
21
+ return modelDetails;
22
+ }
23
+ for (const model in appCfg['models']) {
24
+ modelDetails.push({ modelId: model, version: appCfg['models'][model] });
25
+ }
26
+ return modelDetails;
27
+ }
3
28
 
4
29
  export async function addModel(props: { projectId: string; modelId: string }) {
5
30
  const { projectId, modelId } = props;
31
+ await requireAppInstalled({ projectId });
32
+
6
33
  const appDir = getAppDir(projectId);
7
- if (!(await isAppInstalled({ appDir }))) {
8
- throw new Error('Application is not installed');
9
- }
10
34
  await runCliCmd({
11
35
  cmd: ['app', 'models', 'add', modelId],
12
36
  cwd: appDir,
@@ -16,22 +40,38 @@ export async function addModel(props: { projectId: string; modelId: string }) {
16
40
 
17
41
  export async function removeModel(props: { projectId: string; modelId: string }) {
18
42
  const { projectId, modelId } = props;
43
+ await requireAppInstalled({ projectId });
44
+
45
+ const appDir = getAppDir(projectId);
46
+ await runCliCmd({
47
+ cmd: ['app', 'models', 'remove', modelId, '--purge'],
48
+ cwd: appDir,
49
+ });
50
+ }
51
+
52
+ export async function replaceModels(props: { projectId: string; modelIds: string[] }) {
53
+ const { projectId, modelIds } = props;
54
+ await requireAppInstalled({ projectId });
55
+
19
56
  const appDir = getAppDir(projectId);
20
- if (!(await isAppInstalled({ appDir }))) {
21
- throw new Error('Application is not installed');
22
- }
23
57
  await runCliCmd({
24
- cmd: ['app', 'models', 'remove', modelId],
58
+ cmd: ['app', 'models', 'remove-all', '--purge'],
25
59
  cwd: appDir,
26
60
  });
61
+ for (const modelId of modelIds) {
62
+ await runCliCmd({
63
+ cmd: ['app', 'models', 'add', modelId],
64
+ cwd: appDir,
65
+ });
66
+ }
67
+ await buildApp({ appDir });
27
68
  }
28
69
 
29
70
  export async function updateModels(props: { projectId: string }) {
30
71
  const { projectId } = props;
72
+ await requireAppInstalled({ projectId });
73
+
31
74
  const appDir = getAppDir(projectId);
32
- if (!(await isAppInstalled({ appDir }))) {
33
- throw new Error('Application is not installed');
34
- }
35
75
  await runCliCmd({
36
76
  cmd: ['app', 'models', 'update'],
37
77
  cwd: appDir,