# pyfconfig A clone of [ifconfig.me](https://ifconfig.me/) built on the [kaya](https://gitea.woggioni.net/woggioni/kaya) framework (`kaya-core` + `kaya-rsgi`), served by [Granian](https://github.com/emmett-framework/granian) over the RSGI protocol. ## Stack - **kaya-core** — routing and HTTP request/response handling - **kaya-rsgi** — Granian (RSGI) adapter - **granian** — application server - **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`. Example: ```console $ 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 ### With Docker Compose (recommended) ```bash docker compose up -d --build # App published at http://localhost:8080 ``` ### On the host ```bash 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 --host 0.0.0.0 --port 8000 pyfconfig.app:app ``` ### Configuration Environment variables (see `.env.example`): | Variable | Default | Description | |---|---|---| | `APP_HOST` | `0.0.0.0` | Bind host (informational; granian is configured via `GRANIAN_*`) | | `APP_PORT` | `8000` | Bind port (informational) | | `SITE_NAME` | `pyfconfig` | Public name used in the HTML page title and the curl examples (set to your domain, e.g. `ifconfig.example.com`) | ## Tests ```bash . .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`.