updates2mqtt 1.5.1__tar.gz → 1.7.0__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 (70) hide show
  1. {updates2mqtt-1.5.1 → updates2mqtt-1.7.0}/PKG-INFO +68 -33
  2. {updates2mqtt-1.5.1 → updates2mqtt-1.7.0}/README.md +48 -14
  3. {updates2mqtt-1.5.1 → updates2mqtt-1.7.0}/pyproject.toml +13 -10
  4. {updates2mqtt-1.5.1 → updates2mqtt-1.7.0}/src/updates2mqtt/app.py +23 -11
  5. {updates2mqtt-1.5.1 → updates2mqtt-1.7.0}/src/updates2mqtt/config.py +30 -18
  6. {updates2mqtt-1.5.1 → updates2mqtt-1.7.0}/src/updates2mqtt/hass_formatter.py +7 -17
  7. updates2mqtt-1.7.0/src/updates2mqtt/integrations/docker.py +606 -0
  8. updates2mqtt-1.7.0/src/updates2mqtt/integrations/docker_enrich.py +344 -0
  9. updates2mqtt-1.7.0/src/updates2mqtt/integrations/git_utils.py +123 -0
  10. updates2mqtt-1.7.0/src/updates2mqtt/model.py +246 -0
  11. {updates2mqtt-1.5.1 → updates2mqtt-1.7.0}/src/updates2mqtt/mqtt.py +28 -7
  12. updates2mqtt-1.5.1/.dockerignore +0 -13
  13. updates2mqtt-1.5.1/.github/dependabot.yml +0 -30
  14. updates2mqtt-1.5.1/.github/workflows/auto_assign_issue.yml +0 -18
  15. updates2mqtt-1.5.1/.github/workflows/auto_assign_pr.yml +0 -18
  16. updates2mqtt-1.5.1/.github/workflows/docker-publish.yml +0 -78
  17. updates2mqtt-1.5.1/.github/workflows/main.yml +0 -18
  18. updates2mqtt-1.5.1/.github/workflows/pypi-publish.yml +0 -78
  19. updates2mqtt-1.5.1/.github/workflows/python-package.yml +0 -67
  20. updates2mqtt-1.5.1/.gitignore +0 -171
  21. updates2mqtt-1.5.1/.hintrc +0 -8
  22. updates2mqtt-1.5.1/.pre-commit-config.yaml +0 -71
  23. updates2mqtt-1.5.1/.python-version +0 -1
  24. updates2mqtt-1.5.1/.safety-project.ini +0 -5
  25. updates2mqtt-1.5.1/CHANGELOG.md +0 -42
  26. updates2mqtt-1.5.1/Dockerfile +0 -39
  27. updates2mqtt-1.5.1/LICENSE +0 -201
  28. updates2mqtt-1.5.1/SECURITY.md +0 -29
  29. updates2mqtt-1.5.1/common_packages.yaml +0 -119
  30. updates2mqtt-1.5.1/conftest.py +0 -147
  31. updates2mqtt-1.5.1/docs/configuration.md +0 -149
  32. updates2mqtt-1.5.1/docs/examples/config_maximal.md +0 -7
  33. updates2mqtt-1.5.1/docs/examples/config_minimal.md +0 -7
  34. updates2mqtt-1.5.1/docs/examples/docker_compose.md +0 -9
  35. updates2mqtt-1.5.1/docs/examples/env.md +0 -15
  36. updates2mqtt-1.5.1/docs/examples/index.md +0 -5
  37. updates2mqtt-1.5.1/docs/home_assistant.md +0 -51
  38. updates2mqtt-1.5.1/docs/images/ha_entities.png +0 -0
  39. updates2mqtt-1.5.1/docs/images/ha_mqtt_discovery.png +0 -0
  40. updates2mqtt-1.5.1/docs/images/ha_update_detail.png +0 -0
  41. updates2mqtt-1.5.1/docs/images/ha_update_dialog.png +0 -0
  42. updates2mqtt-1.5.1/docs/images/ha_update_page.png +0 -0
  43. updates2mqtt-1.5.1/docs/images/logo-blank-256x256.png +0 -0
  44. updates2mqtt-1.5.1/docs/images/updates2mqtt-dark-256x256.png +0 -0
  45. updates2mqtt-1.5.1/docs/index.md +0 -1
  46. updates2mqtt-1.5.1/docs/installation.md +0 -58
  47. updates2mqtt-1.5.1/docs/local_builds.md +0 -22
  48. updates2mqtt-1.5.1/docs/robots.txt +0 -4
  49. updates2mqtt-1.5.1/docs/troubleshooting.md +0 -171
  50. updates2mqtt-1.5.1/examples/config.yaml.maximal +0 -37
  51. updates2mqtt-1.5.1/examples/config.yaml.minimal +0 -9
  52. updates2mqtt-1.5.1/examples/docker-compose.yaml +0 -18
  53. updates2mqtt-1.5.1/mkdocs.yml +0 -81
  54. updates2mqtt-1.5.1/refresh-deps.sh +0 -7
  55. updates2mqtt-1.5.1/scripts/healthcheck.sh +0 -91
  56. updates2mqtt-1.5.1/src/updates2mqtt/integrations/docker.py +0 -454
  57. updates2mqtt-1.5.1/src/updates2mqtt/integrations/git_utils.py +0 -66
  58. updates2mqtt-1.5.1/src/updates2mqtt/model.py +0 -112
  59. updates2mqtt-1.5.1/tests/__init__.py +0 -0
  60. updates2mqtt-1.5.1/tests/test_app.py +0 -130
  61. updates2mqtt-1.5.1/tests/test_config.py +0 -99
  62. updates2mqtt-1.5.1/tests/test_docker.py +0 -87
  63. updates2mqtt-1.5.1/tests/test_git_utils.py +0 -49
  64. updates2mqtt-1.5.1/tests/test_hass_formatter.py +0 -82
  65. updates2mqtt-1.5.1/tests/test_mqtt.py +0 -143
  66. updates2mqtt-1.5.1/uv.lock +0 -1454
  67. {updates2mqtt-1.5.1 → updates2mqtt-1.7.0}/src/updates2mqtt/__init__.py +0 -0
  68. {updates2mqtt-1.5.1 → updates2mqtt-1.7.0}/src/updates2mqtt/__main__.py +0 -0
  69. {updates2mqtt-1.5.1 → updates2mqtt-1.7.0}/src/updates2mqtt/integrations/__init__.py +0 -0
  70. {updates2mqtt-1.5.1 → updates2mqtt-1.7.0}/src/updates2mqtt/py.typed +0 -0
