updates2mqtt 1.5.1__py3-none-any.whl → 1.7.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
updates2mqtt/model.py CHANGED
@@ -1,9 +1,33 @@
1
+ import datetime as dt
2
+ import json
3
+ import re
4
+ import time
1
5
  from abc import abstractmethod
2
6
  from collections.abc import AsyncGenerator, Callable
7
+ from enum import StrEnum
3
8
  from threading import Event
4
9
  from typing import Any
5
10
 
6
11
  import structlog
12
+ from tzlocal import get_localzone
13
+
14
+ from updates2mqtt.config import NO_KNOWN_IMAGE, NodeConfig, PublishPolicy, Selector, UpdatePolicy
15
+
16
+
17
+ def timestamp(time_value: float | None) -> str | None:
18
+ if time_value is None:
19
+ return None
20
+ try:
21
+ return dt.datetime.fromtimestamp(time_value, tz=get_localzone()).isoformat()
22
+ except: # noqa: E722
23
+ return None
24
+
25
+
26
+ class VersionPolicy(StrEnum):
27
+ AUTO = "AUTO"
28
+ VERSION = "VERSION"
29
+ DIGEST = "DIGEST"
30
+ VERSION_DIGEST = "VERSION_DIGEST"
7
31
 
8
32
 
9
33
  class Discovery:
@@ -22,15 +46,18 @@ class Discovery:
22
46
  can_build: bool = False,
23
47
  can_restart: bool = False,
24
48
  status: str = "on",
49
+ publish_policy: PublishPolicy = PublishPolicy.HOMEASSISTANT,
25
50
  update_type: str | None = "Update",
26
- update_policy: str | None = None,
27
- update_last_attempt: float | None = None,
51
+ update_policy: UpdatePolicy = UpdatePolicy.PASSIVE,
52
+ version_policy: VersionPolicy = VersionPolicy.AUTO,
28
53
  release_url: str | None = None,
29
54
  release_summary: str | None = None,
30
55
  title_template: str = "{discovery.update_type} for {discovery.name} on {discovery.node}",
31
56
  device_icon: str | None = None,
32
57
  custom: dict[str, Any] | None = None,
33
58
  features: list[str] | None = None,
59
+ throttled: bool = False,
60
+ previous: "Discovery|None" = None,
34
61
  ) -> None:
35
62
  self.provider: ReleaseProvider = provider
36
63
  self.source_type: str = provider.source_type
@@ -49,36 +76,96 @@ class Discovery:
49
76
  self.device_icon: str | None = device_icon
50
77
  self.update_type: str | None = update_type
51
78
  self.status: str = status
52
- self.update_policy: str | None = update_policy
53
- self.update_last_attempt: float | None = update_last_attempt
79
+ self.publish_policy: PublishPolicy = publish_policy
80
+ self.update_policy: UpdatePolicy = update_policy
81
+ self.version_policy: VersionPolicy = version_policy
82
+ self.update_last_attempt: float | None = None
54
83
  self.custom: dict[str, Any] = custom or {}
55
84
  self.features: list[str] = features or []
85
+ self.throttled: bool = throttled
86
+ self.scan_count: int
87
+ self.first_timestamp: float
88
+ self.last_timestamp: float = time.time()
89
+
90
+ if previous:
91
+ self.update_last_attempt = previous.update_last_attempt
92
+ self.first_timestamp = previous.first_timestamp
93
+ self.scan_count = previous.scan_count + 1
94
+ else:
95
+ self.first_timestamp = time.time()
96
+ self.scan_count = 1
56
97
 
57
98
  def __repr__(self) -> str:
58
99
  """Build a custom string representation"""
59
100
  return f"Discovery('{self.name}','{self.source_type}',current={self.current_version},latest={self.latest_version})"
60
101
 
102
+ def __str__(self) -> str:
103
+ """Dump the attrs"""
104
+
105
+ def stringify(v: Any) -> str | int | float | bool:
106
+ return str(v) if not isinstance(v, (str, int, float, bool)) else v
107
+
108
+ dump = {k: stringify(v) for k, v in self.__dict__.items()}
109
+ return json.dumps(dump)
110
+
61
111
  @property
62
112
  def title(self) -> str:
63
113
  if self.title_template:
