goosebit 0.2.5__tar.gz → 0.2.6__tar.gz
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.
- goosebit-0.2.6/PKG-INFO +280 -0
- goosebit-0.2.6/README.md +246 -0
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/__init__.py +41 -7
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/api/telemetry/metrics.py +1 -5
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/api/v1/devices/device/responses.py +1 -0
- goosebit-0.2.6/goosebit/api/v1/devices/device/routes.py +33 -0
- goosebit-0.2.6/goosebit/api/v1/devices/requests.py +27 -0
- goosebit-0.2.6/goosebit/api/v1/devices/routes.py +111 -0
- goosebit-0.2.6/goosebit/api/v1/download/routes.py +33 -0
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/api/v1/rollouts/routes.py +5 -4
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/api/v1/routes.py +2 -1
- goosebit-0.2.6/goosebit/api/v1/settings/routes.py +14 -0
- goosebit-0.2.6/goosebit/api/v1/settings/users/requests.py +16 -0
- goosebit-0.2.6/goosebit/api/v1/settings/users/responses.py +7 -0
- goosebit-0.2.6/goosebit/api/v1/settings/users/routes.py +56 -0
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/api/v1/software/routes.py +18 -14
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/auth/__init__.py +49 -13
- goosebit-0.2.6/goosebit/auth/permissions.py +80 -0
- goosebit-0.2.6/goosebit/db/config.py +66 -0
- goosebit-0.2.6/goosebit/db/migrations/models/2_20241121113728_update.py +11 -0
- goosebit-0.2.6/goosebit/db/migrations/models/3_20241121140210_update.py +11 -0
- goosebit-0.2.6/goosebit/db/migrations/models/4_20250324110331_update.py +16 -0
- goosebit-0.2.6/goosebit/db/migrations/models/4_20250402085235_rename_uuid_to_id.py +11 -0
- goosebit-0.2.6/goosebit/db/migrations/models/5_20250619090242_null_feed.py +83 -0
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/db/models.py +19 -8
- goosebit-0.2.6/goosebit/db/pg_ssl_context.py +51 -0
- goosebit-0.2.6/goosebit/device_manager.py +262 -0
- goosebit-0.2.6/goosebit/plugins/__init__.py +32 -0
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/schema/devices.py +8 -5
- goosebit-0.2.6/goosebit/schema/plugins.py +67 -0
- goosebit-0.2.6/goosebit/schema/updates.py +15 -0
- goosebit-0.2.6/goosebit/schema/users.py +9 -0
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/settings/__init__.py +0 -3
- goosebit-0.2.6/goosebit/settings/schema.py +134 -0
- goosebit-0.2.6/goosebit/storage/__init__.py +62 -0
- goosebit-0.2.6/goosebit/storage/base.py +14 -0
- goosebit-0.2.6/goosebit/storage/filesystem.py +111 -0
- goosebit-0.2.6/goosebit/storage/s3.py +104 -0
- goosebit-0.2.6/goosebit/ui/bff/common/columns.py +50 -0
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/ui/bff/common/responses.py +1 -0
- goosebit-0.2.6/goosebit/ui/bff/devices/device/routes.py +17 -0
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/ui/bff/devices/requests.py +1 -0
- goosebit-0.2.6/goosebit/ui/bff/devices/routes.py +129 -0
- goosebit-0.2.6/goosebit/ui/bff/download/__init__.py +1 -0
- goosebit-0.2.6/goosebit/ui/bff/download/routes.py +33 -0
- goosebit-0.2.6/goosebit/ui/bff/rollouts/routes.py +82 -0
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/ui/bff/routes.py +2 -1
- goosebit-0.2.6/goosebit/ui/bff/settings/routes.py +20 -0
- goosebit-0.2.6/goosebit/ui/bff/settings/users/responses.py +33 -0
- goosebit-0.2.6/goosebit/ui/bff/settings/users/routes.py +80 -0
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/ui/bff/software/routes.py +40 -12
- goosebit-0.2.6/goosebit/ui/nav.py +26 -0
- goosebit-0.2.6/goosebit/ui/routes.py +108 -0
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/ui/static/js/devices.js +32 -24
- goosebit-0.2.6/goosebit/ui/static/js/login.js +39 -0
- goosebit-0.2.6/goosebit/ui/static/js/logs.js +10 -0
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/ui/static/js/rollouts.js +31 -30
- goosebit-0.2.6/goosebit/ui/static/js/settings.js +322 -0
- goosebit-0.2.6/goosebit/ui/static/js/setup.js +28 -0
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/ui/static/js/software.js +127 -121
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/ui/static/js/util.js +25 -4
- goosebit-0.2.6/goosebit/ui/templates/__init__.py +22 -0
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/ui/templates/login.html.jinja +5 -0
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/ui/templates/nav.html.jinja +13 -5
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/ui/templates/rollouts.html.jinja +4 -22
- goosebit-0.2.6/goosebit/ui/templates/settings.html.jinja +88 -0
- goosebit-0.2.6/goosebit/ui/templates/setup.html.jinja +71 -0
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/ui/templates/software.html.jinja +0 -11
- goosebit-0.2.6/goosebit/updater/__init__.py +1 -0
- goosebit-0.2.6/goosebit/updater/controller/__init__.py +1 -0
- goosebit-0.2.6/goosebit/updater/controller/v1/__init__.py +1 -0
- goosebit-0.2.6/goosebit/updater/controller/v1/routes.py +237 -0
- goosebit-0.2.6/goosebit/updater/routes.py +100 -0
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/updates/__init__.py +24 -31
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/updates/swdesc.py +15 -8
- goosebit-0.2.6/goosebit/users/__init__.py +63 -0
- goosebit-0.2.6/goosebit/util/__init__.py +0 -0
- goosebit-0.2.6/goosebit/util/path.py +42 -0
- goosebit-0.2.6/goosebit/util/version.py +92 -0
- goosebit-0.2.6/pyproject.toml +84 -0
- goosebit-0.2.5/PKG-INFO +0 -189
- goosebit-0.2.5/README.md +0 -156
- goosebit-0.2.5/goosebit/api/v1/devices/device/routes.py +0 -33
- goosebit-0.2.5/goosebit/api/v1/devices/requests.py +0 -7
- goosebit-0.2.5/goosebit/api/v1/devices/routes.py +0 -51
- goosebit-0.2.5/goosebit/api/v1/download/routes.py +0 -22
- goosebit-0.2.5/goosebit/db/config.py +0 -10
- goosebit-0.2.5/goosebit/realtime/logs.py +0 -42
- goosebit-0.2.5/goosebit/realtime/routes.py +0 -13
- goosebit-0.2.5/goosebit/settings/schema.py +0 -88
- goosebit-0.2.5/goosebit/ui/bff/devices/routes.py +0 -126
- goosebit-0.2.5/goosebit/ui/bff/download/routes.py +0 -22
- goosebit-0.2.5/goosebit/ui/bff/rollouts/routes.py +0 -54
- goosebit-0.2.5/goosebit/ui/nav.py +0 -16
- goosebit-0.2.5/goosebit/ui/routes.py +0 -55
- goosebit-0.2.5/goosebit/ui/static/js/login.js +0 -23
- goosebit-0.2.5/goosebit/ui/static/js/logs.js +0 -25
- goosebit-0.2.5/goosebit/ui/templates/__init__.py +0 -13
- goosebit-0.2.5/goosebit/updater/controller/v1/routes.py +0 -195
- goosebit-0.2.5/goosebit/updater/manager.py +0 -325
- goosebit-0.2.5/goosebit/updater/routes.py +0 -25
- goosebit-0.2.5/pyproject.toml +0 -74
- {goosebit-0.2.5 → goosebit-0.2.6}/LICENSE +0 -0
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/__main__.py +0 -0
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/api/__init__.py +0 -0
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/api/responses.py +0 -0
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/api/routes.py +0 -0
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/api/telemetry/__init__.py +0 -0
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/api/telemetry/prometheus/__init__.py +0 -0
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/api/telemetry/prometheus/readers.py +0 -0
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/api/telemetry/prometheus/routes.py +0 -0
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/api/telemetry/routes.py +0 -0
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/api/v1/__init__.py +0 -0
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/api/v1/devices/__init__.py +0 -0
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/api/v1/devices/device/__init__.py +0 -0
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/api/v1/devices/responses.py +0 -0
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/api/v1/download/__init__.py +0 -0
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/api/v1/rollouts/__init__.py +0 -0
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/api/v1/rollouts/requests.py +0 -0
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/api/v1/rollouts/responses.py +0 -0
- {goosebit-0.2.5/goosebit/api/v1/software → goosebit-0.2.6/goosebit/api/v1/settings}/__init__.py +0 -0
- {goosebit-0.2.5/goosebit/realtime → goosebit-0.2.6/goosebit/api/v1/settings/users}/__init__.py +0 -0
- {goosebit-0.2.5/goosebit/ui → goosebit-0.2.6/goosebit/api/v1/software}/__init__.py +0 -0
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/api/v1/software/requests.py +0 -0
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/api/v1/software/responses.py +0 -0
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/db/__init__.py +0 -0
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/db/migrations/models/0_20240830054046_init.py +0 -0
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/db/migrations/models/1_20241109151811_update.py +0 -0
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/schema/__init__.py +0 -0
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/schema/rollouts.py +0 -0
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/schema/software.py +0 -0
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/settings/const.py +0 -0
- {goosebit-0.2.5/goosebit/ui/bff → goosebit-0.2.6/goosebit/ui}/__init__.py +0 -0
- {goosebit-0.2.5/goosebit/ui/bff/devices → goosebit-0.2.6/goosebit/ui/bff}/__init__.py +0 -0
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/ui/bff/common/__init__.py +0 -0
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/ui/bff/common/requests.py +0 -0
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/ui/bff/common/util.py +0 -0
- {goosebit-0.2.5/goosebit/ui/bff/rollouts → goosebit-0.2.6/goosebit/ui/bff/devices}/__init__.py +0 -0
- {goosebit-0.2.5/goosebit/ui/bff/download → goosebit-0.2.6/goosebit/ui/bff/devices/device}/__init__.py +0 -0
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/ui/bff/devices/responses.py +0 -0
- {goosebit-0.2.5/goosebit/ui/bff/software → goosebit-0.2.6/goosebit/ui/bff/rollouts}/__init__.py +0 -0
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/ui/bff/rollouts/responses.py +0 -0
- {goosebit-0.2.5/goosebit/updater → goosebit-0.2.6/goosebit/ui/bff/settings}/__init__.py +0 -0
- {goosebit-0.2.5/goosebit/updater/controller → goosebit-0.2.6/goosebit/ui/bff/settings/users}/__init__.py +0 -0
- {goosebit-0.2.5/goosebit/updater/controller/v1 → goosebit-0.2.6/goosebit/ui/bff/software}/__init__.py +0 -0
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/ui/bff/software/responses.py +0 -0
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/ui/static/__init__.py +0 -0
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/ui/static/favicon.ico +0 -0
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/ui/static/favicon.svg +0 -0
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/ui/static/svg/goosebit-logo.svg +0 -0
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/ui/templates/devices.html.jinja +0 -0
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/ui/templates/logs.html.jinja +0 -0
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/updater/controller/routes.py +0 -0
- {goosebit-0.2.5 → goosebit-0.2.6}/goosebit/updater/controller/v1/schema.py +0 -0
goosebit-0.2.6/PKG-INFO
ADDED
@@ -0,0 +1,280 @@
|
|
1
|
+
Metadata-Version: 2.3
|
2
|
+
Name: goosebit
|
3
|
+
Version: 0.2.6
|
4
|
+
Summary:
|
5
|
+
Author: Brett Rowan
|
6
|
+
Author-email: 121075405+b-rowan@users.noreply.github.com
|
7
|
+
Requires-Python: >=3.11,<4.0
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
9
|
+
Classifier: Programming Language :: Python :: 3.11
|
10
|
+
Classifier: Programming Language :: Python :: 3.12
|
11
|
+
Classifier: Programming Language :: Python :: 3.13
|
12
|
+
Provides-Extra: postgresql
|
13
|
+
Requires-Dist: aerich (>=0.9.1,<0.10.0)
|
14
|
+
Requires-Dist: aiocache (>=0.12.3,<0.13.0)
|
15
|
+
Requires-Dist: argon2-cffi (>=25.1.0,<26.0.0)
|
16
|
+
Requires-Dist: asyncpg (>=0.30.0,<0.31.0) ; extra == "postgresql"
|
17
|
+
Requires-Dist: boto3 (>=1.40.8,<2.0.0)
|
18
|
+
Requires-Dist: fastapi (>=0.116.1,<0.117.0)
|
19
|
+
Requires-Dist: httpx (>=0.28.1,<0.29.0)
|
20
|
+
Requires-Dist: itsdangerous (>=2.2.0,<3.0.0)
|
21
|
+
Requires-Dist: jinja2 (>=3.1.6,<4.0.0)
|
22
|
+
Requires-Dist: joserfc (>=1.2.2,<2.0.0)
|
23
|
+
Requires-Dist: libconf (>=2.0.1,<3.0.0)
|
24
|
+
Requires-Dist: opentelemetry-distro (>=0.57b0,<0.58)
|
25
|
+
Requires-Dist: opentelemetry-exporter-prometheus (>=0.57b0,<0.58)
|
26
|
+
Requires-Dist: opentelemetry-instrumentation-fastapi (>=0.57b0,<0.58)
|
27
|
+
Requires-Dist: pydantic-settings[yaml] (>=2.10.1,<3.0.0)
|
28
|
+
Requires-Dist: python-multipart (>=0.0.20,<0.0.21)
|
29
|
+
Requires-Dist: semver (>=3.0.4,<4.0.0)
|
30
|
+
Requires-Dist: tortoise-orm (>=0.25.1,<0.26.0)
|
31
|
+
Requires-Dist: uvicorn (>=0.35.0,<0.36.0)
|
32
|
+
Description-Content-Type: text/markdown
|
33
|
+
|
34
|
+
# gooseBit
|
35
|
+
|
36
|
+
<img src="https://upstreamdatainc.github.io/goosebit/img/goosebit-logo.png" style="width: 100px; height: 100px; display: block;">
|
37
|
+
|
38
|
+
[](https://scorecard.dev/viewer/?uri=github.com/UpstreamDataInc/goosebit)
|
39
|
+
|
40
|
+
---
|
41
|
+
|
42
|
+
A simplistic, opinionated remote update server implementing hawkBit™'s [DDI API](https://eclipse.dev/hawkbit/apis/ddi_api/).
|
43
|
+
|
44
|
+
## Deployment
|
45
|
+
|
46
|
+
### Docker Compose Demo
|
47
|
+
|
48
|
+
The Docker Compose demo [docker/demo/docker-compose.yml] may serve as inspiration for a containerized (cloud) deployment.
|
49
|
+
It uses PostgreSQL as the database and NGINX as a reverse proxy.
|
50
|
+
|
51
|
+
> [!WARNING]
|
52
|
+
> Do not use the demo (as-is) in production!
|
53
|
+
|
54
|
+
Make sure you have [Docker](https://www.docker.com/get-started/) (and Docker Compose) installed.
|
55
|
+
Then run:
|
56
|
+
|
57
|
+
```txt
|
58
|
+
docker compose -f docker/demo/docker-compose.yml up
|
59
|
+
```
|
60
|
+
|
61
|
+
Visit gooseBit at: https://localhost
|
62
|
+
|
63
|
+
[docker/demo/docker-compose.yml]: https://github.com/UpstreamDataInc/goosebit/blob/master/docker/docker-compose-dev.yml
|
64
|
+
|
65
|
+
### Configuration
|
66
|
+
|
67
|
+
gooseBit can be configured through a configuration file (`/etc/goosebit.yaml`) or by setting environment variables.
|
68
|
+
For the available options and their defaults, see [goosebit.yaml].
|
69
|
+
The environment variable corresponding to e.g. the `poll_time` YAML setting would be `GOOSEBIT_POLL_TIME`.
|
70
|
+
Environment variables for nested settings are constructed using `__` as the separator:
|
71
|
+
|
72
|
+
```txt
|
73
|
+
GOOSEBIT_DEVICE_AUTH__ENABLE=true
|
74
|
+
```
|
75
|
+
|
76
|
+
Alternatively, a JSON string can be assigned:
|
77
|
+
|
78
|
+
```txt
|
79
|
+
GOOSEBIT_DEVICE_AUTH='{"enable": true}'
|
80
|
+
```
|
81
|
+
|
82
|
+
[goosebit.yaml]: https://github.com/UpstreamDataInc/goosebit/blob/master/goosebit.yaml
|
83
|
+
|
84
|
+
### Database
|
85
|
+
|
86
|
+
By default, SQLite is used as the database. For more sophisticated setups, PostgreSQL is supported.
|
87
|
+
To use PostgreSQL, set `db_uri` or the `GOSSEBIT_DB_URI` environment variable to something like:
|
88
|
+
|
89
|
+
```txt
|
90
|
+
postgres://user:password@host:5432/db_name
|
91
|
+
```
|
92
|
+
|
93
|
+
### Artifact Storage
|
94
|
+
|
95
|
+
The software packages managed by gooseBit are either stored on the local filesystem (`artifacts_dir` setting) or an S3-compatible object storage.
|
96
|
+
|
97
|
+
## Assumptions
|
98
|
+
|
99
|
+
- Devices use [SWUpdate](https://swupdate.org) for managing software updates.
|
100
|
+
|
101
|
+
## Features
|
102
|
+
|
103
|
+
### Device Registry
|
104
|
+
|
105
|
+
When a device connects to gooseBit for the first time, it is automatically added to the device registry. The server will then request the device's configuration data, including:
|
106
|
+
|
107
|
+
- `hw_model` and `hw_revision`: Used to match compatible software.
|
108
|
+
- `sw_version`: Indicates the currently installed software version.
|
109
|
+
|
110
|
+
The registry tracks each device's status, including the last online timestamp, installed software version, update state, and more.
|
111
|
+
|
112
|
+
### Software Repository
|
113
|
+
|
114
|
+
Software packages (`*.swu` files) can be hosted directly on the gooseBit server or on an external server. gooseBit parses the software metadata to determine compatibility with specific hardware models and revisions.
|
115
|
+
|
116
|
+
### Device Update Modes
|
117
|
+
|
118
|
+
Devices can be configured with different update modes. The default mode is `Rollout`.
|
119
|
+
|
120
|
+
#### 1. Manual Update to Specified Software
|
121
|
+
|
122
|
+
Assign specific software to a device manually. Once installed, no further updates will be triggered.
|
123
|
+
|
124
|
+
#### 2. Automatic Update to Latest Software
|
125
|
+
|
126
|
+
Automatically updates the device to the latest compatible software, based on the reported `hw_model` and `hw_revision`. Note: versions are interpreted as [SemVer](https://semver.org) versions.
|
127
|
+
|
128
|
+
#### 3. Software Rollout
|
129
|
+
|
130
|
+
Rollouts target all devices with a specified "feed" value, ensuring that the assigned software is installed on all matching devices. Rollouts also track success and error rates, with future plans for automatic aborts. If multiple rollouts exist for the same feed, the most recent rollout takes precedence.
|
131
|
+
|
132
|
+
### Pause Updates
|
133
|
+
|
134
|
+
Devices can be pinned to their current software version, preventing any updates from being applied.
|
135
|
+
|
136
|
+
### Real-time Update Logs
|
137
|
+
|
138
|
+
While updates are in progress, gooseBit captures real-time logs, which are accessible through the device repository.
|
139
|
+
|
140
|
+
## Development with Poetry
|
141
|
+
|
142
|
+
### Initial Setup
|
143
|
+
|
144
|
+
Install Poetry as described [here](https://python-poetry.org/docs/#installation).
|
145
|
+
|
146
|
+
Then, to install gooseBit's dependencies, run:
|
147
|
+
|
148
|
+
```txt
|
149
|
+
poetry install
|
150
|
+
```
|
151
|
+
|
152
|
+
Initialize the database:
|
153
|
+
|
154
|
+
```txt
|
155
|
+
poetry run aerich upgrade
|
156
|
+
```
|
157
|
+
|
158
|
+
Launch gooseBit:
|
159
|
+
|
160
|
+
```txt
|
161
|
+
poetry run python -m goosebit
|
162
|
+
```
|
163
|
+
|
164
|
+
The service is now available at: http://localhost:60053
|
165
|
+
|
166
|
+
### Database
|
167
|
+
|
168
|
+
Initialize or migrate database:
|
169
|
+
|
170
|
+
```txt
|
171
|
+
poetry run aerich upgrade
|
172
|
+
```
|
173
|
+
|
174
|
+
After a model change create the migration:
|
175
|
+
|
176
|
+
```txt
|
177
|
+
poetry run aerich migrate
|
178
|
+
```
|
179
|
+
|
180
|
+
To seed some sample data (attention: drops all current data) use:
|
181
|
+
|
182
|
+
```txt
|
183
|
+
poetry run generate-sample-data
|
184
|
+
```
|
185
|
+
|
186
|
+
### Code formatting and linting
|
187
|
+
|
188
|
+
Code is formatted using different tools
|
189
|
+
|
190
|
+
- black and isort for `*.py`
|
191
|
+
- biomejs for `*.js`, `*.json`
|
192
|
+
- prettier for `*.html`, `*.md`, `*.yml`, `*.yaml`
|
193
|
+
|
194
|
+
Code is linted using different tools as well
|
195
|
+
|
196
|
+
- flake8 for `*.py`
|
197
|
+
- biomejs for `*.js`
|
198
|
+
|
199
|
+
Best to have pre-commit install git hooks that run all those tools before a commit:
|
200
|
+
|
201
|
+
```txt
|
202
|
+
poetry run pre-commit install
|
203
|
+
```
|
204
|
+
|
205
|
+
To manually apply the hooks to all files use:
|
206
|
+
|
207
|
+
```txt
|
208
|
+
poetry run pre-commit run --all-files
|
209
|
+
```
|
210
|
+
|
211
|
+
### Testing
|
212
|
+
|
213
|
+
Tests are implemented using pytest. You can run all the tests with:
|
214
|
+
|
215
|
+
```txt
|
216
|
+
poetry run pytest
|
217
|
+
```
|
218
|
+
|
219
|
+
To run only the unit tests:
|
220
|
+
|
221
|
+
```txt
|
222
|
+
poetry run pytest tests/unit
|
223
|
+
```
|
224
|
+
|
225
|
+
To run only the end-to-end tests:
|
226
|
+
|
227
|
+
```txt
|
228
|
+
poetry run pytest tests/e2e
|
229
|
+
```
|
230
|
+
|
231
|
+
## Development with Docker (and PostgreSQL)
|
232
|
+
|
233
|
+
### Running the Containers
|
234
|
+
|
235
|
+
```txt
|
236
|
+
docker compose -f docker/docker-compose-dev.yml up --build
|
237
|
+
```
|
238
|
+
|
239
|
+
### Applying the Migrations
|
240
|
+
|
241
|
+
```txt
|
242
|
+
docker exec goosebit-dev python -m aerich upgrade
|
243
|
+
```
|
244
|
+
|
245
|
+
### Using the Interactive Debugger
|
246
|
+
|
247
|
+
You might need [rlwrap](https://github.com/hanslub42/rlwrap) to fix readline support.
|
248
|
+
|
249
|
+
Place `breakpoint()` before the code you want to debug. The server will reload automatically.
|
250
|
+
Then, connect to remote PDB (when the breakpoint has been hit):
|
251
|
+
|
252
|
+
```txt
|
253
|
+
rlwrap telnet localhost 4444
|
254
|
+
```
|
255
|
+
|
256
|
+
To exit the debugger, press `Ctrl + ]` and then `q`.
|
257
|
+
|
258
|
+
## Architecture
|
259
|
+
|
260
|
+
### Structure
|
261
|
+
|
262
|
+
The structure of gooseBit is as follows:
|
263
|
+
|
264
|
+
- `api`: Files for the API.
|
265
|
+
- `ui`: Files for the UI.
|
266
|
+
- `bff`: Backend for frontend API.
|
267
|
+
- `static`: Static files.
|
268
|
+
- `templates`: Jinja2 formatted templates.
|
269
|
+
- `nav`: Navbar handler.
|
270
|
+
- `updater`: DDI API handler and device update manager.
|
271
|
+
- `updates`: SWUpdate file parsing.
|
272
|
+
- `auth`: Authentication functions and permission handling.
|
273
|
+
- `models`: Database models.
|
274
|
+
- `db`: Database config and initialization.
|
275
|
+
- `schema`: Pydantic models used for API type hinting.
|
276
|
+
- `settings`: Settings loader and handler.
|
277
|
+
- `storage`: Storage for software artifacts.
|
278
|
+
- `telemetry`: Telemetry data handlers.
|
279
|
+
- `routes`: Routes for a giving endpoint, including the router.
|
280
|
+
|
goosebit-0.2.6/README.md
ADDED
@@ -0,0 +1,246 @@
|
|
1
|
+
# gooseBit
|
2
|
+
|
3
|
+
<img src="https://upstreamdatainc.github.io/goosebit/img/goosebit-logo.png" style="width: 100px; height: 100px; display: block;">
|
4
|
+
|
5
|
+
[](https://scorecard.dev/viewer/?uri=github.com/UpstreamDataInc/goosebit)
|
6
|
+
|
7
|
+
---
|
8
|
+
|
9
|
+
A simplistic, opinionated remote update server implementing hawkBit™'s [DDI API](https://eclipse.dev/hawkbit/apis/ddi_api/).
|
10
|
+
|
11
|
+
## Deployment
|
12
|
+
|
13
|
+
### Docker Compose Demo
|
14
|
+
|
15
|
+
The Docker Compose demo [docker/demo/docker-compose.yml] may serve as inspiration for a containerized (cloud) deployment.
|
16
|
+
It uses PostgreSQL as the database and NGINX as a reverse proxy.
|
17
|
+
|
18
|
+
> [!WARNING]
|
19
|
+
> Do not use the demo (as-is) in production!
|
20
|
+
|
21
|
+
Make sure you have [Docker](https://www.docker.com/get-started/) (and Docker Compose) installed.
|
22
|
+
Then run:
|
23
|
+
|
24
|
+
```txt
|
25
|
+
docker compose -f docker/demo/docker-compose.yml up
|
26
|
+
```
|
27
|
+
|
28
|
+
Visit gooseBit at: https://localhost
|
29
|
+
|
30
|
+
[docker/demo/docker-compose.yml]: https://github.com/UpstreamDataInc/goosebit/blob/master/docker/docker-compose-dev.yml
|
31
|
+
|
32
|
+
### Configuration
|
33
|
+
|
34
|
+
gooseBit can be configured through a configuration file (`/etc/goosebit.yaml`) or by setting environment variables.
|
35
|
+
For the available options and their defaults, see [goosebit.yaml].
|
36
|
+
The environment variable corresponding to e.g. the `poll_time` YAML setting would be `GOOSEBIT_POLL_TIME`.
|
37
|
+
Environment variables for nested settings are constructed using `__` as the separator:
|
38
|
+
|
39
|
+
```txt
|
40
|
+
GOOSEBIT_DEVICE_AUTH__ENABLE=true
|
41
|
+
```
|
42
|
+
|
43
|
+
Alternatively, a JSON string can be assigned:
|
44
|
+
|
45
|
+
```txt
|
46
|
+
GOOSEBIT_DEVICE_AUTH='{"enable": true}'
|
47
|
+
```
|
48
|
+
|
49
|
+
[goosebit.yaml]: https://github.com/UpstreamDataInc/goosebit/blob/master/goosebit.yaml
|
50
|
+
|
51
|
+
### Database
|
52
|
+
|
53
|
+
By default, SQLite is used as the database. For more sophisticated setups, PostgreSQL is supported.
|
54
|
+
To use PostgreSQL, set `db_uri` or the `GOSSEBIT_DB_URI` environment variable to something like:
|
55
|
+
|
56
|
+
```txt
|
57
|
+
postgres://user:password@host:5432/db_name
|
58
|
+
```
|
59
|
+
|
60
|
+
### Artifact Storage
|
61
|
+
|
62
|
+
The software packages managed by gooseBit are either stored on the local filesystem (`artifacts_dir` setting) or an S3-compatible object storage.
|
63
|
+
|
64
|
+
## Assumptions
|
65
|
+
|
66
|
+
- Devices use [SWUpdate](https://swupdate.org) for managing software updates.
|
67
|
+
|
68
|
+
## Features
|
69
|
+
|
70
|
+
### Device Registry
|
71
|
+
|
72
|
+
When a device connects to gooseBit for the first time, it is automatically added to the device registry. The server will then request the device's configuration data, including:
|
73
|
+
|
74
|
+
- `hw_model` and `hw_revision`: Used to match compatible software.
|
75
|
+
- `sw_version`: Indicates the currently installed software version.
|
76
|
+
|
77
|
+
The registry tracks each device's status, including the last online timestamp, installed software version, update state, and more.
|
78
|
+
|
79
|
+
### Software Repository
|
80
|
+
|
81
|
+
Software packages (`*.swu` files) can be hosted directly on the gooseBit server or on an external server. gooseBit parses the software metadata to determine compatibility with specific hardware models and revisions.
|
82
|
+
|
83
|
+
### Device Update Modes
|
84
|
+
|
85
|
+
Devices can be configured with different update modes. The default mode is `Rollout`.
|
86
|
+
|
87
|
+
#### 1. Manual Update to Specified Software
|
88
|
+
|
89
|
+
Assign specific software to a device manually. Once installed, no further updates will be triggered.
|
90
|
+
|
91
|
+
#### 2. Automatic Update to Latest Software
|
92
|
+
|
93
|
+
Automatically updates the device to the latest compatible software, based on the reported `hw_model` and `hw_revision`. Note: versions are interpreted as [SemVer](https://semver.org) versions.
|
94
|
+
|
95
|
+
#### 3. Software Rollout
|
96
|
+
|
97
|
+
Rollouts target all devices with a specified "feed" value, ensuring that the assigned software is installed on all matching devices. Rollouts also track success and error rates, with future plans for automatic aborts. If multiple rollouts exist for the same feed, the most recent rollout takes precedence.
|
98
|
+
|
99
|
+
### Pause Updates
|
100
|
+
|
101
|
+
Devices can be pinned to their current software version, preventing any updates from being applied.
|
102
|
+
|
103
|
+
### Real-time Update Logs
|
104
|
+
|
105
|
+
While updates are in progress, gooseBit captures real-time logs, which are accessible through the device repository.
|
106
|
+
|
107
|
+
## Development with Poetry
|
108
|
+
|
109
|
+
### Initial Setup
|
110
|
+
|
111
|
+
Install Poetry as described [here](https://python-poetry.org/docs/#installation).
|
112
|
+
|
113
|
+
Then, to install gooseBit's dependencies, run:
|
114
|
+
|
115
|
+
```txt
|
116
|
+
poetry install
|
117
|
+
```
|
118
|
+
|
119
|
+
Initialize the database:
|
120
|
+
|
121
|
+
```txt
|
122
|
+
poetry run aerich upgrade
|
123
|
+
```
|
124
|
+
|
125
|
+
Launch gooseBit:
|
126
|
+
|
127
|
+
```txt
|
128
|
+
poetry run python -m goosebit
|
129
|
+
```
|
130
|
+
|
131
|
+
The service is now available at: http://localhost:60053
|
132
|
+
|
133
|
+
### Database
|
134
|
+
|
135
|
+
Initialize or migrate database:
|
136
|
+
|
137
|
+
```txt
|
138
|
+
poetry run aerich upgrade
|
139
|
+
```
|
140
|
+
|
141
|
+
After a model change create the migration:
|
142
|
+
|
143
|
+
```txt
|
144
|
+
poetry run aerich migrate
|
145
|
+
```
|
146
|
+
|
147
|
+
To seed some sample data (attention: drops all current data) use:
|
148
|
+
|
149
|
+
```txt
|
150
|
+
poetry run generate-sample-data
|
151
|
+
```
|
152
|
+
|
153
|
+
### Code formatting and linting
|
154
|
+
|
155
|
+
Code is formatted using different tools
|
156
|
+
|
157
|
+
- black and isort for `*.py`
|
158
|
+
- biomejs for `*.js`, `*.json`
|
159
|
+
- prettier for `*.html`, `*.md`, `*.yml`, `*.yaml`
|
160
|
+
|
161
|
+
Code is linted using different tools as well
|
162
|
+
|
163
|
+
- flake8 for `*.py`
|
164
|
+
- biomejs for `*.js`
|
165
|
+
|
166
|
+
Best to have pre-commit install git hooks that run all those tools before a commit:
|
167
|
+
|
168
|
+
```txt
|
169
|
+
poetry run pre-commit install
|
170
|
+
```
|
171
|
+
|
172
|
+
To manually apply the hooks to all files use:
|
173
|
+
|
174
|
+
```txt
|
175
|
+
poetry run pre-commit run --all-files
|
176
|
+
```
|
177
|
+
|
178
|
+
### Testing
|
179
|
+
|
180
|
+
Tests are implemented using pytest. You can run all the tests with:
|
181
|
+
|
182
|
+
```txt
|
183
|
+
poetry run pytest
|
184
|
+
```
|
185
|
+
|
186
|
+
To run only the unit tests:
|
187
|
+
|
188
|
+
```txt
|
189
|
+
poetry run pytest tests/unit
|
190
|
+
```
|
191
|
+
|
192
|
+
To run only the end-to-end tests:
|
193
|
+
|
194
|
+
```txt
|
195
|
+
poetry run pytest tests/e2e
|
196
|
+
```
|
197
|
+
|
198
|
+
## Development with Docker (and PostgreSQL)
|
199
|
+
|
200
|
+
### Running the Containers
|
201
|
+
|
202
|
+
```txt
|
203
|
+
docker compose -f docker/docker-compose-dev.yml up --build
|
204
|
+
```
|
205
|
+
|
206
|
+
### Applying the Migrations
|
207
|
+
|
208
|
+
```txt
|
209
|
+
docker exec goosebit-dev python -m aerich upgrade
|
210
|
+
```
|
211
|
+
|
212
|
+
### Using the Interactive Debugger
|
213
|
+
|
214
|
+
You might need [rlwrap](https://github.com/hanslub42/rlwrap) to fix readline support.
|
215
|
+
|
216
|
+
Place `breakpoint()` before the code you want to debug. The server will reload automatically.
|
217
|
+
Then, connect to remote PDB (when the breakpoint has been hit):
|
218
|
+
|
219
|
+
```txt
|
220
|
+
rlwrap telnet localhost 4444
|
221
|
+
```
|
222
|
+
|
223
|
+
To exit the debugger, press `Ctrl + ]` and then `q`.
|
224
|
+
|
225
|
+
## Architecture
|
226
|
+
|
227
|
+
### Structure
|
228
|
+
|
229
|
+
The structure of gooseBit is as follows:
|
230
|
+
|
231
|
+
- `api`: Files for the API.
|
232
|
+
- `ui`: Files for the UI.
|
233
|
+
- `bff`: Backend for frontend API.
|
234
|
+
- `static`: Static files.
|
235
|
+
- `templates`: Jinja2 formatted templates.
|
236
|
+
- `nav`: Navbar handler.
|
237
|
+
- `updater`: DDI API handler and device update manager.
|
238
|
+
- `updates`: SWUpdate file parsing.
|
239
|
+
- `auth`: Authentication functions and permission handling.
|
240
|
+
- `models`: Database models.
|
241
|
+
- `db`: Database config and initialization.
|
242
|
+
- `schema`: Pydantic models used for API type hinting.
|
243
|
+
- `settings`: Settings loader and handler.
|
244
|
+
- `storage`: Storage for software artifacts.
|
245
|
+
- `telemetry`: Telemetry data handlers.
|
246
|
+
- `routes`: Routes for a giving endpoint, including the router.
|
@@ -13,13 +13,14 @@ from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor as Instrum
|
|
13
13
|
from starlette.exceptions import HTTPException as StarletteHTTPException
|
14
14
|
from tortoise.exceptions import ValidationError
|
15
15
|
|
16
|
-
from goosebit import api, db,
|
17
|
-
from goosebit.api.telemetry import metrics
|
16
|
+
from goosebit import api, db, plugins, ui, updater
|
18
17
|
from goosebit.auth import get_user_from_request, login_user, redirect_if_authenticated
|
19
|
-
from goosebit.
|
18
|
+
from goosebit.device_manager import DeviceManager
|
19
|
+
from goosebit.settings import PWD_CXT, config
|
20
20
|
from goosebit.ui.nav import nav
|
21
21
|
from goosebit.ui.static import static
|
22
22
|
from goosebit.ui.templates import templates
|
23
|
+
from goosebit.users import create_initial_user
|
23
24
|
|
24
25
|
logger = getLogger(__name__)
|
25
26
|
|
@@ -29,7 +30,9 @@ async def lifespan(_: FastAPI):
|
|
29
30
|
db_ready = await db.init()
|
30
31
|
if not db_ready:
|
31
32
|
logger.exception("DB does not exist, try running `poetry run aerich upgrade`.")
|
32
|
-
|
33
|
+
|
34
|
+
logger.debug(f"Initialized storage backend: {config.storage.backend}")
|
35
|
+
|
33
36
|
if db_ready:
|
34
37
|
yield
|
35
38
|
await db.close()
|
@@ -57,10 +60,30 @@ app = FastAPI(
|
|
57
60
|
app.include_router(updater.router)
|
58
61
|
app.include_router(ui.router)
|
59
62
|
app.include_router(api.router)
|
60
|
-
app.include_router(realtime.router)
|
61
63
|
app.mount("/static", static, name="static")
|
62
64
|
Instrumentor.instrument_app(app)
|
63
65
|
|
66
|
+
for plugin in plugins.load():
|
67
|
+
if plugin.middleware is not None:
|
68
|
+
logger.info(f"Adding middleware for plugin: {plugin.name}")
|
69
|
+
app.add_middleware(plugin.middleware)
|
70
|
+
if plugin.router is not None:
|
71
|
+
logger.info(f"Adding routing handler for plugin: {plugin.name}")
|
72
|
+
app.include_router(router=plugin.router, prefix=plugin.url_prefix)
|
73
|
+
if plugin.db_model_path is not None:
|
74
|
+
logger.info(f"Adding db handler for plugin: {plugin.name}")
|
75
|
+
db.config.add_models(plugin.db_model_path)
|
76
|
+
if plugin.static_files is not None:
|
77
|
+
logger.info(f"Adding static files handler for plugin: {plugin.name}")
|
78
|
+
app.mount(f"{plugin.url_prefix}/static", plugin.static_files, name=plugin.static_files_name)
|
79
|
+
if plugin.templates is not None:
|
80
|
+
logger.info(f"Adding template handler for plugin: {plugin.name}")
|
81
|
+
templates.add_template_handler(plugin.templates)
|
82
|
+
if plugin.update_source_hook is not None:
|
83
|
+
DeviceManager.add_update_source(plugin.update_source_hook)
|
84
|
+
if plugin.config_data_hook is not None:
|
85
|
+
DeviceManager.add_config_callback(plugin.config_data_hook)
|
86
|
+
|
64
87
|
|
65
88
|
# Custom exception handler for Tortoise ValidationError
|
66
89
|
@app.exception_handler(ValidationError)
|
@@ -105,7 +128,18 @@ async def login_get(request: Request):
|
|
105
128
|
|
106
129
|
@app.post("/login", tags=["login"])
|
107
130
|
async def login_post(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]):
|
108
|
-
return {"access_token": login_user(form_data.username, form_data.password), "token_type": "bearer"}
|
131
|
+
return {"access_token": await login_user(form_data.username, form_data.password), "token_type": "bearer"}
|
132
|
+
|
133
|
+
|
134
|
+
@app.get("/setup", include_in_schema=False)
|
135
|
+
async def setup_get(request: Request):
|
136
|
+
return templates.TemplateResponse(request, "setup.html.jinja")
|
137
|
+
|
138
|
+
|
139
|
+
@app.post("/setup", include_in_schema=False)
|
140
|
+
async def setup_post(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]):
|
141
|
+
await create_initial_user(form_data.username, PWD_CXT.hash(form_data.password))
|
142
|
+
return {"access_token": await login_user(form_data.username, form_data.password), "token_type": "bearer"}
|
109
143
|
|
110
144
|
|
111
145
|
@app.get("/logout", include_in_schema=False)
|
@@ -115,7 +149,7 @@ async def logout(request: Request):
|
|
115
149
|
return resp
|
116
150
|
|
117
151
|
|
118
|
-
@app.get("/docs")
|
152
|
+
@app.get("/docs", include_in_schema=False)
|
119
153
|
async def swagger_docs(request: Request):
|
120
154
|
return get_swagger_ui_html(
|
121
155
|
title="gooseBit docs",
|
@@ -2,7 +2,7 @@ from opentelemetry import metrics
|
|
2
2
|
from opentelemetry.sdk.metrics import MeterProvider
|
3
3
|
from opentelemetry.sdk.resources import SERVICE_NAME, Resource
|
4
4
|
|
5
|
-
from goosebit.settings import
|
5
|
+
from goosebit.settings import config
|
6
6
|
|
7
7
|
from . import prometheus
|
8
8
|
|
@@ -28,7 +28,3 @@ users_count = meter.create_gauge(
|
|
28
28
|
"users.count",
|
29
29
|
description="The number of registered users",
|
30
30
|
)
|
31
|
-
|
32
|
-
|
33
|
-
async def init():
|
34
|
-
users_count.set(len(USERS))
|
@@ -0,0 +1,33 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from fastapi import APIRouter, Depends, HTTPException, Security
|
4
|
+
from fastapi.requests import Request
|
5
|
+
|
6
|
+
from goosebit.api.v1.devices.device.responses import DeviceLogResponse, DeviceResponse
|
7
|
+
from goosebit.auth import validate_user_permissions
|
8
|
+
from goosebit.auth.permissions import GOOSEBIT_PERMISSIONS
|
9
|
+
from goosebit.db import Device
|
10
|
+
from goosebit.device_manager import get_device
|
11
|
+
|
12
|
+
router = APIRouter(prefix="/{dev_id}")
|
13
|
+
|
14
|
+
|
15
|
+
@router.get(
|
16
|
+
"",
|
17
|
+
dependencies=[Security(validate_user_permissions, scopes=[GOOSEBIT_PERMISSIONS["device"]["read"]()])],
|
18
|
+
)
|
19
|
+
async def device_get(_: Request, device: Device = Depends(get_device)) -> DeviceResponse:
|
20
|
+
if device is None:
|
21
|
+
raise HTTPException(404)
|
22
|
+
await device.fetch_related("assigned_software", "hardware")
|
23
|
+
return DeviceResponse.model_validate(device)
|
24
|
+
|
25
|
+
|
26
|
+
@router.get(
|
27
|
+
"/log",
|
28
|
+
dependencies=[Security(validate_user_permissions, scopes=[GOOSEBIT_PERMISSIONS["device"]["read"]()])],
|
29
|
+
)
|
30
|
+
async def device_logs(_: Request, device: Device = Depends(get_device)) -> DeviceLogResponse:
|
31
|
+
if device is None:
|
32
|
+
raise HTTPException(404)
|
33
|
+
return DeviceLogResponse(log=device.last_log, progress=device.progress)
|
@@ -0,0 +1,27 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from pydantic import BaseModel
|
4
|
+
|
5
|
+
|
6
|
+
class DevicesDeleteRequest(BaseModel):
|
7
|
+
devices: list[str]
|
8
|
+
|
9
|
+
|
10
|
+
class DevicesPatchRequest(BaseModel):
|
11
|
+
devices: list[str]
|
12
|
+
software: str | None = None
|
13
|
+
name: str | None = None
|
14
|
+
pinned: bool | None = None
|
15
|
+
feed: str | None = None
|
16
|
+
force_update: bool | None = None
|
17
|
+
auth_token: str | None = None
|
18
|
+
|
19
|
+
|
20
|
+
class DevicesPutRequest(BaseModel):
|
21
|
+
devices: list[str]
|
22
|
+
software: str | None = None
|
23
|
+
name: str | None = None
|
24
|
+
pinned: bool | None = None
|
25
|
+
feed: str | None = None
|
26
|
+
force_update: bool | None = None
|
27
|
+
auth_token: str | None = None
|