@@ -1,38 +1,39 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: updates2mqtt
3
- Version: 1.5.1
3
+ Version: 1.7.0
4
4
  Summary: System update and docker image notification and execution over MQTT
5
- Project-URL: Homepage, https://updates2mqtt.rhizomatics.org.uk
6
- Project-URL: Repository, https://github.com/rhizomatics/updates2mqtt
7
- Project-URL: Documentation, https://updates2mqtt.rhizomatics.org.uk
8
- Project-URL: Issues, https://github.com/rhizomatics/updates2mqtt/issues
9
- Project-URL: Changelog, https://github.com/rhizomatics/updates2mqtt/blob/main/CHANGELOG.md
5
+ Keywords: mqtt,docker,oci,container,updates,automation,home-assistant,homeassistant,selfhosting
6
+ Author: jey burrows
10
7
  Author-email: jey burrows <jrb@rhizomatics.org.uk>
11
8
  License-Expression: Apache-2.0
12
- License-File: LICENSE
13
- Keywords: automation,docker,home-assistant,homeassistant,mqtt,selfhosting,updates
14
9
  Classifier: Development Status :: 5 - Production/Stable
15
- Classifier: Environment :: Console
16
- Classifier: Intended Audience :: System Administrators
17
- Classifier: License :: OSI Approved :: Apache Software License
18
10
  Classifier: License :: Other/Proprietary License
19
11
  Classifier: Natural Language :: English
20
12
  Classifier: Operating System :: OS Independent
21
- Classifier: Programming Language :: Python
22
- Classifier: Programming Language :: Python :: 3.13
13
+ Classifier: Environment :: Console
23
14
  Classifier: Topic :: Home Automation
24
- Classifier: Topic :: System :: Monitoring
25
15
  Classifier: Topic :: System :: Systems Administration
16
+ Classifier: Topic :: System :: Monitoring
17
+ Classifier: Intended Audience :: System Administrators
18
+ Classifier: License :: OSI Approved :: Apache Software License
26
19
  Classifier: Typing :: Typed
27
- Requires-Python: >=3.13
20
+ Classifier: Programming Language :: Python
21
+ Classifier: Programming Language :: Python :: 3.13
28
22
  Requires-Dist: docker>=7.1.0
29
- Requires-Dist: hishel[httpx]>=0.1.4
30
- Requires-Dist: httpx>=0.28.1
31
- Requires-Dist: omegaconf>=2.3.0
32
23
  Requires-Dist: paho-mqtt>=2.1.0
33
- Requires-Dist: rich>=14.0.0
24
+ Requires-Dist: omegaconf>=2.3.0
34
25
  Requires-Dist: structlog>=25.4.0
26
+ Requires-Dist: rich>=14.0.0
27
+ Requires-Dist: httpx>=0.28.1
28
+ Requires-Dist: hishel[httpx]>=0.1.4
35
29
  Requires-Dist: usingversion>=0.1.2
30
+ Requires-Dist: tzlocal>=5.3.1
31
+ Requires-Python: >=3.13
32
+ Project-URL: Homepage, https://updates2mqtt.rhizomatics.org.uk
33
+ Project-URL: Repository, https://github.com/rhizomatics/updates2mqtt
34
+ Project-URL: Documentation, https://updates2mqtt.rhizomatics.org.uk
35
+ Project-URL: Issues, https://github.com/rhizomatics/updates2mqtt/issues
36
+ Project-URL: Changelog, https://github.com/rhizomatics/updates2mqtt/blob/main/CHANGELOG.md
36
37
  Description-Content-Type: text/markdown
37
38
 
38
39
  ![updates2mqtt](images/updates2mqtt-dark-256x256.png){ align=left }
