feat: Add Uptime Kuma version check via Socket.IO
Retrieve the running Uptime Kuma version by authenticating over Socket.IO and reading the info event. This adds a new login_socket dataset field, the fetch_socket_info helper, and the python-socketio and websocket-client dependencies.
This commit is contained in:
parent
501a84e0a9
commit
f34e2f2d08
6 changed files with 90 additions and 0 deletions
|
|
@ -15,3 +15,7 @@ MATRIX_ACCESS_TOKEN=replace-with-access-token
|
||||||
|
|
||||||
# Uptime Kuma push URL
|
# Uptime Kuma push URL
|
||||||
UPTIME_KUMA_PUSH_URL=https://uptime.example.net/api/push/your-token
|
UPTIME_KUMA_PUSH_URL=https://uptime.example.net/api/push/your-token
|
||||||
|
|
||||||
|
# Uptime Kuma login for version check
|
||||||
|
UPTIME_KUMA_USERNAME=your-username
|
||||||
|
UPTIME_KUMA_PASSWORD=your-password
|
||||||
|
|
|
||||||
|
|
@ -152,3 +152,12 @@ Acceptance criteria:
|
||||||
- The script sends a request to the push URL when it runs.
|
- The script sends a request to the push URL when it runs.
|
||||||
- The push URL is configured via `.env`.
|
- The push URL is configured via `.env`.
|
||||||
- Failures to reach the push URL are logged without crashing the run.
|
- Failures to reach the push URL are logged without crashing the run.
|
||||||
|
|
||||||
|
## US-17 - Uptime Kuma Service Check
|
||||||
|
|
||||||
|
As a maintainer, I want to track the running version of Uptime Kuma so updates are reported like other services.
|
||||||
|
|
||||||
|
Acceptance criteria:
|
||||||
|
- The dataset includes Uptime Kuma with a current version endpoint and extraction rule.
|
||||||
|
- The upstream latest version uses the official GitHub releases.
|
||||||
|
- Any required authentication is documented in the dataset.
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,8 @@ export MATRIX_HOMESERVER=...
|
||||||
export MATRIX_ROOM_ID=...
|
export MATRIX_ROOM_ID=...
|
||||||
export MATRIX_ACCESS_TOKEN=...
|
export MATRIX_ACCESS_TOKEN=...
|
||||||
export UPTIME_KUMA_PUSH_URL=...
|
export UPTIME_KUMA_PUSH_URL=...
|
||||||
|
export UPTIME_KUMA_USERNAME=...
|
||||||
|
export UPTIME_KUMA_PASSWORD=...
|
||||||
```
|
```
|
||||||
|
|
||||||
The script also reads `.env` automatically if present.
|
The script also reads `.env` automatically if present.
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ from urllib.parse import urlencode, quote
|
||||||
from urllib.request import HTTPCookieProcessor, Request, build_opener, urlopen
|
from urllib.request import HTTPCookieProcessor, Request, build_opener, urlopen
|
||||||
|
|
||||||
import bcrypt
|
import bcrypt
|
||||||
|
import socketio
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
|
|
@ -34,6 +35,7 @@ class ServiceConfig:
|
||||||
upstream_latest_headers: Optional[Dict[str, str]]
|
upstream_latest_headers: Optional[Dict[str, str]]
|
||||||
notes: Optional[str]
|
notes: Optional[str]
|
||||||
login: Optional[Dict[str, Any]]
|
login: Optional[Dict[str, Any]]
|
||||||
|
login_socket: Optional[Dict[str, Any]]
|
||||||
|
|
||||||
|
|
||||||
def load_yaml(path: str) -> Dict[str, Any]:
|
def load_yaml(path: str) -> Dict[str, Any]:
|
||||||
|
|
@ -109,6 +111,10 @@ def load_services(config: Dict[str, Any]) -> Dict[str, ServiceConfig]:
|
||||||
if login is not None and not isinstance(login, dict):
|
if login is not None and not isinstance(login, dict):
|
||||||
raise ValueError("login must be a mapping")
|
raise ValueError("login must be a mapping")
|
||||||
|
|
||||||
|
login_socket = entry.get("login_socket")
|
||||||
|
if login_socket is not None and not isinstance(login_socket, dict):
|
||||||
|
raise ValueError("login_socket must be a mapping")
|
||||||
|
|
||||||
loaded[name] = ServiceConfig(
|
loaded[name] = ServiceConfig(
|
||||||
name=name,
|
name=name,
|
||||||
base_url=base_url,
|
base_url=base_url,
|
||||||
|
|
@ -120,6 +126,7 @@ def load_services(config: Dict[str, Any]) -> Dict[str, ServiceConfig]:
|
||||||
upstream_latest_headers=upstream_headers,
|
upstream_latest_headers=upstream_headers,
|
||||||
notes=entry.get("notes"),
|
notes=entry.get("notes"),
|
||||||
login=login,
|
login=login,
|
||||||
|
login_socket=login_socket,
|
||||||
)
|
)
|
||||||
return loaded
|
return loaded
|
||||||
|
|
||||||
|
|
@ -414,6 +421,53 @@ def authenticate_service(service: ServiceConfig, timeout: float, user_agent: str
|
||||||
return "; ".join(cookies)
|
return "; ".join(cookies)
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_socket_info(service: ServiceConfig, timeout: float) -> Optional[str]:
|
||||||
|
if not service.login_socket:
|
||||||
|
return None
|
||||||
|
base_url = service.login_socket.get("url")
|
||||||
|
username_env = service.login_socket.get("username_env")
|
||||||
|
password_env = service.login_socket.get("password_env")
|
||||||
|
if not base_url or not username_env or not password_env:
|
||||||
|
raise ValueError(f"Login socket config incomplete for {service.name}")
|
||||||
|
|
||||||
|
username = os.getenv(username_env)
|
||||||
|
password = os.getenv(password_env)
|
||||||
|
if not username or not password:
|
||||||
|
raise ValueError(f"Missing credentials for {service.name}")
|
||||||
|
|
||||||
|
info_payload: Dict[str, Any] = {}
|
||||||
|
sio = socketio.Client(reconnection=False, request_timeout=timeout)
|
||||||
|
|
||||||
|
@sio.on("info")
|
||||||
|
def on_info(data):
|
||||||
|
nonlocal info_payload
|
||||||
|
info_payload = data or {}
|
||||||
|
|
||||||
|
sio.connect(base_url, transports=["websocket"], wait_timeout=timeout)
|
||||||
|
|
||||||
|
response = sio.call(
|
||||||
|
"login",
|
||||||
|
{
|
||||||
|
"username": username,
|
||||||
|
"password": password,
|
||||||
|
"token": "",
|
||||||
|
},
|
||||||
|
timeout=timeout,
|
||||||
|
)
|
||||||
|
if not response or not response.get("ok"):
|
||||||
|
sio.disconnect()
|
||||||
|
raise ValueError(f"Socket login failed for {service.name}")
|
||||||
|
|
||||||
|
if not info_payload.get("version"):
|
||||||
|
try:
|
||||||
|
info_payload = sio.call("info", timeout=timeout) or info_payload
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
sio.disconnect()
|
||||||
|
return info_payload.get("version")
|
||||||
|
|
||||||
|
|
||||||
def check_service(service: ServiceConfig, timeout: float, user_agent: str) -> Dict[str, Any]:
|
def check_service(service: ServiceConfig, timeout: float, user_agent: str) -> Dict[str, Any]:
|
||||||
result: Dict[str, Any] = {
|
result: Dict[str, Any] = {
|
||||||
"name": service.name,
|
"name": service.name,
|
||||||
|
|
@ -444,6 +498,11 @@ def check_service(service: ServiceConfig, timeout: float, user_agent: str) -> Di
|
||||||
)
|
)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
result["current_error"] = str(exc)
|
result["current_error"] = str(exc)
|
||||||
|
elif service.login_socket:
|
||||||
|
try:
|
||||||
|
result["current"] = fetch_socket_info(service, timeout)
|
||||||
|
except Exception as exc:
|
||||||
|
result["current_error"] = str(exc)
|
||||||
|
|
||||||
if service.upstream_latest_version_url and service.upstream_latest_extract:
|
if service.upstream_latest_version_url and service.upstream_latest_extract:
|
||||||
headers = build_headers(service.upstream_latest_headers)
|
headers = build_headers(service.upstream_latest_headers)
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,9 @@ requires-python = ">=3.10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"PyYAML>=6.0",
|
"PyYAML>=6.0",
|
||||||
"bcrypt>=4.0",
|
"bcrypt>=4.0",
|
||||||
|
"python-socketio>=5.11",
|
||||||
|
"requests>=2.31",
|
||||||
|
"websocket-client>=1.7",
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,19 @@ services:
|
||||||
upstream_latest_extract:
|
upstream_latest_extract:
|
||||||
type: jsonpath
|
type: jsonpath
|
||||||
value: $.tag_name
|
value: $.tag_name
|
||||||
|
- name: Uptime Kuma
|
||||||
|
base_url: https://uptime.jeena.net/
|
||||||
|
current_version_url:
|
||||||
|
current_version_extract:
|
||||||
|
login_socket:
|
||||||
|
url: https://uptime.jeena.net/
|
||||||
|
username_env: UPTIME_KUMA_USERNAME
|
||||||
|
password_env: UPTIME_KUMA_PASSWORD
|
||||||
|
upstream_latest_version_url: https://api.github.com/repos/louislam/uptime-kuma/releases/latest
|
||||||
|
upstream_latest_extract:
|
||||||
|
type: jsonpath
|
||||||
|
value: $.tag_name
|
||||||
|
notes: Version retrieved via authenticated Socket.IO info event.
|
||||||
- name: FreshRSS
|
- name: FreshRSS
|
||||||
base_url: https://rss.jeena.net/
|
base_url: https://rss.jeena.net/
|
||||||
current_version_url: https://rss.jeena.net/i/?a=about
|
current_version_url: https://rss.jeena.net/i/?a=about
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue