bricklogger 0.2.2__tar.gz → 0.2.4__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.
Files changed (169) hide show
  1. {bricklogger-0.2.2 → bricklogger-0.2.4}/PKG-INFO +34 -20
  2. {bricklogger-0.2.2 → bricklogger-0.2.4}/README.md +33 -19
  3. {bricklogger-0.2.2 → bricklogger-0.2.4}/pyproject.toml +1 -1
  4. {bricklogger-0.2.2 → bricklogger-0.2.4}/pyproject.toml.orig +1 -1
  5. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/__init__.py +1 -1
  6. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/cli/__init__.py +3 -1
  7. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/cli/init.py +30 -7
  8. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/cli/mcp_command.py +18 -17
  9. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/cli/model.py +24 -3
  10. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/cli/plugins.py +3 -3
  11. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/cli/roles.py +12 -1
  12. bricklogger-0.2.4/src/bricklogger/cli/services.py +102 -0
  13. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/cli/update.py +2 -2
  14. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/config/loader.py +6 -7
  15. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/daemon/api.py +12 -4
  16. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/daemon/core.py +139 -10
  17. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/daemon/instances.py +25 -1
  18. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/daemon/plugins.py +1 -0
  19. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/daemon/sparql.py +1 -1
  20. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/daemon/spool.py +17 -9
  21. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/docs/architecture.md +49 -11
  22. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/docs/docker.md +14 -14
  23. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/docs/features/api.md +7 -4
  24. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/docs/features/cli.md +87 -37
  25. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/docs/features/configuration.md +17 -17
  26. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/docs/features/daemon.md +5 -4
  27. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/docs/features/destinations.md +77 -0
  28. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/docs/features/mcp.md +12 -9
  29. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/docs/features/plugins.md +50 -36
  30. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/docs/features/web.md +7 -9
  31. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/docs/getting-started.md +73 -47
  32. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/mcp/http.py +5 -1
  33. bricklogger-0.2.4/src/bricklogger/model/timeseries.py +55 -0
  34. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/model/working_graph.py +40 -7
  35. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/ops/environment.py +279 -41
  36. bricklogger-0.2.4/src/bricklogger/ops/services.py +309 -0
  37. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/ops/status.py +51 -13
  38. bricklogger-0.2.4/src/bricklogger/ops/units.py +135 -0
  39. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/ops/updates.py +61 -47
  40. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/plugins/timescaledb/declaration.py +3 -1
  41. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/plugins/timescaledb/destination.py +56 -4
  42. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/sdk/__init__.py +7 -3
  43. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/sdk/contract.py +45 -0
  44. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/sdk/declaration.py +1 -0
  45. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/units/bricklogger-mcp.service +1 -2
  46. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/units/bricklogger-web.service +1 -2
  47. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/units/bricklogger.service +1 -4
  48. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/web/app.py +6 -3
  49. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/web/templates/partials/instances.html +1 -0
  50. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/web/texts.py +4 -2
  51. bricklogger-0.2.2/src/bricklogger/ops/units.py +0 -152
  52. {bricklogger-0.2.2 → bricklogger-0.2.4}/LICENSE +0 -0
  53. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/__main__.py +0 -0
  54. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/cli/client.py +0 -0
  55. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/cli/config.py +0 -0
  56. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/cli/context.py +0 -0
  57. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/cli/daemon_commands.py +0 -0
  58. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/cli/guided.py +0 -0
  59. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/cli/notify.py +0 -0
  60. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/cli/output.py +0 -0
  61. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/cli/query.py +0 -0
  62. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/cli/serve.py +0 -0
  63. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/config/__init__.py +0 -0
  64. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/config/daemon.py +0 -0
  65. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/config/examples.py +0 -0
  66. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/config/instances.py +0 -0
  67. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/config/issues.py +0 -0
  68. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/config/schema.py +0 -0
  69. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/config/validation.py +0 -0
  70. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/config/values.py +0 -0
  71. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/daemon/__init__.py +0 -0
  72. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/daemon/codes.py +0 -0
  73. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/daemon/explorer.py +0 -0
  74. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/daemon/jobs.py +0 -0
  75. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/daemon/logging.py +0 -0
  76. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/daemon/metadata.py +0 -0
  77. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/daemon/notify.py +0 -0
  78. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/daemon/overlay.py +0 -0
  79. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/daemon/plan.py +0 -0
  80. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/daemon/rules.py +0 -0
  81. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/daemon/run.py +0 -0
  82. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/daemon/sink.py +0 -0
  83. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/daemon/state.py +0 -0
  84. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/daemon/updates.py +0 -0
  85. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/docs/README.md +0 -0
  86. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/docs/features/notifications.md +0 -0
  87. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/docs/features/sources.md +0 -0
  88. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/docs/vision.md +0 -0
  89. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/mcp/__init__.py +0 -0
  90. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/mcp/instructions.py +0 -0
  91. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/mcp/server.py +0 -0
  92. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/model/Brick.ttl +0 -0
  93. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/model/LICENSE-Brick.txt +0 -0
  94. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/model/__init__.py +0 -0
  95. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/model/diff.py +0 -0
  96. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/model/inference.py +0 -0
  97. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/model/ontology.py +0 -0
  98. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/model/prefixes.py +0 -0
  99. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/model/shapes.ttl +0 -0
  100. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/model/upload.py +0 -0
  101. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/model/versions.py +0 -0
  102. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/ops/__init__.py +0 -0
  103. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/ops/catalogue.py +0 -0
  104. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/ops/client.py +0 -0
  105. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/ops/config.py +0 -0
  106. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/ops/context.py +0 -0
  107. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/ops/docs.py +0 -0
  108. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/ops/errors.py +0 -0
  109. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/ops/plugin_volume.py +0 -0
  110. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/ops/tools.py +0 -0
  111. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/ops/tree.py +0 -0
  112. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/plugins/__init__.py +0 -0
  113. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/plugins/bacnet_ip/__init__.py +0 -0
  114. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/plugins/bacnet_ip/config.py +0 -0
  115. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/plugins/bacnet_ip/declaration.py +0 -0
  116. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/plugins/bacnet_ip/references.py +0 -0
  117. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/plugins/bacnet_ip/source.py +0 -0
  118. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/plugins/bacnet_ip/tools.py +0 -0
  119. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/plugins/bacnet_ip/values.py +0 -0
  120. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/plugins/timescaledb/__init__.py +0 -0
  121. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/plugins/timescaledb/config.py +0 -0
  122. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/py.typed +0 -0
  123. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/sdk/bacnet.py +0 -0
  124. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/sdk/registry.py +0 -0
  125. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/sdk/testing.py +0 -0
  126. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/web/__init__.py +0 -0
  127. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/web/client.py +0 -0
  128. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/web/forms.py +0 -0
  129. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/web/static/css/app.css +0 -0
  130. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/web/static/css/explorer.css +0 -0
  131. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/web/static/fonts/ibm-plex-mono-400.woff2 +0 -0
  132. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/web/static/fonts/ibm-plex-mono-500.woff2 +0 -0
  133. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/web/static/fonts/ibm-plex-mono-600.woff2 +0 -0
  134. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/web/static/fonts/ibm-plex-sans-var.woff2 +0 -0
  135. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/web/static/js/explorer.js +0 -0
  136. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/web/static/vendor/LICENSES.md +0 -0
  137. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/web/static/vendor/codemirror-yaml.min.js +0 -0
  138. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/web/static/vendor/codemirror.min.css +0 -0
  139. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/web/static/vendor/codemirror.min.js +0 -0
  140. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/web/static/vendor/cytoscape.min.js +0 -0
  141. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/web/static/vendor/htmx.min.js +0 -0
  142. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/web/static/vendor/yasgui.min.css +0 -0
  143. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/web/static/vendor/yasgui.min.js +0 -0
  144. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/web/templates/base.html +0 -0
  145. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/web/templates/config.html +0 -0
  146. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/web/templates/daemon.html +0 -0
  147. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/web/templates/explorer.html +0 -0
  148. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/web/templates/instances.html +0 -0
  149. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/web/templates/login.html +0 -0
  150. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/web/templates/model.html +0 -0
  151. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/web/templates/notifications.html +0 -0
  152. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/web/templates/overview.html +0 -0
  153. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/web/templates/partials/config_result.html +0 -0
  154. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/web/templates/partials/daemon.html +0 -0
  155. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/web/templates/partials/diff.html +0 -0
  156. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/web/templates/partials/health.html +0 -0
  157. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/web/templates/partials/job.html +0 -0
  158. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/web/templates/partials/model_versions.html +0 -0
  159. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/web/templates/partials/notifications.html +0 -0
  160. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/web/templates/partials/notify_test.html +0 -0
  161. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/web/templates/partials/overview.html +0 -0
  162. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/web/templates/partials/plugins.html +0 -0
  163. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/web/templates/partials/points.html +0 -0
  164. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/web/templates/partials/statusline.html +0 -0
  165. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/web/templates/partials/tool_result.html +0 -0
  166. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/web/templates/partials/unreachable.html +0 -0
  167. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/web/templates/plugins.html +0 -0
  168. {bricklogger-0.2.2 → bricklogger-0.2.4}/src/bricklogger/web/templates/points.html +0 -0
  169. {bricklogger-0.2.2 → bricklogger-0.2.4}/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.2
