Refactor configuration management: migrate to per-user storage, update encryption handling, and enhance versioning in CI

This commit is contained in:
SitiWeb
2026-01-23 17:17:25 +01:00
parent 43c5bdac8c
commit 428e3306a0
11 changed files with 570 additions and 279 deletions

127
utils/update_checker.py Normal file
View File

@@ -0,0 +1,127 @@
from __future__ import annotations
from dataclasses import dataclass
from typing import Optional
import requests
from packaging.version import InvalidVersion, Version
@dataclass(frozen=True)
class UpdateInfo:
current_version: str
latest_version: str
latest_tag: str
html_url: Optional[str]
@property
def update_available(self) -> bool:
try:
return Version(self.latest_version) > Version(self.current_version)
except InvalidVersion:
return False
class UpdateCheckError(RuntimeError):
pass
def _normalize_tag_to_version(tag: str) -> str:
tag = (tag or "").strip()
if tag.lower().startswith("v"):
tag = tag[1:]
return tag
def get_current_version() -> str:
# Prefer an explicit env override (useful for ad-hoc builds)
import os
env_ver = os.getenv("IMAGE_PROCESSOR_VERSION")
if env_ver:
return env_ver.strip()
# Prefer version.py in repo / bundled app
try:
from version import __version__ # type: ignore
return str(__version__).strip()
except Exception:
return "0.0.0.dev0"
def _github_get_json(url: str, timeout_seconds: float = 10.0) -> dict:
headers = {
"Accept": "application/vnd.github+json",
"User-Agent": "images_py-update-checker",
}
try:
response = requests.get(url, headers=headers, timeout=timeout_seconds)
except requests.RequestException as exc:
raise UpdateCheckError(f"GitHub request failed: {exc}") from exc
# Rate-limit or forbidden
if response.status_code == 403:
remaining = response.headers.get("X-RateLimit-Remaining")
if remaining == "0":
raise UpdateCheckError("GitHub API rate limit reached. Try again later.")
if response.status_code >= 400:
raise UpdateCheckError(f"GitHub API error {response.status_code}: {response.text[:200]}")
try:
return response.json()
except ValueError as exc:
raise UpdateCheckError("GitHub API returned invalid JSON") from exc
def get_latest_github_release(owner: str, repo: str) -> tuple[str, str, Optional[str]]:
"""Returns (latest_version, latest_tag, html_url).
Uses releases/latest first; falls back to tags if no releases exist.
"""
releases_url = f"https://api.github.com/repos/{owner}/{repo}/releases/latest"
try:
payload = _github_get_json(releases_url)
tag = str(payload.get("tag_name") or "").strip()
html_url = payload.get("html_url")
version = _normalize_tag_to_version(tag)
if version:
return version, tag, html_url
except UpdateCheckError:
# fall back to tags
pass
tags_url = f"https://api.github.com/repos/{owner}/{repo}/tags?per_page=1"
payload = _github_get_json(tags_url)
if not isinstance(payload, list) or not payload:
raise UpdateCheckError("No releases or tags found on GitHub")
tag = str(payload[0].get("name") or "").strip()
version = _normalize_tag_to_version(tag)
if not version:
raise UpdateCheckError("Latest GitHub tag is missing a version")
# Tags endpoint does not include a nice html_url for a release.
return version, tag, f"https://github.com/{owner}/{repo}/releases/tag/{tag}"
def check_for_update(owner: str, repo: str, current_version: Optional[str] = None) -> UpdateInfo:
current = (current_version or get_current_version()).strip()
latest_version, latest_tag, html_url = get_latest_github_release(owner, repo)
# Validate versions for comparison; if invalid, still return info.
try:
Version(current)
Version(latest_version)
except InvalidVersion:
pass
return UpdateInfo(
current_version=current,
latest_version=latest_version,
latest_tag=latest_tag,
html_url=html_url,
)