updates2mqtt 1.6.0__tar.gz → 1.7.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,8 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: updates2mqtt
3
- Version: 1.6.0
3
+ Version: 1.7.0
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
@@ -27,12 +27,13 @@ Requires-Dist: rich>=14.0.0
27
27
  Requires-Dist: httpx>=0.28.1
28
28
  Requires-Dist: hishel[httpx]>=0.1.4
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 }
@@ -132,7 +133,8 @@ file, or as `environment` options inside `docker-compose.yaml`.
132
133
  ### Automated updates
133
134
 
134
135
  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`)
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`.
136
138
 
137
139
  ```yaml title="Example Compose Snippet"
138
140
  restarter:
@@ -142,9 +144,6 @@ restarter:
142
144
  - UPD2MQTT_UPDATE=AUTO
143
145
  ```
144
146
 
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
147
  Automated updates can also apply to local builds, where a `git_repo_path` has been defined - if there are remote
149
148
  commits available to pull, then a `git pull`, `docker compose build` and `docker compose up` will be executed.
150
149
 
@@ -159,8 +158,7 @@ The following environment variables can be used to configure containers for `upd
159
158
  | `UPD2MQTT_RELNOTES` | URL to release notes for the package. | |
160
159
  | `UPD2MQTT_GIT_REPO_PATH` | Relative path to a local git repo if the image is built locally. | |
161
160
  | `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 | |
161
+ | |
164
162
 
165
163
  ### Docker Labels
166
164
 
@@ -173,8 +171,6 @@ Alternatively, use Docker labels
173
171
  | `updates2mqtt.relnotes` | `UPD2MQTT_RELNOTES` |
174
172
  | `updates2mqtt.git_repo_path` | `UPD2MQTT_GIT_REPO_PATH` |
175
173
  | `updates2mqtt.ignore` | `UPD2MQTT_IGNORE` |
176
- | `updates2mqtt.version_include` | `UPD2MQTT_VERSION_INCLUDE` |
177
- | `updates2mqtt.version_exclude` | `UPD2MQTT_VERSION_EXCLUDE` |
178
174
 
179
175
 
180
176
  ```yaml title="Example Compose Snippet"
@@ -95,7 +95,8 @@ file, or as `environment` options inside `docker-compose.yaml`.
95
95
  ### Automated updates
96
96
 
97
97
  If Docker containers should be immediately updated, without any confirmation
98
- or trigger, *e.g.* from the HomeAssistant update dialog, then set an environment variable `UPD2MQTT_UPDATE` in the target container to `Auto` ( it defaults to `Passive`)
98
+ or trigger, *e.g.* from the HomeAssistant update dialog, then set an environment variable `UPD2MQTT_UPDATE` in the target container to `Auto` ( it defaults to `Passive`). If you want it to update without publishing to MQTT and being
99
+ visible to Home Assistant, then use `Silent`.
99
100
 
100
101
  ```yaml title="Example Compose Snippet"
101
102
  restarter:
@@ -105,9 +106,6 @@ restarter:
105
106
  - UPD2MQTT_UPDATE=AUTO
106
107
  ```
107
108
 
108
- This can be used in conjunction with the `UPD2MQTT_VERSION_INCLUDE` and `UPD2MQTT_VERSION_EXCLUDE` to
109
- limit which updates get automatically applied, for example excluding nightly builds.
110
-
111
109
  Automated updates can also apply to local builds, where a `git_repo_path` has been defined - if there are remote
112
110
  commits available to pull, then a `git pull`, `docker compose build` and `docker compose up` will be executed.
113
111
 
@@ -122,8 +120,7 @@ The following environment variables can be used to configure containers for `upd
122
120
  | `UPD2MQTT_RELNOTES` | URL to release notes for the package. | |
123
121
  | `UPD2MQTT_GIT_REPO_PATH` | Relative path to a local git repo if the image is built locally. | |
124
122
  | `UPD2MQTT_IGNORE` | If set to `True`, the container will be ignored by Updates2MQTT. | False |
125
- | `UPD2MQTT_VERSION_INCLUDE` | Only recognize versions matching this string or regular expression | |
126
- | `UPD2MQTT_VERSION_EXCLUDE` | Skip update if version matches this string or regular expression | |
123
+ | |
127
124
 
128
125
  ### Docker Labels
129
126
 