64
114
  return self.title_template.format(discovery=self)
65
115
  return self.name
66
116
 
117
+ def as_dict(self) -> dict[str, str | list | dict | bool | int | None]:
118
+ return {
119
+ "name": self.name,
120
+ "node": self.node,
121
+ "provider": {"source_type": self.provider.source_type},
122
+ "first_scan": {"timestamp": timestamp(self.first_timestamp)},
123
+ "last_scan": {"timestamp": timestamp(self.last_timestamp), "session": self.session, "throttled": self.throttled},
124
+ "scan_count": self.scan_count,
125
+ "installed_version": self.current_version,
126
+ "latest_version": self.latest_version,
127
+ "title": self.title,
128
+ "release_summary": self.release_summary,
129
+ "release_url": self.release_url,
130
+ "entity_picture_url": self.entity_picture_url,
131
+ "can_update": self.can_update,
132
+ "can_build": self.can_build,
133
+ "can_restart": self.can_restart,
134
+ "device_icon": self.device_icon,
135
+ "update_type": self.update_type,
136
+ "status": self.status,
137
+ "features": self.features,
138
+ "update_policy": self.update_policy,
139
+ "publish_policy": self.publish_policy,
140
+ "version_policy": self.version_policy,
141
+ "update": {"last_attempt": timestamp(self.update_last_attempt), "in_progress": False},
142
+ self.source_type: self.custom,
143
+ }
144
+
67
145
 
68
146
  class ReleaseProvider:
69
147
  """Abstract base class for release providers, such as container scanners or package managers API calls"""
70
148
 
71
- def __init__(self, source_type: str = "base") -> None:
149
+ def __init__(self, node_cfg: NodeConfig, source_type: str = "base") -> None:
72
150
  self.source_type: str = source_type
73
151
  self.discoveries: dict[str, Discovery] = {}
152
+ self.node_cfg: NodeConfig = node_cfg
74
153
  self.log: Any = structlog.get_logger().bind(integration=self.source_type)
75
154
  self.stopped = Event()
76
155
 
156
+ def initialize(self) -> None:
157
+ """Initialize any loops or background tasks, make any startup API calls"""
158
+ pass
159
+
77
160
  def stop(self) -> None:
78
161
  """Stop any loops or background tasks"""
79
162
  self.log.info("Asking release provider to stop", source_type=self.source_type)
80
163
  self.stopped.set()
81
164
 
165
+ def __str__(self) -> str:
166
+ """Stringify"""
167
+ return f"{self.source_type} Discovery"
168
+
82
169
  @abstractmethod
83
170
  def update(self, discovery: Discovery) -> bool:
84
171
  """Attempt to update the component version"""
@@ -90,18 +177,9 @@ class ReleaseProvider:
90
177
  @abstractmethod
91
178
  async def scan(self, session: str) -> AsyncGenerator[Discovery]:
92
179
  """Scan for components to monitor"""
93
- raise NotImplementedError
94
180
  # force recognition as an async generator
95
- if False: # type: ignore[unreachable]
96
- yield 0
97
-
98
- def hass_config_format(self, discovery: Discovery) -> dict:
99
- _ = discovery
100
- return {}
101
-
102
- def hass_state_format(self, discovery: Discovery) -> dict:
103
- _ = discovery
104
- return {}
181
+ if False:
182
+ yield 0 # type: ignore[unreachable]
105
183
 
106
184
  @abstractmethod
107
185
  def command(self, discovery_name: str, command: str, on_update_start: Callable, on_update_end: Callable) -> bool:
@@ -110,3 +188,59 @@ class ReleaseProvider:
110
188
  @abstractmethod
111
189
  def resolve(self, discovery_name: str) -> Discovery | None:
112
190
  """Resolve a discovered component by name"""
