updates2mqtt 1.7.3__tar.gz → 1.8.1__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.
- {updates2mqtt-1.7.3 → updates2mqtt-1.8.1}/PKG-INFO +8 -44
- {updates2mqtt-1.7.3 → updates2mqtt-1.8.1}/README.md +7 -43
- {updates2mqtt-1.7.3 → updates2mqtt-1.8.1}/pyproject.toml +5 -5
- {updates2mqtt-1.7.3 → updates2mqtt-1.8.1}/src/updates2mqtt/app.py +10 -6
- {updates2mqtt-1.7.3 → updates2mqtt-1.8.1}/src/updates2mqtt/cli.py +33 -15
- {updates2mqtt-1.7.3 → updates2mqtt-1.8.1}/src/updates2mqtt/config.py +28 -19
- {updates2mqtt-1.7.3 → updates2mqtt-1.8.1}/src/updates2mqtt/helpers.py +44 -4
- {updates2mqtt-1.7.3 → updates2mqtt-1.8.1}/src/updates2mqtt/integrations/docker.py +53 -15
- {updates2mqtt-1.7.3 → updates2mqtt-1.8.1}/src/updates2mqtt/integrations/docker_enrich.py +168 -51
- {updates2mqtt-1.7.3 → updates2mqtt-1.8.1}/src/updates2mqtt/model.py +26 -10
- {updates2mqtt-1.7.3 → updates2mqtt-1.8.1}/src/updates2mqtt/mqtt.py +109 -60
- {updates2mqtt-1.7.3 → updates2mqtt-1.8.1}/src/updates2mqtt/__init__.py +0 -0
- {updates2mqtt-1.7.3 → updates2mqtt-1.8.1}/src/updates2mqtt/__main__.py +0 -0
- {updates2mqtt-1.7.3 → updates2mqtt-1.8.1}/src/updates2mqtt/hass_formatter.py +0 -0
- {updates2mqtt-1.7.3 → updates2mqtt-1.8.1}/src/updates2mqtt/integrations/__init__.py +0 -0
- {updates2mqtt-1.7.3 → updates2mqtt-1.8.1}/src/updates2mqtt/integrations/git_utils.py +0 -0
- {updates2mqtt-1.7.3 → updates2mqtt-1.8.1}/src/updates2mqtt/py.typed +0 -0
|
@@ -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
|
|
|
@@ -34,12 +34,12 @@ Read the release notes, and optionally click *Update* to trigger a Docker *pull*
|
|
|
34
34
|
|
|
35
35
|
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.
|
|
36
36
|
|
|
37
|
-
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
|
|
37
|
+
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
|
|
38
38
|
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.
|
|
39
39
|
|
|
40
40
|
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.
|
|
41
41
|
|
|
42
|
-
To get started, read the [Installation](installation.md) and [Configuration](configuration.md) pages.
|
|
42
|
+
To get started, read the [Installation](installation.md) and [Configuration](configuration/index.md) pages.
|
|
43
43
|
|
|
44
44
|
For a quick spin, try this:
|
|
45
45
|
|
|
@@ -53,6 +53,9 @@ or without Docker, using [uv](https://docs.astral.sh/uv/)
|
|
|
53
53
|
export MQTT_HOST=192.168.1.1;export MQTT_USER=user1;export MQTT_PASS=user1;uv run --with updates2mqtt python -m updates2mqtt
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
+
It also comes with a basic command line tool that will perform the analysis for a single running container, or fetch
|
|
57
|
+
manifests, JSON blobs and lists of tags from remote registries (known to work with GitHub, GitLab, Codeberg, Quay, LSCR and Microsoft MCR).
|
|
58
|
+
|
|
56
59
|
## Release Support
|
|
57
60
|
|
|
58
61
|
Presently only Docker containers are supported, although others are planned, probably with priority for `apt`.
|
|
@@ -90,7 +93,7 @@ restarter:
|
|
|
90
93
|
While `updates2mqtt` will discover and monitor all containers running under the Docker daemon,
|
|
91
94
|
there are some options to make to those containers to tune how it works.
|
|
92
95
|
|
|
93
|
-
These happen by adding environment variables to the containers, typically inside an `.env`
|
|
96
|
+
These happen by adding environment variables or docker labels to the containers, typically inside an `.env`
|
|
94
97
|
file, or as `environment` options inside `docker-compose.yaml`.
|
|
95
98
|
|
|
96
99
|
### Automated updates
|
|
@@ -110,45 +113,6 @@ restarter:
|
|
|
110
113
|
Automated updates can also apply to local builds, where a `git_repo_path` has been defined - if there are remote
|
|
111
114
|
commits available to pull, then a `git pull`, `docker compose build` and `docker compose up` will be executed.
|
|
112
115
|
|
|
113
|
-
### Environment Variables
|
|
114
|
-
|
|
115
|
-
The following environment variables can be used to configure containers for `updates2mqtt`:
|
|
116
|
-
|
|
117
|
-
| Env Var | Description | Default |
|
|
118
|
-
|----------------------------|----------------------------------------------------------------------------------------------|-----------------|
|
|
119
|
-
| `UPD2MQTT_UPDATE` | Update mode, either `Passive` or `Auto`. If `Auto`, updates will be installed automatically. | `Passive` |
|
|
120
|
-
| `UPD2MQTT_PICTURE` | URL to an icon to use in Home Assistant. | Docker logo URL |
|
|
121
|
-
| `UPD2MQTT_RELNOTES` | URL to release notes for the package. | |
|
|
122
|
-
| `UPD2MQTT_GIT_REPO_PATH` | Relative path to a local git repo if the image is built locally. | |
|
|
123
|
-
| `UPD2MQTT_IGNORE` | If set to `True`, the container will be ignored by Updates2MQTT. | False |
|
|
124
|
-
| |
|
|
125
|
-
| `UPD2MQTT_VERSION_POLICY` | Change how version derived from container label or image hash, `Version`,`Digest`,`Version_Digest` with default of `Auto`|
|
|
126
|
-
| `UPD2MQTT_REGISTRY_TOKEN` | Access token for authentication to container distribution API, as alternative to making a call to `token` service |
|
|
127
|
-
|
|
128
|
-
### Docker Labels
|
|
129
|
-
|
|
130
|
-
Alternatively, use Docker labels
|
|
131
|
-
|
|
132
|
-
| Label | Env Var |
|
|
133
|
-
|--------------------------------|----------------------------|
|
|
134
|
-
| `updates2mqtt.update` | `UPD2MQTT_UPDATE` |
|
|
135
|
-
| `updates2mqtt.picture` | `UPD2MQTT_PCITURE` |
|
|
136
|
-
| `updates2mqtt.relnotes` | `UPD2MQTT_RELNOTES` |
|
|
137
|
-
| `updates2mqtt.git_repo_path` | `UPD2MQTT_GIT_REPO_PATH` |
|
|
138
|
-
| `updates2mqtt.ignore` | `UPD2MQTT_IGNORE` |
|
|
139
|
-
| `updates2mqtt.version_policy` | `UPD2MQTT_VERSION_POLICY` |
|
|
140
|
-
| `updates2mqtt.registry_token` | `UPD2MQTT_REGISTRY_TOKEN` |
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
```yaml title="Example Compose Snippet"
|
|
145
|
-
restarter:
|
|
146
|
-
image: docker:cli
|
|
147
|
-
command: ["/bin/sh", "-c", "while true; do sleep 86400; docker restart mailserver; done"]
|
|
148
|
-
labels:
|
|
149
|
-
updates2mqtt.relnotes: https://component.my.com/release_notes
|
|
150
|
-
```
|
|
151
|
-
|
|
152
116
|
|
|
153
117
|
## Related Projects
|
|
154
118
|
|
|
@@ -158,7 +122,7 @@ Other apps useful for self-hosting with the help of MQTT:
|
|
|
158
122
|
|
|
159
123
|
Find more at [awesome-mqtt](https://github.com/rhizomatics/awesome-mqtt)
|
|
160
124
|
|
|
161
|
-
For a more powerful Docker update manager, try [What's Up Docker](https://getwud.github.io/wud/)
|
|
125
|
+
For a more powerful Docker focussed update manager, try [What's Up Docker](https://getwud.github.io/wud/)
|
|
162
126
|
|
|
163
127
|
## Development
|
|
164
128
|
|
|
@@ -7,7 +7,7 @@ authors = [
|
|
|
7
7
|
]
|
|
8
8
|
|
|
9
9
|
requires-python = ">=3.13"
|
|
10
|
-
version = "1.
|
|
10
|
+
version = "1.8.1"
|
|
11
11
|
license="Apache-2.0"
|
|
12
12
|
keywords=["mqtt", "docker", "oci","container","updates", "automation","home-assistant","homeassistant","selfhosting"]
|
|
13
13
|
|
|
@@ -60,7 +60,8 @@ dev = [
|
|
|
60
60
|
"pytest-subprocess>=1.5.3",
|
|
61
61
|
"coverage",
|
|
62
62
|
"icdiff",
|
|
63
|
-
"genbadge[all]"
|
|
63
|
+
"genbadge[all]",
|
|
64
|
+
"actionlint-py"
|
|
64
65
|
]
|
|
65
66
|
docs=[
|
|
66
67
|
"mkdocs",
|
|
@@ -69,12 +70,11 @@ docs=[
|
|
|
69
70
|
"mkdocs-autorefs",
|
|
70
71
|
"mkdocs-pagetree-plugin",
|
|
71
72
|
"mkdocs-coverage",
|
|
72
|
-
"mkdocstrings[python]",
|
|
73
73
|
"pymdown-extensions",
|
|
74
|
-
"mkdocs-git-revision-date-localized-plugin",
|
|
75
74
|
"mkdocs-meta-descriptions-plugin",
|
|
76
75
|
"pngquant",
|
|
77
|
-
"mkdocs-mermaid2-plugin"
|
|
76
|
+
"mkdocs-mermaid2-plugin",
|
|
77
|
+
"mkdocs-htmlproofer-plugin"
|
|
78
78
|
]
|
|
79
79
|
|
|
80
80
|
[build-system]
|
|
@@ -21,7 +21,6 @@ from .mqtt import MqttPublisher
|
|
|
21
21
|
log = structlog.get_logger()
|
|
22
22
|
|
|
23
23
|
CONF_FILE = Path("conf/config.yaml")
|
|
24
|
-
PKG_INFO_FILE = Path("./common_packages.yaml")
|
|
25
24
|
UPDATE_INTERVAL = 60 * 60 * 4
|
|
26
25
|
|
|
27
26
|
# #TODO:
|
|
@@ -56,7 +55,15 @@ class App:
|
|
|
56
55
|
self.scan_count: int = 0
|
|
57
56
|
self.last_scan: str | None = None
|
|
58
57
|
if self.cfg.docker.enabled:
|
|
59
|
-
self.scanners.append(
|
|
58
|
+
self.scanners.append(
|
|
59
|
+
DockerProvider(
|
|
60
|
+
self.cfg.docker,
|
|
61
|
+
self.cfg.node,
|
|
62
|
+
packages=self.cfg.packages,
|
|
63
|
+
github_cfg=self.cfg.github,
|
|
64
|
+
self_bounce=self.self_bounce,
|
|
65
|
+
)
|
|
66
|
+
)
|
|
60
67
|
self.stopped = Event()
|
|
61
68
|
self.healthcheck_topic = self.cfg.node.healthcheck.topic_template.format(node_name=self.cfg.node.name)
|
|
62
69
|
|
|
@@ -71,9 +78,6 @@ class App:
|
|
|
71
78
|
session = uuid.uuid4().hex
|
|
72
79
|
for scanner in self.scanners:
|
|
73
80
|
slog = log.bind(source_type=scanner.source_type, session=session)
|
|
74
|
-
slog.info("Cleaning topics before scan")
|
|
75
|
-
if self.scan_count == 0:
|
|
76
|
-
await self.publisher.clean_topics(scanner, None, force=True)
|
|
77
81
|
if self.stopped.is_set():
|
|
78
82
|
break
|
|
79
83
|
slog.info("Scanning ...")
|
|
@@ -84,7 +88,7 @@ class App:
|
|
|
84
88
|
if self.stopped.is_set():
|
|
85
89
|
slog.debug("Breaking scan loop on stopped event")
|
|
86
90
|
break
|
|
87
|
-
await self.publisher.clean_topics(scanner
|
|
91
|
+
await self.publisher.clean_topics(scanner)
|
|
88
92
|
self.scan_count += 1
|
|
89
93
|
slog.info(f"Scan #{self.scan_count} complete")
|
|
90
94
|
self.last_scan_timestamp = datetime.now(UTC).isoformat()
|
|
@@ -4,7 +4,7 @@ import structlog
|
|
|
4
4
|
from omegaconf import DictConfig, OmegaConf
|
|
5
5
|
from rich import print_json
|
|
6
6
|
|
|
7
|
-
from updates2mqtt.config import DockerConfig, NodeConfig, RegistryConfig
|
|
7
|
+
from updates2mqtt.config import DockerConfig, GitHubConfig, NodeConfig, RegistryConfig
|
|
8
8
|
from updates2mqtt.helpers import Throttler
|
|
9
9
|
from updates2mqtt.integrations.docker import DockerProvider
|
|
10
10
|
from updates2mqtt.integrations.docker_enrich import (
|
|
@@ -24,18 +24,20 @@ log = structlog.get_logger()
|
|
|
24
24
|
"""
|
|
25
25
|
Super simple CLI
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
Command can be `container`,`tags`,`manifest` or `blob`
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
* `container=container-name`
|
|
30
|
+
* `container=hash`
|
|
31
|
+
* `tags=ghcr.io/
|
|
32
|
+
* `blob=mcr.microsoft.com/dotnet/sdk:latest`
|
|
33
|
+
* `tags=quay.io/linuxserver.io/babybuddy`
|
|
34
|
+
* `blob=ghcr.io/blakeblackshear/frigate@sha256:759c36ee869e3e60258350a2e221eae1a4ba1018613e0334f1bc84eb09c4bbbc`
|
|
30
35
|
|
|
31
|
-
|
|
36
|
+
In addition, a `log_level=DEBUG` or other level can be added, `github_token` to try a personal access
|
|
37
|
+
token for GitHub release info retrieval, or `api=docker_client` to use the older API (defaults to `api=OCI_V2`)
|
|
32
38
|
|
|
33
|
-
python3 updates2mqtt/cli.py manifest=ghcr.io/blakeblackshear/frigate:stable
|
|
34
39
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
python3 updates2mqtt/cli.py manifest=ghcr.io/blakeblackshear/frigate@sha256:c68fd78fd3237c9ba81b5aa927f17b54f46705990f43b4b5d5596cfbbb626af4
|
|
38
|
-
""" # noqa: E501
|
|
40
|
+
"""
|
|
39
41
|
|
|
40
42
|
OCI_MANIFEST_TYPES: list[str] = [
|
|
41
43
|
"application/vnd.oci.image.manifest.v1+json",
|
|
@@ -91,7 +93,9 @@ ALL_OCI_MEDIA_TYPES: list[str] = (
|
|
|
91
93
|
)
|
|
92
94
|
|
|
93
95
|
|
|
94
|
-
def dump_url(doc_type: str, img_ref: str) -> None:
|
|
96
|
+
def dump_url(doc_type: str, img_ref: str, cli_conf: DictConfig) -> None:
|
|
97
|
+
structlog.configure(wrapper_class=structlog.make_filtering_bound_logger(cli_conf.get("log_level", "WARNING")))
|
|
98
|
+
|
|
95
99
|
lookup = ContainerDistributionAPIVersionLookup(Throttler(), RegistryConfig())
|
|
96
100
|
img_info = DockerImageInfo(img_ref)
|
|
97
101
|
if not img_info.index_name or not img_info.name:
|
|
@@ -110,35 +114,49 @@ def dump_url(doc_type: str, img_ref: str) -> None:
|
|
|
110
114
|
log.warning("No tag or digest found in %s", img_ref)
|
|
111
115
|
return
|
|
112
116
|
url = f"https://{api_host}/v2/{img_info.name}/manifests/{img_info.tag_or_digest}"
|
|
117
|
+
elif doc_type == "tags":
|
|
118
|
+
url = f"https://{api_host}/v2/{img_info.name}/tags/list"
|
|
113
119
|
else:
|
|
114
120
|
return
|
|
115
121
|
|
|
116
122
|
token: str | None = lookup.fetch_token(img_info.index_name, img_info.name)
|
|
117
123
|
|
|
118
124
|
response: Response | None = fetch_url(url, bearer_token=token, follow_redirects=True, response_type=ALL_OCI_MEDIA_TYPES)
|
|
119
|
-
if response:
|
|
125
|
+
if response and response.is_error:
|
|
126
|
+
log.warning(f"{response.status_code}: {url}")
|
|
127
|
+
log.warning(response.text)
|
|
128
|
+
elif response and response.is_success:
|
|
120
129
|
log.debug(f"{response.status_code}: {url}")
|
|
121
130
|
log.debug("HEADERS")
|
|
122
131
|
for k, v in response.headers.items():
|
|
123
132
|
log.debug(f"{k}: {v}")
|
|
124
133
|
log.debug("CONTENTS")
|
|
134
|
+
|
|
125
135
|
print_json(response.text)
|
|
126
136
|
|
|
127
137
|
|
|
128
138
|
def main() -> None:
|
|
129
139
|
# will be a proper cli someday
|
|
130
140
|
cli_conf: DictConfig = OmegaConf.from_cli()
|
|
131
|
-
structlog.configure(wrapper_class=structlog.make_filtering_bound_logger(cli_conf.get("log_level", "WARNING")))
|
|
132
141
|
|
|
133
142
|
if cli_conf.get("blob"):
|
|
134
|
-
dump_url("blob", cli_conf.get("blob"))
|
|
143
|
+
dump_url("blob", cli_conf.get("blob"), cli_conf)
|
|
135
144
|
elif cli_conf.get("manifest"):
|
|
136
|
-
dump_url("manifest", cli_conf.get("manifest"))
|
|
145
|
+
dump_url("manifest", cli_conf.get("manifest"), cli_conf)
|
|
146
|
+
elif cli_conf.get("tags"):
|
|
147
|
+
dump_url("tags", cli_conf.get("tags"), cli_conf)
|
|
137
148
|
|
|
138
149
|
else:
|
|
150
|
+
structlog.configure(wrapper_class=structlog.make_filtering_bound_logger(cli_conf.get("log_level", "INFO")))
|
|
151
|
+
|
|
139
152
|
docker_scanner = DockerProvider(
|
|
140
|
-
DockerConfig(registry=RegistryConfig(api=cli_conf.get("api", "OCI_V2"))),
|
|
153
|
+
DockerConfig(registry=RegistryConfig(api=cli_conf.get("api", "OCI_V2"))),
|
|
154
|
+
NodeConfig(),
|
|
155
|
+
packages={},
|
|
156
|
+
github_cfg=GitHubConfig(access_token=cli_conf.get("github_token")),
|
|
157
|
+
self_bounce=None,
|
|
141
158
|
)
|
|
159
|
+
docker_scanner.initialize()
|
|
142
160
|
discovery: Discovery | None = docker_scanner.rescan(
|
|
143
161
|
Discovery(docker_scanner, cli_conf.get("container", "frigate"), "cli", "manual")
|
|
144
162
|
)
|
|
@@ -67,6 +67,11 @@ class MqttConfig:
|
|
|
67
67
|
protocol: str = "${oc.env:MQTT_VERSION,3.11}"
|
|
68
68
|
|
|
69
69
|
|
|
70
|
+
@dataclass
|
|
71
|
+
class GitHubConfig:
|
|
72
|
+
access_token: str | None = None
|
|
73
|
+
|
|
74
|
+
|
|
70
75
|
@dataclass
|
|
71
76
|
class MetadataSourceConfig:
|
|
72
77
|
enabled: bool = True
|
|
@@ -84,6 +89,21 @@ class VersionPolicy(StrEnum):
|
|
|
84
89
|
VERSION = "VERSION"
|
|
85
90
|
DIGEST = "DIGEST"
|
|
86
91
|
VERSION_DIGEST = "VERSION_DIGEST"
|
|
92
|
+
TIMESTAMP = "TIMESTAMP"
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@dataclass
|
|
96
|
+
class DockerPackageUpdateInfo:
|
|
97
|
+
image_name: str = MISSING # untagged image ref
|
|
98
|
+
version_policy: VersionPolicy = VersionPolicy.AUTO
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@dataclass
|
|
102
|
+
class PackageUpdateInfo:
|
|
103
|
+
docker: DockerPackageUpdateInfo | None = field(default_factory=DockerPackageUpdateInfo)
|
|
104
|
+
logo_url: str | None = None
|
|
105
|
+
release_notes_url: str | None = None
|
|
106
|
+
source_repo_url: str | None = None
|
|
87
107
|
|
|
88
108
|
|
|
89
109
|
@dataclass
|
|
@@ -149,25 +169,14 @@ class Config:
|
|
|
149
169
|
mqtt: MqttConfig = field(default_factory=MqttConfig) # pyright: ignore[reportArgumentType, reportCallIssue]
|
|
150
170
|
homeassistant: HomeAssistantConfig = field(default_factory=HomeAssistantConfig)
|
|
151
171
|
docker: DockerConfig = field(default_factory=DockerConfig)
|
|
172
|
+
github: GitHubConfig = field(default_factory=GitHubConfig)
|
|
152
173
|
scan_interval: int = 60 * 60 * 3
|
|
174
|
+
packages: dict[str, PackageUpdateInfo] = field(default_factory=dict)
|
|
153
175
|
|
|
154
176
|
|
|
155
177
|
@dataclass
|
|
156
|
-
class
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
@dataclass
|
|
161
|
-
class PackageUpdateInfo:
|
|
162
|
-
docker: DockerPackageUpdateInfo | None = field(default_factory=DockerPackageUpdateInfo)
|
|
163
|
-
logo_url: str | None = None
|
|
164
|
-
release_notes_url: str | None = None
|
|
165
|
-
source_repo_url: str | None = None
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
@dataclass
|
|
169
|
-
class UpdateInfoConfig:
|
|
170
|
-
common_packages: dict[str, PackageUpdateInfo] = field(default_factory=lambda: {})
|
|
178
|
+
class CommonPackages:
|
|
179
|
+
common_packages: dict[str, PackageUpdateInfo] = field(default_factory=dict)
|
|
171
180
|
|
|
172
181
|
|
|
173
182
|
class IncompleteConfigException(BaseException):
|
|
@@ -188,13 +197,13 @@ def load_app_config(conf_file_path: Path, return_invalid: bool = False) -> Confi
|
|
|
188
197
|
try:
|
|
189
198
|
log.debug(f"Creating config directory {conf_file_path.parent} if not already present")
|
|
190
199
|
conf_file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
191
|
-
except Exception:
|
|
192
|
-
log.warning("Unable to create config directory", path=conf_file_path.parent)
|
|
200
|
+
except Exception as e:
|
|
201
|
+
log.warning("Unable to create config directory: %s", e, path=conf_file_path.parent)
|
|
193
202
|
try:
|
|
194
203
|
conf_file_path.write_text(OmegaConf.to_yaml(base_cfg))
|
|
195
204
|
log.info(f"Auto-generated a new config file at {conf_file_path}")
|
|
196
|
-
except Exception:
|
|
197
|
-
log.warning("Unable to write config file", path=conf_file_path)
|
|
205
|
+
except Exception as e:
|
|
206
|
+
log.warning("Unable to write config file: %s", e, path=conf_file_path)
|
|
198
207
|
cfg = base_cfg
|
|
199
208
|
else:
|
|
200
209
|
cfg = base_cfg
|
|
@@ -145,8 +145,8 @@ class APIStats:
|
|
|
145
145
|
"""Log line friendly string summary"""
|
|
146
146
|
return (
|
|
147
147
|
f"fetches: {self.fetches}, cache ratio: {self.hit_ratio():.2%}, revalidated: {self.revalidated}, "
|
|
148
|
-
+ f"errors: {', '.join(f'{status_code}:{fails}' for status_code, fails in self.failed.items())}, "
|
|
149
|
-
+ f"oldest cache hit: {self.max_cache_age:.2f}, avg elapsed: {self.average_elapsed()}"
|
|
148
|
+
+ f"errors: {', '.join(f'{status_code}:{fails}' for status_code, fails in self.failed.items()) or '0'}, "
|
|
149
|
+
+ f"oldest cache hit: {self.max_cache_age:.2f}s, avg elapsed: {self.average_elapsed()}s"
|
|
150
150
|
)
|
|
151
151
|
|
|
152
152
|
|
|
@@ -196,8 +196,9 @@ def fetch_url(
|
|
|
196
196
|
allow_stale=allow_stale,
|
|
197
197
|
)
|
|
198
198
|
)
|
|
199
|
+
log_headers: list[tuple[str, str]] = [h for h in headers if len(h) > 1 and h[0] != "Authorization"]
|
|
199
200
|
with SyncCacheClient(headers=headers, follow_redirects=follow_redirects, policy=cache_policy) as client:
|
|
200
|
-
log.debug(f"Fetching URL {url}, redirects={follow_redirects}, headers={
|
|
201
|
+
log.debug(f"Fetching URL {url}, redirects={follow_redirects}, headers={log_headers}, cache_ttl={cache_ttl}")
|
|
201
202
|
response: Response = client.request(method=method, url=url, extensions={"hishel_ttl": cache_ttl})
|
|
202
203
|
cache_metadata: CacheMetadata = CacheMetadata(response)
|
|
203
204
|
if not response.is_success:
|
|
@@ -221,6 +222,45 @@ def fetch_url(
|
|
|
221
222
|
return None
|
|
222
223
|
|
|
223
224
|
|
|
224
|
-
def validate_url(url: str, cache_ttl: int =
|
|
225
|
+
def validate_url(url: str, cache_ttl: int = 1500) -> bool:
|
|
225
226
|
response: Response | None = fetch_url(url, method="HEAD", cache_ttl=cache_ttl, follow_redirects=True)
|
|
226
227
|
return response is not None and response.status_code != 404
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def sanitize_name(name: str, replacement: str = "_", max_len: int = 64) -> str:
|
|
231
|
+
"""Strict sanitization that removes/replaces common problematic characters for MQTT or HA
|
|
232
|
+
|
|
233
|
+
- Replaces spaces with underscores
|
|
234
|
+
- Removes control characters
|
|
235
|
+
- Ensures alphanumeric safety for broader compatibility
|
|
236
|
+
|
|
237
|
+
Args:
|
|
238
|
+
name: The topic component string to sanitize
|
|
239
|
+
replacement: Character to replace invalid characters with (default: "_")
|
|
240
|
+
max_len: Largest acceptable name size
|
|
241
|
+
|
|
242
|
+
Returns:
|
|
243
|
+
Sanitized topic string safe for most MQTT brokers
|
|
244
|
+
|
|
245
|
+
"""
|
|
246
|
+
if not name:
|
|
247
|
+
raise ValueError("Name cannot be empty")
|
|
248
|
+
orig_name: str = name
|
|
249
|
+
name = re.sub(r"[^A-Za-z0-9_\-\.]+", replacement, name)
|
|
250
|
+
|
|
251
|
+
# Replace multiple consecutive replacement chars with single one
|
|
252
|
+
if replacement:
|
|
253
|
+
pattern = re.escape(replacement) + "+"
|
|
254
|
+
name = re.sub(pattern, replacement, name)
|
|
255
|
+
|
|
256
|
+
# Trim to max length
|
|
257
|
+
topic_bytes = name.encode("utf-8")
|
|
258
|
+
if len(topic_bytes) > max_len:
|
|
259
|
+
name = topic_bytes[:max_len].decode("utf-8", errors="ignore")
|
|
260
|
+
|
|
261
|
+
if not name:
|
|
262
|
+
raise ValueError("Topic became empty after sanitization")
|
|
263
|
+
if name != orig_name:
|
|
264
|
+
log.info("Component name %s changed to %s for MQTT/HA compatibility", orig_name, name)
|
|
265
|
+
|
|
266
|
+
return name
|