@@ -42,7 +43,7 @@ Description-Content-Type: text/markdown
42
43
  [![Rhizomatics Open Source](https://img.shields.io/badge/rhizomatics%20open%20source-lightseagreen)](https://github.com/rhizomatics)
43
44
 
44
45
  [![PyPI - Version](https://img.shields.io/pypi/v/updates2mqtt)](https://pypi.org/project/updates2mqtt/)
45
- [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/rhizomatics/supernotify)
46
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/rhizomatics/updates2mqtt)
46
47
  [![Coverage](https://raw.githubusercontent.com/rhizomatics/updates2mqtt/refs/heads/badges/badges/coverage.svg)](https://updates2mqtt.rhizomatics.org.uk/developer/coverage/)
47
48
  ![Tests](https://raw.githubusercontent.com/rhizomatics/updates2mqtt/refs/heads/badges/badges/tests.svg)
48
49
  [![pre-commit.ci status](https://results.pre-commit.ci/badge/github/rhizomatics/updates2mqtt/main.svg)](https://results.pre-commit.ci/latest/github/rhizomatics/updates2mqtt/main)
@@ -69,7 +70,7 @@ Read the release notes, and optionally click *Update* to trigger a Docker *pull*
69
70
 
70
71
  ## Description
71
72
 
72
- updates2mqtt perioidically checks for new versions of components being available, and publishes new version info to MQTT. HomeAssistant auto discovery is supported, so all updates can be seen in the same place as Home Assistant's own components and add-ins.
73
+ Updates2MQTT perioidically checks for new versions of components being available, and publishes new version info to MQTT. HomeAssistant auto discovery is supported, so all updates can be seen in the same place as Home Assistant's own components and add-ins.
73
74
 
74
75
  Currently only Docker containers are supported, either via an image registry check, or a git repo for source (see [Local Builds](local_builds.md)). The design is modular, so other update sources can be added, at least for notification. The next anticipated is **apt** for Debian based systems.
75
76
 
@@ -79,8 +80,14 @@ To get started, read the [Installation](installation.md) and [Configuration](con
79
80
 
80
81
  For a quick spin, try this:
81
82
 
82
- ```yaml
83
- docker run -e MQTT_USER=user1 -e MQTT_PASS=pass1 -e MQTT_HOST=192.168.1.5 ghcr.io/rhizomatics/updates2mqtt:release
83
+ ```bash
84
+ docker run -v /var/run/docker.sock:/var/run/docker.sock -e MQTT_USER=user1 -e MQTT_PASS=user1 -e MQTT_HOST=192.168.1.5 ghcr.io/rhizomatics/updates2mqtt:release
85
+ ```
86
+
87
+ or without Docker, using [uv](https://docs.astral.sh/uv/)
88
+
89
+ ```bash
90
+ export MQTT_HOST=192.168.1.1;export MQTT_USER=user1;export MQTT_PASS=user1;uv run --with updates2mqtt python -m updates2mqtt
84
91
  ```
85
92
 
86
93
  ## Release Support
@@ -93,7 +100,7 @@ Presently only Docker containers are supported, although others are planned, pro
93
100
 
94
101
  ## Heartbeat
95
102
 
96
- A heartbeat JSON payload is optionally published periodically to a configurable MQTT topic, defaulting to `healthcheck/{node_name}/updates2mqtt`. It contains the current version of updates2mqtt, the node name, a timestamp, and some basic stats.
103
+ A heartbeat JSON payload is optionally published periodically to a configurable MQTT topic, defaulting to `healthcheck/{node_name}/updates2mqtt`. It contains the current version of Updates2MQTT, the node name, a timestamp, and some basic stats.
97
104
 
98
105
  ## Healthcheck
99
106
 
@@ -126,7 +133,8 @@ file, or as `environment` options inside `docker-compose.yaml`.
126
133
  ### Automated updates
127
134
 
128
135
  If Docker containers should be immediately updated, without any confirmation
129
- or trigger, *e.g.* from the HomeAssistant update dialog, then set an environment variable `UPD2MQTT_UPDATE` in the target container to `Auto` ( it defaults to `Passive`)
136
+ or trigger, *e.g.* from the HomeAssistant update dialog, then set an environment variable `UPD2MQTT_UPDATE` in the target container to `Auto` ( it defaults to `Passive`). If you want it to update without publishing to MQTT and being
137
+ visible to Home Assistant, then use `Silent`.
130
138
 
131
139
  ```yaml title="Example Compose Snippet"
132
140
  restarter:
@@ -136,17 +144,42 @@ restarter:
136
144
  - UPD2MQTT_UPDATE=AUTO
137
145
  ```
138
146
 
147
+ Automated updates can also apply to local builds, where a `git_repo_path` has been defined - if there are remote
148
+ commits available to pull, then a `git pull`, `docker compose build` and `docker compose up` will be executed.
149
+
139
150
  ### Environment Variables
140
151
 
141
152
  The following environment variables can be used to configure containers for `updates2mqtt`:
142
153
 
143
- | Env Var | Description | Default |
144
- |---------| ------------|----------|
145
- | `UPD2MQTT_UPDATE` | Update mode, either `Passive` or `Auto`. If `Auto`, updates will be installed automatically. | `Passive` |
146
- | `UPD2MQTT_PICTURE` | URL to an icon to use in Home Assistant. | Docker logo URL |
147
- | `UPD2MQTT_RELNOTES` | URL to release notes for the package. | |
148
- | `UPD2MQTT_GIT_REPO_PATH` | Relative path to a local git repo if the image is built locally. | |
149
- | `UPD2MQTT_IGNORE` | If set to `True`, the container will be ignored by updates2mqtt. | False |
154
+ | Env Var | Description | Default |
155
+ |----------------------------|----------------------------------------------------------------------------------------------|-----------------|
156
+ | `UPD2MQTT_UPDATE` | Update mode, either `Passive` or `Auto`. If `Auto`, updates will be installed automatically. | `Passive` |
157
+ | `UPD2MQTT_PICTURE` | URL to an icon to use in Home Assistant. | Docker logo URL |
158
+ | `UPD2MQTT_RELNOTES` | URL to release notes for the package. | |
159
+ | `UPD2MQTT_GIT_REPO_PATH` | Relative path to a local git repo if the image is built locally. | |
160
+ | `UPD2MQTT_IGNORE` | If set to `True`, the container will be ignored by Updates2MQTT. | False |
161
+ | |
162
+
163
+ ### Docker Labels
164
+
165
+ Alternatively, use Docker labels
166
+
167
+ | Label | Env Var |
168
+ |--------------------------------|----------------------------|
169
+ | `updates2mqtt.update` | `UPD2MQTT_UPDATE` |
170
+ | `updates2mqtt.picture` | `UPD2MQTT_PCITURE` |
171
+ | `updates2mqtt.relnotes` | `UPD2MQTT_RELNOTES` |
172
+ | `updates2mqtt.git_repo_path` | `UPD2MQTT_GIT_REPO_PATH` |
173
+ | `updates2mqtt.ignore` | `UPD2MQTT_IGNORE` |
174
+
175
+
176
+ ```yaml title="Example Compose Snippet"
177
+ restarter:
178
+ image: docker:cli
179
+ command: ["/bin/sh", "-c", "while true; do sleep 86400; docker restart mailserver; done"]
180
+ labels:
181
+ updates2mqtt.relnotes: https://component.my.com/release_notes
182
+ ```
150
183
 
151
184
 
152
185
  ## Related Projects
@@ -157,6 +190,8 @@ Other apps useful for self-hosting with the help of MQTT:
157
190
 
158
191
  Find more at [awesome-mqtt](https://github.com/rhizomatics/awesome-mqtt)
159
192
 
193
+ For a more powerful Docker update manager, try [What's Up Docker](https://getwud.github.io/wud/)
194
+
160
195
  ## Development
161
196
 
162
197
  This component relies on several open source packages:
@@ -169,4 +204,4 @@ This component relies on several open source packages:
169
204
  - [httpx](https://www.python-httpx.org) for retrieving metadata
170
205
  - The Astral [uv](https://docs.astral.sh/uv/) and [ruff](https://docs.astral.sh/ruff/) tools for development and build
171
206
  - [pytest](https://docs.pytest.org/en/stable/) and supporting add-ins for automated testing
172
- - [usingversion](https://pypi.org/project/usingversion/) to log current version info
207
+ - [usingversion](https://pypi.org/project/usingversion/) to log current version info
@@ -5,7 +5,7 @@
5
5
  [![Rhizomatics Open Source](https://img.shields.io/badge/rhizomatics%20open%20source-lightseagreen)](https://github.com/rhizomatics)
6
6
 
7
7
  [![PyPI - Version](https://img.shields.io/pypi/v/updates2mqtt)](https://pypi.org/project/updates2mqtt/)
8
- [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/rhizomatics/supernotify)
8
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/rhizomatics/updates2mqtt)
9
9
  [![Coverage](https://raw.githubusercontent.com/rhizomatics/updates2mqtt/refs/heads/badges/badges/coverage.svg)](https://updates2mqtt.rhizomatics.org.uk/developer/coverage/)
10
10
  ![Tests](https://raw.githubusercontent.com/rhizomatics/updates2mqtt/refs/heads/badges/badges/tests.svg)
11
11
  [![pre-commit.ci status](https://results.pre-commit.ci/badge/github/rhizomatics/updates2mqtt/main.svg)](https://results.pre-commit.ci/latest/github/rhizomatics/updates2mqtt/main)
@@ -32,7 +32,7 @@ Read the release notes, and optionally click *Update* to trigger a Docker *pull*
32
32
 
33
33
  ## Description
34
34
 
35
- updates2mqtt perioidically checks for new versions of components being available, and publishes new version info to MQTT. HomeAssistant auto discovery is supported, so all updates can be seen in the same place as Home Assistant's own components and add-ins.
35
+ Updates2MQTT perioidically checks for new versions of components being available, and publishes new version info to MQTT. HomeAssistant auto discovery is supported, so all updates can be seen in the same place as Home Assistant's own components and add-ins.
36
36
 
37
37
  Currently only Docker containers are supported, either via an image registry check, or a git repo for source (see [Local Builds](local_builds.md)). The design is modular, so other update sources can be added, at least for notification. The next anticipated is **apt** for Debian based systems.
38
38
 
@@ -42,8 +42,14 @@ To get started, read the [Installation](installation.md) and [Configuration](con
42
42
 
43
43
  For a quick spin, try this:
44
44
 
45
- ```yaml
46
- docker run -e MQTT_USER=user1 -e MQTT_PASS=pass1 -e MQTT_HOST=192.168.1.5 ghcr.io/rhizomatics/updates2mqtt:release
45
+ ```bash
46
+ docker run -v /var/run/docker.sock:/var/run/docker.sock -e MQTT_USER=user1 -e MQTT_PASS=user1 -e MQTT_HOST=192.168.1.5 ghcr.io/rhizomatics/updates2mqtt:release
47
+ ```
48
+
49
+ or without Docker, using [uv](https://docs.astral.sh/uv/)
50
+
51
+ ```bash
52
+ export MQTT_HOST=192.168.1.1;export MQTT_USER=user1;export MQTT_PASS=user1;uv run --with updates2mqtt python -m updates2mqtt
47
53
  ```
48
54
 
49
55
  ## Release Support
@@ -56,7 +62,7 @@ Presently only Docker containers are supported, although others are planned, pro
56
62
 
57
63
  ## Heartbeat
58
64
 
59
- A heartbeat JSON payload is optionally published periodically to a configurable MQTT topic, defaulting to `healthcheck/{node_name}/updates2mqtt`. It contains the current version of updates2mqtt, the node name, a timestamp, and some basic stats.
65
+ A heartbeat JSON payload is optionally published periodically to a configurable MQTT topic, defaulting to `healthcheck/{node_name}/updates2mqtt`. It contains the current version of Updates2MQTT, the node name, a timestamp, and some basic stats.
60
66
 
61
67
  ## Healthcheck
62
68
 
@@ -89,7 +95,8 @@ file, or as `environment` options inside `docker-compose.yaml`.
89
95
  ### Automated updates
90
96
 
91
97
  If Docker containers should be immediately updated, without any confirmation
92
- or trigger, *e.g.* from the HomeAssistant update dialog, then set an environment variable `UPD2MQTT_UPDATE` in the target container to `Auto` ( it defaults to `Passive`)
98
+ or trigger, *e.g.* from the HomeAssistant update dialog, then set an environment variable `UPD2MQTT_UPDATE` in the target container to `Auto` ( it defaults to `Passive`). If you want it to update without publishing to MQTT and being
99
+ visible to Home Assistant, then use `Silent`.
93
100
 
94
101
  ```yaml title="Example Compose Snippet"
95
102
  restarter:
@@ -99,17 +106,42 @@ restarter:
99
106
  - UPD2MQTT_UPDATE=AUTO
100
107
  ```
101
108
 
109
+ Automated updates can also apply to local builds, where a `git_repo_path` has been defined - if there are remote
110
+ commits available to pull, then a `git pull`, `docker compose build` and `docker compose up` will be executed.
111
+
102
112
  ### Environment Variables
103
113
 
104
114
  The following environment variables can be used to configure containers for `updates2mqtt`:
105
115
 
106
- | Env Var | Description | Default |
107
- |---------| ------------|----------|
108
- | `UPD2MQTT_UPDATE` | Update mode, either `Passive` or `Auto`. If `Auto`, updates will be installed automatically. | `Passive` |
109
- | `UPD2MQTT_PICTURE` | URL to an icon to use in Home Assistant. | Docker logo URL |
110
- | `UPD2MQTT_RELNOTES` | URL to release notes for the package. | |
111
- | `UPD2MQTT_GIT_REPO_PATH` | Relative path to a local git repo if the image is built locally. | |
112
- | `UPD2MQTT_IGNORE` | If set to `True`, the container will be ignored by updates2mqtt. | False |
116
+ | Env Var | Description | Default |
117
+ |----------------------------|----------------------------------------------------------------------------------------------|-----------------|
118
+ | `UPD2MQTT_UPDATE` | Update mode, either `Passive` or `Auto`. If `Auto`, updates will be installed automatically. | `Passive` |
119
+ | `UPD2MQTT_PICTURE` | URL to an icon to use in Home Assistant. | Docker logo URL |
120
+ | `UPD2MQTT_RELNOTES` | URL to release notes for the package. | |
121
+ | `UPD2MQTT_GIT_REPO_PATH` | Relative path to a local git repo if the image is built locally. | |
122
+ | `UPD2MQTT_IGNORE` | If set to `True`, the container will be ignored by Updates2MQTT. | False |
123
+ | |
124
+
125
+ ### Docker Labels
126
+
127
+ Alternatively, use Docker labels
128
+
129
+ | Label | Env Var |
130
+ |--------------------------------|----------------------------|
131
+ | `updates2mqtt.update` | `UPD2MQTT_UPDATE` |
132
+ | `updates2mqtt.picture` | `UPD2MQTT_PCITURE` |
133
+ | `updates2mqtt.relnotes` | `UPD2MQTT_RELNOTES` |
134
+ | `updates2mqtt.git_repo_path` | `UPD2MQTT_GIT_REPO_PATH` |
135
+ | `updates2mqtt.ignore` | `UPD2MQTT_IGNORE` |
136
+
137
+
138
+ ```yaml title="Example Compose Snippet"
139
+ restarter:
140
+ image: docker:cli
141
+ command: ["/bin/sh", "-c", "while true; do sleep 86400; docker restart mailserver; done"]
142
+ labels:
143
+ updates2mqtt.relnotes: https://component.my.com/release_notes
144
+ ```
113
145
 
114
146
 
115
147
  ## Related Projects
@@ -120,6 +152,8 @@ Other apps useful for self-hosting with the help of MQTT:
120
152
 
121
153
  Find more at [awesome-mqtt](https://github.com/rhizomatics/awesome-mqtt)
122
154
 
155
+ For a more powerful Docker update manager, try [What's Up Docker](https://getwud.github.io/wud/)
156
+
123
157
  ## Development
124
158
 
125
159
  This component relies on several open source packages:
@@ -132,4 +166,4 @@ This component relies on several open source packages:
132
166
  - [httpx](https://www.python-httpx.org) for retrieving metadata
133
167
  - The Astral [uv](https://docs.astral.sh/uv/) and [ruff](https://docs.astral.sh/ruff/) tools for development and build
134
168
  - [pytest](https://docs.pytest.org/en/stable/) and supporting add-ins for automated testing
135
- - [usingversion](https://pypi.org/project/usingversion/) to log current version info
169
+ - [usingversion](https://pypi.org/project/usingversion/) to log current version info
@@ -7,9 +7,9 @@ authors = [
7
7
  ]
8
8
 
9
9
  requires-python = ">=3.13"
10
- version = "1.5.1"
10
+ version = "1.7.0"
11
11
  license="Apache-2.0"
12
- keywords=["mqtt", "docker", "updates", "automation","home-assistant","homeassistant","selfhosting"]
12
+ keywords=["mqtt", "docker", "oci","container","updates", "automation","home-assistant","homeassistant","selfhosting"]
13
13
 
14
14
  dependencies = [
15
15
  "docker>=7.1.0",
@@ -20,6 +20,7 @@ dependencies = [
20
20
  "httpx>=0.28.1",
21
21
  "hishel[httpx]>=0.1.4",
22
22
  "usingversion>=0.1.2",
23
+ "tzlocal>=5.3.1",
23
24
  ]
24
25
  classifiers = [
25
26
  "Development Status :: 5 - Production/Stable",
@@ -59,7 +60,7 @@ dev = [
59
60
  "icdiff",
60
61
  "genbadge[all]"
61
62
  ]
62
- mkdocs=[
63
+ docs=[
63
64
  "mkdocs",
64
65
  "mkdocs-material",
65
66
  "mkdocs-minify-plugin",
@@ -70,14 +71,17 @@ mkdocs=[
70
71
  "pymdown-extensions",
71
72
  "mkdocs-git-revision-date-localized-plugin",
72
73
  "mkdocs-meta-descriptions-plugin",
73
- "pngquant"
74
+ "pngquant",
75
+ "mkdocs-mermaid2-plugin"
74
76
  ]
75
77
 
76
78
  [build-system]
77
- build-backend = "hatchling.build"
78
- requires=[
79
- "hatchling"
80
- ]
79
+ requires = ["uv_build>=0.9.18,<0.10.0"]
80
+ build-backend = "uv_build"
81
+
82
+ [tool.uv.build-backend]
83
+ module-root = "src"
84
+ module-name = "updates2mqtt"
81
85
 
82
86
  [tool.bandit]
83
87
  exclude_dirs = ["tests"]
@@ -148,7 +152,6 @@ skip-magic-trailing-comma = false
148
152
  # Like Black, automatically detect the appropriate line ending.
149
153
  line-ending = "auto"
150
154
 
151
-
152
155
  [tool.pytest.ini_options]
153
156
  asyncio_mode = "auto"
154
157
  testpaths = [
@@ -185,4 +188,4 @@ Homepage="https://updates2mqtt.rhizomatics.org.uk"
185
188
  Repository="https://github.com/rhizomatics/updates2mqtt"
186
189
  Documentation="https://updates2mqtt.rhizomatics.org.uk"
187
190
  Issues="https://github.com/rhizomatics/updates2mqtt/issues"
188
- Changelog="https://github.com/rhizomatics/updates2mqtt/blob/main/CHANGELOG.md"
191
+ Changelog="https://github.com/rhizomatics/updates2mqtt/blob/main/CHANGELOG.md"
@@ -14,7 +14,7 @@ import structlog
14
14
  import updates2mqtt
15
15
  from updates2mqtt.model import Discovery, ReleaseProvider
16
16
 
17
- from .config import Config, PackageUpdateInfo, load_app_config, load_package_info
17
+ from .config import Config, PublishPolicy, UpdatePolicy, load_app_config
18
18
  from .integrations.docker import DockerProvider
19
19
  from .mqtt import MqttPublisher
20
20
 
@@ -39,14 +39,16 @@ class App:
39
39
  self.last_scan_timestamp: str | None = None
40
40
  app_config: Config | None = load_app_config(CONF_FILE)
41
41
  if app_config is None:
42
- log.error(f"Invalid configuration at {CONF_FILE}, edit config to fix missing or invalid values and restart")
42
+ log.error(f"Invalid configuration at {CONF_FILE}")
43
+ log.error("Edit config to fix missing or invalid values and restart")
44
+ log.error("Alternately supply correct MQTT_HOST,MQTT_USER,MQTT_PASSWORD environment variables")
43
45
  log.error("Exiting app")
44
46
  sys.exit(1)
45
47
  self.cfg: Config = app_config
48
+ self.self_bounce: Event = Event()
46
49
 
47
50
  structlog.configure(wrapper_class=structlog.make_filtering_bound_logger(getattr(logging, str(self.cfg.log.level))))
48
51
  log.debug("Logging initialized", level=self.cfg.log.level)
49
- self.common_pkg: dict[str, PackageUpdateInfo] = load_package_info(PKG_INFO_FILE)
50
52
 
51
53
  self.publisher = MqttPublisher(self.cfg.mqtt, self.cfg.node, self.cfg.homeassistant)
52
54
 
@@ -54,7 +56,7 @@ class App:
54
56
  self.scan_count: int = 0
55
57
  self.last_scan: str | None = None
56
58
  if self.cfg.docker.enabled:
57
- self.scanners.append(DockerProvider(self.cfg.docker, self.common_pkg, self.cfg.node))
59
+ self.scanners.append(DockerProvider(self.cfg.docker, self.cfg.node, self.self_bounce))
58
60
  self.stopped = Event()
59
61
  self.healthcheck_topic = self.cfg.node.healthcheck.topic_template.format(node_name=self.cfg.node.name)
60
62
 
@@ -101,6 +103,7 @@ class App:
101
103
  )
102
104
 
103
105
  for scanner in self.scanners:
106
+ scanner.initialize()
104
107
  self.publisher.subscribe_hass_command(scanner)
105
108
 
106
109
  while not self.stopped.is_set() and self.publisher.is_available():
@@ -119,11 +122,18 @@ class App:
119
122
  async def on_discovery(self, discovery: Discovery) -> None:
120
123
  dlog = log.bind(name=discovery.name)
121
124
  try:
122
- if self.cfg.homeassistant.discovery.enabled:
125
+ if discovery.publish_policy == PublishPolicy.HOMEASSISTANT and self.cfg.homeassistant.discovery.enabled:
126
+ # Switch off MQTT discovery if not Home Assistant enabled
123
127
  self.publisher.publish_hass_config(discovery)
124
-
125
- self.publisher.publish_hass_state(discovery)
126
- if discovery.update_policy == "Auto":
128
+ if discovery.publish_policy in (PublishPolicy.HOMEASSISTANT):
129
+ self.publisher.publish_hass_state(discovery)
130
+ if discovery.publish_policy in (PublishPolicy.HOMEASSISTANT, PublishPolicy.MQTT):
131
+ self.publisher.publish_discovery(discovery)
132
+ if (
133
+ discovery.update_policy == UpdatePolicy.AUTO
134
+ and discovery.can_update
135
+ and discovery.latest_version != discovery.current_version
136
+ ):
127
137
  # TODO: review auto update, trigger by version, use update interval as throttle
128
138
  elapsed: float = (
129
139
  time.time() - discovery.update_last_attempt if discovery.update_last_attempt is not None else -1
@@ -155,7 +165,11 @@ class App:
155
165
  log.debug("Cancellation task completed")
156
166
 
157
167
  def shutdown(self, *args, exit_code: int = 143) -> None: # noqa: ANN002, ARG002
158
- log.info("Shutting down, exit_code: %s", exit_code)
168
+ if self.self_bounce.is_set():
169
+ exit_code = 1
170
+ log.info("Self bouncing, overriding exit_code: %s", exit_code)
171
+ else:
172
+ log.info("Shutting down, exit_code: %s", exit_code)
159
173
  self.stopped.set()
160
174
  for scanner in self.scanners:
161
175
  scanner.stop()
@@ -206,8 +220,6 @@ def run() -> None:
206
220
  import asyncio
207
221
  import signal
208
222
 
209
- from .app import App
210
-
211
223
  # pyright: ignore[reportAttributeAccessIssue]
212
224
  log.debug(f"Starting updates2mqtt v{updates2mqtt.version}") # pyright: ignore[reportAttributeAccessIssue]
213
225
  app = App()
@@ -9,6 +9,20 @@ from omegaconf import MISSING, DictConfig, MissingMandatoryValue, OmegaConf, Val
9
9
 
10
10
  log = structlog.get_logger()
11
11
 
12
+ PKG_INFO_FILE = Path("./common_packages.yaml")
13
+ NO_KNOWN_IMAGE = "UNKNOWN"
14
+
15
+
16
+ class UpdatePolicy(StrEnum):
17
+ AUTO = "Auto"
18
+ PASSIVE = "Passive"
19
+
20
+
21
+ class PublishPolicy(StrEnum):
22
+ HOMEASSISTANT = "HomeAssistant"
23
+ MQTT = "MQTT"
24
+ SILENT = "Silent"
25
+
12
26
 
13
27
  class LogLevel(StrEnum):
14
28
  DEBUG = "DEBUG"
@@ -18,6 +32,13 @@ class LogLevel(StrEnum):
18
32
  CRITICAL = "CRITICAL"
19
33
 
20
34
 
35
+ class VersionType:
36
+ SHORT_SHA = "short_sha"
37
+ FULL_SHA = "full_sha"
38
+ VERSION_REVISION = "version_revision"
39
+ VERSION = "version"
40
+
41
+
21
42
  @dataclass
22
43
  class MqttConfig:
23
44
  host: str = "${oc.env:MQTT_HOST,localhost}"
@@ -34,6 +55,12 @@ class MetadataSourceConfig:
34
55
  cache_ttl: int = 60 * 60 * 24 * 7 # 1 week
35
56
 
36
57
 
58
+ @dataclass
59
+ class Selector:
60
+ include: list[str] | None = None
61
+ exclude: list[str] | None = None
62
+
63
+
37
64
  @dataclass
38
65
  class DockerConfig:
39
66
  enabled: bool = True
@@ -47,6 +74,8 @@ class DockerConfig:
47
74
  discover_metadata: dict[str, MetadataSourceConfig] = field(
48
75
  default_factory=lambda: {"linuxserver.io": MetadataSourceConfig(enabled=True)}
49
76
  )
77
+ default_api_backoff: int = 60 * 15
78
+ image_ref_select: Selector = field(default_factory=lambda: Selector())
50
79
 
51
80
 
52
81
  @dataclass
@@ -61,6 +90,7 @@ class HomeAssistantConfig:
61
90
  state_topic_suffix: str = "state"
62
91
  device_creation: bool = True
63
92
  force_command_topic: bool = False
93
+ extra_attributes: bool = True
64
94
  area: str | None = None
65
95
 
66
96
 
@@ -114,24 +144,6 @@ class IncompleteConfigException(BaseException):
114
144
  pass
115
145
 
116
146
 
117
- def load_package_info(pkginfo_file_path: Path) -> dict[str, PackageUpdateInfo]:
118
- if pkginfo_file_path.exists():
119
- log.debug("Loading common package update info", path=pkginfo_file_path)
120
- cfg = OmegaConf.load(pkginfo_file_path)
121
- else:
122
- log.warn("No common package update info found", path=pkginfo_file_path)
123
- cfg = OmegaConf.structured(UpdateInfoConfig)
124
- try:
125
- # omegaconf broken-ness on optional fields and converting to backclasses
126
- pkg_conf: dict[str, PackageUpdateInfo] = {
127
- pkg: PackageUpdateInfo(**pkg_cfg) for pkg, pkg_cfg in cfg.common_packages.items()
128
- }
129
- return pkg_conf
130
- except (MissingMandatoryValue, ValidationError) as e:
131
- log.error("Configuration error %s", e, path=pkginfo_file_path.as_posix())
132
- raise
133
-
134
-
135
147
  def is_autogen_config() -> bool:
136
148
  env_var: str | None = os.environ.get("U2M_AUTOGEN_CONFIG")
137
149
  return not (env_var and env_var.lower() in ("no", "0", "false"))
@@ -23,28 +23,26 @@ def hass_format_config(
23
23
  object_id: str,
24
24
  state_topic: str,
25
25
  command_topic: str | None,
26
+ attrs_topic: str | None,
26
27
  force_command_topic: bool | None,
27
28
  device_creation: bool = True,
28
29
  area: str | None = None,
29
- session: str | None = None,
30
30
  ) -> dict[str, Any]:
31
31
  config: dict[str, Any] = {
32
32
  "name": discovery.title,
33
33
  "device_class": None, # not firmware, so defaults to null
34
34
  "unique_id": object_id,
35
35
  "state_topic": state_topic,
36
- "source_session": session,
37
36
  "supported_features": discovery.features,
38
- "can_update": discovery.can_update,
39
- "can_build": discovery.can_build,
40
- "can_restart": discovery.can_restart,
41
- "update_policy": discovery.update_policy,
37
+ "default_entity_id": f"update.{discovery.node}_{discovery.provider.source_type}_{discovery.name}",
42
38
  "origin": {
43
39
  "name": f"{discovery.node} updates2mqtt",
44
40
  "sw_version": updates2mqtt.version, # pyright: ignore[reportAttributeAccessIssue]
45
41
  "support_url": "https://github.com/rhizomatics/updates2mqtt/issues",
46
42
  },
47
43
  }
44
+ if attrs_topic:
45
+ config["json_attributes_topic"] = attrs_topic
48
46
  if discovery.entity_picture_url:
49
47
  config["entity_picture"] = discovery.entity_picture_url
50
48
  if discovery.device_icon:
@@ -62,14 +60,12 @@ def hass_format_config(
62
60
  config["command_topic"] = command_topic
63
61
  if discovery.can_update:
64
62
  config["payload_install"] = f"{discovery.source_type}|{discovery.name}|install"
65
- if discovery.custom.get("git_repo_path"):
66
- config["git_repo_path"] = discovery.custom["git_repo_path"]
67
- config.update(discovery.provider.hass_config_format(discovery))
63
+
68
64
  return config
69
65
 
70
66
 
71
67
  def hass_format_state(discovery: Discovery, session: str, in_progress: bool = False) -> dict[str, Any]: # noqa: ARG001
72
- state = {
68
+ state: dict[str, str | dict | list | bool | None] = {
73
69
  "installed_version": discovery.current_version,
74
70
  "latest_version": discovery.latest_version,
75
71
  "title": discovery.title,
@@ -79,11 +75,5 @@ def hass_format_state(discovery: Discovery, session: str, in_progress: bool = Fa
79
75
  state["release_summary"] = discovery.release_summary
80
76
  if discovery.release_url:
81
77
  state["release_url"] = discovery.release_url
82
- custom_state = discovery.provider.hass_state_format(discovery)
83
- if custom_state:
84
- state.update(custom_state)
85
- invalid_keys = [k for k in state if k not in HASS_UPDATE_SCHEMA]
86
- if invalid_keys:
87
- log.warning(f"Invalid keys in state: {invalid_keys}")
88
- state = {k: v for k, v in state.items() if k in HASS_UPDATE_SCHEMA}
78
+
89
79
  return state