updates2mqtt 1.3.7__py3-none-any.whl → 1.4.1__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/app.py +15 -10
- updates2mqtt/config.py +1 -0
- updates2mqtt/integrations/docker.py +11 -5
- updates2mqtt/integrations/git_utils.py +3 -1
- updates2mqtt/model.py +4 -0
- updates2mqtt/mqtt.py +54 -9
- {updates2mqtt-1.3.7.dist-info → updates2mqtt-1.4.1.dist-info}/METADATA +69 -23
- updates2mqtt-1.4.1.dist-info/RECORD +16 -0
- {updates2mqtt-1.3.7.dist-info → updates2mqtt-1.4.1.dist-info}/WHEEL +1 -1
- updates2mqtt-1.3.7.dist-info/RECORD +0 -16
- {updates2mqtt-1.3.7.dist-info → updates2mqtt-1.4.1.dist-info}/entry_points.txt +0 -0
- {updates2mqtt-1.3.7.dist-info → updates2mqtt-1.4.1.dist-info}/licenses/LICENSE +0 -0
updates2mqtt/app.py
CHANGED
|
@@ -16,7 +16,7 @@ from updates2mqtt.model import Discovery, ReleaseProvider
|
|
|
16
16
|
|
|
17
17
|
from .config import Config, load_app_config, load_package_info
|
|
18
18
|
from .integrations.docker import DockerProvider
|
|
19
|
-
from .mqtt import
|
|
19
|
+
from .mqtt import MqttPublisher
|
|
20
20
|
|
|
21
21
|
log = structlog.get_logger()
|
|
22
22
|
|
|
@@ -48,7 +48,7 @@ class App:
|
|
|
48
48
|
log.debug("Logging initialized", level=self.cfg.log.level)
|
|
49
49
|
self.common_pkg = load_package_info(PKG_INFO_FILE)
|
|
50
50
|
|
|
51
|
-
self.publisher =
|
|
51
|
+
self.publisher = MqttPublisher(self.cfg.mqtt, self.cfg.node, self.cfg.homeassistant)
|
|
52
52
|
|
|
53
53
|
self.scanners: list[ReleaseProvider] = []
|
|
54
54
|
self.scan_count: int = 0
|
|
@@ -74,7 +74,7 @@ class App:
|
|
|
74
74
|
break
|
|
75
75
|
log.info("Scanning", source=scanner.source_type, session=session)
|
|
76
76
|
async with asyncio.TaskGroup() as tg:
|
|
77
|
-
async for discovery in scanner.scan(session): #
|
|
77
|
+
async for discovery in scanner.scan(session): # xtype: ignore[attr-defined]
|
|
78
78
|
tg.create_task(self.on_discovery(discovery), name=f"discovery-{discovery.name}")
|
|
79
79
|
if self.stopped.is_set():
|
|
80
80
|
break
|
|
@@ -83,7 +83,7 @@ class App:
|
|
|
83
83
|
log.info("Scan complete", source_type=scanner.source_type)
|
|
84
84
|
self.last_scan_timestamp = datetime.now(UTC).isoformat()
|
|
85
85
|
|
|
86
|
-
async def
|
|
86
|
+
async def main_loop(self) -> None:
|
|
87
87
|
log.debug("Starting run loop")
|
|
88
88
|
self.publisher.start()
|
|
89
89
|
|
|
@@ -93,7 +93,7 @@ class App:
|
|
|
93
93
|
f"Setting up healthcheck every {self.cfg.node.healthcheck.interval} seconds to topic {self.healthcheck_topic}"
|
|
94
94
|
)
|
|
95
95
|
self.healthcheck_loop_task = asyncio.create_task(
|
|
96
|
-
repeated_call(self.healthcheck, interval=self.cfg.node.healthcheck.interval)
|
|
96
|
+
repeated_call(self.healthcheck, interval=self.cfg.node.healthcheck.interval), name="healthcheck"
|
|
97
97
|
)
|
|
98
98
|
|
|
99
99
|
for scanner in self.scanners:
|
|
@@ -145,7 +145,8 @@ class App:
|
|
|
145
145
|
log.info(f"Cancelling {len(running_tasks)} tasks")
|
|
146
146
|
for t in running_tasks:
|
|
147
147
|
log.debug("Cancelling task", task=t.get_name())
|
|
148
|
-
t.
|
|
148
|
+
if t.get_name() == "healthcheck" or t.get_name().startswith("discovery-"):
|
|
149
|
+
t.cancel()
|
|
149
150
|
await asyncio.gather(*running_tasks, return_exceptions=True)
|
|
150
151
|
log.debug("Cancellation task completed")
|
|
151
152
|
|
|
@@ -154,7 +155,11 @@ class App:
|
|
|
154
155
|
self.stopped.set()
|
|
155
156
|
for scanner in self.scanners:
|
|
156
157
|
scanner.stop()
|
|
157
|
-
interrupt_task = asyncio.get_event_loop().create_task(
|
|
158
|
+
interrupt_task = asyncio.get_event_loop().create_task(
|
|
159
|
+
self.interrupt_tasks(),
|
|
160
|
+
eager_start=True, # type: ignore[call-arg] # pyright: ignore[reportCallIssue]
|
|
161
|
+
name="interrupt",
|
|
162
|
+
)
|
|
158
163
|
for t in asyncio.all_tasks():
|
|
159
164
|
log.debug("Tasks waiting = %s", t)
|
|
160
165
|
self.publisher.stop()
|
|
@@ -168,7 +173,7 @@ class App:
|
|
|
168
173
|
self.publisher.publish(
|
|
169
174
|
topic=self.healthcheck_topic,
|
|
170
175
|
payload={
|
|
171
|
-
"version": updates2mqtt.version,
|
|
176
|
+
"version": updates2mqtt.version, # pyright: ignore[reportAttributeAccessIssue]
|
|
172
177
|
"node": self.cfg.node.name,
|
|
173
178
|
"heartbeat_raw": time.time(),
|
|
174
179
|
"heartbeat_stamp": datetime.now(UTC).isoformat(),
|
|
@@ -197,12 +202,12 @@ def run() -> None:
|
|
|
197
202
|
|
|
198
203
|
from .app import App
|
|
199
204
|
|
|
200
|
-
log.debug(f"Starting updates2mqtt v{updates2mqtt.version}")
|
|
205
|
+
log.debug(f"Starting updates2mqtt v{updates2mqtt.version}") # pyright: ignore[reportAttributeAccessIssue]
|
|
201
206
|
app = App()
|
|
202
207
|
|
|
203
208
|
signal.signal(signal.SIGTERM, app.shutdown)
|
|
204
209
|
try:
|
|
205
|
-
asyncio.run(app.
|
|
210
|
+
asyncio.run(app.main_loop(), debug=False)
|
|
206
211
|
log.debug("App exited gracefully")
|
|
207
212
|
except asyncio.CancelledError:
|
|
208
213
|
log.debug("App exited on cancelled task")
|
updates2mqtt/config.py
CHANGED
|
@@ -135,8 +135,8 @@ class DockerProvider(ReleaseProvider):
|
|
|
135
135
|
image_ref = None
|
|
136
136
|
image_name = None
|
|
137
137
|
local_versions = None
|
|
138
|
-
if c.attrs is None:
|
|
139
|
-
logger.warn("No container attributes found, discovery rejected")
|
|
138
|
+
if c.attrs is None or not c.attrs:
|
|
139
|
+
logger.warn("No container attributes found, discovery rejected")
|
|
140
140
|
return None
|
|
141
141
|
if c.name is None:
|
|
142
142
|
logger.warn("No container name found, discovery rejected")
|
|
@@ -145,7 +145,7 @@ class DockerProvider(ReleaseProvider):
|
|
|
145
145
|
def env_override(env_var: str, default: Any) -> Any | None:
|
|
146
146
|
return default if c_env.get(env_var) is None else c_env.get(env_var)
|
|
147
147
|
|
|
148
|
-
env_str = c.attrs
|
|
148
|
+
env_str = c.attrs.get("Config", {}).get("Env")
|
|
149
149
|
c_env = dict(env.split("=", maxsplit=1) for env in env_str if "==" not in env)
|
|
150
150
|
ignore_container: str | None = env_override("UPD2MQTT_IGNORE", "FALSE")
|
|
151
151
|
if ignore_container and ignore_container.upper() in ("1", "TRUE"):
|
|
@@ -247,11 +247,17 @@ class DockerProvider(ReleaseProvider):
|
|
|
247
247
|
can_build: bool = self.cfg.allow_build and custom.get("git_repo_path") is not None
|
|
248
248
|
can_restart: bool = self.cfg.allow_restart and custom.get("compose_path") is not None
|
|
249
249
|
can_update: bool = False
|
|
250
|
+
if self.cfg.allow_pull and not can_pull and not can_build:
|
|
251
|
+
logger.info(
|
|
252
|
+
f"Pull not available, image_ref:{image_ref},local_version:{local_version},latest_version:{latest_version}"
|
|
253
|
+
)
|
|
250
254
|
if can_pull or can_build or can_restart:
|
|
251
255
|
# public install-neutral capabilities and Home Assistant features
|
|
252
256
|
can_update = True
|
|
253
257
|
features.append("INSTALL")
|
|
254
258
|
features.append("PROGRESS")
|
|
259
|
+
elif any((self.cfg.allow_build, self.cfg.allow_restart, self.cfg.allow_pull)):
|
|
260
|
+
logger.info(f"Update not available, can_pull:{can_pull}, can_build:{can_build},can_restart{can_restart}")
|
|
255
261
|
if relnotes_url:
|
|
256
262
|
features.append("RELEASE_NOTES")
|
|
257
263
|
custom["can_pull"] = can_pull
|
|
@@ -279,7 +285,7 @@ class DockerProvider(ReleaseProvider):
|
|
|
279
285
|
logger.exception("Docker Discovery Failure", container_attrs=c.attrs)
|
|
280
286
|
return None
|
|
281
287
|
|
|
282
|
-
async def scan(self, session: str) -> AsyncGenerator[Discovery]:
|
|
288
|
+
async def scan(self, session: str) -> AsyncGenerator[Discovery]:
|
|
283
289
|
logger = self.log.bind(session=session, action="scan")
|
|
284
290
|
containers = results = 0
|
|
285
291
|
for c in self.client.containers.list():
|
|
@@ -287,7 +293,7 @@ class DockerProvider(ReleaseProvider):
|
|
|
287
293
|
logger.info(f"Shutdown detected, aborting scan at {c}")
|
|
288
294
|
break
|
|
289
295
|
containers = containers + 1
|
|
290
|
-
result = self.analyze(
|
|
296
|
+
result = self.analyze(c, session)
|
|
291
297
|
if result:
|
|
292
298
|
self.discoveries[result.name] = result
|
|
293
299
|
results = results + 1
|
|
@@ -28,8 +28,10 @@ def git_timestamp(repo_path: Path, git_path: Path) -> datetime.datetime | None:
|
|
|
28
28
|
check=True,
|
|
29
29
|
)
|
|
30
30
|
return datetime.datetime.fromisoformat(result.stdout.strip())
|
|
31
|
+
except subprocess.CalledProcessError as cpe:
|
|
32
|
+
log.warn("GIT No result from git log at %s: %s", repo_path, cpe)
|
|
31
33
|
except Exception as e:
|
|
32
|
-
log.
|
|
34
|
+
log.error("GIT Unable to parse timestamp at %s - %s: %s", repo_path, result.stdout if result else "<NO RESULT>", e)
|
|
33
35
|
return None
|
|
34
36
|
|
|
35
37
|
|
updates2mqtt/model.py
CHANGED
|
@@ -80,6 +80,10 @@ class ReleaseProvider:
|
|
|
80
80
|
@abstractmethod
|
|
81
81
|
async def scan(self, session: str) -> AsyncGenerator[Discovery]:
|
|
82
82
|
"""Scan for components to monitor"""
|
|
83
|
+
raise NotImplementedError
|
|
84
|
+
# force recognition as an async generator
|
|
85
|
+
if False: # type: ignore[unreachable]
|
|
86
|
+
yield 0
|
|
83
87
|
|
|
84
88
|
def hass_config_format(self, discovery: Discovery) -> dict:
|
|
85
89
|
_ = discovery
|
updates2mqtt/mqtt.py
CHANGED
|
@@ -9,8 +9,8 @@ from typing import Any
|
|
|
9
9
|
import paho.mqtt.client as mqtt
|
|
10
10
|
import paho.mqtt.subscribeoptions
|
|
11
11
|
import structlog
|
|
12
|
-
from paho.mqtt.client import MQTTMessage
|
|
13
|
-
from paho.mqtt.enums import CallbackAPIVersion, MQTTErrorCode
|
|
12
|
+
from paho.mqtt.client import MQTT_CLEAN_START_FIRST_ONLY, MQTTMessage
|
|
13
|
+
from paho.mqtt.enums import CallbackAPIVersion, MQTTErrorCode, MQTTProtocolVersion
|
|
14
14
|
from paho.mqtt.properties import Properties
|
|
15
15
|
from paho.mqtt.reasoncodes import ReasonCode
|
|
16
16
|
|
|
@@ -28,7 +28,7 @@ class LocalMessage:
|
|
|
28
28
|
payload: str | None = field(default=None)
|
|
29
29
|
|
|
30
30
|
|
|
31
|
-
class
|
|
31
|
+
class MqttPublisher:
|
|
32
32
|
def __init__(self, cfg: MqttConfig, node_cfg: NodeConfig, hass_cfg: HomeAssistantConfig) -> None:
|
|
33
33
|
self.cfg: MqttConfig = cfg
|
|
34
34
|
self.node_cfg: NodeConfig = node_cfg
|
|
@@ -42,19 +42,39 @@ class MqttClient:
|
|
|
42
42
|
def start(self, event_loop: asyncio.AbstractEventLoop | None = None) -> None:
|
|
43
43
|
logger = self.log.bind(action="start")
|
|
44
44
|
try:
|
|
45
|
+
protocol: MQTTProtocolVersion
|
|
46
|
+
if self.cfg.protocol in ("3", "3.11"):
|
|
47
|
+
protocol = MQTTProtocolVersion.MQTTv311
|
|
48
|
+
elif self.cfg.protocol == "3.1":
|
|
49
|
+
protocol = MQTTProtocolVersion.MQTTv31
|
|
50
|
+
elif self.cfg.protocol in ("5", "5.0"):
|
|
51
|
+
protocol = MQTTProtocolVersion.MQTTv5
|
|
52
|
+
else:
|
|
53
|
+
self.log.info("No valid MQTT protocol version found (%s), setting to default v3.11", self.cfg.protocol)
|
|
54
|
+
protocol = MQTTProtocolVersion.MQTTv311
|
|
55
|
+
self.log.debug("MQTT protocol set to %r", protocol)
|
|
56
|
+
|
|
45
57
|
self.event_loop = event_loop or asyncio.get_event_loop()
|
|
46
58
|
self.client = mqtt.Client(
|
|
47
59
|
callback_api_version=CallbackAPIVersion.VERSION2,
|
|
48
60
|
client_id=f"updates2mqtt_{self.node_cfg.name}",
|
|
49
|
-
clean_session=True,
|
|
61
|
+
clean_session=True if protocol != MQTTProtocolVersion.MQTTv5 else None,
|
|
62
|
+
protocol=protocol,
|
|
50
63
|
)
|
|
51
64
|
self.client.username_pw_set(self.cfg.user, password=self.cfg.password)
|
|
52
|
-
rc: MQTTErrorCode = self.client.connect(
|
|
65
|
+
rc: MQTTErrorCode = self.client.connect(
|
|
66
|
+
host=self.cfg.host,
|
|
67
|
+
port=self.cfg.port,
|
|
68
|
+
keepalive=60,
|
|
69
|
+
clean_start=MQTT_CLEAN_START_FIRST_ONLY,
|
|
70
|
+
)
|
|
53
71
|
self.log.info("Client connection requested", result_code=rc)
|
|
54
72
|
|
|
55
73
|
self.client.on_connect = self.on_connect
|
|
56
74
|
self.client.on_disconnect = self.on_disconnect
|
|
57
75
|
self.client.on_message = self.on_message
|
|
76
|
+
self.client.on_subscribe = self.on_subscribe
|
|
77
|
+
self.client.on_unsubscribe = self.on_unsubscribe
|
|
58
78
|
|
|
59
79
|
self.client.loop_start()
|
|
60
80
|
|
|
@@ -70,7 +90,7 @@ class MqttClient:
|
|
|
70
90
|
self.client = None
|
|
71
91
|
|
|
72
92
|
def is_available(self) -> bool:
|
|
73
|
-
return not self.fatal_failure.is_set()
|
|
93
|
+
return self.client is not None and not self.fatal_failure.is_set()
|
|
74
94
|
|
|
75
95
|
def on_connect(
|
|
76
96
|
self, _client: mqtt.Client, _userdata: Any, _flags: mqtt.ConnectFlags, rc: ReasonCode, _props: Properties | None
|
|
@@ -83,8 +103,8 @@ class MqttClient:
|
|
|
83
103
|
log.error("Invalid MQTT credentials", result_code=rc)
|
|
84
104
|
|
|
85
105
|
self.log.info("Connected to broker", result_code=rc)
|
|
86
|
-
for topic in self.providers_by_topic:
|
|
87
|
-
self.log.info("(Re)subscribing", topic=topic)
|
|
106
|
+
for topic, provider in self.providers_by_topic.items():
|
|
107
|
+
self.log.info("(Re)subscribing", topic=topic, provider=provider.source_type)
|
|
88
108
|
self.client.subscribe(topic)
|
|
89
109
|
|
|
90
110
|
def on_disconnect(
|
|
@@ -223,12 +243,37 @@ class MqttClient:
|
|
|
223
243
|
)
|
|
224
244
|
self.handle_message(msg)
|
|
225
245
|
|
|
246
|
+
def on_subscribe(
|
|
247
|
+
self,
|
|
248
|
+
_client: mqtt.Client,
|
|
249
|
+
userdata: Any,
|
|
250
|
+
mid: int,
|
|
251
|
+
reason_code_list: list[ReasonCode],
|
|
252
|
+
properties: Properties | None = None,
|
|
253
|
+
) -> None:
|
|
254
|
+
self.log.debug(
|
|
255
|
+
"on_subscribe, userdata=%s, mid=%s, reasons=%s, properties=%s", userdata, mid, reason_code_list, properties
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
def on_unsubscribe(
|
|
259
|
+
self,
|
|
260
|
+
_client: mqtt.Client,
|
|
261
|
+
userdata: Any,
|
|
262
|
+
mid: int,
|
|
263
|
+
reason_code_list: list[ReasonCode],
|
|
264
|
+
properties: Properties | None = None,
|
|
265
|
+
) -> None:
|
|
266
|
+
self.log.debug(
|
|
267
|
+
"on_unsubscribe, userdata=%s, mid=%s, reasons=%s, properties=%s", userdata, mid, reason_code_list, properties
|
|
268
|
+
)
|
|
269
|
+
|
|
226
270
|
def on_message(self, _client: mqtt.Client, _userdata: Any, msg: mqtt.MQTTMessage) -> None:
|
|
227
271
|
"""Callback for incoming MQTT messages""" # noqa: D401
|
|
228
272
|
if msg.topic in self.providers_by_topic:
|
|
229
273
|
self.handle_message(msg)
|
|
230
274
|
else:
|
|
231
|
-
|
|
275
|
+
# apparently the root non-wildcard sub sometimes brings in child topics
|
|
276
|
+
self.log.debug("Unhandled message #%s on %s:%s", msg.mid, msg.topic, msg.payload)
|
|
232
277
|
|
|
233
278
|
def handle_message(self, msg: mqtt.MQTTMessage | LocalMessage) -> None:
|
|
234
279
|
def update_start(discovery: Discovery) -> None:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: updates2mqtt
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.1
|
|
4
4
|
Summary: System update and docker image notification and execution over MQTT
|
|
5
5
|
Project-URL: Homepage, https://updates2mqtt.rhizomatics.org.uk
|
|
6
6
|
Project-URL: Repository, https://github.com/rhizomatics/updates2mqtt
|
|
@@ -39,8 +39,12 @@ Description-Content-Type: text/markdown
|
|
|
39
39
|
|
|
40
40
|
# updates2mqtt
|
|
41
41
|
|
|
42
|
+
[](https://github.com/rhizomatics)
|
|
43
|
+
|
|
42
44
|
[](https://pypi.org/project/updates2mqtt/)
|
|
43
45
|
[](https://github.com/rhizomatics/supernotify)
|
|
46
|
+

|
|
47
|
+

|
|
44
48
|
[](https://results.pre-commit.ci/latest/github/rhizomatics/updates2mqtt/main)
|
|
45
49
|
[](https://github.com/rhizomatics/updates2mqtt/actions/workflows/pypi-publish.yml)
|
|
46
50
|
[](https://github.com/rhizomatics/updates2mqtt/actions/workflows/python-package.yml)
|
|
@@ -49,9 +53,14 @@ Description-Content-Type: text/markdown
|
|
|
49
53
|
|
|
50
54
|
## Summary
|
|
51
55
|
|
|
52
|
-
|
|
56
|
+
Let Home Assistant tell you about new updates to Docker images for your containers.
|
|
57
|
+
|
|
58
|
+

|
|
59
|
+
|
|
60
|
+
Read the release notes, and optionally click *Update* to trigger a Docker *pull* (or optionally *build*) and *update*.
|
|
61
|
+
|
|
62
|
+
{width=480}
|
|
53
63
|
|
|
54
|
-

|
|
55
64
|
|
|
56
65
|
## Description
|
|
57
66
|
|
|
@@ -59,7 +68,7 @@ updates2mqtt perioidically checks for new versions of components being available
|
|
|
59
68
|
|
|
60
69
|
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.
|
|
61
70
|
|
|
62
|
-
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.
|
|
71
|
+
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.
|
|
63
72
|
|
|
64
73
|
To get started, read the [Installation](installation.md) and [Configuration](configuration.md) pages.
|
|
65
74
|
|
|
@@ -71,8 +80,7 @@ docker run -e MQTT_USER=user1 -e MQTT_PASS=pass1 -e MQTT_HOST=192.168.1.5 ghcr.i
|
|
|
71
80
|
|
|
72
81
|
## Release Support
|
|
73
82
|
|
|
74
|
-
Presently only Docker containers are supported, although others are planned,
|
|
75
|
-
probably with priority for `apt`.
|
|
83
|
+
Presently only Docker containers are supported, although others are planned, probably with priority for `apt`.
|
|
76
84
|
|
|
77
85
|
| Ecosystem | Support | Comments |
|
|
78
86
|
|-----------|-------------|----------------------------------------------------------------------------------------------------|
|
|
@@ -82,31 +90,57 @@ probably with priority for `apt`.
|
|
|
82
90
|
|
|
83
91
|
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.
|
|
84
92
|
|
|
85
|
-
A `healthcheck.sh` script is included in the Docker image, and can be used as a Docker healthcheck, if the container environment variables are set for `MQTT_HOST`, `MQTT_PORT`, `MQTT_USER` and `MQTT_PASS`.
|
|
93
|
+
A `healthcheck.sh` script is included in the Docker image, and can be used as a Docker healthcheck, if the container environment variables are set for `MQTT_HOST`, `MQTT_PORT`, `MQTT_USER` and `MQTT_PASS`. It uses the `mosquitto-clients` Linux package which provides `mosquitto_sub` command to subscribe to topics.
|
|
86
94
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
## HomeAssistant integration
|
|
95
|
+
!!! tip
|
|
90
96
|
|
|
91
|
-
|
|
92
|
-
Home Assistant settings page if the [MQTT Integration](https://www.home-assistant.io/integrations/mqtt/) is installed and automatic discovery is not disabled.
|
|
97
|
+
Check healthcheck is working using `docker inspect --format "{{json .State.Health }}" updates2mqtt | jq`
|
|
93
98
|
|
|
94
|
-
|
|
99
|
+
Another approach is using a restarter service directly in Docker Compose to force a restart, in this case once a day:
|
|
95
100
|
|
|
96
|
-
|
|
101
|
+
```yaml title="Example Compose Service"
|
|
102
|
+
restarter:
|
|
103
|
+
image: docker:cli
|
|
104
|
+
volumes: ["/var/run/docker.sock:/var/run/docker.sock"]
|
|
105
|
+
command: ["/bin/sh", "-c", "while true; do sleep 86400; docker restart updates2mqtt; done"]
|
|
106
|
+
restart: unless-stopped
|
|
107
|
+
environment:
|
|
108
|
+
- UPD2MQTT_UPDATE=AUTO
|
|
109
|
+
```
|
|
97
110
|
|
|
98
|
-
|
|
111
|
+
## Target Containers
|
|
99
112
|
|
|
100
|
-
|
|
113
|
+
While `updates2mqtt` will discover and monitor all containers running under the Docker daemon,
|
|
114
|
+
there are some options to make to those containers to tune how it works.
|
|
101
115
|
|
|
102
|
-
|
|
116
|
+
These happen by adding environment variables to the containers, typically inside an `.env`
|
|
117
|
+
file, or as `environment` options inside `docker-compose.yaml`.
|
|
103
118
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
119
|
+
### Automated updates
|
|
120
|
+
|
|
121
|
+
If Docker containers should be immediately updated, without any confirmation
|
|
122
|
+
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`)
|
|
123
|
+
|
|
124
|
+
```yaml title="Example Compose Snippet"
|
|
125
|
+
restarter:
|
|
126
|
+
image: docker:cli
|
|
127
|
+
command: ["/bin/sh", "-c", "while true; do sleep 86400; docker restart mailserver; done"]
|
|
128
|
+
environment:
|
|
129
|
+
- UPD2MQTT_UPDATE=AUTO
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Environment Variables
|
|
133
|
+
|
|
134
|
+
The following environment variables can be used to configure containers for `updates2mqtt`:
|
|
135
|
+
|
|
136
|
+
| Env Var | Description | Default |
|
|
137
|
+
|---------| ------------|----------|
|
|
138
|
+
| `UPD2MQTT_UPDATE` | Update mode, either `Passive` or `Auto`. If `Auto`, updates will be installed automatically. | `Passive` |
|
|
139
|
+
| `UPD2MQTT_PICTURE` | URL to an icon to use in Home Assistant. | Docker logo URL |
|
|
140
|
+
| `UPD2MQTT_RELNOTES` | URL to release notes for the package. | |
|
|
141
|
+
| `UPD2MQTT_GIT_REPO_PATH` | Relative path to a local git repo if the image is built locally. | |
|
|
142
|
+
| `UPD2MQTT_IGNORE` | If set to `True`, the container will be ignored by updates2mqtt. | False |
|
|
107
143
|
|
|
108
|
-
If the package supports automated update, then *Skip* and *Install* buttons will appear on the Home Assistant
|
|
109
|
-
interface, and the package can be remotely fetched and the component restarted.
|
|
110
144
|
|
|
111
145
|
## Related Projects
|
|
112
146
|
|
|
@@ -114,6 +148,18 @@ Other apps useful for self-hosting with the help of MQTT:
|
|
|
114
148
|
|
|
115
149
|
- [psmqtt](https://github.com/eschava/psmqtt) - Report system health and metrics via MQTT
|
|
116
150
|
|
|
151
|
+
Find more at [awesome-mqtt](https://github.com/rhizomatics/awesome-mqtt)
|
|
152
|
+
|
|
117
153
|
## Development
|
|
118
154
|
|
|
119
|
-
|
|
155
|
+
This component relies on several open source packages:
|
|
156
|
+
|
|
157
|
+
- [docker-py](https://docker-py.readthedocs.io/en/stable/) SDK for Python for access to Docker APIs
|
|
158
|
+
- [Eclipse Paho](https://eclipse.dev/paho/files/paho.mqtt.python/html/client.html) MQTT client
|
|
159
|
+
- [OmegaConf](https://omegaconf.readthedocs.io) for configuration and validation
|
|
160
|
+
- [structlog](https://www.structlog.org/en/stable/) for structured logging and [rich](https://rich.readthedocs.io/en/stable/) for better exception reporting
|
|
161
|
+
- [hishel](https://hishel.com/1.0/) for caching metadata
|
|
162
|
+
- [httpx](https://www.python-httpx.org) for retrieving metadata
|
|
163
|
+
- The Astral [uv](https://docs.astral.sh/uv/) and [ruff](https://docs.astral.sh/ruff/) tools for development and build
|
|
164
|
+
- [pytest](https://docs.pytest.org/en/stable/) and supporting add-ins for automated testing
|
|
165
|
+
- [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=-jAe9HcSSRmq5SZCvlsb8bFq2yVBNZQ0alQ5iRjl3tY,8518
|
|
4
|
+
updates2mqtt/config.py,sha256=SK6uhDyUb9C2JYVd0j6KBHzSAfaCFcOUbmmgsq6VSs0,5027
|
|
5
|
+
updates2mqtt/hass_formatter.py,sha256=Ulfj8F0e_1QMmRuJzHsNM2WxHbz9sIkWOWjRq3kQZzs,2772
|
|
6
|
+
updates2mqtt/model.py,sha256=O6GQFhfQvwQDxlZScFHrpQgFNkUrUAeaGGP6AqNua78,3827
|
|
7
|
+
updates2mqtt/mqtt.py,sha256=WiGB2yFj2xUb-5LZh84zTPLilSvHn0CgUCn41FhC5Sk,14664
|
|
8
|
+
updates2mqtt/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
updates2mqtt/integrations/__init__.py,sha256=KmNTUxvVWvqI7rl4I0xZg7XaCmcMS2O4OSv-ClsWM4Q,109
|
|
10
|
+
updates2mqtt/integrations/docker.py,sha256=Yb0XsrRyNZYAkw_ayd2iYLPFwgeA51Lz9HBVAGq3rs4,19273
|
|
11
|
+
updates2mqtt/integrations/git_utils.py,sha256=bPCmQiZpKpMcrGI7xAVmePXHFn8WwjcPNkf7xqDsGQA,2319
|
|
12
|
+
updates2mqtt-1.4.1.dist-info/METADATA,sha256=Q_yunyG90zU0B3uw2U5_FIImF28DHLNcK0otr6tTzVU,9310
|
|
13
|
+
updates2mqtt-1.4.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
14
|
+
updates2mqtt-1.4.1.dist-info/entry_points.txt,sha256=Hc6NZ2dBevYSUKTJU6NOs8Mw7Vt0S-9lq5FuKb76NCc,54
|
|
15
|
+
updates2mqtt-1.4.1.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
16
|
+
updates2mqtt-1.4.1.dist-info/RECORD,,
|
|
@@ -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=7jnmtIkXlX4e4lIt8WCzV19IYrICkA7cU4m9u9QXvRU,8229
|
|
4
|
-
updates2mqtt/config.py,sha256=NiaFdMTXXAjZIbtW7LHYjSqu-ONEoouT2uUu506CTtM,5000
|
|
5
|
-
updates2mqtt/hass_formatter.py,sha256=Ulfj8F0e_1QMmRuJzHsNM2WxHbz9sIkWOWjRq3kQZzs,2772
|
|
6
|
-
updates2mqtt/model.py,sha256=5tWlj3appGGZjkuBeYR2lb-kXoy5mzCn4P_EJQjnwok,3676
|
|
7
|
-
updates2mqtt/mqtt.py,sha256=i2l1BlEmnkp3Ie2qeAPVmdhIO1I_mH8Zxbm84cIYtGI,12741
|
|
8
|
-
updates2mqtt/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
-
updates2mqtt/integrations/__init__.py,sha256=KmNTUxvVWvqI7rl4I0xZg7XaCmcMS2O4OSv-ClsWM4Q,109
|
|
10
|
-
updates2mqtt/integrations/docker.py,sha256=OX_sXtWVgUJfRSFi_tTBib4gvP_foQI4MoeTkTeLbZc,18868
|
|
11
|
-
updates2mqtt/integrations/git_utils.py,sha256=SkAp6XcvCHwaiy17t6F97kcWTjBd7RyEmfhz6M_EhP0,2196
|
|
12
|
-
updates2mqtt-1.3.7.dist-info/METADATA,sha256=yFJy-VGcnmHYdFksLx2teBHbuPi-B3J7pObaE5-XVEk,7990
|
|
13
|
-
updates2mqtt-1.3.7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
14
|
-
updates2mqtt-1.3.7.dist-info/entry_points.txt,sha256=Hc6NZ2dBevYSUKTJU6NOs8Mw7Vt0S-9lq5FuKb76NCc,54
|
|
15
|
-
updates2mqtt-1.3.7.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
16
|
-
updates2mqtt-1.3.7.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|