diff --git a/module.json b/module.json new file mode 100644 index 0000000..8f2b612 --- /dev/null +++ b/module.json @@ -0,0 +1,82 @@ +{ + "module": "f1-circuits", + "slug": "f1", + "version": "1", + "capabilities": [ + "container-runtime" + ], + "requires": [ + "route" + ], + "contributes": { + "route": { + "label": "f1-circuits", + "endpoint": "web" + } + }, + "binds": { + "route": "${dir:state}/route.json" + }, + "listens": [ + { + "name": "web", + "port": 8000, + "protocol": "tcp", + "from": "mesh", + "why": "the track-layout CDN, the race posters and the Kometa metadata over http; its public name is a route grant and the proxy reaches it here. Kometa fetches the metadata and every poster by that public name" + } + ], + "resources": [ + { + "id": "state", + "type": "directory", + "mode": "0700", + "place": "." + }, + { + "id": "cache", + "type": "directory", + "mode": "0700" + }, + { + "id": "site-env", + "type": "file", + "path": "${dir:state}/site.env", + "mode": "0644", + "content": "F1_CIRCUITS_BASE_URL=https://${bound:route:name}\n" + }, + { + "id": "server", + "type": "container", + "name": "f1-circuits", + "artifact": "cdn", + "ports": [ + "8000" + ], + "env-file": [ + "${dir:state}/site.env" + ], + "volumes": [ + "${dir:cache}:/app/cache" + ], + "restart-on": [ + "site-env" + ] + } + ], + "build": { + "on": [ + { + "arg": "PYTHON_BASE", + "image": "python@sha256:6d43704baacd1bfbe7c295d7f13079d5d8104ed33568873133f8fc69980419df" + } + ], + "artifacts": [ + { + "name": "cdn", + "kind": "image", + "from": "service/Dockerfile" + } + ] + } +} diff --git a/posters_service.py b/posters_service.py index 9df3981..51509a6 100644 --- a/posters_service.py +++ b/posters_service.py @@ -16,6 +16,7 @@ import io import json import logging import math +import os import urllib.request from enum import Enum from pathlib import Path @@ -61,8 +62,13 @@ TILE_PROVIDERS = { }, } +# The address this service is reached at, which the Kometa metadata links every poster to. The +# mesh writes it from the name the route is served under (F1_CIRCUITS_BASE_URL); the fallback is +# the address HAL served it at, so a deployment that sets nothing behaves as before. +PUBLIC_BASE_URL = os.environ.get("F1_CIRCUITS_BASE_URL", "https://f1-circuits.zurag.be").rstrip("/") + # Polite UA — both providers expect one and Esri throttles aggressively without it. -TILE_USER_AGENT = "f1-circuits-posters/1.0 (+https://f1-circuits.zurag.be)" +TILE_USER_AGENT = f"f1-circuits-posters/1.0 (+{PUBLIC_BASE_URL})" logger = logging.getLogger(__name__) @@ -641,7 +647,7 @@ class PostersService: # ------------------------------------------------------------------ kometa metadata - BASE_URL = "https://f1-circuits.zurag.be" + BASE_URL = PUBLIC_BASE_URL def render_kometa_metadata(self, first_season: int = 1950, last_offset: int = 1) -> str: """Emit the Kometa metadata YAML for the Formula 1 library, covering diff --git a/service/Dockerfile b/service/Dockerfile index 9cdeb11..385eb00 100644 --- a/service/Dockerfile +++ b/service/Dockerfile @@ -1,4 +1,8 @@ -FROM python:3.12-alpine +# The base arrives pinned from module.json's build.on (python 3.12.13 on alpine 3.24); the mesh +# builds this image from this repository and a commit (novox/hq ADR 0069). The build context is +# the repository root, so every COPY names its path from there. +ARG PYTHON_BASE +FROM ${PYTHON_BASE} WORKDIR /app @@ -14,13 +18,9 @@ RUN apk add --no-cache \ # All pip deps ship musllinux wheels — no build-deps needed. # numpy is pulled in by models/geo_json/geometry.py for vector ops at import time. -RUN pip install --no-cache-dir \ - fastapi \ - uvicorn \ - python-slugify \ - pillow \ - cairosvg \ - numpy +# The versions are the ones the running deployment had (service/requirements.txt). +COPY service/requirements.txt ./requirements.txt +RUN pip install --no-cache-dir -r requirements.txt COPY cdn-api.py circuits_service.py ergast_service.py posters_service.py ./ COPY models/ ./models/ diff --git a/service/requirements.txt b/service/requirements.txt new file mode 100644 index 0000000..4f78ed7 --- /dev/null +++ b/service/requirements.txt @@ -0,0 +1,27 @@ +# The exact set the deployment ace ran on 2026-09-30 was built with (pip freeze inside it). +# Pinned so a rebuild from this commit installs what was running, not whatever is newest. +annotated-doc==0.0.4 +annotated-types==0.7.0 +anyio==4.13.0 +cairocffi==1.7.1 +CairoSVG==2.9.0 +cffi==2.0.0 +click==8.4.1 +cssselect2==0.9.0 +defusedxml==0.7.1 +fastapi==0.137.0 +h11==0.16.0 +idna==3.18 +numpy==2.4.6 +pillow==12.2.0 +pycparser==3.0 +pydantic==2.13.4 +pydantic_core==2.46.4 +python-slugify==8.0.4 +starlette==1.3.1 +text-unidecode==1.3 +tinycss2==1.5.1 +typing-inspection==0.4.2 +typing_extensions==4.15.0 +uvicorn==0.49.0 +webencodings==0.5.1