3
+ Version: 0.2.4
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
@@ -74,16 +74,18 @@ page is the short version.
74
74
 
75
75
  ## Install
76
76
 
77
+ As the login that is to run the logger, not as root:
78
+
77
79
  ```bash
78
- curl -fsSL https://github.com/CX1-ApS/bricklogger/releases/latest/download/install.sh | sudo sh
80
+ curl -LsSf https://astral.sh/uv/install.sh | sh # uv, if the machine has none
81
+ uv tool install bricklogger
79
82
  ```
80
83
 
81
- The script brings its own Python, so the machine needs only `curl`. With
82
- `sudo` it installs a service: the program under `/opt/bricklogger`, the
83
- configuration in `/etc/bricklogger`, the data in `/var/lib/bricklogger` and
84
- three systemd units, left stopped. Without `sudo` everything stays in your home
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`.
84
+ uv brings a Python that fits, so the machine needs nothing else. Everything
85
+ stays in that login's home directory: the program in uv's tool directory, the
86
+ command in `~/.local/bin`, the configuration in `~/.config/bricklogger` and the
87
+ data in `~/.local/share/bricklogger`. `uv tool install bricklogger==0.2.4`
88
+ installs a given version.
87
89
 
88
90
  **As a container** the same program is `ghcr.io/cx1-aps/bricklogger`, with a
89
91
  compose file for the daemon, the web interface and the MCP server; see
@@ -104,9 +106,9 @@ Bricklogger creates and migrates its own tables at the first start.
104
106
  ## First setup
105
107
 
106
108
  ```bash