191
+
192
+
193
+ class Selection:
194
+ def __init__(self, selector: Selector, value: str | None) -> None:
195
+ self.result: bool = True
196
+ self.matched: str | None = None
197
+ if value is None:
198
+ self.result = selector.include is None
199
+ return
200
+ if selector.exclude is not None:
201
+ self.result = True
202
+ if any(re.search(pat, value) for pat in selector.exclude):
203
+ self.matched = value
204
+ self.result = False
205
+ if selector.include is not None:
206
+ self.result = False
207
+ if any(re.search(pat, value) for pat in selector.include):
208
+ self.matched = value
209
+ self.result = True
210
+
211
+
212
+ VERSION_RE = r"[vV]?[0-9]+(\.[0-9]+)*"
213
+
214
+
215
+ def select_version(
216
+ version_policy: VersionPolicy,
217
+ version: str | None,
218
+ digest: str | None,
219
+ other_version: str | None = None,
220
+ other_digest: str | None = None,
221
+ ) -> str:
222
+ if version_policy == VersionPolicy.VERSION and version:
223
+ return version
224
+ if version_policy == VersionPolicy.DIGEST and digest and digest != NO_KNOWN_IMAGE:
225
+ return digest
226
+ if version_policy == VersionPolicy.VERSION_DIGEST and version and digest and digest != NO_KNOWN_IMAGE:
227
+ return f"{version} ({digest})"
228
+ # AUTO or fallback
229
+ if version_policy == VersionPolicy.AUTO and version and re.match(VERSION_RE, version or ""):
230
+ # Smells like semver
231
+ if other_version is None and other_digest is None:
232
+ return version
233
+ if re.match(VERSION_RE, other_version or "") and (
234
+ (version == other_version and digest == other_digest) or (version != other_version and digest != other_digest)
235
+ ):
236
+ # Only semver if versions and digest consistently same or different
237
+ return version
238
+
239
+ if version and digest and digest != NO_KNOWN_IMAGE:
240
+ return f"{version}:{digest}"
241
+ if version:
242
+ return version
243
+ if digest and digest != NO_KNOWN_IMAGE:
244
+ return digest
245
+
246
+ return other_version or other_version or NO_KNOWN_IMAGE
updates2mqtt/mqtt.py CHANGED
@@ -16,7 +16,7 @@ from paho.mqtt.reasoncodes import ReasonCode
16
16
 
17
17
  from updates2mqtt.model import Discovery, ReleaseProvider
18
18
 
19
- from .config import HomeAssistantConfig, MqttConfig, NodeConfig
19
+ from .config import HomeAssistantConfig, MqttConfig, NodeConfig, PublishPolicy
20
20
  from .hass_formatter import hass_format_config, hass_format_state
21
21
 
22
22
  log = structlog.get_logger()
@@ -193,11 +193,11 @@ class MqttPublisher:
193
193
  try:
194
194
  return json.loads(jsonish)
195
195
  except Exception:
196
- log.exception("JSON decode fail (%s); %s", jsonish)
196
+ log.exception("JSON decode fail (%s)", jsonish)
197
197
  try:
198
198
  return json.loads(jsonish[1:-1])
199
199
  except Exception:
200
- log.exception("JSON decode fail (%s): %s", jsonish[1:-1])
200
+ log.exception("JSON decode fail (%s)", jsonish[1:-1])
201
201
  return {}
202
202
 