@@ -136,8 +133,6 @@ Alternatively, use Docker labels
136
133
  | `updates2mqtt.relnotes` | `UPD2MQTT_RELNOTES` |
137
134
  | `updates2mqtt.git_repo_path` | `UPD2MQTT_GIT_REPO_PATH` |
138
135
  | `updates2mqtt.ignore` | `UPD2MQTT_IGNORE` |
139
- | `updates2mqtt.version_include` | `UPD2MQTT_VERSION_INCLUDE` |
140
- | `updates2mqtt.version_exclude` | `UPD2MQTT_VERSION_EXCLUDE` |
141
136
 
142
137
 
143
138
  ```yaml title="Example Compose Snippet"
@@ -7,9 +7,9 @@ authors = [
7
7
  ]
8
8
 
9
9
  requires-python = ">=3.13"
10
- version = "1.6.0"
10
+ version = "1.7.0"
11
11
  license="Apache-2.0"
12
- keywords=["mqtt", "docker", "updates", "automation","home-assistant","homeassistant","selfhosting"]
12
+ keywords=["mqtt", "docker", "oci","container","updates", "automation","home-assistant","homeassistant","selfhosting"]
13
13
 
14
14
  dependencies = [
15
15
  "docker>=7.1.0",
@@ -20,6 +20,7 @@ dependencies = [
20
20
  "httpx>=0.28.1",
21
21
  "hishel[httpx]>=0.1.4",
22
22
  "usingversion>=0.1.2",
23
+ "tzlocal>=5.3.1",
23
24
  ]
24
25
  classifiers = [
25
26
  "Development Status :: 5 - Production/Stable",
@@ -70,7 +71,8 @@ docs=[
70
71
  "pymdown-extensions",
71
72
  "mkdocs-git-revision-date-localized-plugin",
72
73
  "mkdocs-meta-descriptions-plugin",
73
- "pngquant"
74
+ "pngquant",
75
+ "mkdocs-mermaid2-plugin"
74
76
  ]
75
77
 
76
78
  [build-system]
@@ -150,7 +152,6 @@ skip-magic-trailing-comma = false
150
152
  # Like Black, automatically detect the appropriate line ending.
151
153
  line-ending = "auto"
152
154
 
153
-
154
155
  [tool.pytest.ini_options]
155
156
  asyncio_mode = "auto"
156
157
  testpaths = [
@@ -187,4 +188,4 @@ Homepage="https://updates2mqtt.rhizomatics.org.uk"
187
188
  Repository="https://github.com/rhizomatics/updates2mqtt"
188
189
  Documentation="https://updates2mqtt.rhizomatics.org.uk"
189
190
  Issues="https://github.com/rhizomatics/updates2mqtt/issues"
190
- Changelog="https://github.com/rhizomatics/updates2mqtt/blob/main/CHANGELOG.md"
191
+ Changelog="https://github.com/rhizomatics/updates2mqtt/blob/main/CHANGELOG.md"
@@ -14,7 +14,7 @@ import structlog
14
14
  import updates2mqtt
15
15
  from updates2mqtt.model import Discovery, ReleaseProvider
16
16
 
17
- from .config import Config, PackageUpdateInfo, load_app_config, load_package_info
17
+ from .config import Config, PublishPolicy, UpdatePolicy, load_app_config
18
18
  from .integrations.docker import DockerProvider
19
19
  from .mqtt import MqttPublisher
20
20
 
@@ -49,7 +49,6 @@ class App:
49
49
 
50
50
  structlog.configure(wrapper_class=structlog.make_filtering_bound_logger(getattr(logging, str(self.cfg.log.level))))
51
51
  log.debug("Logging initialized", level=self.cfg.log.level)
52
- self.common_pkg: dict[str, PackageUpdateInfo] = load_package_info(PKG_INFO_FILE)
53
52
 
54
53
  self.publisher = MqttPublisher(self.cfg.mqtt, self.cfg.node, self.cfg.homeassistant)
55
54
 
@@ -57,7 +56,7 @@ class App:
57
56
  self.scan_count: int = 0
58
57
  self.last_scan: str | None = None
59
58
  if self.cfg.docker.enabled:
60
- self.scanners.append(DockerProvider(self.cfg.docker, self.common_pkg, self.cfg.node, self.self_bounce))
59
+ self.scanners.append(DockerProvider(self.cfg.docker, self.cfg.node, self.self_bounce))
61
60
  self.stopped = Event()
62
61
  self.healthcheck_topic = self.cfg.node.healthcheck.topic_template.format(node_name=self.cfg.node.name)
63
62
 
@@ -104,6 +103,7 @@ class App:
104
103
  )
105
104
 
106
105
  for scanner in self.scanners:
106
+ scanner.initialize()
107
107
  self.publisher.subscribe_hass_command(scanner)
108
108
 
109
109
  while not self.stopped.is_set() and self.publisher.is_available():
@@ -122,12 +122,15 @@ class App:
122
122
  async def on_discovery(self, discovery: Discovery) -> None:
123
123
  dlog = log.bind(name=discovery.name)
124
124
  try:
125
- if self.cfg.homeassistant.discovery.enabled:
125
+ if discovery.publish_policy == PublishPolicy.HOMEASSISTANT and self.cfg.homeassistant.discovery.enabled:
126
+ # Switch off MQTT discovery if not Home Assistant enabled
126
127
  self.publisher.publish_hass_config(discovery)
127
-
128
- self.publisher.publish_hass_state(discovery)
128
+ if discovery.publish_policy in (PublishPolicy.HOMEASSISTANT):
129
+ self.publisher.publish_hass_state(discovery)
130
+ if discovery.publish_policy in (PublishPolicy.HOMEASSISTANT, PublishPolicy.MQTT):
131
+ self.publisher.publish_discovery(discovery)
129
132
  if (
130
- discovery.update_policy == "Auto"
133
+ discovery.update_policy == UpdatePolicy.AUTO
131
134
  and discovery.can_update
132
135
  and discovery.latest_version != discovery.current_version
133
136
  ):
@@ -9,6 +9,20 @@ from omegaconf import MISSING, DictConfig, MissingMandatoryValue, OmegaConf, Val
9
9
 
10
10
  log = structlog.get_logger()
11
11
 
12
+ PKG_INFO_FILE = Path("./common_packages.yaml")
13
+ NO_KNOWN_IMAGE = "UNKNOWN"
14
+
15
+
16
+ class UpdatePolicy(StrEnum):
17
+ AUTO = "Auto"
18
+ PASSIVE = "Passive"
19
+
20
+
21
+ class PublishPolicy(StrEnum):
22
+ HOMEASSISTANT = "HomeAssistant"
23
+ MQTT = "MQTT"
24
+ SILENT = "Silent"
25
+
12
26
 
13
27
  class LogLevel(StrEnum):
14
28
  DEBUG = "DEBUG"
@@ -18,6 +32,13 @@ class LogLevel(StrEnum):
18
32
  CRITICAL = "CRITICAL"
19
33
 
20
34
 
35
+ class VersionType:
36
+ SHORT_SHA = "short_sha"
37
+ FULL_SHA = "full_sha"
38
+ VERSION_REVISION = "version_revision"
39
+ VERSION = "version"
40
+
41
+
21
42
  @dataclass
22
43
  class MqttConfig:
23
44
  host: str = "${oc.env:MQTT_HOST,localhost}"
@@ -34,6 +55,12 @@ class MetadataSourceConfig:
34
55
  cache_ttl: int = 60 * 60 * 24 * 7 # 1 week
35
56
 
36
57
 
58
+ @dataclass
59
+ class Selector:
60
+ include: list[str] | None = None
61
+ exclude: list[str] | None = None
62
+
63
+
37
64
  @dataclass
38
65
  class DockerConfig:
39
66
  enabled: bool = True
@@ -47,7 +74,8 @@ class DockerConfig:
47
74
  discover_metadata: dict[str, MetadataSourceConfig] = field(
48
75
  default_factory=lambda: {"linuxserver.io": MetadataSourceConfig(enabled=True)}
49
76
  )
50
- api_throttle_wait: int = 60 * 15
77
+ default_api_backoff: int = 60 * 15
78
+ image_ref_select: Selector = field(default_factory=lambda: Selector())
51
79
 
52
80
 
53
81
  @dataclass
@@ -62,6 +90,7 @@ class HomeAssistantConfig:
62
90
  state_topic_suffix: str = "state"
63
91
  device_creation: bool = True
64
92
  force_command_topic: bool = False
93
+ extra_attributes: bool = True
65
94
  area: str | None = None
66
95
 
67
96
 
@@ -115,24 +144,6 @@ class IncompleteConfigException(BaseException):
115
144
  pass
116
145
 
117
146
 
118
- def load_package_info(pkginfo_file_path: Path) -> dict[str, PackageUpdateInfo]:
119
- if pkginfo_file_path.exists():
120
- log.debug("Loading common package update info", path=pkginfo_file_path)
121
- cfg = OmegaConf.load(pkginfo_file_path)
122
- else:
123
- log.warn("No common package update info found", path=pkginfo_file_path)
124
- cfg = OmegaConf.structured(UpdateInfoConfig)
125
- try:
126
- # omegaconf broken-ness on optional fields and converting to backclasses
127
- pkg_conf: dict[str, PackageUpdateInfo] = {
128
- pkg: PackageUpdateInfo(**pkg_cfg) for pkg, pkg_cfg in cfg.common_packages.items()
129
- }
130
- return pkg_conf
131
- except (MissingMandatoryValue, ValidationError) as e:
132
- log.error("Configuration error %s", e, path=pkginfo_file_path.as_posix())
133
- raise
134
-
135
-
136
147
  def is_autogen_config() -> bool:
137
148
  env_var: str | None = os.environ.get("U2M_AUTOGEN_CONFIG")
138
149
  return not (env_var and env_var.lower() in ("no", "0", "false"))
@@ -23,28 +23,26 @@ def hass_format_config(
23
23
  object_id: str,
24
24
  state_topic: str,
25
25
  command_topic: str | None,
26
+ attrs_topic: str | None,
26
27
  force_command_topic: bool | None,
27
28
  device_creation: bool = True,
28
29
  area: str | None = None,
29
- session: str | None = None,
30
30
  ) -> dict[str, Any]:
31
31
  config: dict[str, Any] = {
32
32
  "name": discovery.title,
33
33
  "device_class": None, # not firmware, so defaults to null
34
34
  "unique_id": object_id,
35
35
  "state_topic": state_topic,
36
- "source_session": session,
37
36
  "supported_features": discovery.features,
38
- "can_update": discovery.can_update,
39
- "can_build": discovery.can_build,
40
- "can_restart": discovery.can_restart,
41
- "update_policy": discovery.update_policy,
37
+ "default_entity_id": f"update.{discovery.node}_{discovery.provider.source_type}_{discovery.name}",
42
38
  "origin": {
43
39
  "name": f"{discovery.node} updates2mqtt",
44
40
  "sw_version": updates2mqtt.version, # pyright: ignore[reportAttributeAccessIssue]
45
41
  "support_url": "https://github.com/rhizomatics/updates2mqtt/issues",
46
42
  },
47
43
  }
44
+ if attrs_topic:
45
+ config["json_attributes_topic"] = attrs_topic
48
46
  if discovery.entity_picture_url:
49
47
  config["entity_picture"] = discovery.entity_picture_url
50
48
  if discovery.device_icon:
@@ -62,14 +60,12 @@ def hass_format_config(
62
60
  config["command_topic"] = command_topic
63
61
  if discovery.can_update:
64
62
  config["payload_install"] = f"{discovery.source_type}|{discovery.name}|install"
65
- config["custom"] = {}
66
- config["custom"][discovery.source_type] = discovery.custom
67
- config.update(discovery.provider.hass_config_format(discovery))
63
+
68
64
  return config
69
65
 
70
66
 
71
67
  def hass_format_state(discovery: Discovery, session: str, in_progress: bool = False) -> dict[str, Any]: # noqa: ARG001
72
- state = {
68
+ state: dict[str, str | dict | list | bool | None] = {
73
69
  "installed_version": discovery.current_version,
74
70
  "latest_version": discovery.latest_version,
75
71
  "title": discovery.title,
@@ -79,11 +75,5 @@ def hass_format_state(discovery: Discovery, session: str, in_progress: bool = Fa
79
75
  state["release_summary"] = discovery.release_summary
80
76
  if discovery.release_url:
81
77
  state["release_url"] = discovery.release_url
82
- custom_state = discovery.provider.hass_state_format(discovery)
83
- if custom_state:
84
- state.update(custom_state)
85
- invalid_keys = [k for k in state if k not in HASS_UPDATE_SCHEMA]
86
- if invalid_keys:
87
- log.warning(f"Invalid keys in state: {invalid_keys}")
88
- state = {k: v for k, v in state.items() if k in HASS_UPDATE_SCHEMA}
78
+
89
79
  return state