updates2mqtt 1.6.0__py3-none-any.whl → 1.7.2__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.
@@ -44,7 +44,7 @@ def git_iso_timestamp(repo_path: Path, git_path: Path) -> str | None:
44
44
  return None
45
45
 
46
46
 
47
- def git_local_version(repo_path: Path, git_path: Path) -> str | None:
47
+ def git_local_digest(repo_path: Path, git_path: Path) -> str | None:
48
48
  result = None
49
49
  try:
50
50
  result = subprocess.run(
@@ -56,17 +56,17 @@ def git_local_version(repo_path: Path, git_path: Path) -> str | None:
56
56
  check=True,
57
57
  )
58
58
  if result.returncode == 0:
59
- log.debug("Local git rev-parse", action="git_local_version", path=repo_path, version=result.stdout.strip())
60
- return f"git:{result.stdout.strip()}"[:19]
59
+ log.debug("Local git rev-parse", action="git_local_digest", path=repo_path, version=result.stdout.strip())
60
+ return result.stdout.strip()[:15]
61
61
  except subprocess.CalledProcessError as cpe:
62
- log.warn("GIT No result from git rev-parse at %s: %s", repo_path, cpe, action="git_local_version")
62
+ log.warn("GIT No result from git rev-parse at %s: %s", repo_path, cpe, action="git_local_digest")
63
63
  except Exception as e:
64
64
  log.error(
65
65
  "GIT Unable to retrieve version at %s - %s: %s",
66
66
  repo_path,
67
67
  result.stdout if result else "<NO RESULT>",
68
68
  e,
69
- action="git_local_version",
69
+ action="git_local_digest",
70
70
  )
71
71
  return None
72
72
 
updates2mqtt/model.py CHANGED
@@ -1,4 +1,5 @@
1
1
  import json
2
+ import time
2
3
  from abc import abstractmethod
3
4
  from collections.abc import AsyncGenerator, Callable
4
5
  from threading import Event
@@ -6,6 +7,58 @@ from typing import Any
6
7
 
7
8
  import structlog
8
9
 
10
+ from updates2mqtt.config import NodeConfig, PublishPolicy, UpdatePolicy, VersionPolicy
11
+ from updates2mqtt.helpers import timestamp
12
+
13
+
14
+ class DiscoveryArtefactDetail:
15
+ """Provider specific detail"""
16
+
17
+ def as_dict(self) -> dict[str, str | list | dict | bool | int | None]:
18
+ return {}
19
+
20
+
21
+ class DiscoveryInstallationDetail:
22
+ """Provider specific detail"""
23
+
24
+ @abstractmethod
25
+ def as_dict(self) -> dict[str, str | list | dict | bool | int | None]:
26
+ return {}
27
+
28
+
29
+ class ReleaseDetail:
30
+ """The artefact source details
31
+
32
+ Note this may be an actual software package, or the source details of the wrapping of it
33
+ For example, some Docker images report the main source repo, and others where the Dockerfile deploy project lives
34
+ """
35
+
36
+ def __init__(self, notes_url: str | None = None, summary: str | None = None) -> None:
37
+ self.source_platform: str | None = None
38
+ self.source_repo_url: str | None = None
39
+ self.source_url: str | None = None
40
+ self.version: str | None = None
41
+ self.revision: str | None = None
42
+ self.diff_url: str | None = None
43
+ self.notes_url: str | None = notes_url
44
+ self.title: str | None = None
45
+ self.summary: str | None = summary
46
+ self.net_score: int | None = None
47
+
48
+ def as_dict(self) -> dict[str, str | None]:
49
+ return {
50
+ "title": self.title,
51
+ "version": self.version,
52
+ "source_platform": self.source_platform,
53
+ "source_repo": self.source_repo_url,
54
+ "source": self.source_url,
55
+ "revision": self.revision,
56
+ "diff_url": self.diff_url,
57
+ "notes_url": self.notes_url,
58
+ "summary": self.summary,
59
+ "net_score": str(self.net_score) if self.net_score is not None else None,
60
+ }
61
+
9
62
 
10
63
  class Discovery:
11
64
  """Discovered component from a scan"""
@@ -19,20 +72,24 @@ class Discovery:
19
72
  entity_picture_url: str | None = None,
20
73
  current_version: str | None = None,
21
74
  latest_version: str | None = None,
22
- can_update: bool = False,
23
75
  can_build: bool = False,
