goosebit 0.2.4__py3-none-any.whl → 0.2.6__py3-none-any.whl

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 (96) hide show
  1. goosebit/__init__.py +56 -6
  2. goosebit/api/telemetry/metrics.py +1 -5
  3. goosebit/api/v1/devices/device/responses.py +1 -0
  4. goosebit/api/v1/devices/device/routes.py +8 -8
  5. goosebit/api/v1/devices/requests.py +20 -0
  6. goosebit/api/v1/devices/routes.py +83 -8
  7. goosebit/api/v1/download/routes.py +14 -3
  8. goosebit/api/v1/rollouts/routes.py +5 -4
  9. goosebit/api/v1/routes.py +2 -1
  10. goosebit/api/v1/settings/routes.py +14 -0
  11. goosebit/api/v1/settings/users/__init__.py +1 -0
  12. goosebit/api/v1/settings/users/requests.py +16 -0
  13. goosebit/api/v1/settings/users/responses.py +7 -0
  14. goosebit/api/v1/settings/users/routes.py +56 -0
  15. goosebit/api/v1/software/routes.py +18 -14
  16. goosebit/auth/__init__.py +54 -14
  17. goosebit/auth/permissions.py +80 -0
  18. goosebit/db/config.py +57 -1
  19. goosebit/db/migrations/models/1_20241109151811_update.py +11 -0
  20. goosebit/db/migrations/models/2_20241121113728_update.py +11 -0
  21. goosebit/db/migrations/models/3_20241121140210_update.py +11 -0
  22. goosebit/db/migrations/models/4_20250324110331_update.py +16 -0
  23. goosebit/db/migrations/models/4_20250402085235_rename_uuid_to_id.py +11 -0
  24. goosebit/db/migrations/models/5_20250619090242_null_feed.py +83 -0
  25. goosebit/db/models.py +22 -7
  26. goosebit/db/pg_ssl_context.py +51 -0
  27. goosebit/device_manager.py +262 -0
  28. goosebit/plugins/__init__.py +32 -0
  29. goosebit/schema/devices.py +9 -6
  30. goosebit/schema/plugins.py +67 -0
  31. goosebit/schema/updates.py +15 -0
  32. goosebit/schema/users.py +9 -0
  33. goosebit/settings/__init__.py +0 -3
  34. goosebit/settings/schema.py +62 -14
  35. goosebit/storage/__init__.py +62 -0
  36. goosebit/storage/base.py +14 -0
  37. goosebit/storage/filesystem.py +111 -0
  38. goosebit/storage/s3.py +104 -0
  39. goosebit/ui/bff/common/columns.py +50 -0
  40. goosebit/ui/bff/common/requests.py +3 -15
  41. goosebit/ui/bff/common/responses.py +17 -0
  42. goosebit/ui/bff/devices/device/__init__.py +1 -0
  43. goosebit/ui/bff/devices/device/routes.py +17 -0
  44. goosebit/ui/bff/devices/requests.py +1 -0
  45. goosebit/ui/bff/devices/responses.py +6 -2
  46. goosebit/ui/bff/devices/routes.py +71 -17
  47. goosebit/ui/bff/download/routes.py +14 -3
  48. goosebit/ui/bff/rollouts/responses.py +6 -2
  49. goosebit/ui/bff/rollouts/routes.py +32 -4
  50. goosebit/ui/bff/routes.py +6 -3
  51. goosebit/ui/bff/settings/__init__.py +1 -0
  52. goosebit/ui/bff/settings/routes.py +20 -0
  53. goosebit/ui/bff/settings/users/__init__.py +1 -0
  54. goosebit/ui/bff/settings/users/responses.py +33 -0
  55. goosebit/ui/bff/settings/users/routes.py +80 -0
  56. goosebit/ui/bff/software/responses.py +19 -9
  57. goosebit/ui/bff/software/routes.py +40 -12
  58. goosebit/ui/nav.py +12 -2
  59. goosebit/ui/routes.py +70 -26
  60. goosebit/ui/static/js/devices.js +72 -80
  61. goosebit/ui/static/js/login.js +21 -5
  62. goosebit/ui/static/js/logs.js +7 -22
  63. goosebit/ui/static/js/rollouts.js +39 -35
  64. goosebit/ui/static/js/settings.js +322 -0
  65. goosebit/ui/static/js/setup.js +28 -0
  66. goosebit/ui/static/js/software.js +127 -127
  67. goosebit/ui/static/js/util.js +45 -4
  68. goosebit/ui/templates/__init__.py +10 -1
  69. goosebit/ui/templates/devices.html.jinja +0 -20
  70. goosebit/ui/templates/login.html.jinja +5 -0
  71. goosebit/ui/templates/nav.html.jinja +26 -7
  72. goosebit/ui/templates/rollouts.html.jinja +4 -22
  73. goosebit/ui/templates/settings.html.jinja +88 -0
  74. goosebit/ui/templates/setup.html.jinja +71 -0
  75. goosebit/ui/templates/software.html.jinja +0 -11
  76. goosebit/updater/controller/v1/routes.py +120 -72
  77. goosebit/updater/routes.py +86 -7
  78. goosebit/updates/__init__.py +24 -31
  79. goosebit/updates/swdesc.py +15 -8
  80. goosebit/users/__init__.py +63 -0
  81. goosebit/util/__init__.py +0 -0
  82. goosebit/util/path.py +42 -0
  83. goosebit/util/version.py +92 -0
  84. goosebit-0.2.6.dist-info/METADATA +280 -0
  85. goosebit-0.2.6.dist-info/RECORD +133 -0
  86. {goosebit-0.2.4.dist-info → goosebit-0.2.6.dist-info}/WHEEL +1 -1
  87. goosebit-0.2.6.dist-info/entry_points.txt +3 -0
  88. goosebit/realtime/logs.py +0 -42
  89. goosebit/realtime/routes.py +0 -13
  90. goosebit/ui/static/js/index.js +0 -155
  91. goosebit/ui/templates/index.html.jinja +0 -25
  92. goosebit/updater/manager.py +0 -357
  93. goosebit-0.2.4.dist-info/METADATA +0 -181
  94. goosebit-0.2.4.dist-info/RECORD +0 -98
  95. /goosebit/{realtime → api/v1/settings}/__init__.py +0 -0
  96. {goosebit-0.2.4.dist-info → goosebit-0.2.6.dist-info}/LICENSE +0 -0