203
203
  async def execute_command(
@@ -235,7 +235,10 @@ class MqttPublisher:
235
235
  updated = provider.command(comp_name, command, on_update_start, on_update_end)
236
236
  discovery = provider.resolve(comp_name)
237
237
  if updated and discovery:
238
- self.publish_hass_state(discovery)
238
+ if discovery.publish_policy in (PublishPolicy.HOMEASSISTANT, PublishPolicy.MQTT):
239
+ self.publish_discovery(discovery)
240
+ if discovery.publish_policy == PublishPolicy.HOMEASSISTANT:
241
+ self.publish_hass_state(discovery)
239
242
  else:
240
243
  logger.debug("No change to republish after execution")
241
244
  logger.info("Execution ended")
@@ -283,10 +286,12 @@ class MqttPublisher:
283
286
 
284
287
  def handle_message(self, msg: mqtt.MQTTMessage | LocalMessage) -> None:
285
288
  def update_start(discovery: Discovery) -> None:
286
- self.publish_hass_state(discovery, in_progress=True)
289
+ if discovery.publish_policy in (PublishPolicy.HOMEASSISTANT, PublishPolicy.MQTT):
290
+ self.publish_hass_state(discovery, in_progress=True)
287
291
 
288
292
  def update_end(discovery: Discovery) -> None:
289
- self.publish_hass_state(discovery, in_progress=False)
293
+ if discovery.publish_policy in (PublishPolicy.HOMEASSISTANT, PublishPolicy.MQTT):
294
+ self.publish_hass_state(discovery, in_progress=False)
290
295
 
291
296
  if self.event_loop is not None:
292
297
  asyncio.run_coroutine_threadsafe(self.execute_command(msg, update_start, update_end), self.event_loop)
@@ -298,12 +303,26 @@ class MqttPublisher:
298
303
  return f"{prefix}/update/{self.node_cfg.name}_{discovery.source_type}_{discovery.name}/update/config"
299
304
 
300
305
  def state_topic(self, discovery: Discovery) -> str:
306
+ return f"{self.cfg.topic_root}/{self.node_cfg.name}/{discovery.source_type}/{discovery.name}/state"
307
+
308
+ def general_topic(self, discovery: Discovery) -> str:
301
309
  return f"{self.cfg.topic_root}/{self.node_cfg.name}/{discovery.source_type}/{discovery.name}"
302
310
 
303
311
  def command_topic(self, provider: ReleaseProvider) -> str:
304
312
  return f"{self.cfg.topic_root}/{self.node_cfg.name}/{provider.source_type}"
305
313
 
314
+ def publish_discovery(self, discovery: Discovery, in_progress: bool = False) -> None:
315
+ """Comprehensive, non Home Assistant specific, base publication"""
316
+ if discovery.publish_policy not in (PublishPolicy.HOMEASSISTANT, PublishPolicy.MQTT):
317
+ return
318
+ self.log.debug("Discovery publish: %s", discovery)
319
+ payload: dict[str, Any] = discovery.as_dict()
320
+ payload["update"]["in_progress"] = in_progress # ty:ignore[invalid-assignment]
321
+ self.publish(self.general_topic(discovery), payload)
322
+
306
323
  def publish_hass_state(self, discovery: Discovery, in_progress: bool = False) -> None:
324
+ if discovery.publish_policy != PublishPolicy.HOMEASSISTANT:
325
+ return
307
326
  self.log.debug("HASS State update, in progress: %s, discovery: %s", in_progress, discovery)
308
327
  self.publish(
309
328
  self.state_topic(discovery),
@@ -315,6 +334,8 @@ class MqttPublisher:
315
334
  )
316
335
 
317
336
  def publish_hass_config(self, discovery: Discovery) -> None:
337
+ if discovery.publish_policy != PublishPolicy.HOMEASSISTANT:
338
+ return
318
339
  object_id = f"{discovery.source_type}_{self.node_cfg.name}_{discovery.name}"
319
340
  self.publish(
320
341
  self.config_topic(discovery),
@@ -323,10 +344,10 @@ class MqttPublisher:
323
344
  object_id=object_id,
324
345
  area=self.hass_cfg.area,
325
346
  state_topic=self.state_topic(discovery),
347
+ attrs_topic=self.general_topic(discovery) if self.hass_cfg.extra_attributes else None,
326
348
  command_topic=self.command_topic(discovery.provider),
327
349
  force_command_topic=self.hass_cfg.force_command_topic,
328
350
  device_creation=self.hass_cfg.device_creation,
329
- session=discovery.session,
330
351
  ),
331
352
  )
332
353
 