24
76
  can_restart: bool = False,
77
+ can_pull: bool = False,
25
78
  status: str = "on",
79
+ publish_policy: PublishPolicy = PublishPolicy.HOMEASSISTANT,
26
80
  update_type: str | None = "Update",
27
- update_policy: str | None = None,
28
- update_last_attempt: float | None = None,
29
- release_url: str | None = None,
30
- release_summary: str | None = None,
81
+ update_policy: UpdatePolicy = UpdatePolicy.PASSIVE,
82
+ version_policy: VersionPolicy = VersionPolicy.AUTO,
83
+ version_basis: str | None = None,
31
84
  title_template: str = "{discovery.update_type} for {discovery.name} on {discovery.node}",
32
85
  device_icon: str | None = None,
33
86
  custom: dict[str, Any] | None = None,
34
- features: list[str] | None = None,
35
87
  throttled: bool = False,
88
+ previous: "Discovery|None" = None,
89
+ release_detail: ReleaseDetail | None = None,
90
+ installation_detail: DiscoveryInstallationDetail | None = None,
91
+ current_detail: DiscoveryArtefactDetail | None = None,
92
+ latest_detail: DiscoveryArtefactDetail | None = None,
36
93
  ) -> None:
37
94
  self.provider: ReleaseProvider = provider
38
95
  self.source_type: str = provider.source_type
@@ -42,20 +99,41 @@ class Discovery:
42
99
  self.entity_picture_url: str | None = entity_picture_url
43
100
  self.current_version: str | None = current_version
44
101
  self.latest_version: str | None = latest_version
45
- self.can_update: bool = can_update
102
+ self.can_pull: bool = can_pull
46
103
  self.can_build: bool = can_build
47
104
  self.can_restart: bool = can_restart
48
- self.release_url: str | None = release_url
49
- self.release_summary: str | None = release_summary
50
105
  self.title_template: str | None = title_template
51
106
  self.device_icon: str | None = device_icon
52
107
  self.update_type: str | None = update_type
53
108
  self.status: str = status
54
- self.update_policy: str | None = update_policy
55
- self.update_last_attempt: float | None = update_last_attempt
109
+ self.publish_policy: PublishPolicy = publish_policy
110
+ self.update_policy: UpdatePolicy = update_policy
111
+ self.version_policy: VersionPolicy = version_policy
112
+ self.version_basis: str | None = version_basis
113
+ self.update_last_attempt: float | None = None
56
114
  self.custom: dict[str, Any] = custom or {}
57
- self.features: list[str] = features or []
58
115
  self.throttled: bool = throttled
116
+ self.scan_count: int
117
+ self.first_timestamp: float
118
+ self.last_timestamp: float = time.time()
119
+ self.check_timestamp: float | None = time.time()
120
+ self.release_detail: ReleaseDetail | None = release_detail
121
+ self.current_detail: DiscoveryArtefactDetail | None = current_detail
122
+ self.latest_detail: DiscoveryArtefactDetail | None = latest_detail
123
+ self.installation_detail: DiscoveryInstallationDetail | None = installation_detail
124
+
125
+ if previous:
126
+ self.update_last_attempt = previous.update_last_attempt
127
+ self.first_timestamp = previous.first_timestamp
128
+ self.scan_count = previous.scan_count + 1
129
+ else:
130
+ self.first_timestamp = time.time()
131
+ self.scan_count = 1
132
+ if throttled and previous:
133
+ # roll forward last non-throttled check
134
+ self.check_timestamp = previous.check_timestamp
135
+ elif not throttled:
136
+ self.check_timestamp = time.time()
59
137
 
60
138
  def __repr__(self) -> str:
61
139
  """Build a custom string representation"""
@@ -70,22 +148,76 @@ class Discovery:
70
148
  dump = {k: stringify(v) for k, v in self.__dict__.items()}
71
149
  return json.dumps(dump)
72
150
 
151
+ @property
152
+ def can_update(self) -> bool:
153
+ return self.can_pull or self.can_build or self.can_restart
154
+
155
+ @property
156
+ def features(self) -> list[str]:
157
+ results = []
158
+ if self.can_update:
159
+ # public install-neutral capabilities and Home Assistant features
160
+ results.append("INSTALL")
161
+ results.append("PROGRESS")
162
+ if self.release_detail and self.release_detail.notes_url:
163
+ results.append("RELEASE_NOTES")
164
+ return results
165
+
73
166
  @property