107
- bricklogger init # guided: sources, destinations, secrets
109
+ bricklogger init # guided: sources, destinations, secrets, services
108
110
  bricklogger validate # after any edit by hand
109
- bricklogger daemon start # or: sudo systemctl enable --now bricklogger
111
+ bricklogger services install --web # if init did not: the daemon and the web interface
110
112
  bricklogger model upload building.ttl # validate, infer, activate
111
113
  bricklogger status
112
114
  bricklogger points --outcome active # the last value of every point
@@ -115,17 +117,17 @@ bricklogger points --outcome active # the last value of every point
115
117
  `init` asks for the settings of each source and destination and writes four
116
118
  files: `sources.yaml`, `destinations.yaml`, `rules.yaml` and `daemon.yaml`.
117
119
  Secrets, such as the database password, go into an `env` file beside them and
118
- are referred to as `${NAME}`. The first rule set accepts every point in the
120
+ are referred to as `${NAME}`. Last, `init` offers to run the daemon — and the
121
+ web interface and the MCP server, if you want them — as `systemd --user`
122
+ services that start at boot. The first rule set accepts every point in the
119
123
  model every five minutes; [rules](https://cx1-aps.github.io/bricklogger/features/configuration/)
120
124
  narrow that down by class, location, equipment or any SPARQL pattern.
121
125
 
122
126
  ## The web interface and an assistant
123
127
 
124
- ```bash
125
- bricklogger serve # http://127.0.0.1:8421
126
- ```
127
-
128
- Everything the CLI does can be done there, and the model explorer shows the
128
+ The web interface is at `http://127.0.0.1:8421` once its service runs, or
129
+ after `bricklogger serve` in a terminal. Everything the CLI does can be done
130
+ there, and the model explorer shows the
129
131
  building as a tree and a graph, with every point's outcome and value.
130
132
 
131
133
  To let an AI assistant that speaks MCP set up sources, destinations and rules,
@@ -145,7 +147,7 @@ before it writes, and never takes a secret's value. See
145
147
  bricklogger plugins # what is installed
146
148
  bricklogger plugins add bricklogger-ibos # add a plugin from PyPI
147
149
  bricklogger plugins remove ibos # remove one by its type
148
- sudo systemctl restart bricklogger # the daemon reads plugins at start
150
+ systemctl --user restart bricklogger # the daemon reads plugins at start
149
151
  ```
150
152
 
151
153
  A plugin is a Python package that provides a source or a destination type. A
@@ -169,14 +171,26 @@ See [notifications](https://cx1-aps.github.io/bricklogger/features/notifications
169
171
 
170
172
  ```bash
171
173
  bricklogger update status # what there is to upgrade
172
- sudo bricklogger update all # Bricklogger and the plugins together
174
+ bricklogger update all # Bricklogger and the plugins together
173
175
  ```
174
176
 
175
177
  `update` checks the configuration with the new version before it restarts
176
178
  anything, and puts the previous versions back if it does not hold; the
177
179
  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.
180
+ Bricklogger alone, `update <type>` one plugin. Do not run
181
+ `uv tool install bricklogger` again over an installation with plugins: it
182
+ uninstalls every plugin not named with `--with`. A container pulls the new
183
+ image instead.
184
+
185
+ **Upgrading from 0.2 installed with the install script:** the install script
186
+ is gone, and so is installing as root. Remove the old installation with the
187
+ script it came from — `curl -fsSL
188
+ https://github.com/CX1-ApS/bricklogger/releases/download/v0.2.2/install.sh |
189
+ sudo sh -s -- --uninstall`, without `sudo` for one in a home directory — then
190
+ install as above, add the plugins again with `bricklogger plugins add`, copy
191
+ the configuration, its `env` file and the data over if they lay in
192
+ `/etc/bricklogger` and `/var/lib/bricklogger` — with `data_dir` in
193
+ `daemon.yaml` changed to the new place — and run `bricklogger services install`.
180
194
 
181
195
  **Upgrading from 0.1 with an iBOS source:** iBOS is no longer built in. The
182
196
  `ibos` instance shows as failed until the plugin is added with
@@ -29,16 +29,18 @@ page is the short version.
29
29
 
30
30
  ## Install
31
31
 
32
+ As the login that is to run the logger, not as root:
33
+
32
34
  ```bash
33
- curl -fsSL https://github.com/CX1-ApS/bricklogger/releases/latest/download/install.sh | sudo sh
35
+ curl -LsSf https://astral.sh/uv/install.sh | sh # uv, if the machine has none
36
+ uv tool install bricklogger
34
37
  ```
35
38
 
36
- The script brings its own Python, so the machine needs only `curl`. With
37
- `sudo` it installs a service: the program under `/opt/bricklogger`, the
38
- configuration in `/etc/bricklogger`, the data in `/var/lib/bricklogger` and
39
- three systemd units, left stopped. Without `sudo` everything stays in your home
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`.
39
+ uv brings a Python that fits, so the machine needs nothing else. Everything
40
+ stays in that login's home directory: the program in uv's tool directory, the
41
+ command in `~/.local/bin`, the configuration in `~/.config/bricklogger` and the
42
+ data in `~/.local/share/bricklogger`. `uv tool install bricklogger==0.2.4`
43
+ installs a given version.
42
44
 
43
45
  **As a container** the same program is `ghcr.io/cx1-aps/bricklogger`, with a
44
46
  compose file for the daemon, the web interface and the MCP server; see
@@ -59,9 +61,9 @@ Bricklogger creates and migrates its own tables at the first start.
59
61
  ## First setup
60
62
 
61
63
  ```bash
62
- bricklogger init # guided: sources, destinations, secrets
64
+ bricklogger init # guided: sources, destinations, secrets, services
63
65
  bricklogger validate # after any edit by hand
64
- bricklogger daemon start # or: sudo systemctl enable --now bricklogger
66
+ bricklogger services install --web # if init did not: the daemon and the web interface
65
67
  bricklogger model upload building.ttl # validate, infer, activate
66
68
  bricklogger status
67
69
  bricklogger points --outcome active # the last value of every point
@@ -70,17 +72,17 @@ bricklogger points --outcome active # the last value of every point
70
72
  `init` asks for the settings of each source and destination and writes four
71
73
  files: `sources.yaml`, `destinations.yaml`, `rules.yaml` and `daemon.yaml`.
72
74
  Secrets, such as the database password, go into an `env` file beside them and
73
- are referred to as `${NAME}`. The first rule set accepts every point in the
75
+ are referred to as `${NAME}`. Last, `init` offers to run the daemon — and the
76
+ web interface and the MCP server, if you want them — as `systemd --user`
77
+ services that start at boot. The first rule set accepts every point in the
74
78
  model every five minutes; [rules](https://cx1-aps.github.io/bricklogger/features/configuration/)
75
79
  narrow that down by class, location, equipment or any SPARQL pattern.
76
80
 
77
81
  ## The web interface and an assistant
78
82
 
79
- ```bash
80
- bricklogger serve # http://127.0.0.1:8421
81
- ```
82
-
83
- Everything the CLI does can be done there, and the model explorer shows the
83
+ The web interface is at `http://127.0.0.1:8421` once its service runs, or
84
+ after `bricklogger serve` in a terminal. Everything the CLI does can be done
85
+ there, and the model explorer shows the
84
86
  building as a tree and a graph, with every point's outcome and value.
85
87
 
86
88
  To let an AI assistant that speaks MCP set up sources, destinations and rules,
@@ -100,7 +102,7 @@ before it writes, and never takes a secret's value. See
100
102
  bricklogger plugins # what is installed
101
103
  bricklogger plugins add bricklogger-ibos # add a plugin from PyPI
102
104
  bricklogger plugins remove ibos # remove one by its type
103
- sudo systemctl restart bricklogger # the daemon reads plugins at start
105
+ systemctl --user restart bricklogger # the daemon reads plugins at start
104
106
  ```
105
107
 
106
108
  A plugin is a Python package that provides a source or a destination type. A
@@ -124,14 +126,26 @@ See [notifications](https://cx1-aps.github.io/bricklogger/features/notifications
124
126
 
125
127
  ```bash
126
128
  bricklogger update status # what there is to upgrade
127
- sudo bricklogger update all # Bricklogger and the plugins together
129
+ bricklogger update all # Bricklogger and the plugins together
128
130
  ```
129
131
 
130
132
  `update` checks the configuration with the new version before it restarts
131
133
  anything, and puts the previous versions back if it does not hold; the
132
134
  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.
135
+ Bricklogger alone, `update <type>` one plugin. Do not run
136
+ `uv tool install bricklogger` again over an installation with plugins: it
137
+ uninstalls every plugin not named with `--with`. A container pulls the new
138
+ image instead.
139
+
140
+ **Upgrading from 0.2 installed with the install script:** the install script
141
+ is gone, and so is installing as root. Remove the old installation with the
142
+ script it came from — `curl -fsSL
143
+ https://github.com/CX1-ApS/bricklogger/releases/download/v0.2.2/install.sh |
144
+ sudo sh -s -- --uninstall`, without `sudo` for one in a home directory — then
145
+ install as above, add the plugins again with `bricklogger plugins add`, copy
146
+ the configuration, its `env` file and the data over if they lay in
147
+ `/etc/bricklogger` and `/var/lib/bricklogger` — with `data_dir` in
148
+ `daemon.yaml` changed to the new place — and run `bricklogger services install`.
135
149
 
136
150
  **Upgrading from 0.1 with an iBOS source:** iBOS is no longer built in. The
137
151
  `ibos` instance shows as failed until the plugin is added with
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "bricklogger"
3
- version = "0.2.2"
3
+ version = "0.2.4"
4
4
  description = "Data bridge for building automation: collects data based on a Brick model and writes it to time-series databases"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "bricklogger"
3
- version = "0.2.2"
3
+ version = "0.2.4"
4
4
  description = "Data bridge for building automation: collects data based on a Brick model and writes it to time-series databases"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -14,4 +14,4 @@ The package is laid out after the architecture in ``docs/architecture.md``:
14
14
  external plugins.
15
15
  """
16
16
 
17
- __version__ = "0.2.2"
17
+ __version__ = "0.2.4"
@@ -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.services import services_app
27
28
  from bricklogger.cli.update import update_app
28
29
  from bricklogger.config import resolve_config_dir
29
30
 
@@ -49,7 +50,7 @@ def _root(
49
50
  typer.Option(
50
51
  "--config-dir",
51
52
  help="The config directory; default $BRICKLOGGER_CONFIG_DIR, "
52
- "then /etc/bricklogger when it exists, then ~/.config/bricklogger.",
53
+ "then ~/.config/bricklogger.",
53
54
  show_default=False,
54
55
  ),
55
56
  ] = None,
@@ -92,6 +93,7 @@ app.command("validate")(validate_command)
92
93
  app.command("status")(status_command)
93
94
  app.command("points")(points_command)
94
95
  app.add_typer(daemon_app, name="daemon")
96
+ app.add_typer(services_app, name="services")
95
97
  app.add_typer(sources_app, name="sources")
96
98
  app.add_typer(destinations_app, name="destinations")
97
99
  app.add_typer(rules_app, name="rules")
@@ -2,9 +2,10 @@
2
2
 
3
3
  In a terminal it is a guided setup — which sources and destinations, a name
4
4
  and the settings of each from the plugin's own schema, secrets straight into
5
- the ``env`` file — and ``--non-interactive`` writes the four files with
6
- commented examples instead. Either way it refuses to overwrite a file that
7
- exists, so it can never destroy a configuration.
5
+ the ``env`` file, and last the offer to set up the services — and
6
+ ``--non-interactive`` writes the four files with commented examples instead.
7
+ Either way it refuses to overwrite a file that exists, so it can never destroy
8
+ a configuration.
8
9
  """
9
10
 
10
11
  from __future__ import annotations
@@ -19,6 +20,7 @@ from bricklogger.cli.config import registry, report_validation
19
20
  from bricklogger.cli.context import CliContext, cli_context
20
21
  from bricklogger.cli.guided import ask_settings, write_env
21
22
  from bricklogger.cli.output import fail
23
+ from bricklogger.cli.services import install as install_services
22
24
  from bricklogger.config import (
23
25
  CONFIG_FILES,
24
26
  RESERVED_INSTANCE_NAMES,
@@ -31,6 +33,7 @@ from bricklogger.config import (
31
33
  write_examples,
32
34
  write_text,
33
35
  )
36
+ from bricklogger.ops import services
34
37
  from bricklogger.sdk.declaration import DestinationDeclaration, SourceDeclaration
35
38
 
36
39
  NonInteractiveFlag = Annotated[
@@ -102,8 +105,7 @@ def _guided(context: CliContext) -> None:
102
105
  write_text(config_dir, "destinations", destinations),
103
106
  write_text(config_dir, "rules", examples["rules"]),
104
107
  ]
105
- if secrets:
106
- written.append(write_env(config_dir, secrets))
108
+ written.append(write_env(config_dir, secrets))
107
109
  except OSError as exc:
108
110
  raise fail(f"could not write to {config_dir}: {exc}") from exc
109
111
  typer.echo("")
@@ -119,12 +121,33 @@ def _guided(context: CliContext) -> None:
119
121
  where=str(config_dir),
120
122
  )
121
123
  typer.echo("")
122
- typer.echo("Next: start the daemon and upload the model.")
123
- typer.echo(" bricklogger daemon start (or: systemctl enable --now bricklogger)")
124
+ started = _offer_services(context)
125
+ typer.echo("")
126
+ if started:
127
+ typer.echo("Next: upload the model.")
128
+ else:
129
+ typer.echo("Next: start the daemon and upload the model.")
130
+ typer.echo(" bricklogger daemon start")
124
131
  typer.echo(" bricklogger model upload <model.ttl>")
125
132
  typer.echo(" bricklogger status")
126
133
 
127
134
 
135
+ def _offer_services(context: CliContext) -> bool:
136
+ """Ask whether to set up the services, and do it on a yes; whether the
137
+ daemon now runs as one. Nothing is asked where they cannot be set up."""
138
+ if services.refusal(services.this_host(), context.config_dir) is not None:
139
+ return False
140
+ if not typer.confirm(
141
+ "Set up the services now? The daemon then starts at boot", default=True
142
+ ):
143
+ typer.echo("The same later: bricklogger services install [--web] [--mcp]")
144
+ return False
145
+ web = typer.confirm(" Also the web interface?", default=True)
146
+ mcp = typer.confirm(" Also the MCP server over HTTP?", default=False)
147
+ install_services(context.config_dir, web=web, mcp=mcp)
148
+ return True
149
+
150
+
128
151
  def _collect(
129
152
  role: str,
130
153
  file: str,
@@ -208,27 +208,28 @@ def _restart_line(before: dict[str, object], restart: bool) -> str:
208
208
  )
209
209
  if _restart_unit(command, str(before["url"]).removesuffix("/mcp")):
210
210
  return f"restarted {UNIT}; the new token is in effect"
211
- return f"{UNIT} was restarted but does not answer yet; check it with systemctl"
211
+ return (
212
+ f"{UNIT} was restarted but does not answer yet; check it with "
213
+ f"systemctl --user status {UNIT}"
214
+ )
212
215
 
213
216
 
214
217
  def _systemctl() -> list[str] | None:
215
- """The systemctl that has the MCP unit active, system or user; else ``None``."""
218
+ """The login's systemctl when it has the MCP unit active; else ``None``."""
216
219
  if shutil.which("systemctl") is None:
217
220
  return None
218
- for command in (["systemctl"], ["systemctl", "--user"]):
219
- try:
220
- answer = subprocess.run(
221
- [*command, "is-active", UNIT],
222
- capture_output=True,
223
- text=True,
224
- timeout=5,
225
- check=False,
226
- )
227
- except (OSError, subprocess.SubprocessError):
228
- continue
229
- if answer.stdout.strip() == "active":
230
- return command
231
- return None
221
+ command = ["systemctl", "--user"]
222
+ try:
223
+ answer = subprocess.run(
224
+ [*command, "is-active", UNIT],
225
+ capture_output=True,
226
+ text=True,
227
+ timeout=5,
228
+ check=False,
229
+ )
230
+ except (OSError, subprocess.SubprocessError):
231
+ return None
232
+ return command if answer.stdout.strip() == "active" else None
232
233
 
233
234
 
234
235
  def _restart_unit(command: list[str], url: str, timeout: float = 15.0) -> bool:
@@ -282,7 +283,7 @@ def _server_line(settings: DaemonSettings, token: str | None) -> str:
282
283
  return f"server: running at {url} — accepts this token"
283
284
  return (
284
285
  f"server: running at {url} — rejects this token; restart it, "
285
- f"e.g. systemctl restart {UNIT}"
286
+ f"e.g. systemctl --user restart {UNIT}"
286
287
  )
287
288
 
288
289
 
@@ -299,11 +299,30 @@ def export(
299
299
  values: Annotated[
300
300
  bool, typer.Option("--values", help="Add the value overlay.")
301
301
  ] = False,
302
+ timeseries: Annotated[
303
+ bool,
304
+ typer.Option(
305
+ "--timeseries",
306
+ help="Add the time-series references of the destination that stores "
307
+ "the model.",
308
+ ),
309
+ ] = False,
310
+ destination: Annotated[
311
+ str | None,
312
+ typer.Option(
313
+ "--destination",
314
+ help="With --timeseries: the destination instance, when several "
315
+ "store the model.",
316
+ ),
317
+ ] = None,
302
318
  output: Annotated[
303
319
  Path | None, typer.Option("-o", "--output", help="Write to a file.")
304
320
  ] = None,
305
321
  ) -> None:
306
- """The model as uploaded; the inferred graph and the values need the daemon."""
322
+ """The model as uploaded; the inferred graph, the values and the time-series
323
+ references need the daemon."""
324
+ if destination is not None and not timeseries:
325
+ raise fail("--destination goes with --timeseries")
307
326
  context = cli_context(ctx)
308
327
  client = reachable_client(context)
309
328
  if client is not None:
@@ -316,11 +335,13 @@ def export(
316
335
  f"/v1/models/{number}",
317
336
  inferred="true" if inferred else None,
318
337
  values="true" if values else None,
338
+ timeseries=(destination or "true") if timeseries else None,
319
339
  )
320
340
  else:
321
- if inferred or values:
341
+ if inferred or values or timeseries:
322
342
  raise fail(
323
- "--inferred and --values need the running daemon's working graph"
343
+ "--inferred, --values and --timeseries need the running daemon's "
344
+ "working graph"
324
345
  )
325
346
  store = _store(ctx)
326
347
  number = version if version is not None else store.active()
@@ -155,9 +155,8 @@ def _restart_note() -> None:
155
155
  if plugin_directory() is not None:
156
156
  typer.echo(" docker compose restart daemon (in a container)")
157
157
  else:
158
- typer.echo(
159
- " bricklogger daemon restart (or: systemctl restart bricklogger)"
160
- )
158
+ typer.echo(" systemctl --user restart bricklogger (a service)")
159
+ typer.echo(" bricklogger daemon restart (started by hand)")
161
160
  typer.echo(
162
161
  "The MCP server over HTTP reads them the same way; the web interface "
163
162
  "needs nothing."
@@ -328,6 +327,7 @@ def _echo_declaration(data: dict[str, Any]) -> None:
328
327
  echo_schema(tool.get("parameters"), indent=" ")
329
328
  else:
330
329
  typer.echo(f"stores metadata: {'yes' if data.get('stores_metadata') else 'no'}")
330
+ typer.echo(f"stores the model: {'yes' if data.get('stores_model') else 'no'}")
331
331
  typer.echo("configuration:")
332
332
  echo_schema(data.get("config_schema"), indent=" ")
333
333
 
@@ -335,6 +335,8 @@ def _row(role: str, name: str, type_name: str, state: str) -> dict[str, Any]:
335
335
  "written": 0,
336
336
  "last_write": None,
337
337
  "stores_metadata": None,
338
+ "stores_model": None,
339
+ "model_version": None,
338
340
  "spool": {
339
341
  "observations": 0,
340
342
  "bytes": 0,
@@ -438,7 +440,8 @@ def _echo_detail(role: str, row: Mapping[str, Any]) -> None:
438
440
  age = spool.get("oldest_age_seconds")
439
441
  typer.echo(
440
442
  f"{row['name']}: last write {row.get('last_write') or 'never'}, "
441
- f"stores metadata {'yes' if row.get('stores_metadata') else 'no'}"
443
+ f"stores metadata {'yes' if row.get('stores_metadata') else 'no'}, "
444
+ f"stores the model {_model_note(row)}"
442
445
  )
443
446
  typer.echo(
444
447
  f" spool: {spool.get('observations', 0)} observations, "
@@ -683,3 +686,11 @@ def _file(role: str) -> str:
683
686
 
684
687
  sources_app = _role_app("source", _SourcesGroup)
685
688
  destinations_app = _role_app("destination", _DestinationsGroup)
689
+
690
+
691
+ def _model_note(row: Mapping[str, Any]) -> str:
692
+ """Whether a destination stores the model, and the version it last wrote."""
693
+ if not row.get("stores_model"):
694
+ return "no"
695
+ version = row.get("model_version")
696
+ return "yes" if version is None else f"yes (version {version})"
@@ -0,0 +1,102 @@
1
+ """``bricklogger services``: the daemon, the web interface and the MCP server as
2
+ ``systemd --user`` units of the login. See ``docs/features/cli.md``,
3
+ "services"."""
4
+
5
+ from __future__ import annotations
6
+
7
+ from pathlib import Path
8
+ from typing import Annotated
9
+
10
+ import typer
11
+ from rich.table import Table
12
+
13
+ from bricklogger.cli.context import cli_context
14
+ from bricklogger.cli.output import fail, print_json, stdout
15
+ from bricklogger.ops import services
16
+ from bricklogger.ops.errors import OperationError
17
+
18
+ services_app = typer.Typer(
19
+ no_args_is_help=True,
20
+ help="Run the daemon, the web interface and the MCP server as systemd --user "
21
+ "services of this login, started at boot.",
22
+ )
23
+
24
+ WebFlag = Annotated[bool, typer.Option("--web", help="The web interface.")]
25
+ McpFlag = Annotated[bool, typer.Option("--mcp", help="The MCP server over HTTP.")]
26
+
27
+
28
+ @services_app.command("install")
29
+ def install_command(
30
+ ctx: typer.Context, web: WebFlag = False, mcp: McpFlag = False
31
+ ) -> None:
32
+ """Set up the daemon as a service, and the web interface and the MCP server
33
+ when their flag is given; enable lingering, and start what was written.
34
+
35
+ Can be run again: a flag adds its unit and leaves the others as they are.
36
+ """
37
+ install(cli_context(ctx).config_dir, web=web, mcp=mcp)
38
+
39
+
40
+ def install(config_dir: Path, *, web: bool, mcp: bool) -> None:
41
+ """``services install``, and what ``init`` runs when it is told yes."""
42
+ try:
43
+ result = services.install(config_dir=config_dir, web=web, mcp=mcp)
44
+ except OperationError as exc:
45
+ raise fail(exc.message) from exc
46
+ if result.written:
47
+ typer.echo(f"wrote {', '.join(result.written)} in {result.unit_dir}")
48
+ if result.started:
49
+ typer.echo(f"started {', '.join(result.started)}")
50
+ if result.restarted:
51
+ typer.echo(f"restarted {', '.join(result.restarted)} with the new unit")
52
+ if not (result.written or result.started or result.restarted):
53
+ typer.echo(f"{', '.join(result.units)} already set up and running")
54
+ if result.linger_command is not None:
55
+ typer.echo(
56
+ "lingering could not be enabled, so the services stop at logout and do "
57
+ "not start at boot; enable it once with:"
58
+ )
59
+ typer.echo(f" {result.linger_command}")
60
+
61
+
62
+ @services_app.command("uninstall")
63
+ def uninstall_command(web: WebFlag = False, mcp: McpFlag = False) -> None:
64
+ """Stop, disable and remove the services: all of them without a flag, the
65
+ ones named with one. The configuration and the data stay."""
66
+ try:
67
+ result = services.uninstall(web=web, mcp=mcp)
68
+ except OperationError as exc:
69
+ raise fail(exc.message) from exc
70
+ if result.removed:
71
+ typer.echo(f"removed {', '.join(result.removed)}")
72
+ else:
73
+ typer.echo("no service was set up")
74
+
75
+
76
+ @services_app.command("status")
77
+ def status_command(
78
+ json_output: Annotated[
79
+ bool, typer.Option("--json", help="Emit JSON instead of a table.")
80
+ ] = False,
81
+ ) -> None:
82
+ """For each unit: written, enabled and active; and whether lingering is on."""
83
+ state = services.status()
84
+ if json_output:
85
+ print_json(state.as_dict())
86
+ return
87
+ table = Table(box=None)
88
+ for column in ("Unit", "Written", "Enabled", "Active"):
89
+ table.add_column(column)
90
+ for unit in state.units:
91
+ table.add_row(
92
+ unit.name, _word(unit.written), _word(unit.enabled), _word(unit.active)
93
+ )
94
+ stdout.print(table)
95
+ if not state.systemd:
96
+ typer.echo("systemctl --user does not answer on this machine")
97
+ else:
98
+ typer.echo(f"lingering: {_word(state.linger)}")
99
+
100
+
101
+ def _word(value: bool | None) -> str:
102
+ return "?" if value is None else "yes" if value else "no"
@@ -1,7 +1,7 @@
1
1
  """``bricklogger update``: what there is to upgrade, and the upgrade itself.
2
2
 
3
3
  ``update status`` installs nothing. ``update core``, ``update all`` and
4
- ``update TYPE`` upgrade from PyPI with the environment's own uv, validate with
4
+ ``update TYPE`` upgrade from PyPI with ``uv tool install``, validate with
5
5
  the new version, put the previous versions back when that fails, write the
6
6
  units again and restart what runs as a unit. ``update`` alone shows this help,
7
7
  as every group does. See ``docs/features/cli.md``, "update".
@@ -168,7 +168,7 @@ def _report(result: Updated, target: str, no_restart: bool) -> None:
168
168
  for unit in result.not_answering:
169
169
  typer.echo(
170
170
  f"{unit} was restarted but does not answer yet; check its log with "
171
- f"journalctl -u {unit}"
171
+ f"journalctl --user -u {unit}"
172
172
  )
173
173
  for note in result.notes:
174
174
  typer.echo(note)
@@ -30,7 +30,7 @@ CONFIG_FILES: tuple[str, ...] = ("daemon", "sources", "destinations", "rules")
30
30
  """The four files, by name without extension, in the order they are reported."""
31
31
 
32
32
  SYSTEM_CONFIG_DIR = Path("/etc/bricklogger")
33
- """The config directory of a machine-wide installation."""
33
+ """The config directory in the container, which names it with the variable."""
34
34
 
35
35
  SYSTEM_DATA_DIR = Path("/var/lib/bricklogger")
36
36
  """The data directory that belongs to it."""
@@ -58,22 +58,21 @@ def resolve_config_dir(
58
58
  env: Mapping[str, str] | None = None,
59
59
  home: Path | None = None,
60
60
  ) -> Path:
61
- """The flag, then the environment variable, then the system directory when it
62
- exists, then the one in the home directory."""
61
+ """The flag, then the environment variable, then the one in the home
62
+ directory."""
63
63
  if cli_value is not None:
64
64
  return cli_value
65
65
  environment = os.environ if env is None else env
66
66
  from_env = environment.get(CONFIG_DIR_ENV)
67
67
  if from_env:
68
68
  return Path(from_env)
69
- if SYSTEM_CONFIG_DIR.is_dir():
70
- return SYSTEM_CONFIG_DIR
71
69
  return user_config_dir(home)
72
70
 
73
71
 
74
72
  def default_data_dir(config_dir: Path, home: Path | None = None) -> Path:
75
- """Where the data goes when ``daemon.yaml`` does not say: beside a system
76
- configuration in ``/var/lib``, otherwise in the home directory."""
73
+ """Where the data goes when ``daemon.yaml`` does not say: beside the
74
+ container's configuration in ``/var/lib``, otherwise in the home
75
+ directory."""
77
76
  return SYSTEM_DATA_DIR if config_dir == SYSTEM_CONFIG_DIR else user_data_dir(home)
78
77
 
79
78