updates2mqtt 1.7.3__py3-none-any.whl → 1.8.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 +10 -6
- updates2mqtt/cli.py +33 -15
- updates2mqtt/config.py +28 -19
- updates2mqtt/helpers.py +44 -4
- updates2mqtt/integrations/docker.py +53 -15
- updates2mqtt/integrations/docker_enrich.py +168 -51
- updates2mqtt/model.py +26 -10
- updates2mqtt/mqtt.py +109 -60
- {updates2mqtt-1.7.3.dist-info → updates2mqtt-1.8.1.dist-info}/METADATA +8 -44
- updates2mqtt-1.8.1.dist-info/RECORD +18 -0
- {updates2mqtt-1.7.3.dist-info → updates2mqtt-1.8.1.dist-info}/WHEEL +1 -1
- updates2mqtt-1.7.3.dist-info/RECORD +0 -18
- {updates2mqtt-1.7.3.dist-info → updates2mqtt-1.8.1.dist-info}/entry_points.txt +0 -0
updates2mqtt/mqtt.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import json
|
|
3
|
+
import re
|
|
3
4
|
import time
|
|
4
5
|
from collections.abc import Callable
|
|
5
6
|
from dataclasses import dataclass, field
|
|
@@ -9,7 +10,7 @@ from typing import Any
|
|
|
9
10
|
import paho.mqtt.client as mqtt
|
|
10
11
|
import paho.mqtt.subscribeoptions
|
|
11
12
|
import structlog
|
|
12
|
-
from paho.mqtt.client import MQTT_CLEAN_START_FIRST_ONLY, MQTTMessage
|
|
13
|
+
from paho.mqtt.client import MQTT_CLEAN_START_FIRST_ONLY, MQTTMessage, MQTTMessageInfo
|
|
13
14
|
from paho.mqtt.enums import CallbackAPIVersion, MQTTErrorCode, MQTTProtocolVersion
|
|
14
15
|
from paho.mqtt.properties import Properties
|
|
15
16
|
from paho.mqtt.reasoncodes import ReasonCode
|
|
@@ -21,6 +22,8 @@ from .hass_formatter import hass_format_config, hass_format_state
|
|
|
21
22
|
|
|
22
23
|
log = structlog.get_logger()
|
|
23
24
|
|
|
25
|
+
MQTT_NAME = r"[A-Za-z0-9_\-\.]+"
|
|
26
|
+
|
|
24
27
|
|
|
25
28
|
@dataclass
|
|
26
29
|
class LocalMessage:
|
|
@@ -34,6 +37,7 @@ class MqttPublisher:
|
|
|
34
37
|
self.node_cfg: NodeConfig = node_cfg
|
|
35
38
|
self.hass_cfg: HomeAssistantConfig = hass_cfg
|
|
36
39
|
self.providers_by_topic: dict[str, ReleaseProvider] = {}
|
|
40
|
+
self.providers_by_type: dict[str, ReleaseProvider] = {}
|
|
37
41
|
self.event_loop: asyncio.AbstractEventLoop | None = None
|
|
38
42
|
self.client: mqtt.Client | None = None
|
|
39
43
|
self.fatal_failure = Event()
|
|
@@ -123,69 +127,60 @@ class MqttPublisher:
|
|
|
123
127
|
else:
|
|
124
128
|
self.log.warning("Disconnect failure from broker", result_code=rc)
|
|
125
129
|
|
|
126
|
-
async def clean_topics(
|
|
127
|
-
self, provider: ReleaseProvider, last_scan_session: str | None, wait_time: int = 5, force: bool = False
|
|
128
|
-
) -> None:
|
|
130
|
+
async def clean_topics(self, provider: ReleaseProvider, wait_time: int = 5, max_time: int = 120) -> None:
|
|
129
131
|
logger = self.log.bind(action="clean")
|
|
130
132
|
if self.fatal_failure.is_set():
|
|
131
133
|
return
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
134
|
+
try:
|
|
135
|
+
logger.info("Starting clean cycle, max time: %s", max_time)
|
|
136
|
+
cutoff_time: float = time.time() + max_time
|
|
137
|
+
cleaner = mqtt.Client(
|
|
138
|
+
callback_api_version=CallbackAPIVersion.VERSION1,
|
|
139
|
+
client_id=f"updates2mqtt_clean_{self.node_cfg.name}",
|
|
140
|
+
clean_session=True,
|
|
141
|
+
)
|
|
142
|
+
results = {"cleaned": 0, "matched": 0, "discovered": 0, "last_timestamp": time.time()}
|
|
143
|
+
cleaner.username_pw_set(self.cfg.user, password=self.cfg.password)
|
|
144
|
+
cleaner.connect(host=self.cfg.host, port=self.cfg.port, keepalive=60)
|
|
145
|
+
|
|
146
|
+
def cleanup(_client: mqtt.Client, _userdata: Any, msg: mqtt.MQTTMessage) -> None:
|
|
147
|
+
discovery: Discovery | None = None
|
|
148
|
+
if msg.topic.startswith(
|
|
149
|
+
f"{self.hass_cfg.discovery.prefix}/update/{self.node_cfg.name}_{provider.source_type}_"
|
|
150
|
+
):
|
|
151
|
+
discovery = self.reverse_config_topic(msg.topic, provider.source_type)
|
|
152
|
+
elif msg.topic.startswith(
|
|
153
|
+
f"{self.cfg.topic_root}/{self.node_cfg.name}/{provider.source_type}/"
|
|
154
|
+
) and msg.topic.endswith("/state"):
|
|
155
|
+
discovery = self.reverse_state_topic(msg.topic, provider.source_type)
|
|
156
|
+
elif msg.topic.startswith(f"{self.cfg.topic_root}/{self.node_cfg.name}/{provider.source_type}/"):
|
|
157
|
+
discovery = self.reverse_general_topic(msg.topic, provider.source_type)
|
|
158
|
+
else:
|
|
159
|
+
logger.debug("Ignoring other topic ", topic=msg.topic)
|
|
160
|
+
return
|
|
161
|
+
|
|
149
162
|
results["discovered"] += 1
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
session = payload.get("source_session")
|
|
153
|
-
except Exception as e:
|
|
154
|
-
log.warn(
|
|
155
|
-
"Unable to handle payload for %s: %s",
|
|
156
|
-
msg.topic,
|
|
157
|
-
e,
|
|
158
|
-
exc_info=1,
|
|
159
|
-
)
|
|
160
|
-
results["handled"] += 1
|
|
163
|
+
if discovery is not None:
|
|
164
|
+
results["matched"] += 1
|
|
161
165
|
results["last_timestamp"] = time.time()
|
|
162
|
-
if
|
|
163
|
-
|
|
166
|
+
if discovery is None:
|
|
167
|
+
logger.debug("Removing unknown discovery", topic=msg.topic)
|
|
164
168
|
cleaner.publish(msg.topic, "", retain=True)
|
|
165
169
|
results["cleaned"] += 1
|
|
166
|
-
elif session is None and force:
|
|
167
|
-
log.debug("Removing untrackable msg", topic=msg.topic)
|
|
168
|
-
cleaner.publish(msg.topic, "", retain=True)
|
|
169
|
-
results["cleaned"] += 1
|
|
170
|
-
else:
|
|
171
|
-
log.debug(
|
|
172
|
-
"Retaining topic with current session: %s",
|
|
173
|
-
msg.topic,
|
|
174
|
-
)
|
|
175
|
-
else:
|
|
176
|
-
log.debug("Skipping clean of %s", msg.topic)
|
|
177
170
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
171
|
+
cleaner.on_message = cleanup
|
|
172
|
+
options = paho.mqtt.subscribeoptions.SubscribeOptions(noLocal=True)
|
|
173
|
+
cleaner.subscribe(f"{self.hass_cfg.discovery.prefix}/update/#", options=options)
|
|
174
|
+
cleaner.subscribe(f"{self.cfg.topic_root}/{self.node_cfg.name}/{provider.source_type}/#", options=options)
|
|
182
175
|
|
|
183
|
-
|
|
184
|
-
|
|
176
|
+
while time.time() - results["last_timestamp"] <= wait_time and time.time() <= cutoff_time:
|
|
177
|
+
cleaner.loop(0.5)
|
|
185
178
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
179
|
+
logger.info(
|
|
180
|
+
f"Cleaned - discovered:{results['discovered']}, matched:{results['matched']}, cleaned:{results['cleaned']}"
|
|
181
|
+
)
|
|
182
|
+
except Exception as e:
|
|
183
|
+
logger.error("Cleaning topics of stale entries failed: %s", e)
|
|
189
184
|
|
|
190
185
|
def safe_json_decode(self, jsonish: str | bytes | None) -> dict:
|
|
191
186
|
if jsonish is None:
|
|
@@ -218,6 +213,7 @@ class MqttPublisher:
|
|
|
218
213
|
payload = msg.payload
|
|
219
214
|
if payload and "|" in payload:
|
|
220
215
|
source_type, comp_name, command = payload.split("|")
|
|
216
|
+
logger.debug("Executing %s:%s:%s", source_type, comp_name, command)
|
|
221
217
|
|
|
222
218
|
provider: ReleaseProvider | None = self.providers_by_topic.get(msg.topic) if msg.topic else None
|
|
223
219
|
if not provider:
|
|
@@ -233,14 +229,14 @@ class MqttPublisher:
|
|
|
233
229
|
source_type,
|
|
234
230
|
comp_name,
|
|
235
231
|
)
|
|
236
|
-
updated = provider.command(comp_name, command, on_update_start, on_update_end)
|
|
232
|
+
updated: bool = provider.command(comp_name, command, on_update_start, on_update_end)
|
|
237
233
|
discovery = provider.resolve(comp_name)
|
|
238
234
|
if updated and discovery:
|
|
239
235
|
if discovery.publish_policy == PublishPolicy.HOMEASSISTANT and self.hass_cfg.discovery.enabled:
|
|
240
236
|
self.publish_hass_config(discovery)
|
|
241
237
|
if discovery.publish_policy in (PublishPolicy.HOMEASSISTANT, PublishPolicy.MQTT):
|
|
242
238
|
self.publish_discovery(discovery)
|
|
243
|
-
if discovery.publish_policy == PublishPolicy.HOMEASSISTANT:
|
|
239
|
+
if discovery and discovery.publish_policy == PublishPolicy.HOMEASSISTANT:
|
|
244
240
|
self.publish_hass_state(discovery)
|
|
245
241
|
else:
|
|
246
242
|
logger.debug("No change to republish after execution")
|
|
@@ -289,15 +285,25 @@ class MqttPublisher:
|
|
|
289
285
|
|
|
290
286
|
def handle_message(self, msg: mqtt.MQTTMessage | LocalMessage) -> None:
|
|
291
287
|
def update_start(discovery: Discovery) -> None:
|
|
292
|
-
|
|
288
|
+
self.log.debug("on_update_start: %s", topic=msg.topic)
|
|
289
|
+
if discovery.publish_policy == PublishPolicy.HOMEASSISTANT:
|
|
293
290
|
self.publish_hass_state(discovery, in_progress=True)
|
|
291
|
+
if discovery.publish_policy in (PublishPolicy.HOMEASSISTANT, PublishPolicy.MQTT):
|
|
292
|
+
self.publish_discovery(discovery, in_progress=True)
|
|
294
293
|
|
|
295
294
|
def update_end(discovery: Discovery) -> None:
|
|
296
|
-
|
|
295
|
+
self.log.debug("on_update_end: %s", topic=msg.topic)
|
|
296
|
+
if discovery.publish_policy == PublishPolicy.HOMEASSISTANT:
|
|
297
297
|
self.publish_hass_state(discovery, in_progress=False)
|
|
298
|
+
if discovery.publish_policy in (PublishPolicy.HOMEASSISTANT, PublishPolicy.MQTT):
|
|
299
|
+
self.publish_discovery(discovery, in_progress=False)
|
|
298
300
|
|
|
301
|
+
# TODO: fix double publish on callback and in command exec
|
|
299
302
|
if self.event_loop is not None:
|
|
300
|
-
|
|
303
|
+
self.log.debug("Executing command topic", topic=msg.topic)
|
|
304
|
+
asyncio.run_coroutine_threadsafe(
|
|
305
|
+
self.execute_command(msg=msg, on_update_start=update_start, on_update_end=update_end), loop=self.event_loop
|
|
306
|
+
)
|
|
301
307
|
else:
|
|
302
308
|
self.log.error("No event loop to handle message", topic=msg.topic)
|
|
303
309
|
|
|
@@ -305,12 +311,48 @@ class MqttPublisher:
|
|
|
305
311
|
prefix = self.hass_cfg.discovery.prefix
|
|
306
312
|
return f"{prefix}/update/{self.node_cfg.name}_{discovery.source_type}_{discovery.name}/update/config"
|
|
307
313
|
|
|
314
|
+
def reverse_config_topic(self, topic: str, source_type: str) -> Discovery | None:
|
|
315
|
+
match = re.fullmatch(
|
|
316
|
+
f"{self.hass_cfg.discovery.prefix}/update/{self.node_cfg.name}_{source_type}_({MQTT_NAME})/update/config",
|
|
317
|
+
topic,
|
|
318
|
+
)
|
|
319
|
+
if match and len(match.groups()) == 1:
|
|
320
|
+
discovery_name: str = match.group(1)
|
|
321
|
+
if discovery_name in self.providers_by_type[source_type].discoveries:
|
|
322
|
+
return self.providers_by_type[source_type].discoveries[discovery_name]
|
|
323
|
+
|
|
324
|
+
self.log.debug("MQTT CONFIG no match for %s", topic)
|
|
325
|
+
return None
|
|
326
|
+
|
|
308
327
|
def state_topic(self, discovery: Discovery) -> str:
|
|
309
328
|
return f"{self.cfg.topic_root}/{self.node_cfg.name}/{discovery.source_type}/{discovery.name}/state"
|
|
310
329
|
|
|
330
|
+
def reverse_state_topic(self, topic: str, source_type: str) -> Discovery | None:
|
|
331
|
+
match = re.fullmatch(
|
|
332
|
+
f"{self.cfg.topic_root}/{self.node_cfg.name}/{source_type}/({MQTT_NAME})/state",
|
|
333
|
+
topic,
|
|
334
|
+
)
|
|
335
|
+
if match and len(match.groups()) == 1:
|
|
336
|
+
discovery_name: str = match.group(1)
|
|
337
|
+
if discovery_name in self.providers_by_type[source_type].discoveries:
|
|
338
|
+
return self.providers_by_type[source_type].discoveries[discovery_name]
|
|
339
|
+
|
|
340
|
+
self.log.debug("MQTT STATE no match for %s", topic)
|
|
341
|
+
return None
|
|
342
|
+
|
|
311
343
|
def general_topic(self, discovery: Discovery) -> str:
|
|
312
344
|
return f"{self.cfg.topic_root}/{self.node_cfg.name}/{discovery.source_type}/{discovery.name}"
|
|
313
345
|
|
|
346
|
+
def reverse_general_topic(self, topic: str, source_type: str) -> Discovery | None:
|
|
347
|
+
match = re.fullmatch(f"{self.cfg.topic_root}/{self.node_cfg.name}/{source_type}/({MQTT_NAME})", topic)
|
|
348
|
+
if match and len(match.groups()) == 1:
|
|
349
|
+
discovery_name: str = match.group(1)
|
|
350
|
+
if discovery_name in self.providers_by_type[source_type].discoveries:
|
|
351
|
+
return self.providers_by_type[source_type].discoveries[discovery_name]
|
|
352
|
+
|
|
353
|
+
self.log.debug("MQTT ATTR no match for %s", topic)
|
|
354
|
+
return None
|
|
355
|
+
|
|
314
356
|
def command_topic(self, provider: ReleaseProvider) -> str:
|
|
315
357
|
return f"{self.cfg.topic_root}/{self.node_cfg.name}/{provider.source_type}"
|
|
316
358
|
|
|
@@ -363,6 +405,7 @@ class MqttPublisher:
|
|
|
363
405
|
else:
|
|
364
406
|
self.log.info("Handler subscribing", topic=topic)
|
|
365
407
|
self.providers_by_topic[topic] = provider
|
|
408
|
+
self.providers_by_type[provider.source_type] = provider
|
|
366
409
|
self.client.subscribe(topic)
|
|
367
410
|
return topic
|
|
368
411
|
|
|
@@ -372,4 +415,10 @@ class MqttPublisher:
|
|
|
372
415
|
|
|
373
416
|
def publish(self, topic: str, payload: dict, qos: int = 0, retain: bool = True) -> None:
|
|
374
417
|
if self.client:
|
|
375
|
-
self.client.publish(topic, payload=json.dumps(payload), qos=qos, retain=retain)
|
|
418
|
+
info: MQTTMessageInfo = self.client.publish(topic, payload=json.dumps(payload), qos=qos, retain=retain)
|
|
419
|
+
if info.rc == MQTTErrorCode.MQTT_ERR_SUCCESS:
|
|
420
|
+
self.log.debug("Publish to %s, mid: %s, published: %s, rc: %s", topic, info.mid, info.is_published(), info.rc)
|
|
421
|
+
else:
|
|
422
|
+
self.log.warning("Problem publishing to %s, mid: %s, rc: %s", topic, info.mid, info.rc)
|
|
423
|
+
else:
|
|
424
|
+
self.log.debug("No client to publish at %s", topic)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: updates2mqtt
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.8.1
|
|
4
4
|
Summary: System update and docker image notification and execution over MQTT
|
|
5
5
|
Keywords: mqtt,docker,oci,container,updates,automation,home-assistant,homeassistant,selfhosting
|
|
6
6
|
Author: jey burrows
|
|
@@ -72,12 +72,12 @@ Read the release notes, and optionally click *Update* to trigger a Docker *pull*
|
|
|
72
72
|
|
|
73
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.
|
|
74
74
|
|
|
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
|
|
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, Quay and LinuxServer Registry, with adaptive behaviour to cope with most
|
|
76
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.
|
|
77
77
|
|
|
78
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.
|
|
79
79
|
|
|
80
|
-
To get started, read the [Installation](installation.md) and [Configuration](configuration.md) pages.
|
|
80
|
+
To get started, read the [Installation](installation.md) and [Configuration](configuration/index.md) pages.
|
|
81
81
|
|
|
82
82
|
For a quick spin, try this:
|
|
83
83
|
|
|
@@ -91,6 +91,9 @@ or without Docker, using [uv](https://docs.astral.sh/uv/)
|
|
|
91
91
|
export MQTT_HOST=192.168.1.1;export MQTT_USER=user1;export MQTT_PASS=user1;uv run --with updates2mqtt python -m updates2mqtt
|
|
92
92
|
```
|
|
93
93
|
|
|
94
|
+
It also comes with a basic command line tool that will perform the analysis for a single running container, or fetch
|
|
95
|
+
manifests, JSON blobs and lists of tags from remote registries (known to work with GitHub, GitLab, Codeberg, Quay, LSCR and Microsoft MCR).
|
|
96
|
+
|
|
94
97
|
## Release Support
|
|
95
98
|
|
|
96
99
|
Presently only Docker containers are supported, although others are planned, probably with priority for `apt`.
|
|
@@ -128,7 +131,7 @@ restarter:
|
|
|
128
131
|
While `updates2mqtt` will discover and monitor all containers running under the Docker daemon,
|
|
129
132
|
there are some options to make to those containers to tune how it works.
|
|
130
133
|
|
|
131
|
-
These happen by adding environment variables to the containers, typically inside an `.env`
|
|
134
|
+
These happen by adding environment variables or docker labels to the containers, typically inside an `.env`
|
|
132
135
|
file, or as `environment` options inside `docker-compose.yaml`.
|
|
133
136
|
|
|
134
137
|
### Automated updates
|
|
@@ -148,45 +151,6 @@ restarter:
|
|
|
148
151
|
Automated updates can also apply to local builds, where a `git_repo_path` has been defined - if there are remote
|
|
149
152
|
commits available to pull, then a `git pull`, `docker compose build` and `docker compose up` will be executed.
|
|
150
153
|
|
|
151
|
-
### Environment Variables
|
|
152
|
-
|
|
153
|
-
The following environment variables can be used to configure containers for `updates2mqtt`:
|
|
154
|
-
|
|
155
|
-
| Env Var | Description | Default |
|
|
156
|
-
|----------------------------|----------------------------------------------------------------------------------------------|-----------------|
|
|
157
|
-
| `UPD2MQTT_UPDATE` | Update mode, either `Passive` or `Auto`. If `Auto`, updates will be installed automatically. | `Passive` |
|
|
158
|
-
| `UPD2MQTT_PICTURE` | URL to an icon to use in Home Assistant. | Docker logo URL |
|
|
159
|
-
| `UPD2MQTT_RELNOTES` | URL to release notes for the package. | |
|
|
160
|
-
| `UPD2MQTT_GIT_REPO_PATH` | Relative path to a local git repo if the image is built locally. | |
|
|
161
|
-
| `UPD2MQTT_IGNORE` | If set to `True`, the container will be ignored by Updates2MQTT. | False |
|
|
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 |
|
|
165
|
-
|
|
166
|
-
### Docker Labels
|
|
167
|
-
|
|
168
|
-
Alternatively, use Docker labels
|
|
169
|
-
|
|
170
|
-
| Label | Env Var |
|
|
171
|
-
|--------------------------------|----------------------------|
|
|
172
|
-
| `updates2mqtt.update` | `UPD2MQTT_UPDATE` |
|
|
173
|
-
| `updates2mqtt.picture` | `UPD2MQTT_PCITURE` |
|
|
174
|
-
| `updates2mqtt.relnotes` | `UPD2MQTT_RELNOTES` |
|
|
175
|
-
| `updates2mqtt.git_repo_path` | `UPD2MQTT_GIT_REPO_PATH` |
|
|
176
|
-
| `updates2mqtt.ignore` | `UPD2MQTT_IGNORE` |
|
|
177
|
-
| `updates2mqtt.version_policy` | `UPD2MQTT_VERSION_POLICY` |
|
|
178
|
-
| `updates2mqtt.registry_token` | `UPD2MQTT_REGISTRY_TOKEN` |
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
```yaml title="Example Compose Snippet"
|
|
183
|
-
restarter:
|
|
184
|
-
image: docker:cli
|
|
185
|
-
command: ["/bin/sh", "-c", "while true; do sleep 86400; docker restart mailserver; done"]
|
|
186
|
-
labels:
|
|
187
|
-
updates2mqtt.relnotes: https://component.my.com/release_notes
|
|
188
|
-
```
|
|
189
|
-
|
|
190
154
|
|
|
191
155
|
## Related Projects
|
|
192
156
|
|
|
@@ -196,7 +160,7 @@ Other apps useful for self-hosting with the help of MQTT:
|
|
|
196
160
|
|
|
197
161
|
Find more at [awesome-mqtt](https://github.com/rhizomatics/awesome-mqtt)
|
|
198
162
|
|
|
199
|
-
For a more powerful Docker update manager, try [What's Up Docker](https://getwud.github.io/wud/)
|
|
163
|
+
For a more powerful Docker focussed update manager, try [What's Up Docker](https://getwud.github.io/wud/)
|
|
200
164
|
|
|
201
165
|
## Development
|
|
202
166
|
|
|
@@ -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=FcfYGF5gUO5GMNVECJtRDF3PdJNQG5q7YRkTZGqPuq4,9632
|
|
4
|
+
updates2mqtt/cli.py,sha256=LE9iVS7NldA3nyq44J2RfhL4grwB3X92tvhRWgv0CuU,5820
|
|
5
|
+
updates2mqtt/config.py,sha256=I2td2oUM_IbV7mKvje3EdVYxkYd6oDF8GnebWwz7Y6U,7547
|
|
6
|
+
updates2mqtt/hass_formatter.py,sha256=k0aLGg-7wI_C4TixhY-L-iz7n0QCKQ_Pvv37hSp22ww,2779
|
|
7
|
+
updates2mqtt/helpers.py,sha256=ZT70gL3Hia9BhTas_cTOoDAChD6LMBFYljNX97-lNok,10706
|
|
8
|
+
updates2mqtt/integrations/__init__.py,sha256=KmNTUxvVWvqI7rl4I0xZg7XaCmcMS2O4OSv-ClsWM4Q,109
|
|
9
|
+
updates2mqtt/integrations/docker.py,sha256=eD8GrDBwkHydZmaD1XrLCkOeDBcWaGyM3fh9yeFbbjg,31893
|
|
10
|
+
updates2mqtt/integrations/docker_enrich.py,sha256=X8c4sWgznOIamMEbUwP9iQ5L8IRHW_V0Fn-G5ZWTlTs,45418
|
|
11
|
+
updates2mqtt/integrations/git_utils.py,sha256=AnMiVW-noaBQ-17FeIl93jwpTSzvr70nIDEcJN3D-gw,4356
|
|
12
|
+
updates2mqtt/model.py,sha256=gC-JT3-D7d_qwFmXTbR0PXvDBMX4Ndm_ivToPbn4tmU,10620
|
|
13
|
+
updates2mqtt/mqtt.py,sha256=ZOdfBYWO9vqPSi--Hc5iMeZte3cUtIS2LyrxWpMcCx8,19674
|
|
14
|
+
updates2mqtt/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
+
updates2mqtt-1.8.1.dist-info/WHEEL,sha256=fAguSjoiATBe7TNBkJwOjyL1Tt4wwiaQGtNtjRPNMQA,80
|
|
16
|
+
updates2mqtt-1.8.1.dist-info/entry_points.txt,sha256=qtMKoTPaodbFC3YG7MLElWDjl7CfJdbrxxZyH6Bua8E,83
|
|
17
|
+
updates2mqtt-1.8.1.dist-info/METADATA,sha256=ky3uQt8qeOzmY2HTN1Y95M6jrVpCSEMbkqtNnVj4arQ,10069
|
|
18
|
+
updates2mqtt-1.8.1.dist-info/RECORD,,
|
|
@@ -1,18 +0,0 @@
|
|
|
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=mEFTTIQjSFml58Ek8cDRIHSz2JGJyNNT9TECBsqdp6o,9272
|
|
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.3.dist-info/WHEEL,sha256=XV0cjMrO7zXhVAIyyc8aFf1VjZ33Fen4IiJk5zFlC3g,80
|
|
16
|
-
updates2mqtt-1.7.3.dist-info/entry_points.txt,sha256=qtMKoTPaodbFC3YG7MLElWDjl7CfJdbrxxZyH6Bua8E,83
|
|
17
|
-
updates2mqtt-1.7.3.dist-info/METADATA,sha256=LZ_ncnPQG9qVKChVIewF5YmNDRdqCzceR2MPunL2Zno,12131
|
|
18
|
-
updates2mqtt-1.7.3.dist-info/RECORD,,
|
|
File without changes
|