74
167
  def title(self) -> str:
75
168
  if self.title_template:
76
169
  return self.title_template.format(discovery=self)
77
170
  return self.name
78
171
 
172
+ def as_dict(self) -> dict[str, str | list | dict | bool | int | None]:
173
+ results: dict[str, str | list | dict | bool | int | None] = {
174
+ "name": self.name,
175
+ "node": self.node,
176
+ "provider": {"source_type": self.provider.source_type},
177
+ "first_scan": {"timestamp": timestamp(self.first_timestamp)},
178
+ "last_scan": {"timestamp": timestamp(self.last_timestamp), "session": self.session, "throttled": self.throttled},
179
+ "scan_count": self.scan_count,
180
+ "installed_version": self.current_version,
181
+ "latest_version": self.latest_version,
182
+ "version_basis": self.version_basis,
183
+ "title": self.title,
184
+ "can_update": self.can_update,
185
+ "can_build": self.can_build,
186
+ "can_restart": self.can_restart,
187
+ "device_icon": self.device_icon,
188
+ "update_type": self.update_type,
189
+ "status": self.status,
190
+ "features": self.features,
191
+ "entity_picture_url": self.entity_picture_url,
192
+ "update_policy": str(self.update_policy),
193
+ "publish_policy": str(self.publish_policy),
194
+ "version_policy": str(self.version_policy),
195
+ "update": {"last_attempt": timestamp(self.update_last_attempt), "in_progress": False},
196
+ "installation_detail": self.installation_detail.as_dict() if self.installation_detail else None,
197
+ "current_detail": self.current_detail.as_dict() if self.current_detail else None,
198
+ "latest_detail": self.latest_detail.as_dict() if self.latest_detail else None,
199
+ }
200
+ if self.release_detail:
201
+ results["release"] = self.release_detail.as_dict() if self.release_detail else None
202
+ if self.custom:
203
+ results[self.source_type] = self.custom
204
+ return results
205
+
79
206
 
80
207
  class ReleaseProvider:
81
208
  """Abstract base class for release providers, such as container scanners or package managers API calls"""
82
209
 
83
- def __init__(self, source_type: str = "base") -> None:
210
+ def __init__(self, node_cfg: NodeConfig, source_type: str = "base") -> None:
84
211
  self.source_type: str = source_type
85
212
  self.discoveries: dict[str, Discovery] = {}
213
+ self.node_cfg: NodeConfig = node_cfg
86
214
  self.log: Any = structlog.get_logger().bind(integration=self.source_type)
87
215
  self.stopped = Event()
88
216
 
217
+ def initialize(self) -> None:
218
+ """Initialize any loops or background tasks, make any startup API calls"""
219
+ pass
220
+
89
221
  def stop(self) -> None:
90
222
  """Stop any loops or background tasks"""
91
223
  self.log.info("Asking release provider to stop", source_type=self.source_type)
@@ -106,18 +238,9 @@ class ReleaseProvider:
106
238
  @abstractmethod
107
239
  async def scan(self, session: str) -> AsyncGenerator[Discovery]:
108
240
  """Scan for components to monitor"""
109
- raise NotImplementedError
110
241
  # force recognition as an async generator
111
- if False: # type: ignore[unreachable]
112
- yield 0
113
-
114
- def hass_config_format(self, discovery: Discovery) -> dict:
115
- _ = discovery
116
- return {}
117
-
118
- def hass_state_format(self, discovery: Discovery) -> dict:
119
- _ = discovery
120
- return {}
242
+ if False:
243
+ yield 0 # type: ignore[unreachable]
121
244
 
122
245
  @abstractmethod
123
246
  def command(self, discovery_name: str, command: str, on_update_start: Callable, on_update_end: Callable) -> bool:
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()
@@ -203,6 +203,7 @@ class MqttPublisher:
203
203
  async def execute_command(
204
204
  self, msg: MQTTMessage | LocalMessage, on_update_start: Callable, on_update_end: Callable
205
205
  ) -> None:
206
+ # TODO: defer handling of commands where repository is throttled
206
207
  logger = self.log.bind(topic=msg.topic, payload=msg.payload)
207
208
  comp_name: str | None = None
208
209
  command: str | None = None
@@ -235,7 +236,12 @@ class MqttPublisher:
235
236
  updated = provider.command(comp_name, command, on_update_start, on_update_end)
