@alwaysai/device-agent 0.0.3 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/application-control/backup.d.ts +8 -0
- package/lib/application-control/backup.d.ts.map +1 -0
- package/lib/application-control/backup.js +34 -0
- package/lib/application-control/backup.js.map +1 -0
- package/lib/application-control/environment-variables.d.ts +9 -0
- package/lib/application-control/environment-variables.d.ts.map +1 -0
- package/lib/application-control/environment-variables.js +82 -0
- package/lib/application-control/environment-variables.js.map +1 -0
- package/lib/application-control/index.d.ts +9 -0
- package/lib/application-control/index.d.ts.map +1 -0
- package/lib/application-control/index.js +27 -0
- package/lib/application-control/index.js.map +1 -0
- package/lib/application-control/install.d.ts +16 -0
- package/lib/application-control/install.d.ts.map +1 -0
- package/lib/application-control/install.js +117 -0
- package/lib/application-control/install.js.map +1 -0
- package/lib/application-control/models.d.ts +8 -0
- package/lib/application-control/models.d.ts.map +1 -1
- package/lib/application-control/models.js +44 -11
- package/lib/application-control/models.js.map +1 -1
- package/lib/application-control/status.d.ts +26 -0
- package/lib/application-control/status.d.ts.map +1 -0
- package/lib/application-control/status.js +138 -0
- package/lib/application-control/status.js.map +1 -0
- package/lib/application-control/types.d.ts +5 -0
- package/lib/application-control/types.d.ts.map +1 -0
- package/lib/{util/spawner → application-control}/types.js +0 -0
- package/lib/{util/spawner → application-control}/types.js.map +1 -1
- package/lib/application-control/utils.d.ts +2 -9
- package/lib/application-control/utils.d.ts.map +1 -1
- package/lib/application-control/utils.js +14 -29
- package/lib/application-control/utils.js.map +1 -1
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts +16 -1
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts.map +1 -1
- package/lib/cloud-connection/device-agent-cloud-connection.js +239 -9
- package/lib/cloud-connection/device-agent-cloud-connection.js.map +1 -1
- package/lib/docker/docker-cmd.js +2 -2
- package/lib/docker/docker-cmd.js.map +1 -1
- package/lib/docker/docker-compose-cmd.js +2 -2
- package/lib/docker/docker-compose-cmd.js.map +1 -1
- package/lib/environment.d.ts +2 -0
- package/lib/environment.d.ts.map +1 -1
- package/lib/environment.js +3 -1
- package/lib/environment.js.map +1 -1
- package/lib/index.js +10 -8
- package/lib/index.js.map +1 -1
- package/lib/infrastructure/agent-config.d.ts +73 -0
- package/lib/infrastructure/agent-config.d.ts.map +1 -0
- package/lib/infrastructure/agent-config.js +186 -0
- package/lib/infrastructure/agent-config.js.map +1 -0
- package/lib/infrastructure/agent-config.test.d.ts +2 -0
- package/lib/infrastructure/agent-config.test.d.ts.map +1 -0
- package/lib/infrastructure/agent-config.test.js +135 -0
- package/lib/infrastructure/agent-config.test.js.map +1 -0
- package/lib/infrastructure/certificates-and-tokens.d.ts +6 -0
- package/lib/infrastructure/certificates-and-tokens.d.ts.map +1 -0
- package/lib/infrastructure/certificates-and-tokens.js +69 -0
- package/lib/infrastructure/certificates-and-tokens.js.map +1 -0
- package/lib/{util → infrastructure}/urls.d.ts +0 -0
- package/lib/infrastructure/urls.d.ts.map +1 -0
- package/lib/{util → infrastructure}/urls.js +3 -3
- package/lib/infrastructure/urls.js.map +1 -0
- package/lib/root.js +3 -3
- package/lib/root.js.map +1 -1
- package/lib/subcommands/app/app.d.ts +31 -14
- package/lib/subcommands/app/app.d.ts.map +1 -1
- package/lib/subcommands/app/app.js +117 -62
- package/lib/subcommands/app/app.js.map +1 -1
- package/lib/subcommands/app/index.d.ts.map +1 -1
- package/lib/subcommands/app/index.js +6 -1
- package/lib/subcommands/app/index.js.map +1 -1
- package/lib/subcommands/device/device.d.ts +6 -0
- package/lib/subcommands/device/device.d.ts.map +1 -0
- package/lib/subcommands/device/device.js +62 -0
- package/lib/subcommands/device/device.js.map +1 -0
- package/lib/subcommands/device/index.d.ts +2 -0
- package/lib/subcommands/device/index.d.ts.map +1 -0
- package/lib/subcommands/device/index.js +11 -0
- package/lib/subcommands/device/index.js.map +1 -0
- package/lib/subcommands/get-model-package.d.ts +5 -0
- package/lib/subcommands/get-model-package.d.ts.map +1 -0
- package/lib/subcommands/get-model-package.js +51 -0
- package/lib/subcommands/get-model-package.js.map +1 -0
- package/lib/subcommands/index.d.ts +8 -1
- package/lib/subcommands/index.d.ts.map +1 -1
- package/lib/subcommands/index.js +6 -6
- package/lib/subcommands/index.js.map +1 -1
- package/lib/subcommands/login.d.ts +3 -2
- package/lib/subcommands/login.d.ts.map +1 -1
- package/lib/subcommands/login.js +11 -4
- package/lib/subcommands/login.js.map +1 -1
- package/lib/util/copy-dir.js +3 -3
- package/lib/util/copy-dir.js.map +1 -1
- package/lib/util/directories.d.ts +1 -1
- package/lib/util/directories.d.ts.map +1 -1
- package/lib/util/directories.js +9 -14
- package/lib/util/directories.js.map +1 -1
- package/lib/util/get-device-id.d.ts +2 -0
- package/lib/util/get-device-id.d.ts.map +1 -0
- package/lib/util/get-device-id.js +24 -0
- package/lib/util/get-device-id.js.map +1 -0
- package/package.json +19 -14
- package/readme.md +176 -72
- package/src/application-control/backup.ts +32 -0
- package/src/application-control/environment-variables.ts +81 -0
- package/src/application-control/index.ts +40 -0
- package/src/application-control/install.ts +126 -0
- package/src/application-control/models.ts +51 -11
- package/src/application-control/status.ts +156 -0
- package/src/application-control/types.ts +1 -0
- package/src/application-control/utils.ts +12 -27
- package/src/cloud-connection/device-agent-cloud-connection.ts +280 -13
- package/src/docker/docker-cmd.ts +1 -1
- package/src/docker/docker-compose-cmd.ts +1 -1
- package/src/environment.ts +2 -0
- package/src/index.ts +10 -7
- package/src/infrastructure/agent-config.test.ts +143 -0
- package/src/infrastructure/agent-config.ts +217 -0
- package/src/infrastructure/certificates-and-tokens.ts +71 -0
- package/src/{util → infrastructure}/urls.ts +1 -1
- package/src/root.ts +3 -3
- package/src/subcommands/app/app.ts +135 -62
- package/src/subcommands/app/index.ts +11 -1
- package/src/subcommands/device/device.ts +63 -0
- package/src/subcommands/device/index.ts +8 -0
- package/src/subcommands/get-model-package.ts +60 -0
- package/src/subcommands/index.ts +5 -5
- package/src/subcommands/login.ts +11 -4
- package/src/util/copy-dir.ts +1 -1
- package/src/util/directories.ts +12 -17
- package/src/util/get-device-id.ts +22 -0
- package/lib/application-control/application-control.d.ts +0 -46
- package/lib/application-control/application-control.d.ts.map +0 -1
- package/lib/application-control/application-control.js +0 -234
- package/lib/application-control/application-control.js.map +0 -1
- package/lib/constants.d.ts +0 -17
- package/lib/constants.d.ts.map +0 -1
- package/lib/constants.js +0 -24
- package/lib/constants.js.map +0 -1
- package/lib/subcommands/device-control.d.ts +0 -2
- package/lib/subcommands/device-control.d.ts.map +0 -1
- package/lib/subcommands/device-control.js +0 -19
- package/lib/subcommands/device-control.js.map +0 -1
- package/lib/subcommands/test-app.d.ts +0 -2
- package/lib/subcommands/test-app.d.ts.map +0 -1
- package/lib/subcommands/test-app.js +0 -29
- package/lib/subcommands/test-app.js.map +0 -1
- package/lib/util/spawner/gnu-spawner.d.ts +0 -9
- package/lib/util/spawner/gnu-spawner.d.ts.map +0 -1
- package/lib/util/spawner/gnu-spawner.js +0 -102
- package/lib/util/spawner/gnu-spawner.js.map +0 -1
- package/lib/util/spawner/js-spawner.d.ts +0 -5
- package/lib/util/spawner/js-spawner.d.ts.map +0 -1
- package/lib/util/spawner/js-spawner.js +0 -89
- package/lib/util/spawner/js-spawner.js.map +0 -1
- package/lib/util/spawner/types.d.ts +0 -28
- package/lib/util/spawner/types.d.ts.map +0 -1
- package/lib/util/spawner-base/index.d.ts +0 -17
- package/lib/util/spawner-base/index.d.ts.map +0 -1
- package/lib/util/spawner-base/index.js +0 -30
- package/lib/util/spawner-base/index.js.map +0 -1
- package/lib/util/spawner-base/run-foreground-sync.d.ts +0 -3
- package/lib/util/spawner-base/run-foreground-sync.d.ts.map +0 -1
- package/lib/util/spawner-base/run-foreground-sync.js +0 -18
- package/lib/util/spawner-base/run-foreground-sync.js.map +0 -1
- package/lib/util/spawner-base/run-foreground.d.ts +0 -3
- package/lib/util/spawner-base/run-foreground.d.ts.map +0 -1
- package/lib/util/spawner-base/run-foreground.js +0 -49
- package/lib/util/spawner-base/run-foreground.js.map +0 -1
- package/lib/util/spawner-base/run-streaming.d.ts +0 -4
- package/lib/util/spawner-base/run-streaming.d.ts.map +0 -1
- package/lib/util/spawner-base/run-streaming.js +0 -35
- package/lib/util/spawner-base/run-streaming.js.map +0 -1
- package/lib/util/spawner-base/run.d.ts +0 -4
- package/lib/util/spawner-base/run.d.ts.map +0 -1
- package/lib/util/spawner-base/run.js +0 -56
- package/lib/util/spawner-base/run.js.map +0 -1
- package/lib/util/urls.d.ts.map +0 -1
- package/lib/util/urls.js.map +0 -1
- package/lib/web/index.html +0 -229
- package/lib/web/static/Karla.css +0 -18
- package/lib/web/static/bootstrap-4.3.1.min.css +0 -7
- package/lib/web/static/bootstrap-4.3.1.min.js +0 -7
- package/lib/web/static/favicon.ico +0 -0
- package/lib/web/static/jquery-3.3.1.slim.min.js +0 -2
- package/lib/web/static/popper-1.14.7.min.js +0 -5
- package/lib/web/web-interface.d.ts +0 -2
- package/lib/web/web-interface.d.ts.map +0 -1
- package/lib/web/web-interface.js +0 -74
- package/lib/web/web-interface.js.map +0 -1
- package/src/application-control/application-control.ts +0 -273
- package/src/constants.ts +0 -32
- package/src/subcommands/device-control.ts +0 -16
- package/src/subcommands/test-app.ts +0 -30
- package/src/util/spawner/gnu-spawner.ts +0 -114
- package/src/util/spawner/js-spawner.ts +0 -110
- package/src/util/spawner/types.ts +0 -28
- package/src/util/spawner-base/index.ts +0 -28
- package/src/util/spawner-base/run-foreground-sync.ts +0 -16
- package/src/util/spawner-base/run-foreground.ts +0 -49
- package/src/util/spawner-base/run-streaming.ts +0 -40
- package/src/util/spawner-base/run.ts +0 -60
- package/src/web/index.html +0 -229
- package/src/web/static/Karla.css +0 -18
- package/src/web/static/bootstrap-4.3.1.min.css +0 -7
- package/src/web/static/bootstrap-4.3.1.min.js +0 -7
- package/src/web/static/favicon.ico +0 -0
- package/src/web/static/jquery-3.3.1.slim.min.js +0 -2
- package/src/web/static/popper-1.14.7.min.js +0 -5
- package/src/web/web-interface.ts +0 -89
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
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
|
-
|
|
14
|
-
$ npm run build
|
|
28
|
+
sudo npm install -g @alwaysai/device-agent@latest
|
|
15
29
|
```
|
|
16
30
|
|
|
17
|
-
|
|
31
|
+
Otherwise, you can run the provision script to install the agent and its dependencies:
|
|
18
32
|
|
|
19
|
-
|
|
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
|
-
|
|
42
|
+
aai-agent login --email <username> --password <password> [--device <device_id>]
|
|
24
43
|
```
|
|
25
44
|
|
|
26
|
-
|
|
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
|
-
|
|
55
|
+
$ aai-agent device init --name <name> [--description <description>]
|
|
29
56
|
```
|
|
30
57
|
|
|
31
|
-
|
|
58
|
+
For example:
|
|
32
59
|
|
|
33
|
-
On Mac you need to override the OS to remove some usage restrictions:
|
|
34
60
|
```
|
|
35
|
-
|
|
61
|
+
$ aai-agent device init --name test-dev-1 --description "Device for testing alwaysAI Device Agent"
|
|
36
62
|
```
|
|
37
63
|
|
|
38
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
73
|
+
aai app configure
|
|
74
|
+
aai app publish
|
|
75
|
+
```
|
|
45
76
|
|
|
46
|
-
|
|
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
|
-
|
|
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
|
-
|
|
91
|
+
Run the following commands on the device where the Device Agent is installed:
|
|
92
|
+
|
|
93
|
+
Get application status:
|
|
61
94
|
```
|
|
62
|
-
|
|
95
|
+
aai-agent app status --project <project_id>
|
|
63
96
|
```
|
|
64
97
|
|
|
65
|
-
|
|
98
|
+
Start the application:
|
|
99
|
+
```
|
|
100
|
+
aai-agent app start --project <project_id>
|
|
101
|
+
```
|
|
66
102
|
|
|
67
|
-
|
|
103
|
+
Show the application logs:
|
|
68
104
|
```
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
176
|
+
aai-agent app set-env <key=val> --project <project_id> [--service <service>]
|
|
107
177
|
```
|
|
108
178
|
|
|
109
|
-
|
|
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
|
-
|
|
183
|
+
aai-agent app set-env TEST_ENV=1 --project <project_id> --service alwaysai
|
|
114
184
|
```
|
|
115
|
-
|
|
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 {
|
|
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',
|
|
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,
|