woggioni-opencode-agent c5b653efba
CI / Build and push docker image (push) Successful in 1m39s
Upgrade to kaya 0.0.2 and run on the rloop event loop
- pin kaya-core/kaya-rsgi >= 0.0.2 (ctx.client now honors Forwarded and
  X-Forwarded-* headers); adapt route tests and add explicit coverage
- add rloop dependency and set GRANIAN_LOOP=rloop in the Docker image so
  granian serves the app on the Rust event loop instead of asyncio
- regenerate requirements.txt (kaya 0.0.2, rloop 0.5.0)
- document the new loop and forwarded-header behavior in the README
2026-09-04 19:57:32 +08:00

pyfconfig

A clone of ifconfig.me built on the kaya framework (kaya-core + kaya-rsgi), served by Granian over the RSGI protocol.

Stack

  • kaya-core — routing and HTTP request/response handling
  • kaya-rsgi — Granian (RSGI) adapter
  • granian — application server
  • rloop — Rust event loop used by Granian instead of the stdlib asyncio loop
  • httpx + pwo — test client over kaya's ASGI transport

No database, sessions, or authentication — the app is stateless.

Endpoints

Method Path Response
GET / HTML page for browsers (Accept: text/html), plain-text IP otherwise
GET /ip Client IP address
GET /ua User-Agent header
GET /lang Accept-Language header
GET /encoding Accept-Encoding header
GET /mime Accept header
GET /charset Accept-Charset header
GET /forwarded X-Forwarded-For header
GET /all All connection fields as key: value lines (empty fields included; remote_host shown as unavailable)
GET /all.json Same fields as a JSON object, empty fields omitted
GET /api/health Liveness probe ({"status":"ok"})

The /all field order mirrors the reference site: ip_addr, remote_host, user_agent, port, language, referer, connection, keep_alive, method, encoding, mime, charset, via, forwarded.

The reported client IP/port honor the Forwarded, X-Forwarded-For, X-Forwarded-Host and X-Forwarded-Port proxy headers (kaya ≥ 0.0.2); without them the socket peer address is used.

Example:

$ curl http://localhost:8080/
203.0.113.7
$ curl http://localhost:8080/all.json
{"ip_addr":"203.0.113.7","user_agent":"curl/8.21.0","port":"51342","method":"GET","mime":"*/*"}

Running

docker compose up -d --build
# App published at http://localhost:8080

On the host

cp .env.example .env   # optional; all settings have defaults
python -m venv .venv && . .venv/bin/activate
pip install --index-url https://gitea.woggioni.net/api/packages/woggioni/pypi/simple \
            --extra-index-url https://pypi.org/simple \
            -e .
granian --interface rsgi --loop rloop --host 0.0.0.0 --port 8000 pyfconfig.app:app

The --loop rloop flag (or GRANIAN_LOOP=rloop, already set in the Docker image) makes Granian run the app on the rloop Rust event loop instead of the stdlib asyncio loop.

Configuration

Environment variables (see .env.example):

Variable Default Description
SITE_NAME pyfconfig Public name used in the HTML page title and the curl examples (set to your domain, e.g. ifconfig.example.com)

The bind address is configured through Granian itself (GRANIAN_HOST / GRANIAN_PORT env vars or --host / --port CLI flags).

Tests

. .venv/bin/activate
python -m unittest discover -s tests -t .

The tests drive the app in-process through kaya's ASGI interface using httpx.ASGITransport; no external services are needed.

Project layout

src/pyfconfig/
├── app.py              # KayaApp assembly; imports route modules
├── config.py           # env-driven Settings dataclass
├── page.py             # HTML landing page builder (kaya has no templating)
└── routes/
    ├── health.py       # GET /api/health
    ├── root.py         # GET / with content negotiation
    └── connection.py   # /ip /ua /lang /encoding /mime /charset /forwarded /all /all.json
tests/
└── test_routes.py      # unittest + httpx.ASGITransport

Route modules import the module-level app from pyfconfig.app and register handlers with @app.GET(...) decorators at import time — pyfconfig.app imports them last to complete the wiring.

Deployment

The Dockerfile is a multi-stage alpine build producing a minimal, non-root runtime image; pushing a release/* tag triggers the Gitea Actions workflow in .gitea/workflows/build.yaml, which builds and publishes the image to gitea.woggioni.net/woggioni-opencode-agent/pyfconfig.

S
Description
No description provided
Readme
69 KiB
Languages
Python 91.8%
Dockerfile 8.2%