236
237
  discovery = provider.resolve(comp_name)
237
238
  if updated and discovery:
238
- self.publish_hass_state(discovery)
239
+ if discovery.publish_policy == PublishPolicy.HOMEASSISTANT and self.hass_cfg.discovery.enabled:
240
+ self.publish_hass_config(discovery)
241
+ if discovery.publish_policy in (PublishPolicy.HOMEASSISTANT, PublishPolicy.MQTT):
242
+ self.publish_discovery(discovery)
243
+ if discovery.publish_policy == PublishPolicy.HOMEASSISTANT:
244
+ self.publish_hass_state(discovery)
239
245
  else:
240
246
  logger.debug("No change to republish after execution")
241
247
  logger.info("Execution ended")
@@ -283,10 +289,12 @@ class MqttPublisher:
283
289
 
284
290
  def handle_message(self, msg: mqtt.MQTTMessage | LocalMessage) -> None:
285
291
  def update_start(discovery: Discovery) -> None:
286
- self.publish_hass_state(discovery, in_progress=True)
292
+ if discovery.publish_policy in (PublishPolicy.HOMEASSISTANT, PublishPolicy.MQTT):
293
+ self.publish_hass_state(discovery, in_progress=True)
287
294
 
288
295
  def update_end(discovery: Discovery) -> None:
289
- self.publish_hass_state(discovery, in_progress=False)
296
+ if discovery.publish_policy in (PublishPolicy.HOMEASSISTANT, PublishPolicy.MQTT):
297
+ self.publish_hass_state(discovery, in_progress=False)
290
298
 
291
299
  if self.event_loop is not None:
292
300
  asyncio.run_coroutine_threadsafe(self.execute_command(msg, update_start, update_end), self.event_loop)
@@ -298,12 +306,26 @@ class MqttPublisher:
298
306
  return f"{prefix}/update/{self.node_cfg.name}_{discovery.source_type}_{discovery.name}/update/config"
299
307
 
300
308
  def state_topic(self, discovery: Discovery) -> str:
309
+ return f"{self.cfg.topic_root}/{self.node_cfg.name}/{discovery.source_type}/{discovery.name}/state"
310
+
311
+ def general_topic(self, discovery: Discovery) -> str:
301
312
  return f"{self.cfg.topic_root}/{self.node_cfg.name}/{discovery.source_type}/{discovery.name}"
302
313
 
303
314
  def command_topic(self, provider: ReleaseProvider) -> str:
304
315
  return f"{self.cfg.topic_root}/{self.node_cfg.name}/{provider.source_type}"
305
316
 
317
+ def publish_discovery(self, discovery: Discovery, in_progress: bool = False) -> None:
318
+ """Comprehensive, non Home Assistant specific, base publication"""
319
+ if discovery.publish_policy not in (PublishPolicy.HOMEASSISTANT, PublishPolicy.MQTT):
320
+ return
321
+ self.log.debug("Discovery publish: %s", discovery)
322
+ payload: dict[str, Any] = discovery.as_dict()
323
+ payload["update"]["in_progress"] = in_progress # ty:ignore[invalid-assignment]
324
+ self.publish(self.general_topic(discovery), payload)
325
+
306
326
  def publish_hass_state(self, discovery: Discovery, in_progress: bool = False) -> None:
327
+ if discovery.publish_policy != PublishPolicy.HOMEASSISTANT:
328
+ return
307
329
  self.log.debug("HASS State update, in progress: %s, discovery: %s", in_progress, discovery)
308
330
  self.publish(
309
331
  self.state_topic(discovery),
@@ -315,7 +337,11 @@ class MqttPublisher:
315
337
  )
316
338
 
317
339
  def publish_hass_config(self, discovery: Discovery) -> None:
340
+ if discovery.publish_policy != PublishPolicy.HOMEASSISTANT:
341
+ return
318
342
  object_id = f"{discovery.source_type}_{self.node_cfg.name}_{discovery.name}"
343
+ self.log.debug("HASS Config: %s", object_id)
344
+
319
345
  self.publish(
320
346
  self.config_topic(discovery),
321
347
  hass_format_config(
@@ -323,10 +349,10 @@ class MqttPublisher:
323
349
  object_id=object_id,
324
350
  area=self.hass_cfg.area,
325
351
  state_topic=self.state_topic(discovery),
352
+ attrs_topic=self.general_topic(discovery) if self.hass_cfg.extra_attributes else None,
326
353
  command_topic=self.command_topic(discovery.provider),
327
354
  force_command_topic=self.hass_cfg.force_command_topic,
328
355
  device_creation=self.hass_cfg.device_creation,
329
- session=discovery.session,
330
356
  ),
331
357
  )
