bricklogger 0.2.0__tar.gz → 0.2.2__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.
- {bricklogger-0.2.0 → bricklogger-0.2.2}/PKG-INFO +12 -13
- {bricklogger-0.2.0 → bricklogger-0.2.2}/README.md +11 -12
- {bricklogger-0.2.0 → bricklogger-0.2.2}/pyproject.toml +1 -1
- {bricklogger-0.2.0 → bricklogger-0.2.2}/pyproject.toml.orig +1 -1
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/__init__.py +1 -1
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/cli/__init__.py +2 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/cli/daemon_commands.py +4 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/cli/plugins.py +2 -2
- bricklogger-0.2.2/src/bricklogger/cli/update.py +205 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/config/__init__.py +2 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/config/schema.py +14 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/config/validation.py +55 -1
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/daemon/core.py +14 -9
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/daemon/notify.py +4 -0
- bricklogger-0.2.2/src/bricklogger/daemon/updates.py +99 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/docs/architecture.md +7 -3
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/docs/docker.md +21 -12
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/docs/features/cli.md +66 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/docs/features/configuration.md +7 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/docs/features/daemon.md +7 -5
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/docs/features/mcp.md +2 -2
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/docs/features/notifications.md +1 -1
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/docs/features/plugins.md +43 -15
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/docs/features/web.md +15 -5
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/docs/getting-started.md +15 -11
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/ops/environment.py +3 -3
- bricklogger-0.2.2/src/bricklogger/ops/units.py +152 -0
- bricklogger-0.2.2/src/bricklogger/ops/updates.py +663 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/sdk/registry.py +9 -0
- bricklogger-0.2.2/src/bricklogger/units/bricklogger-mcp.service +13 -0
- bricklogger-0.2.2/src/bricklogger/units/bricklogger-web.service +13 -0
- bricklogger-0.2.2/src/bricklogger/units/bricklogger.service +14 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/web/app.py +82 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/web/templates/partials/plugins.html +8 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/web/texts.py +20 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/LICENSE +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/__main__.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/cli/client.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/cli/config.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/cli/context.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/cli/guided.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/cli/init.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/cli/mcp_command.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/cli/model.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/cli/notify.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/cli/output.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/cli/query.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/cli/roles.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/cli/serve.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/config/daemon.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/config/examples.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/config/instances.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/config/issues.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/config/loader.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/config/values.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/daemon/__init__.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/daemon/api.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/daemon/codes.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/daemon/explorer.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/daemon/instances.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/daemon/jobs.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/daemon/logging.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/daemon/metadata.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/daemon/overlay.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/daemon/plan.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/daemon/plugins.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/daemon/rules.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/daemon/run.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/daemon/sink.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/daemon/sparql.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/daemon/spool.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/daemon/state.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/docs/README.md +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/docs/features/api.md +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/docs/features/destinations.md +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/docs/features/sources.md +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/docs/vision.md +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/mcp/__init__.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/mcp/http.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/mcp/instructions.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/mcp/server.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/model/Brick.ttl +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/model/LICENSE-Brick.txt +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/model/__init__.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/model/diff.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/model/inference.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/model/ontology.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/model/prefixes.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/model/shapes.ttl +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/model/upload.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/model/versions.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/model/working_graph.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/ops/__init__.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/ops/catalogue.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/ops/client.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/ops/config.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/ops/context.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/ops/docs.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/ops/errors.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/ops/plugin_volume.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/ops/status.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/ops/tools.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/ops/tree.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/plugins/__init__.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/plugins/bacnet_ip/__init__.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/plugins/bacnet_ip/config.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/plugins/bacnet_ip/declaration.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/plugins/bacnet_ip/references.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/plugins/bacnet_ip/source.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/plugins/bacnet_ip/tools.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/plugins/bacnet_ip/values.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/plugins/timescaledb/__init__.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/plugins/timescaledb/config.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/plugins/timescaledb/declaration.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/plugins/timescaledb/destination.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/py.typed +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/sdk/__init__.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/sdk/bacnet.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/sdk/contract.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/sdk/declaration.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/sdk/testing.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/web/__init__.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/web/client.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/web/forms.py +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/web/static/css/app.css +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/web/static/css/explorer.css +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/web/static/fonts/ibm-plex-mono-400.woff2 +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/web/static/fonts/ibm-plex-mono-500.woff2 +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/web/static/fonts/ibm-plex-mono-600.woff2 +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/web/static/fonts/ibm-plex-sans-var.woff2 +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/web/static/js/explorer.js +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/web/static/vendor/LICENSES.md +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/web/static/vendor/codemirror-yaml.min.js +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/web/static/vendor/codemirror.min.css +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/web/static/vendor/codemirror.min.js +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/web/static/vendor/cytoscape.min.js +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/web/static/vendor/htmx.min.js +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/web/static/vendor/yasgui.min.css +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/web/static/vendor/yasgui.min.js +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/web/templates/base.html +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/web/templates/config.html +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/web/templates/daemon.html +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/web/templates/explorer.html +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/web/templates/instances.html +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/web/templates/login.html +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/web/templates/model.html +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/web/templates/notifications.html +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/web/templates/overview.html +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/web/templates/partials/config_result.html +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/web/templates/partials/daemon.html +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/web/templates/partials/diff.html +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/web/templates/partials/health.html +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/web/templates/partials/instances.html +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/web/templates/partials/job.html +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/web/templates/partials/model_versions.html +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/web/templates/partials/notifications.html +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/web/templates/partials/notify_test.html +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/web/templates/partials/overview.html +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/web/templates/partials/points.html +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/web/templates/partials/statusline.html +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/web/templates/partials/tool_result.html +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/web/templates/partials/unreachable.html +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/web/templates/plugins.html +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/web/templates/points.html +0 -0
- {bricklogger-0.2.0 → bricklogger-0.2.2}/src/bricklogger/web/templates/query.html +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bricklogger
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Data bridge for building automation: collects data based on a Brick model and writes it to time-series databases
|
|
5
5
|
Keywords: brick,bacnet,building-automation,timescaledb,data-logger
|
|
6
6
|
Author: Michael Rosenlund
|
|
@@ -82,8 +82,8 @@ The script brings its own Python, so the machine needs only `curl`. With
|
|
|
82
82
|
`sudo` it installs a service: the program under `/opt/bricklogger`, the
|
|
83
83
|
configuration in `/etc/bricklogger`, the data in `/var/lib/bricklogger` and
|
|
84
84
|
three systemd units, left stopped. Without `sudo` everything stays in your home
|
|
85
|
-
directory. `--version 0.2.
|
|
86
|
-
`--` when the script is piped: `… | sudo sh -s -- --version 0.2.
|
|
85
|
+
directory. `--version 0.2.2` installs a given version; options come after
|
|
86
|
+
`--` when the script is piped: `… | sudo sh -s -- --version 0.2.2`.
|
|
87
87
|
|
|
88
88
|
**As a container** the same program is `ghcr.io/cx1-aps/bricklogger`, with a
|
|
89
89
|
compose file for the daemon, the web interface and the MCP server; see
|
|
@@ -149,8 +149,8 @@ sudo systemctl restart bricklogger # the daemon reads plugins at start
|
|
|
149
149
|
```
|
|
150
150
|
|
|
151
151
|
A plugin is a Python package that provides a source or a destination type. A
|
|
152
|
-
plugin that cannot load
|
|
153
|
-
rest keep running. Writing one is described under
|
|
152
|
+
plugin that cannot load, or is configured but not installed, stops nothing:
|
|
153
|
+
its instances show as failed and the rest keep running. Writing one is described under
|
|
154
154
|
[plugins](https://cx1-aps.github.io/bricklogger/features/plugins/).
|
|
155
155
|
|
|
156
156
|
## Mail notifications
|
|
@@ -167,17 +167,16 @@ See [notifications](https://cx1-aps.github.io/bricklogger/features/notifications
|
|
|
167
167
|
|
|
168
168
|
## Upgrade
|
|
169
169
|
|
|
170
|
-
Run the install script again; it leaves the plugins, the configuration and the
|
|
171
|
-
data where they are. Then restart the daemon. A container pulls the new image
|
|
172
|
-
instead:
|
|
173
|
-
|
|
174
170
|
```bash
|
|
175
|
-
|
|
176
|
-
sudo
|
|
171
|
+
bricklogger update status # what there is to upgrade
|
|
172
|
+
sudo bricklogger update all # Bricklogger and the plugins together
|
|
177
173
|
```
|
|
178
174
|
|
|
179
|
-
|
|
180
|
-
|
|
175
|
+
`update` checks the configuration with the new version before it restarts
|
|
176
|
+
anything, and puts the previous versions back if it does not hold; the
|
|
177
|
+
configuration and the data stay where they are. `update core` upgrades
|
|
178
|
+
Bricklogger alone, `update <type>` one plugin. Running the install script
|
|
179
|
+
again works too. A container pulls the new image instead.
|
|
181
180
|
|
|
182
181
|
**Upgrading from 0.1 with an iBOS source:** iBOS is no longer built in. The
|
|
183
182
|
`ibos` instance shows as failed until the plugin is added with
|
|
@@ -37,8 +37,8 @@ The script brings its own Python, so the machine needs only `curl`. With
|
|
|
37
37
|
`sudo` it installs a service: the program under `/opt/bricklogger`, the
|
|
38
38
|
configuration in `/etc/bricklogger`, the data in `/var/lib/bricklogger` and
|
|
39
39
|
three systemd units, left stopped. Without `sudo` everything stays in your home
|
|
40
|
-
directory. `--version 0.2.
|
|
41
|
-
`--` when the script is piped: `… | sudo sh -s -- --version 0.2.
|
|
40
|
+
directory. `--version 0.2.2` installs a given version; options come after
|
|
41
|
+
`--` when the script is piped: `… | sudo sh -s -- --version 0.2.2`.
|
|
42
42
|
|
|
43
43
|
**As a container** the same program is `ghcr.io/cx1-aps/bricklogger`, with a
|
|
44
44
|
compose file for the daemon, the web interface and the MCP server; see
|
|
@@ -104,8 +104,8 @@ sudo systemctl restart bricklogger # the daemon reads plugins at start
|
|
|
104
104
|
```
|
|
105
105
|
|
|
106
106
|
A plugin is a Python package that provides a source or a destination type. A
|
|
107
|
-
plugin that cannot load
|
|
108
|
-
rest keep running. Writing one is described under
|
|
107
|
+
plugin that cannot load, or is configured but not installed, stops nothing:
|
|
108
|
+
its instances show as failed and the rest keep running. Writing one is described under
|
|
109
109
|
[plugins](https://cx1-aps.github.io/bricklogger/features/plugins/).
|
|
110
110
|
|
|
111
111
|
## Mail notifications
|
|
@@ -122,17 +122,16 @@ See [notifications](https://cx1-aps.github.io/bricklogger/features/notifications
|
|
|
122
122
|
|
|
123
123
|
## Upgrade
|
|
124
124
|
|
|
125
|
-
Run the install script again; it leaves the plugins, the configuration and the
|
|
126
|
-
data where they are. Then restart the daemon. A container pulls the new image
|
|
127
|
-
instead:
|
|
128
|
-
|
|
129
125
|
```bash
|
|
130
|
-
|
|
131
|
-
sudo
|
|
126
|
+
bricklogger update status # what there is to upgrade
|
|
127
|
+
sudo bricklogger update all # Bricklogger and the plugins together
|
|
132
128
|
```
|
|
133
129
|
|
|
134
|
-
|
|
135
|
-
|
|
130
|
+
`update` checks the configuration with the new version before it restarts
|
|
131
|
+
anything, and puts the previous versions back if it does not hold; the
|
|
132
|
+
configuration and the data stay where they are. `update core` upgrades
|
|
133
|
+
Bricklogger alone, `update <type>` one plugin. Running the install script
|
|
134
|
+
again works too. A container pulls the new image instead.
|
|
136
135
|
|
|
137
136
|
**Upgrading from 0.1 with an iBOS source:** iBOS is no longer built in. The
|
|
138
137
|
`ibos` instance shows as failed until the plugin is added with
|
|
@@ -24,6 +24,7 @@ from bricklogger.cli.plugins import plugins_app
|
|
|
24
24
|
from bricklogger.cli.query import query_command
|
|
25
25
|
from bricklogger.cli.roles import destinations_app, sources_app
|
|
26
26
|
from bricklogger.cli.serve import serve_command
|
|
27
|
+
from bricklogger.cli.update import update_app
|
|
27
28
|
from bricklogger.config import resolve_config_dir
|
|
28
29
|
|
|
29
30
|
app = typer.Typer(
|
|
@@ -96,6 +97,7 @@ app.add_typer(destinations_app, name="destinations")
|
|
|
96
97
|
app.add_typer(rules_app, name="rules")
|
|
97
98
|
app.add_typer(notify_app, name="notify")
|
|
98
99
|
app.add_typer(plugins_app, name="plugins")
|
|
100
|
+
app.add_typer(update_app, name="update")
|
|
99
101
|
app.add_typer(model_app, name="model")
|
|
100
102
|
app.command("query")(query_command)
|
|
101
103
|
app.command("serve")(serve_command)
|
|
@@ -28,6 +28,7 @@ from bricklogger.ops.status import PID_FILE as PID_FILE
|
|
|
28
28
|
from bricklogger.ops.status import probe
|
|
29
29
|
from bricklogger.ops.status import process_alive as process_alive
|
|
30
30
|
from bricklogger.ops.status import read_pid as read_pid
|
|
31
|
+
from bricklogger.ops.updates import available_line
|
|
31
32
|
|
|
32
33
|
daemon_app = typer.Typer(
|
|
33
34
|
no_args_is_help=True,
|
|
@@ -363,6 +364,9 @@ def _echo_summary(data: dict[str, Any]) -> None:
|
|
|
363
364
|
]
|
|
364
365
|
for row in data.get("instances", []):
|
|
365
366
|
lines.append(f" {row['role']} {row['name']} ({row['type']}): {row['state']}")
|
|
367
|
+
newer = available_line(data.get("updates"))
|
|
368
|
+
if newer is not None:
|
|
369
|
+
lines.append(newer)
|
|
366
370
|
for line in lines:
|
|
367
371
|
typer.echo(line)
|
|
368
372
|
|
|
@@ -129,8 +129,8 @@ def remove_command(
|
|
|
129
129
|
) -> None:
|
|
130
130
|
"""Uninstall the distribution that provides a plugin type.
|
|
131
131
|
|
|
132
|
-
Refuses while an instance of the type is configured, unless --force;
|
|
133
|
-
daemon
|
|
132
|
+
Refuses while an instance of the type is configured, unless --force; from
|
|
133
|
+
the daemon's next start such an instance would be failed.
|
|
134
134
|
"""
|
|
135
135
|
context = cli_context(ctx)
|
|
136
136
|
try:
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
"""``bricklogger update``: what there is to upgrade, and the upgrade itself.
|
|
2
|
+
|
|
3
|
+
``update status`` installs nothing. ``update core``, ``update all`` and
|
|
4
|
+
``update TYPE`` upgrade from PyPI with the environment's own uv, validate with
|
|
5
|
+
the new version, put the previous versions back when that fails, write the
|
|
6
|
+
units again and restart what runs as a unit. ``update`` alone shows this help,
|
|
7
|
+
as every group does. See ``docs/features/cli.md``, "update".
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from typing import Annotated, Any
|
|
13
|
+
|
|
14
|
+
import typer
|
|
15
|
+
from rich.table import Table
|
|
16
|
+
from typer.core import TyperGroup
|
|
17
|
+
from typer.main import get_command as typer_command
|
|
18
|
+
|
|
19
|
+
from bricklogger.cli.context import cli_context
|
|
20
|
+
from bricklogger.cli.output import fail, print_json, stderr, stdout
|
|
21
|
+
from bricklogger.ops.errors import OperationError
|
|
22
|
+
from bricklogger.ops.updates import CORE, Updated, UpdateRefused, status, update
|
|
23
|
+
|
|
24
|
+
NoRestart = Annotated[
|
|
25
|
+
bool,
|
|
26
|
+
typer.Option(
|
|
27
|
+
"--no-restart",
|
|
28
|
+
help="Leave the daemon, the web interface and the MCP server alone.",
|
|
29
|
+
),
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class _UpdateGroup(TyperGroup):
|
|
34
|
+
"""A group whose unknown subcommand is a plugin type to upgrade."""
|
|
35
|
+
|
|
36
|
+
# Typer carries its own copy of click, so the types are left open here.
|
|
37
|
+
def get_command(self, ctx: Any, cmd_name: str) -> Any:
|
|
38
|
+
command = super().get_command(ctx, cmd_name)
|
|
39
|
+
if command is not None or cmd_name.startswith("-"):
|
|
40
|
+
return command
|
|
41
|
+
return _type_command(cmd_name)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
update_app = typer.Typer(
|
|
45
|
+
cls=_UpdateGroup,
|
|
46
|
+
add_completion=False,
|
|
47
|
+
no_args_is_help=True,
|
|
48
|
+
help="Upgrade Bricklogger and its plugins from PyPI. `update status` shows what "
|
|
49
|
+
"there is; `update core` upgrades Bricklogger alone, as far as the plugins "
|
|
50
|
+
"allow; `update TYPE` upgrades the plugin that provides the type; `update all` "
|
|
51
|
+
"upgrades everything together. The new version validates the configuration "
|
|
52
|
+
"before anything is restarted, and the previous versions are put back when it "
|
|
53
|
+
"does not hold.",
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@update_app.command("status")
|
|
58
|
+
def status_command(
|
|
59
|
+
json_output: Annotated[
|
|
60
|
+
bool, typer.Option("--json", help="Emit JSON instead of a table.")
|
|
61
|
+
] = False,
|
|
62
|
+
) -> None:
|
|
63
|
+
"""For Bricklogger and every plugin: installed, newest, and newest that fits."""
|
|
64
|
+
try:
|
|
65
|
+
rows = status()
|
|
66
|
+
except OperationError as exc:
|
|
67
|
+
raise fail(exc.message) from exc
|
|
68
|
+
if json_output:
|
|
69
|
+
print_json([row.as_dict() for row in rows])
|
|
70
|
+
return
|
|
71
|
+
table = Table(box=None)
|
|
72
|
+
for column in ("Package", "Installed", "Newest", "Fits", "Note"):
|
|
73
|
+
table.add_column(column)
|
|
74
|
+
for row in rows:
|
|
75
|
+
table.add_row(
|
|
76
|
+
row.name,
|
|
77
|
+
row.installed,
|
|
78
|
+
row.newest or "?",
|
|
79
|
+
row.fits or "?",
|
|
80
|
+
_note(row.name, row.error, row.newest, row.fits, row.held_by),
|
|
81
|
+
)
|
|
82
|
+
stdout.print(table)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _note(
|
|
86
|
+
name: str,
|
|
87
|
+
error: str | None,
|
|
88
|
+
newest: str | None,
|
|
89
|
+
fits: str | None,
|
|
90
|
+
held_by: tuple[str, ...],
|
|
91
|
+
) -> str:
|
|
92
|
+
if error is not None:
|
|
93
|
+
return f"not looked up: {error}"
|
|
94
|
+
if newest is not None and fits is not None and newest != fits and held_by:
|
|
95
|
+
return (
|
|
96
|
+
f"{newest} held back by {', '.join(held_by)}; "
|
|
97
|
+
"`update all` moves them together"
|
|
98
|
+
)
|
|
99
|
+
return ""
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@update_app.command("core")
|
|
103
|
+
def core_command(ctx: typer.Context, no_restart: NoRestart = False) -> None:
|
|
104
|
+
"""Upgrade Bricklogger alone, to the newest release the plugins allow."""
|
|
105
|
+
_update(ctx, "core", no_restart)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@update_app.command("all")
|
|
109
|
+
def all_command(ctx: typer.Context, no_restart: NoRestart = False) -> None:
|
|
110
|
+
"""Upgrade Bricklogger and every plugin to the newest releases that fit together."""
|
|
111
|
+
_update(ctx, "all", no_restart)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _type_command(type_name: str) -> Any:
|
|
115
|
+
"""``update TYPE``, made for the type that was typed."""
|
|
116
|
+
sub = typer.Typer(add_completion=False)
|
|
117
|
+
|
|
118
|
+
@sub.command(type_name)
|
|
119
|
+
def command(ctx: typer.Context, no_restart: NoRestart = False) -> None:
|
|
120
|
+
"""Upgrade the plugin that provides this type, the rest held."""
|
|
121
|
+
_update(ctx, type_name, no_restart)
|
|
122
|
+
|
|
123
|
+
return typer_command(sub)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _update(ctx: typer.Context, target: str, no_restart: bool) -> None:
|
|
127
|
+
context = cli_context(ctx)
|
|
128
|
+
try:
|
|
129
|
+
result = update(target, config_dir=context.config_dir, restart=not no_restart)
|
|
130
|
+
except UpdateRefused as exc:
|
|
131
|
+
for error in exc.errors:
|
|
132
|
+
where = ".".join(
|
|
133
|
+
str(part)
|
|
134
|
+
for part in (
|
|
135
|
+
f"{error.get('file')}.yaml",
|
|
136
|
+
error.get("subject"),
|
|
137
|
+
error.get("key"),
|
|
138
|
+
)
|
|
139
|
+
if part and part != "None.yaml"
|
|
140
|
+
)
|
|
141
|
+
stderr.print(f" {where}: {error.get('message')}", highlight=False)
|
|
142
|
+
back = "; the previous versions are back in place" if exc.restored else ""
|
|
143
|
+
raise fail(f"{exc.message}{back}") from exc
|
|
144
|
+
except OperationError as exc:
|
|
145
|
+
raise fail(exc.message) from exc
|
|
146
|
+
_report(result, target, no_restart)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _report(result: Updated, target: str, no_restart: bool) -> None:
|
|
150
|
+
if not result.moved:
|
|
151
|
+
typer.echo("already up to date")
|
|
152
|
+
if target == "core":
|
|
153
|
+
_held_back()
|
|
154
|
+
return
|
|
155
|
+
typer.echo(
|
|
156
|
+
"updated "
|
|
157
|
+
+ ", ".join(
|
|
158
|
+
_movement(name, old, new) for name, (old, new) in result.moved.items()
|
|
159
|
+
)
|
|
160
|
+
)
|
|
161
|
+
for warning in result.warnings:
|
|
162
|
+
subject = warning.get("subject") or warning.get("file")
|
|
163
|
+
typer.echo(f"warning: {subject}: {warning.get('message')}")
|
|
164
|
+
if result.units:
|
|
165
|
+
typer.echo(f"rewrote {', '.join(result.units)} from the new templates")
|
|
166
|
+
if result.restarted:
|
|
167
|
+
typer.echo(f"restarted {', '.join(result.restarted)}")
|
|
168
|
+
for unit in result.not_answering:
|
|
169
|
+
typer.echo(
|
|
170
|
+
f"{unit} was restarted but does not answer yet; check its log with "
|
|
171
|
+
f"journalctl -u {unit}"
|
|
172
|
+
)
|
|
173
|
+
for note in result.notes:
|
|
174
|
+
typer.echo(note)
|
|
175
|
+
if result.container:
|
|
176
|
+
typer.echo("Restart the containers to run it: docker compose restart")
|
|
177
|
+
if target == "all":
|
|
178
|
+
typer.echo("Bricklogger itself comes with a new image.")
|
|
179
|
+
elif no_restart:
|
|
180
|
+
typer.echo("nothing was restarted (--no-restart)")
|
|
181
|
+
if target == "core":
|
|
182
|
+
_held_back()
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _movement(name: str, old: str | None, new: str | None) -> str:
|
|
186
|
+
if old is None:
|
|
187
|
+
return f"{name} {new} (new)"
|
|
188
|
+
if new is None:
|
|
189
|
+
return f"{name} {old} (removed)"
|
|
190
|
+
return f"{name} {old} → {new}"
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _held_back() -> None:
|
|
194
|
+
"""Name a newer Bricklogger the installed plugins exclude."""
|
|
195
|
+
try:
|
|
196
|
+
rows = status()
|
|
197
|
+
except OperationError:
|
|
198
|
+
return
|
|
199
|
+
core = next((row for row in rows if row.name == CORE), None)
|
|
200
|
+
if core is None or not core.held_by or core.newest is None:
|
|
201
|
+
return
|
|
202
|
+
typer.echo(
|
|
203
|
+
f"bricklogger {core.newest} is out, but {', '.join(core.held_by)} does not "
|
|
204
|
+
"allow it yet; `bricklogger update all` moves them together"
|
|
205
|
+
)
|
|
@@ -51,6 +51,7 @@ from bricklogger.config.schema import (
|
|
|
51
51
|
from bricklogger.config.validation import (
|
|
52
52
|
RESERVED_INSTANCE_NAMES,
|
|
53
53
|
ValidationResult,
|
|
54
|
+
not_installed,
|
|
54
55
|
validate_configuration,
|
|
55
56
|
)
|
|
56
57
|
|
|
@@ -81,6 +82,7 @@ __all__ = [
|
|
|
81
82
|
"examples_for",
|
|
82
83
|
"instances_in",
|
|
83
84
|
"load_configuration",
|
|
85
|
+
"not_installed",
|
|
84
86
|
"parse_text",
|
|
85
87
|
"read_env_file",
|
|
86
88
|
"read_texts",
|
|
@@ -185,6 +185,16 @@ class NotificationSettings(_Strict):
|
|
|
185
185
|
)
|
|
186
186
|
|
|
187
187
|
|
|
188
|
+
class UpdateSettings(_Strict):
|
|
189
|
+
"""``updates`` in ``daemon.yaml``. See ``docs/features/cli.md``, "update"."""
|
|
190
|
+
|
|
191
|
+
check: bool = Field(
|
|
192
|
+
default=True,
|
|
193
|
+
description="Whether the daemon looks on PyPI once a day for newer "
|
|
194
|
+
"releases of Bricklogger and its plugins; off on a network with no way out",
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
|
|
188
198
|
class DaemonSettings(_Strict):
|
|
189
199
|
"""``daemon.yaml``."""
|
|
190
200
|
|
|
@@ -216,6 +226,10 @@ class DaemonSettings(_Strict):
|
|
|
216
226
|
default_factory=NotificationSettings,
|
|
217
227
|
description="Mail to the administrator, off by default",
|
|
218
228
|
)
|
|
229
|
+
updates: UpdateSettings = Field(
|
|
230
|
+
default_factory=UpdateSettings,
|
|
231
|
+
description="The daily look for newer releases",
|
|
232
|
+
)
|
|
219
233
|
|
|
220
234
|
@property
|
|
221
235
|
def log_file(self) -> Path:
|
|
@@ -65,6 +65,51 @@ def _unloadable(file: str, name: str, error: str) -> ConfigIssue:
|
|
|
65
65
|
)
|
|
66
66
|
|
|
67
67
|
|
|
68
|
+
def not_installed(type_name: str) -> str:
|
|
69
|
+
"""What is said of a configured type that no installed plugin provides,
|
|
70
|
+
by validation and by the daemon that shows its instance failed."""
|
|
71
|
+
return (
|
|
72
|
+
f"the type {type_name!r} is not installed; add its plugin with "
|
|
73
|
+
"`bricklogger plugins add`"
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _not_installed(file: str, name: str, type_name: str) -> ConfigIssue:
|
|
78
|
+
"""A warning, not an error: the instance will be failed while everything
|
|
79
|
+
else runs, as for a plugin that cannot load. See
|
|
80
|
+
``docs/features/plugins.md``, "A type that is not installed"."""
|
|
81
|
+
return ConfigIssue(
|
|
82
|
+
file, f"the instance will be failed: {not_installed(type_name)}", name, "type"
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _changed_instances(
|
|
87
|
+
config_dir: Path,
|
|
88
|
+
config: Configuration,
|
|
89
|
+
env: Mapping[str, str] | None,
|
|
90
|
+
texts: Mapping[str, str | None] | None,
|
|
91
|
+
) -> set[tuple[str, str]]:
|
|
92
|
+
"""``(file, name)`` for every instance a proposed change adds or changes,
|
|
93
|
+
compared with the files on disk; nothing when no change is proposed.
|
|
94
|
+
When the files on disk do not load, every instance counts as changed."""
|
|
95
|
+
if not texts:
|
|
96
|
+
return set()
|
|
97
|
+
current = load_configuration(config_dir, env).configuration
|
|
98
|
+
changed: set[tuple[str, str]] = set()
|
|
99
|
+
for file, new, old in (
|
|
100
|
+
("sources", config.sources, current.sources if current else {}),
|
|
101
|
+
(
|
|
102
|
+
"destinations",
|
|
103
|
+
config.destinations,
|
|
104
|
+
current.destinations if current else {},
|
|
105
|
+
),
|
|
106
|
+
):
|
|
107
|
+
for name, instance in new.items():
|
|
108
|
+
if old.get(name) != instance:
|
|
109
|
+
changed.add((file, name))
|
|
110
|
+
return changed
|
|
111
|
+
|
|
112
|
+
|
|
68
113
|
def _reserved(file: str, name: str) -> ConfigIssue:
|
|
69
114
|
return ConfigIssue(
|
|
70
115
|
file,
|
|
@@ -81,7 +126,9 @@ def validate_configuration(
|
|
|
81
126
|
) -> ValidationResult:
|
|
82
127
|
"""Validate the whole directory; the configuration comes back only when valid.
|
|
83
128
|
|
|
84
|
-
``texts`` replaces files as they are on disk, for a proposed change.
|
|
129
|
+
``texts`` replaces files as they are on disk, for a proposed change. A
|
|
130
|
+
configured type that is not installed is a warning, except in an
|
|
131
|
+
instance the proposed change adds or changes, where it is an error.
|
|
85
132
|
"""
|
|
86
133
|
loaded = load_configuration(config_dir, env, texts)
|
|
87
134
|
if loaded.configuration is None:
|
|
@@ -89,6 +136,7 @@ def validate_configuration(
|
|
|
89
136
|
config = loaded.configuration
|
|
90
137
|
errors: list[ConfigIssue] = []
|
|
91
138
|
warnings: list[ConfigIssue] = []
|
|
139
|
+
changed = _changed_instances(config_dir, config, env, texts)
|
|
92
140
|
|
|
93
141
|
daemon = config.daemon
|
|
94
142
|
if not is_loopback(daemon.api.host) and not daemon.api.token:
|
|
@@ -126,6 +174,9 @@ def validate_configuration(
|
|
|
126
174
|
if failure is not None:
|
|
127
175
|
warnings.append(_unloadable("sources", name, failure.error))
|
|
128
176
|
continue
|
|
177
|
+
if ("sources", name) not in changed:
|
|
178
|
+
warnings.append(_not_installed("sources", name, source.type))
|
|
179
|
+
continue
|
|
129
180
|
errors.append(
|
|
130
181
|
ConfigIssue(
|
|
131
182
|
"sources",
|
|
@@ -150,6 +201,9 @@ def validate_configuration(
|
|
|
150
201
|
if failure is not None:
|
|
151
202
|
warnings.append(_unloadable("destinations", name, failure.error))
|
|
152
203
|
continue
|
|
204
|
+
if ("destinations", name) not in changed:
|
|
205
|
+
warnings.append(_not_installed("destinations", name, destination.type))
|
|
206
|
+
continue
|
|
153
207
|
errors.append(
|
|
154
208
|
ConfigIssue(
|
|
155
209
|
"destinations",
|
|
@@ -29,6 +29,7 @@ from bricklogger.config import (
|
|
|
29
29
|
DestinationInstance,
|
|
30
30
|
SourceInstance,
|
|
31
31
|
ValidationResult,
|
|
32
|
+
not_installed,
|
|
32
33
|
read_texts,
|
|
33
34
|
validate_configuration,
|
|
34
35
|
write_text,
|
|
@@ -51,6 +52,7 @@ from bricklogger.daemon.sink import DaemonSink
|
|
|
51
52
|
from bricklogger.daemon.sparql import run_query
|
|
52
53
|
from bricklogger.daemon.spool import SPOOL_DIR, Spool
|
|
53
54
|
from bricklogger.daemon.state import STATE_FILE, RuntimeState
|
|
55
|
+
from bricklogger.daemon.updates import Lookup, UpdateWatch
|
|
54
56
|
from bricklogger.model import (
|
|
55
57
|
INFERRED_GRAPH,
|
|
56
58
|
MODEL_GRAPH,
|
|
@@ -197,6 +199,7 @@ class Daemon:
|
|
|
197
199
|
config_dir: Path,
|
|
198
200
|
registry: PluginRegistry | None = None,
|
|
199
201
|
env: Mapping[str, str] | None = None,
|
|
202
|
+
update_lookup: Lookup | None = None,
|
|
200
203
|
) -> None:
|
|
201
204
|
self.config_dir = config_dir
|
|
202
205
|
if registry is None:
|
|
@@ -215,6 +218,8 @@ class Daemon:
|
|
|
215
218
|
self.unloadable: dict[str, Unloadable] = {}
|
|
216
219
|
self.spools: dict[str, Spool] = {}
|
|
217
220
|
self.notifier: Notifier | None = None
|
|
221
|
+
self.updates: UpdateWatch | None = None
|
|
222
|
+
self._update_lookup = update_lookup
|
|
218
223
|
self.plan: Plan | None = None
|
|
219
224
|
self.active_version: int | None = None
|
|
220
225
|
self.model_error: str | None = None
|
|
@@ -258,10 +263,14 @@ class Daemon:
|
|
|
258
263
|
self.notifier = Notifier(self, config.daemon.notifications)
|
|
259
264
|
self.notifier.record_event("started")
|
|
260
265
|
self.notifier.start()
|
|
266
|
+
self.updates = UpdateWatch(config.daemon.updates.check, self._update_lookup)
|
|
267
|
+
self.updates.start()
|
|
261
268
|
log.info("daemon started with config %s", self.config_dir)
|
|
262
269
|
|
|
263
270
|
def stop(self) -> None:
|
|
264
271
|
self.jobs.stop()
|
|
272
|
+
if self.updates is not None:
|
|
273
|
+
self.updates.stop()
|
|
265
274
|
if self.notifier is not None:
|
|
266
275
|
# Held rather than sent, so an upgrade gives one mail that says
|
|
267
276
|
# the daemon stopped and started; see docs/features/notifications.md.
|
|
@@ -297,6 +306,8 @@ class Daemon:
|
|
|
297
306
|
self._start_sources()
|
|
298
307
|
if self.notifier is not None:
|
|
299
308
|
self.notifier.reconfigure(new.daemon.notifications)
|
|
309
|
+
if self.updates is not None:
|
|
310
|
+
self.updates.reconfigure(new.daemon.updates.check)
|
|
300
311
|
log.info("configuration reloaded from %s", self.config_dir)
|
|
301
312
|
return result
|
|
302
313
|
|
|
@@ -656,11 +667,7 @@ class Daemon:
|
|
|
656
667
|
and the same warning a crashed instance raises."""
|
|
657
668
|
assert self.state is not None
|
|
658
669
|
failure = self.registry.failure_of(type_name)
|
|
659
|
-
error = (
|
|
660
|
-
failure.error
|
|
661
|
-
if failure is not None
|
|
662
|
-
else f"the plugin {type_name!r} is not installed"
|
|
663
|
-
)
|
|
670
|
+
error = failure.error if failure is not None else not_installed(type_name)
|
|
664
671
|
self.unloadable[name] = Unloadable(role, type_name, error)
|
|
665
672
|
self.state.set_instance(
|
|
666
673
|
name, role=role, type_name=type_name, state="failed", error=error
|
|
@@ -780,10 +787,7 @@ class Daemon:
|
|
|
780
787
|
else:
|
|
781
788
|
entry = self.unloadable.get(instance)
|
|
782
789
|
if entry is not None and entry.type_name == type_name:
|
|
783
|
-
raise InstanceNotRunning(
|
|
784
|
-
f"{instance} cannot start: its plugin could not be loaded: "
|
|
785
|
-
f"{entry.error}"
|
|
786
|
-
)
|
|
790
|
+
raise InstanceNotRunning(f"{instance} cannot start: {entry.error}")
|
|
787
791
|
raise NotFound(f"unknown plugin type {type_name!r}")
|
|
788
792
|
if runner is None or runner.type_name != type_name:
|
|
789
793
|
raise NotFound(f"no instance {instance!r} of {type_name!r}")
|
|
@@ -982,6 +986,7 @@ class Daemon:
|
|
|
982
986
|
],
|
|
983
987
|
"warnings": len(self.state.warnings()),
|
|
984
988
|
"observations_received": self.state.counter("observations_received"),
|
|
989
|
+
"updates": self.updates.summary() if self.updates else None,
|
|
985
990
|
}
|
|
986
991
|
|
|
987
992
|
def status_sources(self) -> list[dict[str, Any]]:
|
|
@@ -38,6 +38,7 @@ from bricklogger.daemon.codes import (
|
|
|
38
38
|
from bricklogger.daemon.rules import BRICK, REC
|
|
39
39
|
from bricklogger.daemon.state import RuntimeState
|
|
40
40
|
from bricklogger.model.working_graph import MODEL_GRAPH, WorkingGraph
|
|
41
|
+
from bricklogger.ops.updates import available_line
|
|
41
42
|
|
|
42
43
|
log = logging.getLogger("bricklogger.notify")
|
|
43
44
|
|
|
@@ -711,6 +712,9 @@ def _digest_sections(
|
|
|
711
712
|
)
|
|
712
713
|
else:
|
|
713
714
|
sections.append(("Warnings", ["Nothing stands."], []))
|
|
715
|
+
newer = available_line(status.get("updates"))
|
|
716
|
+
if newer is not None:
|
|
717
|
+
sections.append(("Updates", [newer], []))
|
|
714
718
|
return sections
|
|
715
719
|
|
|
716
720
|
|