@@ -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
@@ -0,0 +1,16 @@
1
+ updates2mqtt/__init__.py,sha256=gnmHrLOSYc-N1-c5VG46OpNpoXEybKzYhEvFMm955P8,237
2
+ updates2mqtt/__main__.py,sha256=HBF00oH5fhS33sI_CdbxNlaUvbIzuuGxwnRYdhHqx0M,194
3
+ updates2mqtt/app.py,sha256=4OOzVTuOw5Zxrm6zppRG6kq7x6bOY6S0h44yRnoYoVk,9651
4
+ updates2mqtt/config.py,sha256=Hulk-zynpWq6JUv_ftEY5tmY-Vr8tQU9vV8nuhKXmJ4,5936
5
+ updates2mqtt/hass_formatter.py,sha256=xRm_iJiHU02v4KxNwps3V3g4dl_A7wNvQpLHCODzZlM,2690
6
+ updates2mqtt/integrations/__init__.py,sha256=KmNTUxvVWvqI7rl4I0xZg7XaCmcMS2O4OSv-ClsWM4Q,109
7
+ updates2mqtt/integrations/docker.py,sha256=GBYmzfzXuoI6j9GGIiDdq4KnR4677RCgzfxF6r4vTRM,28099
8
+ updates2mqtt/integrations/docker_enrich.py,sha256=wdJOOvZw7gG5ncHy6BqeIc9vPhR94YJYa9dumLmh8SY,15279
9
+ updates2mqtt/integrations/git_utils.py,sha256=ODCKecWnom1NEKsmDZ2vFmYfnVGtWUgq7svVGcOtSaU,4369
10
+ updates2mqtt/model.py,sha256=MmWzHq8ibXYRVMLmATrmhMp05q3neAIkP8Q1OkEepBs,9264
11
+ updates2mqtt/mqtt.py,sha256=KC8VYQ_OYiCoFehLT90dS5n8ZOZfffThapmQ29kyifE,16384
12
+ updates2mqtt/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
+ updates2mqtt-1.7.0.dist-info/WHEEL,sha256=XV0cjMrO7zXhVAIyyc8aFf1VjZ33Fen4IiJk5zFlC3g,80
14
+ updates2mqtt-1.7.0.dist-info/entry_points.txt,sha256=Nt1kQQfJ1M2RvcRUnVxe3KCMiX8puHPqz-D7BwqV1L8,55
15
+ updates2mqtt-1.7.0.dist-info/METADATA,sha256=POaF66x0AbR8qLJFyR3tDv27xRIbAu0Msyt2_o26TGU,11363
16
+ updates2mqtt-1.7.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.28.0
2
+ Generator: uv 0.9.26
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,2 +1,3 @@
1
1
  [console_scripts]
2
2
  updates2mqtt = updates2mqtt.app:run
3
+
@@ -1,16 +0,0 @@
1
- updates2mqtt/__init__.py,sha256=gnmHrLOSYc-N1-c5VG46OpNpoXEybKzYhEvFMm955P8,237
2
- updates2mqtt/__main__.py,sha256=HBF00oH5fhS33sI_CdbxNlaUvbIzuuGxwnRYdhHqx0M,194
3
- updates2mqtt/app.py,sha256=pYcNprv7_htqD6wiXX5j24FhPglEeFNdqMfPclq5SfU,8887
4
- updates2mqtt/config.py,sha256=nlAszykZa7t9nu99RB8g_tIzQxmdfWyilTZJY3Fl4e4,6099
5
- updates2mqtt/hass_formatter.py,sha256=bj6qpElMqt1DqKlxp4ZjwaCJAD-ed3xsq5ZOg4FbeC8,3216
6
- updates2mqtt/model.py,sha256=KNsLflgWaRvGrNdq1Vy2QnDLfVSLPWLSt4gBXj9COa4,4170
7
- updates2mqtt/mqtt.py,sha256=j7nlSka-LqRethZk_rc4Z8iOV7ey28TQFjSfrPeTjv4,14995
8
- updates2mqtt/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
- updates2mqtt/integrations/__init__.py,sha256=KmNTUxvVWvqI7rl4I0xZg7XaCmcMS2O4OSv-ClsWM4Q,109
10
- updates2mqtt/integrations/docker.py,sha256=Mi3tFjVDbCIsHBgH6UQ3yC5Y7HQk9JgEyH6PQuNJtFU,21263
11
- updates2mqtt/integrations/git_utils.py,sha256=bPCmQiZpKpMcrGI7xAVmePXHFn8WwjcPNkf7xqDsGQA,2319
12
- updates2mqtt-1.5.1.dist-info/METADATA,sha256=iPMKAgEFTFZ98pouv3SnTTStP74SEPBuV8jN3QbBpX8,9392
13
- updates2mqtt-1.5.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
14
- updates2mqtt-1.5.1.dist-info/entry_points.txt,sha256=Hc6NZ2dBevYSUKTJU6NOs8Mw7Vt0S-9lq5FuKb76NCc,54
15
- updates2mqtt-1.5.1.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
16
- updates2mqtt-1.5.1.dist-info/RECORD,,