From 965c060dca284edb285cbb4e7ecd2e251fa39fc0 Mon Sep 17 00:00:00 2001 From: Christian Cleberg Date: Sat, 11 Apr 2026 13:19:37 -0500 Subject: chore: harden repo defaults for secrets, deployment, and logging --- .dockerignore | 26 ++++++++++++++++++++++++ .env.example | 6 +++--- README.md | 34 +++++++++++++++++++------------- compose.yml | 6 +++--- src/srht_contrib/services/srht_client.py | 23 ++++++++++++++------- 5 files changed, 68 insertions(+), 27 deletions(-) create mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..c53d12d --- /dev/null +++ b/.dockerignore @@ -0,0 +1,26 @@ +.git +.gitignore +.env +.env.* +!.env.example +.venv/ +venv/ +__pycache__/ +*.py[cod] +*.db +*.sqlite +*.sqlite3 +.pytest_cache/ +.mypy_cache/ +.ruff_cache/ +.hypothesis/ +.coverage +.coverage.* +htmlcov/ +build/ +dist/ +*.egg-info/ +.eggs/ +.DS_Store +.idea/ +.vscode/ diff --git a/.env.example b/.env.example index 1dd57f4..95d9645 100644 --- a/.env.example +++ b/.env.example @@ -4,11 +4,11 @@ SRHT_TOKEN=replace-me TODO_SRHT_ENDPOINT=https://todo.sr.ht/query GIT_SRHT_ENDPOINT=https://git.sr.ht/query DATABASE_URL=sqlite:///./srht_contrib.db -DEFAULT_ACTOR=~ccleberg +DEFAULT_ACTOR=~your-user POLL_INTERVAL_SECONDS=900 # Optional JSON object. Example: -# {"~ccleberg":["cmc@example.com","Chris Cleberg"]} +# {"~your-user":["you@example.com","Your Name"]} ACTOR_ALIASES_JSON={} # Optional JSON array. Example: -# ["Hutch","~ccleberg/cleberg.net"] +# ["your-repo","~your-user/your-site"] GIT_TRACKED_REPOSITORIES=[] diff --git a/README.md b/README.md index b05e71a..97c9b43 100644 --- a/README.md +++ b/README.md @@ -95,10 +95,10 @@ SRHT_TOKEN=replace-me TODO_SRHT_ENDPOINT=https://todo.sr.ht/query GIT_SRHT_ENDPOINT=https://git.sr.ht/query DATABASE_URL=sqlite:///./srht_contrib.db -DEFAULT_ACTOR=~ccleberg +DEFAULT_ACTOR=~your-user POLL_INTERVAL_SECONDS=900 -ACTOR_ALIASES_JSON={"~ccleberg":["cmc@example.com","Chris Cleberg"]} -GIT_TRACKED_REPOSITORIES=["Hutch","~ccleberg/cleberg.net"] +ACTOR_ALIASES_JSON={"~your-user":["you@example.com","Your Name"]} +GIT_TRACKED_REPOSITORIES=["your-repo","~your-user/your-site"] ``` ## Local Run Instructions @@ -151,7 +151,7 @@ uvicorn srht_contrib.main:app --reload Manual polling is exposed as an API endpoint: ```bash -curl -X POST "http://127.0.0.1:8000/api/contributions/poll?actor=~ccleberg" \ +curl -X POST "http://127.0.0.1:8000/api/contributions/poll?actor=~your-user" \ -H "X-API-Key: replace-me" ``` @@ -159,7 +159,7 @@ Example response: ```json { - "actor": "~ccleberg", + "actor": "~your-user", "inserted_events": 3, "services": ["todo", "git"] } @@ -170,7 +170,7 @@ Scheduled polling only runs when `ENABLE_SCHEDULER=true` and uses `DEFAULT_ACTOR For `git.sr.ht`, tracked repositories are configured via `GIT_TRACKED_REPOSITORIES`. Entries may be either: - `"Hutch"` for a repository owned by `DEFAULT_ACTOR` -- `"~ccleberg/cleberg.net"` for an explicit owner/repository pair +- `"~your-user/your-site"` for an explicit owner/repository pair ## API Endpoints @@ -189,20 +189,20 @@ Response: ### Contribution Calendar by Year ```bash -curl "http://127.0.0.1:8000/api/contributions/~ccleberg?year=2026" +curl "http://127.0.0.1:8000/api/contributions/~your-user?year=2026" ``` ### Contribution Calendar by Date Range ```bash -curl "http://127.0.0.1:8000/api/contributions/~ccleberg?from=2026-01-01&to=2026-03-30" +curl "http://127.0.0.1:8000/api/contributions/~your-user?from=2026-01-01&to=2026-03-30" ``` Example response: ```json { - "actor": "~ccleberg", + "actor": "~your-user", "from": "2026-01-01", "to": "2026-03-30", "days": [ @@ -216,14 +216,14 @@ Example response: ### Contribution Stats ```bash -curl "http://127.0.0.1:8000/api/contributions/~ccleberg/stats?year=2026" +curl "http://127.0.0.1:8000/api/contributions/~your-user/stats?year=2026" ``` Example response: ```json { - "actor": "~ccleberg", + "actor": "~your-user", "from": "2026-01-01", "to": "2026-12-31", "total_events": 42, @@ -239,7 +239,7 @@ Example response: List tracked repositories: ```bash -curl "http://127.0.0.1:8000/api/repositories?actor=~ccleberg" \ +curl "http://127.0.0.1:8000/api/repositories?actor=~your-user" \ -H "X-API-Key: replace-me" ``` @@ -249,7 +249,7 @@ Create a tracked repository: curl -X POST "http://127.0.0.1:8000/api/repositories" \ -H "X-API-Key: replace-me" \ -H "Content-Type: application/json" \ - -d '{"actor":"~ccleberg","repo_name":"Hutch"}' + -d '{"actor":"~your-user","repo_name":"your-repo"}' ``` Get, update, and delete a tracked repository: @@ -261,7 +261,7 @@ curl "http://127.0.0.1:8000/api/repositories/1" \ curl -X PATCH "http://127.0.0.1:8000/api/repositories/1" \ -H "X-API-Key: replace-me" \ -H "Content-Type: application/json" \ - -d '{"repo_name":"~ccleberg/cleberg.net"}' + -d '{"repo_name":"~your-user/your-site"}' curl -X DELETE "http://127.0.0.1:8000/api/repositories/1" \ -H "X-API-Key: replace-me" @@ -316,6 +316,12 @@ The SourceHut-specific assumptions are isolated to the service modules: - alias management is config-driven; there is no alias CRUD API yet - current deployment model is trusted-operator V1, not a public multi-tenant service +## Deployment Notes + +- Add a `.dockerignore` when building container images so local secrets and SQLite files are never sent to the build context. +- For production, prefer exposing the service behind a reverse proxy instead of publishing the application port directly to the internet. +- Set `ENABLE_SCHEDULER=true` only for single-instance deployments where this service should own polling. + ## Recommended Next Steps 1. Add alias-management APIs or seed files for stronger actor identity mapping. diff --git a/compose.yml b/compose.yml index 90d10e9..3ec0560 100644 --- a/compose.yml +++ b/compose.yml @@ -2,12 +2,12 @@ services: hutch-stats: build: context: . - container_name: hutch-stats + container_name: ${COMPOSE_PROJECT_NAME:-hutch-stats} ports: - - "8000:8000" + - "${HOST_PORT:-8000}:8000" environment: API_KEY: ${API_KEY} - ENABLE_SCHEDULER: "true" + ENABLE_SCHEDULER: ${ENABLE_SCHEDULER:-false} SRHT_TOKEN: ${SRHT_TOKEN} TODO_SRHT_ENDPOINT: ${TODO_SRHT_ENDPOINT:-https://todo.sr.ht/query} GIT_SRHT_ENDPOINT: ${GIT_SRHT_ENDPOINT:-https://git.sr.ht/query} diff --git a/src/srht_contrib/services/srht_client.py b/src/srht_contrib/services/srht_client.py index c8e2649..ee09bff 100644 --- a/src/srht_contrib/services/srht_client.py +++ b/src/srht_contrib/services/srht_client.py @@ -13,6 +13,12 @@ class SourceHutClientError(RuntimeError): """Raised when a SourceHut GraphQL request fails.""" +def _graphql_error_summary(errors: Any) -> str: + if not isinstance(errors, list): + return "unexpected error payload" + return f"{len(errors)} GraphQL error(s)" + + class SourceHutGraphQLClient: def __init__( self, @@ -42,20 +48,16 @@ class SourceHutGraphQLClient: response.raise_for_status() body = response.json() except httpx.HTTPStatusError as exc: - response_text = exc.response.text[:500] logger.warning( - "SourceHut HTTP failure from %s on attempt %s/%s: %s %s", + "SourceHut HTTP failure from %s on attempt %s/%s: status=%s", self.endpoint, attempt, attempts, exc.response.status_code, - response_text, ) if exc.response.status_code >= 500 and attempt < attempts: continue - raise SourceHutClientError( - f"HTTP error from SourceHut: {exc.response.status_code} {response_text}".strip() - ) from exc + raise SourceHutClientError(f"HTTP error from SourceHut: {exc.response.status_code}") from exc except httpx.HTTPError as exc: logger.warning( "SourceHut network failure from %s on attempt %s/%s", @@ -68,7 +70,14 @@ class SourceHutGraphQLClient: raise SourceHutClientError("Network error while contacting SourceHut") from exc if "errors" in body: - raise SourceHutClientError(f"GraphQL errors returned by SourceHut: {body['errors']}") + logger.warning( + "SourceHut GraphQL failure from %s: %s", + self.endpoint, + _graphql_error_summary(body["errors"]), + ) + raise SourceHutClientError( + f"GraphQL errors returned by SourceHut: {_graphql_error_summary(body['errors'])}" + ) return body.get("data", {}) raise SourceHutClientError("SourceHut request exhausted retries") -- cgit v1.2.3