From e9f5586f4854b85a5b8bf772f6e415b176267e42 Mon Sep 17 00:00:00 2001 From: LunaChocken Date: Mon, 16 Mar 2026 20:07:33 +0000 Subject: [PATCH] add docker build --- Dockerfile | 12 ++++++ libs/parse_caddyfile.py | 77 ++++++++++++++++++++++++++++++++++++ main.py | 88 +++++++++++++++++++++++++++++++++++++++-- pyproject.toml | 5 ++- uv.lock | 36 +++++++++++++++++ 5 files changed, 214 insertions(+), 4 deletions(-) create mode 100644 Dockerfile create mode 100644 libs/parse_caddyfile.py create mode 100644 uv.lock diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..c6f9446 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,12 @@ +FROM python:3.12-slim-trixie +COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/ + +# Copy the project into the image +COPY . /app + +# Disable development dependencies +ENV UV_NO_DEV=1 + +# Sync the project into a new environment, asserting the lockfile is up to date +WORKDIR /app +RUN uv sync --locked diff --git a/libs/parse_caddyfile.py b/libs/parse_caddyfile.py new file mode 100644 index 0000000..a5fcb57 --- /dev/null +++ b/libs/parse_caddyfile.py @@ -0,0 +1,77 @@ +import shlex + + +def tokenize(text): + lexer = shlex.shlex(text, posix=True) + lexer.whitespace_split = True + lexer.commenters = "#" + lexer.wordchars += ".:/-@()," + return list(lexer) + + +def parse_sites_imports(text): + tokens = tokenize(text) + + result = {} + current_site = None + depth = 0 + i = 0 + + while i < len(tokens): + tok = tokens[i] + + # detect site start + if i + 1 < len(tokens) and tokens[i + 1] == "{": + if depth == 0: + sites = [s.strip() for s in tok.split(",")] + + for s in sites: + result.setdefault(s, []) + + current_site = sites + + depth += 1 + i += 2 + continue + + if tok == "{": + depth += 1 + + elif tok == "}": + depth -= 1 + if depth == 0: + current_site = None + + elif tok == "import" and i + 1 < len(tokens) and current_site: + name = tokens[i + 1] + for site in current_site: + result[site].append(name) + i += 2 + continue + + i += 1 + + return result + + +def filter_globals_out(data): + import re + + ret = dict() + for key, values in data.items(): + if re.match(r"\(\w+\)", key): # exclude import definitions e.g (auth) + continue + ret[key] = values + return ret + + +def parse(data): + data = parse_sites_imports(data) + data = filter_globals_out(data) + return data + + +if __name__ == "__main__": + with open("Caddyfile") as f: + data = parse(f.read()) + print(data) diff --git a/main.py b/main.py index a58cc7a..35e25d4 100644 --- a/main.py +++ b/main.py @@ -1,6 +1,88 @@ -def main(): - print("Hello from authelia-metrics!") +import os +import time +from prometheus_client import start_http_server, Gauge, REGISTRY +from dotenv import load_dotenv + +# 1. Define Metrics with Labels +# Use labels for domain and feature to avoid creating 100s of unique metric names +DOMAIN_FEATURE = Gauge( + "caddy_domain_imports", + "Binary status of domain features (1=enabled, 0=disabled)", + ["domain", "feature"], +) + +GLOBAL_STATS = Gauge( + "caddy_global_stats", "Aggregated counts of features across all domains", ["type"] +) + + +load_dotenv() + +CADDY_PATH = os.environ["CADDY_PATH"] + + +def load_caddyfile(): + with open(CADDY_PATH, "r") as f: + return f.read() + + +def parse(data): + from libs.parse_caddyfile import parse as caddy_parse + + return caddy_parse(data) + + +def mathing(data: dict): + total_domains = len(data.items()) + # info = dict({"auth": 0, "wan": 0, "noauth": 0, "nowan": 0, "none": 0}) + info = dict({"no_auth": 0, "no_wan": 0}) + for domain, imports in data.items(): + if len(imports) == 0: + if "none" not in info: + info["none"] = 1 + info["none"] += 1 + continue + for imp in imports: + if imp not in info: + info[imp] = 1 + info[imp] += 1 + if "auth" not in imports: + info["no_auth"] += 1 + if "wan" not in imports: + info["no_wan"] += 1 + info["total_domains"] = total_domains + return info + + +def update_metrics(): + # Your existing parsing logic + try: + raw_data = load_caddyfile() + hosts = parse(raw_data) + info = mathing(hosts) + + # --- Update Global Metrics --- + for key, value in info.items(): + GLOBAL_STATS.labels(type=key).set(value) + + # --- Update Per-Domain Metrics --- + tracked_features = ["auth", "wan"] + + for domain, imports in hosts.items(): + for feature in tracked_features: + is_enabled = 1 if feature in imports else 0 + DOMAIN_FEATURE.labels(domain=domain, feature=feature).set(is_enabled) + + print(f"Metrics updated at {time.ctime()}") + + except Exception as e: + print(f"Error updating metrics: {e}") if __name__ == "__main__": - main() + start_http_server(port=8000, addr="0.0.0.0") + print("Prometheus server started on port 8000") + + while True: + update_metrics() + time.sleep(60) diff --git a/pyproject.toml b/pyproject.toml index bf14e26..da95789 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,4 +4,7 @@ version = "0.1.0" description = "Add your description here" readme = "README.md" requires-python = ">=3.12" -dependencies = [] +dependencies = [ + "prometheus-client>=0.24.1", + "python-dotenv>=1.2.2", +] diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..d9a5c36 --- /dev/null +++ b/uv.lock @@ -0,0 +1,36 @@ +version = 1 +revision = 3 +requires-python = ">=3.12" + +[[package]] +name = "authelia-metrics" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "prometheus-client" }, + { name = "python-dotenv" }, +] + +[package.metadata] +requires-dist = [ + { name = "prometheus-client", specifier = ">=0.24.1" }, + { name = "python-dotenv", specifier = ">=1.2.2" }, +] + +[[package]] +name = "prometheus-client" +version = "0.24.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f0/58/a794d23feb6b00fc0c72787d7e87d872a6730dd9ed7c7b3e954637d8f280/prometheus_client-0.24.1.tar.gz", hash = "sha256:7e0ced7fbbd40f7b84962d5d2ab6f17ef88a72504dcf7c0b40737b43b2a461f9", size = 85616, upload-time = "2026-01-14T15:26:26.965Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/74/c3/24a2f845e3917201628ecaba4f18bab4d18a337834c1df2a159ee9d22a42/prometheus_client-0.24.1-py3-none-any.whl", hash = "sha256:150db128af71a5c2482b36e588fc8a6b95e498750da4b17065947c16070f4055", size = 64057, upload-time = "2026-01-14T15:26:24.42Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135, upload-time = "2026-03-01T16:00:26.196Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" }, +]