@@ -0,0 +1,92 @@
1
+ from functools import total_ordering
2
+
3
+ from semver import Version as SemVersion
4
+
5
+
6
+ # Class that replicates version handling of swupdate. For reference see
7
+ # * https://sbabic.github.io/swupdate/sw-description.html#versioning-schemas-in-swupdate
8
+ # * https://github.com/sbabic/swupdate/blob/60322d5ad668e5603341c5f42b3c51d0a1c60226/core/artifacts_versions.c#L158
9
+ # * https://github.com/sbabic/swupdate/blob/60322d5ad668e5603341c5f42b3c51d0a1c60226/core/artifacts_versions.c#L217
10
+ @total_ordering
11
+ class Version:
12
+ version_str: str
13
+ default_version: int | None
14
+ sem_version: SemVersion
15
+
16
+ def __init__(self, version_str: str, default_version: int | None = None, sem_version: SemVersion = None):
17
+ self.version_str = version_str
18
+ self.default_version = default_version
19
+ self.sem_version = sem_version
20
+
21
+ @staticmethod
22
+ def parse(version_str: str):
23
+ default_version = Version._default_version_to_number(version_str)
24
+ sem_version = None
25
+ try:
26
+ sem_version = SemVersion.parse(version_str, optional_minor_and_patch=True)
27
+ except (TypeError, ValueError):
28
+ pass
29
+
30
+ if not default_version and not sem_version:
31
+ raise ValueError(f"{version_str} is not valid swupdate version")
32
+
33
+ return Version(version_str, default_version, sem_version)
34
+
35
+ def __str__(self):
36
+ return self.version_str
37
+
38
+ def __eq__(self, other):
39
+ # support comparison with strings as a convenience
40
+ if isinstance(other, str):
41
+ try:
42
+ other = Version.parse(other)
43
+ except ValueError:
44
+ return False
45
+
46
+ if not isinstance(other, Version):
47
+ return NotImplemented
48
+
49
+ if self.default_version and other.default_version:
50
+ return self.default_version == other.default_version
51
+
52
+ if self.sem_version and other.sem_version:
53
+ return self.sem_version == other.sem_version
54
+
55
+ # fallback to lexical comparison of no of the same type
56
+ return self.version_str == other.version_str
57
+
58
+ def __lt__(self, other):
59
+ if not isinstance(other, Version):
60
+ return NotImplemented
61
+
62
+ if self.default_version and other.default_version:
63
+ return self.default_version < other.default_version
64
+
65
+ if self.sem_version and other.sem_version:
66
+ return self.sem_version < other.sem_version
67
+
68
+ # fallback to lexical comparison of no of the same type
69
+ return self.version_str < other.version_str
70
+
71
+ @staticmethod
72
+ def _default_version_to_number(version_string: str) -> int | None:
73
+ parts = version_string.split(".")
74
+ count = min(len(parts), 4)
75
+ version = 0
76
+
77
+ for i in range(count):
78
+ try:
79
+ fld = int(parts[i])
80
+ except ValueError:
81
+ return None
82
+
83
+ if fld > 0xFFFF:
84
+ print(f"Version {version_string} had an element > 65535, falling back to semver")
85
+ return None
86
+
87
+ version = (version << 16) | fld
88
+
89
+ if count < 4:
90
+ version <<= 16 * (4 - count)
91
+
92
+ return version
@@ -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
+ [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/UpstreamDataInc/goosebit/badge)](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
+
@@ -0,0 +1,133 @@
1
+ goosebit/__init__.py,sha256=XKhr0ZPw1NSCTWc1lYHXuh6R_msEj6BB3M7yu44t-Yg,5878
2
+ goosebit/__main__.py,sha256=ezSLOobcrbnBspFOSbyjuATEdk8B3aAj9cgEu647ujk,161
3
+ goosebit/api/__init__.py,sha256=4RRIzqC6KbCESIC9Vc6G4iAa28IWQH4KROTGd2S4AoI,41
4
+ goosebit/api/responses.py,sha256=HXLpFgL-iv5Ts6LKa3AMt1XGV1GbDE5GzjK66_3Gz5o,84
5
+ goosebit/api/routes.py,sha256=KOA1r8Ripl9nhZV4gZpuQrRi0fG9I3VEqcAAMeBGqBk,272
6
+ goosebit/api/telemetry/__init__.py,sha256=4RRIzqC6KbCESIC9Vc6G4iAa28IWQH4KROTGd2S4AoI,41
7
+ goosebit/api/telemetry/metrics.py,sha256=NEQYotA6-uoJTv7Vsa6Ene7M8h_tCENXmn4aP5JrIj4,732
8
+ goosebit/api/telemetry/prometheus/__init__.py,sha256=nvVtx3DsqBhoXE4Y4L6k7nYoQTl1VV0afyVW2YHLQGs,83
9
+ goosebit/api/telemetry/prometheus/readers.py,sha256=TKD7LT-lYS53xdt-tLMxqyWAeya62299cJtok9Q2MSE,104
10
+ goosebit/api/telemetry/prometheus/routes.py,sha256=91TNk_pbxi5BfCLo_OVojubL9kxFuL84dJ55RmsZ0eE,676
11
+ goosebit/api/telemetry/routes.py,sha256=nuv9bClyo_P8TAkcp3huqxZoUn-ATlRAMnl0OXbWNpw,217
12
+ goosebit/api/v1/__init__.py,sha256=4RRIzqC6KbCESIC9Vc6G4iAa28IWQH4KROTGd2S4AoI,41
13
+ goosebit/api/v1/devices/__init__.py,sha256=kzt5TBA-bJmDZFXYPohggbMOW7GhklyflaRKZNC0yDY,42
14
+ goosebit/api/v1/devices/device/__init__.py,sha256=kzt5TBA-bJmDZFXYPohggbMOW7GhklyflaRKZNC0yDY,42
15
+ goosebit/api/v1/devices/device/responses.py,sha256=ZanMcXGD4Z2y3KfPLWoKIRaYTxuQ2mddSm2s0O58U9c,247
16
+ goosebit/api/v1/devices/device/routes.py,sha256=x3vp7XK6lkWlYm3eH8LfFEWnKG3S7eMr7hAX6UP1nKA,1207
17
+ goosebit/api/v1/devices/requests.py,sha256=ncP3hdJrWriBKvYxxLlDnWH9B9XeDCRaijA9srReTlA,635
18
+ goosebit/api/v1/devices/responses.py,sha256=NmaxpXKJ_YsQ92mvoTXG8HYB39_HLFmZaHo_uvXFmhg,185
19
+ goosebit/api/v1/devices/routes.py,sha256=ridIQYUO34Ge9HQwOO64aKb3VaUl_JYkjVz_CXVQng0,4844
20
+ goosebit/api/v1/download/__init__.py,sha256=kzt5TBA-bJmDZFXYPohggbMOW7GhklyflaRKZNC0yDY,42
21
+ goosebit/api/v1/download/routes.py,sha256=W3_mhj2MDjZmMh1YCtJp4JzNUAh-G5u6Y2kim2tjb30,1125
22
+ goosebit/api/v1/rollouts/__init__.py,sha256=4RRIzqC6KbCESIC9Vc6G4iAa28IWQH4KROTGd2S4AoI,41
23
+ goosebit/api/v1/rollouts/requests.py,sha256=EF1VUwaK-f-2SSKxl2klKrMGfBkBo72iWKtw3cAzzW0,257
24
+ goosebit/api/v1/rollouts/responses.py,sha256=yKljabI1wQB6MXNXrF4aIi5csKRCo0A96RqTW6OwAjE,311
25
+ goosebit/api/v1/rollouts/routes.py,sha256=CakFss5aB1nRX7iUasT-PGZwI9I4K9d5tv8bwpLizV0,2094
26
+ goosebit/api/v1/routes.py,sha256=39HcFAgJ3DqPSYLeKfq33vo8kDLJHeQjBqNT3T77bLs,321
27
+ goosebit/api/v1/settings/__init__.py,sha256=4RRIzqC6KbCESIC9Vc6G4iAa28IWQH4KROTGd2S4AoI,41
28
+ goosebit/api/v1/settings/routes.py,sha256=vd_1-qgP1pBI_2Tz2pn4MDzRPicpqPvt2ClXc6x31Lo,367
29
+ goosebit/api/v1/settings/users/__init__.py,sha256=4RRIzqC6KbCESIC9Vc6G4iAa28IWQH4KROTGd2S4AoI,41
30
+ goosebit/api/v1/settings/users/requests.py,sha256=4K9UvuYmjJXvkoGFGbuxiTbBZT8hgVVWrfhnPMdJnqg,275
31
+ goosebit/api/v1/settings/users/responses.py,sha256=rPAMHabzSdiXmaJKuUzdZJY6s132TEGAJO_JW7eBN4w,147
32
+ goosebit/api/v1/settings/users/routes.py,sha256=lHMJXaKk4iMnCMYlB02VkPhl0eUnEmuZE5Kve1ZVfUM,2096
33
+ goosebit/api/v1/software/__init__.py,sha256=4RRIzqC6KbCESIC9Vc6G4iAa28IWQH4KROTGd2S4AoI,41
34
+ goosebit/api/v1/software/requests.py,sha256=visd81BMeWDKJADcC10NBJDs7Z3xQC-5nod9z_WHiPg,101
35
+ goosebit/api/v1/software/responses.py,sha256=l41-JWqyJmmEslXkx4NEJsGOqMrAB__nBUZS3E_9le0,192
36
+ goosebit/api/v1/software/routes.py,sha256=-RJeXxs_NUMxmpw-Hx1h9ImsE4ImcPo0EyH4SUpZ8x8,3379
37
+ goosebit/auth/__init__.py,sha256=UrgZzDRrpxngaOxxo6d5OoU2RD_uy8DvbwGqdQPTx8E,5981
38
+ goosebit/auth/permissions.py,sha256=UjklB7VoGZcoRgnq56Db2U5uR7OoJYDbOYs2gi67qfU,2684
39
+ goosebit/db/__init__.py,sha256=ktnV4302iWzzTr-3oHXv3ud-23qTcAJ4tPLAXyTZZJw,462
40
+ goosebit/db/config.py,sha256=g74kz9S4o0CYtrjUwnB7cB5rHEVQLLdob1d8z2_AOek,1909
41
+ goosebit/db/migrations/models/0_20240830054046_init.py,sha256=EBig7b0-AOWt_AE10rQf_0ZrserjKTm0r53wYInK5Co,5526
42
+ goosebit/db/migrations/models/1_20241109151811_update.py,sha256=cPbX-8Ga7HYKHQkESR5FXNlS-s-bsx4bsLrXvcfVIkQ,309
43
+ goosebit/db/migrations/models/2_20241121113728_update.py,sha256=rWVFBWdeX_i0d5cMdmoVNhSSRpZGUhAKSxyt1Uk48UQ,310
44
+ goosebit/db/migrations/models/3_20241121140210_update.py,sha256=VB_zhZmu7_dw4blQel_NzX4o1Mma_Thf7A9OZjNTuJk,293
45
+ goosebit/db/migrations/models/4_20250324110331_update.py,sha256=GnXlb37X2l2VEHrN6wiNyG0iUd5-KLJmJiOu85rHkDE,425
46
+ goosebit/db/migrations/models/4_20250402085235_rename_uuid_to_id.py,sha256=HnE7MebCxMVPG9JLcSqRGLGiwkL4PVahd6YU0Hklfp0,297
47
+ goosebit/db/migrations/models/5_20250619090242_null_feed.py,sha256=jNU0mjUF4veuQZdjiFBNI5K8O2L7hBoqZF1H4znChuQ,2436
48
+ goosebit/db/models.py,sha256=Vau-LAo14cghXhb4dFhlyThTwVvt3BxavQfB9BgJNqk,5663
49
+ goosebit/db/pg_ssl_context.py,sha256=OyNJBYPLb_yRpA3AaesYr3bro9R3_20fzcAFUUTl00c,2344
50
+ goosebit/device_manager.py,sha256=UwAslhSk6FEt2cJOMujj6enfmSR-KPrNoAeSCa56Zpg,9284
51
+ goosebit/plugins/__init__.py,sha256=9VLjGc2F72teF9dxGGkwDXTbL0Q1wbjLTheb4hY1qEg,1185
52
+ goosebit/schema/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
53
+ goosebit/schema/devices.py,sha256=4LBYGnceK2IWQFhcIEMYC7bLlHPoTYO0ciKcnUnwSkA,2787
54
+ goosebit/schema/plugins.py,sha256=jJFQ-o6QY04YQPz-ovCpnMf-yKlEmoZqq3gJlHUJJ1I,2281
55
+ goosebit/schema/rollouts.py,sha256=Kp--XRC39SDs1bKf59q8fBHzitoW88ZMN1GVlbCQotQ,882
56
+ goosebit/schema/software.py,sha256=W02rA0guQ7zeeVMTdwxt6EjCnTdf-9JewVJRqdN_GK0,951
57
+ goosebit/schema/updates.py,sha256=aZdNAfXZote6JxgH1Ut0YZMCLlOmtBz3Ia3nXRtxfQA,343
58
+ goosebit/schema/users.py,sha256=ZKp4eHh4EH0XRZcmpw7exZO-ypcDmzAtJXJ7zVSbans,190
59
+ goosebit/settings/__init__.py,sha256=zj2Y9kUkJ_aS4ts6trRcTCxapXmH3pCGvqazjy-Oiv8,312
60
+ goosebit/settings/const.py,sha256=hOpeA9pGmbU4V4mqcfhpHV7cmEjf2GIenmzCSz2Jpj8,779
61
+ goosebit/settings/schema.py,sha256=UDPBrM-prukmvOc6Hl701rYoLQkI9FrHCmk3hu7KPeM,4276
62
+ goosebit/storage/__init__.py,sha256=Hu9InMfBtcmP3QZZMd08ncDiWzk_x5sKDTaPv_cyY7c,2235
63
+ goosebit/storage/base.py,sha256=GzChGHfhAZs2mQxEk6tOZ4SkUodROcdvH0PfCYZxlU0,415
64
+ goosebit/storage/filesystem.py,sha256=GdqQJr861FcUShp5O3pC1JTcKORU-5hj4sRx6yMIPCI,3774
65
+ goosebit/storage/s3.py,sha256=buvLjn10AHRvI48-OfYbppcRVyjq3z2C-PvZvt94XX8,3627
66
+ goosebit/ui/__init__.py,sha256=4RRIzqC6KbCESIC9Vc6G4iAa28IWQH4KROTGd2S4AoI,41
67
+ goosebit/ui/bff/__init__.py,sha256=4RRIzqC6KbCESIC9Vc6G4iAa28IWQH4KROTGd2S4AoI,41
68
+ goosebit/ui/bff/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
69
+ goosebit/ui/bff/common/columns.py,sha256=NiHPjStNz4eZH2RlMvI6fPYZXd5oLTqP8NXG84iOAd4,3026
70
+ goosebit/ui/bff/common/requests.py,sha256=Db8AohkRfsjUbx3pbdKVva7ZkLhAfBiDwAA221OXd5M,1183
71
+ goosebit/ui/bff/common/responses.py,sha256=DKEvkpOsFcOkUmYaF1YDqElEOFIqV-uH9yUd5e4e2ak,335
72
+ goosebit/ui/bff/common/util.py,sha256=_hz89EFLAL1UvbIvJJJSF80PL4T1mz1Vk7354wHBzOE,1144
73
+ goosebit/ui/bff/devices/__init__.py,sha256=4RRIzqC6KbCESIC9Vc6G4iAa28IWQH4KROTGd2S4AoI,41
74
+ goosebit/ui/bff/devices/device/__init__.py,sha256=kzt5TBA-bJmDZFXYPohggbMOW7GhklyflaRKZNC0yDY,42
75
+ goosebit/ui/bff/devices/device/routes.py,sha256=d3ZwswAPR5J8CvbjmJ8gH9KtXTHfZmSFR5txufLtCEU,495
76
+ goosebit/ui/bff/devices/requests.py,sha256=DRt8gz8TFpgCsaevDc_s-V2XKfDwHHirXE-LOuiMPZU,320
77
+ goosebit/ui/bff/devices/responses.py,sha256=4279xeu-soqgyOc4ncKZpai6juBQ3dl096p2LgtIu2s,1208
78
+ goosebit/ui/bff/devices/routes.py,sha256=ljvIgN2MGJv_j59YPNoeSIC5w5kBc6hvfDagF0GbLNQ,5093
79
+ goosebit/ui/bff/download/__init__.py,sha256=kzt5TBA-bJmDZFXYPohggbMOW7GhklyflaRKZNC0yDY,42
80
+ goosebit/ui/bff/download/routes.py,sha256=zaGZmHBUGRwxKTjDfpr151Yt1fpC803b1rj25PvxE-Y,1126
81
+ goosebit/ui/bff/rollouts/__init__.py,sha256=4RRIzqC6KbCESIC9Vc6G4iAa28IWQH4KROTGd2S4AoI,41
82
+ goosebit/ui/bff/rollouts/responses.py,sha256=gFEzWkFUBIka98lrA0ivdXBiTqaU5AugfJm2F3p33uk,1180
83
+ goosebit/ui/bff/rollouts/routes.py,sha256=kxuV_wW1xIS4UzWAw47ibJ7XNcRK5c90eA2AyVftSNU,2533
84
+ goosebit/ui/bff/routes.py,sha256=V-C-WS92PvhT6SJJzezF1t6M7w8e_lWsvZE-g5n7w1Y,477
85
+ goosebit/ui/bff/settings/__init__.py,sha256=4RRIzqC6KbCESIC9Vc6G4iAa28IWQH4KROTGd2S4AoI,41
86
+ goosebit/ui/bff/settings/routes.py,sha256=5WOlsPg552pb9tSrKlAw3FbEW734hGCfT5zGMxkatCg,594
87
+ goosebit/ui/bff/settings/users/__init__.py,sha256=4RRIzqC6KbCESIC9Vc6G4iAa28IWQH4KROTGd2S4AoI,41
88
+ goosebit/ui/bff/settings/users/responses.py,sha256=_y-qVOQlZjiY-Ru4ejh_LxoR6u8tkR572CyNbe2wOmw,1173
89
+ goosebit/ui/bff/settings/users/routes.py,sha256=YlAWOcRmmeeaUGzu4ZNyu1yELbyFQkvY8fJiUD79WAQ,2548
90
+ goosebit/ui/bff/software/__init__.py,sha256=4RRIzqC6KbCESIC9Vc6G4iAa28IWQH4KROTGd2S4AoI,41
91
+ goosebit/ui/bff/software/responses.py,sha256=e7zMP8q2uWgKRy1q5Jp0HTNx-lggljOy2BFQRJglDSo,1920
92
+ goosebit/ui/bff/software/routes.py,sha256=rBD7rDFr4QsnOHRhrY_92b2vjoBcTcCciHFMJV090Q0,4123
93
+ goosebit/ui/nav.py,sha256=4s-SoIIYhJxOeTSsxnkbzErSzIkgwIUO-1ubc4ayemI,561
94
+ goosebit/ui/routes.py,sha256=DuRh-RAahamL4E6p_NaXW2nDWGGIyLL611KCRhhf6O8,3720
95
+ goosebit/ui/static/__init__.py,sha256=AsZiM3h9chQ_YaBbAD6TVFf0244Q9DetkDMl70q8swQ,135
96
+ goosebit/ui/static/favicon.ico,sha256=MKAqEjW7F1-j2dmW4s08y4U0dFteIlU7rpLKakU0Lxk,4286
97
+ goosebit/ui/static/favicon.svg,sha256=8zvM7o2Ob3YmTEV6Rj7rCTgSajvY6wIaAQAAjWwlDsw,8733
98
+ goosebit/ui/static/js/devices.js,sha256=Fa6iySMMmgAS2KvaVGGrw_OBGXuHmbLhefLCcu8uGaA,13919
99
+ goosebit/ui/static/js/login.js,sha256=bnWfjkx199VNS-tgVzBM49q4uhuK3TRZREtAQi4FyAU,1249
100
+ goosebit/ui/static/js/logs.js,sha256=jDus5FaNYuQov5ulffcpt4Pky4iLfAaUhtiS2sfsvtQ,398
101
+ goosebit/ui/static/js/rollouts.js,sha256=4wCEndE0fek1Yy5Qdy_lSqiuKOD9nGIGrorpHUPsrJQ,7433
102
+ goosebit/ui/static/js/settings.js,sha256=IZB3z8-1Q3RWnivcUud3VmVfTMbtMNDV8RlghP9pc2w,12267
103
+ goosebit/ui/static/js/setup.js,sha256=RCeUXTU1KMZmGHBPGZpQWu0guiSkuTWSLnjnNYe0Ab4,721
104
+ goosebit/ui/static/js/software.js,sha256=aSOyZCkMPgZXsZlh6xykgNfzhNqWA9OaWY51eeJQC-Y,8835
105
+ goosebit/ui/static/js/util.js,sha256=cBV7rRqweqQZ11bOBkJy_TmdR0CPnfDTTm3o2JyzJA8,5187
106
+ goosebit/ui/static/svg/goosebit-logo.svg,sha256=8zvM7o2Ob3YmTEV6Rj7rCTgSajvY6wIaAQAAjWwlDsw,8733
107
+ goosebit/ui/templates/__init__.py,sha256=ggNQfTkZlZ_mExMROCampSOGd6su7FkKpgV6xHvx6a8,651
108
+ goosebit/ui/templates/devices.html.jinja,sha256=G2t3pKIe0e7LDkm9H0YwDddSvyXY4PaSPM11Rl9d1BY,5773
109
+ goosebit/ui/templates/login.html.jinja,sha256=20wlS8SAgF8ueuqcKmPtgyGQfEKSaY3zoPyOufKHDyw,3382
110
+ goosebit/ui/templates/logs.html.jinja,sha256=JLHogVHWcP_Y321dd-gHexNqix-PFfGVpEKbNmzsMU8,1310
111
+ goosebit/ui/templates/nav.html.jinja,sha256=qgjxA-hxTr6UG600fju-OiPrrRCQV87-K_22OapVsiQ,6273
112
+ goosebit/ui/templates/rollouts.html.jinja,sha256=cprN5d8lqpGgUdRVC7WrCY9iNlDICXoQoSJAlp85xpE,3944
113
+ goosebit/ui/templates/settings.html.jinja,sha256=EWGErVNp0Q-0AXb4EhGzaGt_sBPeXpJ3N27M-pFVwf0,4325
114
+ goosebit/ui/templates/setup.html.jinja,sha256=5h-02XXpBltmQ7sK1xu_yF3SO5VhCeepcY13frRcfK8,3908
115
+ goosebit/ui/templates/software.html.jinja,sha256=zFtM-UGq-7jHPgoObD15pl3pwqO5FIAR9kp4Ee-5WmE,7139
116
+ goosebit/updater/__init__.py,sha256=4RRIzqC6KbCESIC9Vc6G4iAa28IWQH4KROTGd2S4AoI,41
117
+ goosebit/updater/controller/__init__.py,sha256=4RRIzqC6KbCESIC9Vc6G4iAa28IWQH4KROTGd2S4AoI,41
118
+ goosebit/updater/controller/routes.py,sha256=8CnLb-kDuO-yFeWdu4apIyctCf9OzvJ011Az3QDGemU,123
119
+ goosebit/updater/controller/v1/__init__.py,sha256=4RRIzqC6KbCESIC9Vc6G4iAa28IWQH4KROTGd2S4AoI,41
120
+ goosebit/updater/controller/v1/routes.py,sha256=qFD6NqqyEfk4xZqq5JvCLkfiG3laoRJYgPepC2-NveQ,9542
121
+ goosebit/updater/controller/v1/schema.py,sha256=NwSyPx3A38iFabfOfzaVtxPozJQNacikP5WOhxMHqdg,1228
122
+ goosebit/updater/routes.py,sha256=09FVP3vwc5Up926Ll_GzaAQevt3o3ODkSONr7Fupgj4,4271
123
+ goosebit/updates/__init__.py,sha256=ErUsH7eoiM0kVrBgKWNkB1E_p3dRL7F4cNBAiWDW9qg,4207
124
+ goosebit/updates/swdesc.py,sha256=XDKToV9raYe11q9Qws-AvK_fQqq_c0UM9KgPGBiwhAQ,3407
125
+ goosebit/users/__init__.py,sha256=fmoq3LtDFk0nKUaJyvOFunRMSMiUvEgRQYu56BcGlo8,2187
126
+ goosebit/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
127
+ goosebit/util/path.py,sha256=Ir6h3C_kfsVDbzt0icHhH3zDhLnEdW7AeKN0BkjeIJY,1584
128
+ goosebit/util/version.py,sha256=dLBOn8Pb3CeIHhTXYxRfDE3fWGoAl0LPjCTF_xHGFzo,3137
129
+ goosebit-0.2.6.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
130
+ goosebit-0.2.6.dist-info/METADATA,sha256=Z-bROtKw2zWs_JrI0e2Y1SL924XPEhK7vqCR0PmVKeU,8168
131
+ goosebit-0.2.6.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
132
+ goosebit-0.2.6.dist-info/entry_points.txt,sha256=5p3wNB9_WEljksEBgZmOxO0DrBVhRxP20JD9JJ_lpb4,57
133
+ goosebit-0.2.6.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.9.0
2
+ Generator: poetry-core 2.1.3
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ generate-sample-data=sample_data:main
3
+
goosebit/realtime/logs.py DELETED
@@ -1,42 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from fastapi import APIRouter, Security
4
- from fastapi.websockets import WebSocket, WebSocketDisconnect
5
- from pydantic import BaseModel
6
- from websockets.exceptions import ConnectionClosed
7
-
8
- from goosebit.auth import validate_user_permissions
9
- from goosebit.updater.manager import get_update_manager
10
-
11
- router = APIRouter(prefix="/logs")
12
-
13
-
14
- class RealtimeLogModel(BaseModel):
15
- log: str | None
16
- progress: int | None
17
- clear: bool = False
18
-
19
-
20
- @router.websocket(
21
- "/{dev_id}",
22
- dependencies=[Security(validate_user_permissions, scopes=["home.read"])],
23
- )
24
- async def device_logs(websocket: WebSocket, dev_id: str):
25
- await websocket.accept()
26
-
27
- manager = await get_update_manager(dev_id)
28
- device = await manager.get_device()
29
-
30
- async def callback(log_update):
31
- data = RealtimeLogModel(log=log_update, progress=device.progress)
32
- if log_update is None:
33
- data.clear = True
34
- data.log = ""
35
- await websocket.send_json(dict(data))
36
-
37
- async with manager.subscribe_log(callback):
38
- try:
39
- while True:
40
- await websocket.receive()
41
- except (WebSocketDisconnect, ConnectionClosed, RuntimeError):
42
- pass
@@ -1,13 +0,0 @@
1
- from fastapi import APIRouter, Depends
2
-
3
- from goosebit.auth import validate_current_user
4
-
5
- from . import logs
6
-
7
- router = APIRouter(
8
- prefix="/realtime",
9
- dependencies=[Depends(validate_current_user)],
10
- tags=["realtime"],
11
- )
12
-
13
- router.include_router(logs.router)