332
358
 
@@ -1,8 +1,8 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: updates2mqtt
3
- Version: 1.6.0
3
+ Version: 1.7.2
4
4
  Summary: System update and docker image notification and execution over MQTT
5
- Keywords: mqtt,docker,updates,automation,home-assistant,homeassistant,selfhosting
5
+ Keywords: mqtt,docker,oci,container,updates,automation,home-assistant,homeassistant,selfhosting
6
6
  Author: jey burrows
7
7
  Author-email: jey burrows <jrb@rhizomatics.org.uk>
8
8
  License-Expression: Apache-2.0
@@ -18,21 +18,22 @@ Classifier: Intended Audience :: System Administrators
18
18
  Classifier: License :: OSI Approved :: Apache Software License
19
19
  Classifier: Typing :: Typed
20
20
  Classifier: Programming Language :: Python
21
- Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
22
22
  Requires-Dist: docker>=7.1.0
23
23
  Requires-Dist: paho-mqtt>=2.1.0
24
24
  Requires-Dist: omegaconf>=2.3.0
25
25
  Requires-Dist: structlog>=25.4.0
26
26
  Requires-Dist: rich>=14.0.0
27
27
  Requires-Dist: httpx>=0.28.1
28
- Requires-Dist: hishel[httpx]>=0.1.4
28
+ Requires-Dist: hishel[httpx]>=1.1.0
29
29
  Requires-Dist: usingversion>=0.1.2
30
+ Requires-Dist: tzlocal>=5.3.1
30
31
  Requires-Python: >=3.13
31
- Project-URL: Changelog, https://github.com/rhizomatics/updates2mqtt/blob/main/CHANGELOG.md
32
- Project-URL: Documentation, https://updates2mqtt.rhizomatics.org.uk
33
32
  Project-URL: Homepage, https://updates2mqtt.rhizomatics.org.uk
34
- Project-URL: Issues, https://github.com/rhizomatics/updates2mqtt/issues
35
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 }
@@ -60,7 +61,7 @@ Description-Content-Type: text/markdown
60
61
 
61
62
  Let Home Assistant tell you about new updates to Docker images for your containers.
62
63
 
63
- ![Example Home Assistant update page](images/ha_update_detail.png "Home Assistant Updates")
64
+ ![Example Home Assistant update page](images/ha_update_detail.png "Home Assistant Updates")![Example Home Assistant Release Notes](images/ha_release_notes.png "Home Assistant Release Notes"){width=300}
64
65
 
65
66
  Read the release notes, and optionally click *Update* to trigger a Docker *pull* (or optionally *build*) and *update*.
66
67
 
@@ -71,7 +72,8 @@ Read the release notes, and optionally click *Update* to trigger a Docker *pull*
71
72
 
72
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
- 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
+ Currently only Docker containers are supported, either via an image registry check (using either v1 Docker APIs or the OCI v2 API), or a git repo for source (see [Local Builds](local_builds.md)), with specific handling for Docker, Github Container Registry, Gitlab, Codeberg, Microsoft Container Registry and LinuxServer Registry, with adaptive behaviour to cope with most
76
+ others. 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
77
 
76
78
  Components can also be updated, either automatically or triggered via MQTT, for example by hitting the *Install* button in the HomeAssistant update dialog. Icons and release notes can be specified for a better HA experience. See [Home Assistant Integration](home_assistant.md) for details.
77
79
 
@@ -80,7 +82,7 @@ To get started, read the [Installation](installation.md) and [Configuration](con
80
82
  For a quick spin, try this:
81
83
 
82
84
  ```bash
83
- 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
+ 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:latest
84
86
  ```
85
87
 
86
88
  or without Docker, using [uv](https://docs.astral.sh/uv/)
@@ -132,7 +134,8 @@ file, or as `environment` options inside `docker-compose.yaml`.
132
134
  ### Automated updates
133
135
 
134
136
  If Docker containers should be immediately updated, without any confirmation
135
- 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`)
137
+ 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
138
+ visible to Home Assistant, then use `Silent`.
136
139
 
137
140
  ```yaml title="Example Compose Snippet"
138
141
  restarter:
@@ -142,9 +145,6 @@ restarter:
142
145
  - UPD2MQTT_UPDATE=AUTO
143
146
  ```
144
147
 
145
- This can be used in conjunction with the `UPD2MQTT_VERSION_INCLUDE` and `UPD2MQTT_VERSION_EXCLUDE` to
146
- limit which updates get automatically applied, for example excluding nightly builds.
147
-
148
148
  Automated updates can also apply to local builds, where a `git_repo_path` has been defined - if there are remote
149
149
  commits available to pull, then a `git pull`, `docker compose build` and `docker compose up` will be executed.
150
150
 
@@ -159,8 +159,9 @@ The following environment variables can be used to configure containers for `upd
159
159
  | `UPD2MQTT_RELNOTES` | URL to release notes for the package. | |
160
160
  | `UPD2MQTT_GIT_REPO_PATH` | Relative path to a local git repo if the image is built locally. | |
161
161
  | `UPD2MQTT_IGNORE` | If set to `True`, the container will be ignored by Updates2MQTT. | False |
162
- | `UPD2MQTT_VERSION_INCLUDE` | Only recognize versions matching this string or regular expression | |
163
- | `UPD2MQTT_VERSION_EXCLUDE` | Skip update if version matches this string or regular expression | |
162
+ | |
163
+ | `UPD2MQTT_VERSION_POLICY` | Change how version derived from container label or image hash, `Version`,`Digest`,`Version_Digest` with default of `Auto`|
164
+ | `UPD2MQTT_REGISTRY_TOKEN` | Access token for authentication to container distribution API, as alternative to making a call to `token` service |
164
165
 
165
166
  ### Docker Labels
166
167
 
@@ -173,8 +174,9 @@ Alternatively, use Docker labels
173
174
  | `updates2mqtt.relnotes` | `UPD2MQTT_RELNOTES` |
174
175
  | `updates2mqtt.git_repo_path` | `UPD2MQTT_GIT_REPO_PATH` |
175
176
  | `updates2mqtt.ignore` | `UPD2MQTT_IGNORE` |
176
- | `updates2mqtt.version_include` | `UPD2MQTT_VERSION_INCLUDE` |
177
- | `updates2mqtt.version_exclude` | `UPD2MQTT_VERSION_EXCLUDE` |
177
+ | `updates2mqtt.version_policy` | `UPD2MQTT_VERSION_POLICY` |
178
+ | `updates2mqtt.registry_token` | `UPD2MQTT_REGISTRY_TOKEN` |
179
+
178
180
 
179
181
 
180
182
  ```yaml title="Example Compose Snippet"
@@ -204,7 +206,7 @@ This component relies on several open source packages:
204
206
  - [Eclipse Paho](https://eclipse.dev/paho/files/paho.mqtt.python/html/client.html) MQTT client
205
207
  - [OmegaConf](https://omegaconf.readthedocs.io) for configuration and validation
206
208
  - [structlog](https://www.structlog.org/en/stable/) for structured logging and [rich](https://rich.readthedocs.io/en/stable/) for better exception reporting
207
- - [hishel](https://hishel.com/1.0/) for caching metadata
209
+ - [hishel](https://hishel.com/) for caching metadata
208
210
  - [httpx](https://www.python-httpx.org) for retrieving metadata
209
211
  - The Astral [uv](https://docs.astral.sh/uv/) and [ruff](https://docs.astral.sh/ruff/) tools for development and build
210
212
  - [pytest](https://docs.pytest.org/en/stable/) and supporting add-ins for automated testing
@@ -0,0 +1,18 @@
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/cli.py,sha256=1ntGaJc8rOv8uU5l5oOCs80yey8A--CkHGPFYND0A6U,5237
5
+ updates2mqtt/config.py,sha256=Yfr5tHTVj4Tl-Zpmx6UZ4HBOOFvdoIYXi91bUVgl8E0,7243
6
+ updates2mqtt/hass_formatter.py,sha256=k0aLGg-7wI_C4TixhY-L-iz7n0QCKQ_Pvv37hSp22ww,2779
7
+ updates2mqtt/helpers.py,sha256=fGGBA8JrneAji0AdqyA2waivV9Jq_rXB-CT6TzIFNZ8,9282
8
+ updates2mqtt/integrations/__init__.py,sha256=KmNTUxvVWvqI7rl4I0xZg7XaCmcMS2O4OSv-ClsWM4Q,109
9
+ updates2mqtt/integrations/docker.py,sha256=848AbaNiRGdIRi0nG9-_3JePBHy5d48ETDXilIf_emM,30171
10
+ updates2mqtt/integrations/docker_enrich.py,sha256=1szsw7JpCWQcAG-rRp0KJ3Yj4MgE28C_RCKZngbnPv0,39474
11
+ updates2mqtt/integrations/git_utils.py,sha256=AnMiVW-noaBQ-17FeIl93jwpTSzvr70nIDEcJN3D-gw,4356
12
+ updates2mqtt/model.py,sha256=Pfwy2nSAq6-_ACEhZRsCVXlahTbDd07Ej2o_81SPdxc,10093
13
+ updates2mqtt/mqtt.py,sha256=EYsWKGKzmwf3VIjQmf_oI2C962k3QTWJKXRnLC9kzEU,16687
14
+ updates2mqtt/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
+ updates2mqtt-1.7.2.dist-info/WHEEL,sha256=XV0cjMrO7zXhVAIyyc8aFf1VjZ33Fen4IiJk5zFlC3g,80
16
+ updates2mqtt-1.7.2.dist-info/entry_points.txt,sha256=qtMKoTPaodbFC3YG7MLElWDjl7CfJdbrxxZyH6Bua8E,83
17
+ updates2mqtt-1.7.2.dist-info/METADATA,sha256=7FEYhqFY1T77JJx54AsJ5GmtJDVydCHQm7g_IiMwraA,12131
18
+ updates2mqtt-1.7.2.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: uv 0.9.24
2
+ Generator: uv 0.9.26
3
3
  Root-Is-Purelib: true
4
- Tag: py3-none-any
4
+ Tag: py3-none-any
@@ -1,3 +1,4 @@
1
1
  [console_scripts]
2
+ cli = updates2mqtt.cli:main
2
3
  updates2mqtt = updates2mqtt.app:run
3
4
 
@@ -1,15 +0,0 @@
1
- updates2mqtt/__init__.py,sha256=gnmHrLOSYc-N1-c5VG46OpNpoXEybKzYhEvFMm955P8,237
2
- updates2mqtt/__main__.py,sha256=HBF00oH5fhS33sI_CdbxNlaUvbIzuuGxwnRYdhHqx0M,194
3
- updates2mqtt/app.py,sha256=uTgGgZX0xT7ee2-AB7tWTV8zKmtZiIWXHInmP8mrYZs,9357
4
- updates2mqtt/config.py,sha256=VN594apCjeUAwCekl0ItEMx-4eSeJsZIamHzsKoS0Ns,6136
5
- updates2mqtt/hass_formatter.py,sha256=6UfBhQLmFEYVVct_JeepIERwNvdwM2z8jkHAv-vOWa0,3191
6
- updates2mqtt/integrations/__init__.py,sha256=KmNTUxvVWvqI7rl4I0xZg7XaCmcMS2O4OSv-ClsWM4Q,109
7
- updates2mqtt/integrations/docker.py,sha256=3BQWgAENaoZjmJizrv_yHc3L6GXn8iQuUAXLcrXCS6g,28079
8
- updates2mqtt/integrations/git_utils.py,sha256=ODCKecWnom1NEKsmDZ2vFmYfnVGtWUgq7svVGcOtSaU,4369
9
- updates2mqtt/model.py,sha256=YkJyeoEgO1ApqEfcCmeMu9ROk9tOZPYxGx2oBkbXyy0,4657
10
- updates2mqtt/mqtt.py,sha256=W5kerew922CwaPvaNczVm61bvh3DGJ3k41BNZ1WJuuE,14987
11
- updates2mqtt/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
- updates2mqtt-1.6.0.dist-info/WHEEL,sha256=eycQt0QpYmJMLKpE3X9iDk8R04v2ZF0x82ogq-zP6bQ,79
13
- updates2mqtt-1.6.0.dist-info/entry_points.txt,sha256=Nt1kQQfJ1M2RvcRUnVxe3KCMiX8puHPqz-D7BwqV1L8,55
14
- updates2mqtt-1.6.0.dist-info/METADATA,sha256=iVHtji5oJMxb05RD5rSoECjIgD_Bk6aa_vGwBQuCoyI,11769
15
- updates2mqtt-1.6.0.dist-info/RECORD,,