HTTP transport + hardened Docker image, published to Gitea (dev + main) #62

Closed
Latte wants to merge 2 commits from feat/http-docker into dev
pull from: feat/http-docker
Owner

Adds an opt-in HTTP transport so familiar can be reached from the home lab over HTTPS behind a TLS reverse proxy, plus a hardened Docker image published to the Gitea registry.

Serving

  • config.http_settings() — env-driven transport/host/port/path/token (no I/O).
  • serve.run() — dispatch stdio (default) vs http; fails closed on a non-loopback bind without a bearer token (FAMILIAR_ALLOW_INSECURE_BIND escape hatch).
  • server.build_http_auth() — FastMCP StaticTokenVerifier bearer gate (ignored by stdio); unauthenticated /healthz route for container/proxy probes.
  • familiar serve CLI alias; clean exit on InsecureBind.

Docker

  • docker/Dockerfile — multi-stage, non-root (uid 1000), healthcheck on /healthz.
  • docker-compose.yml — read-only rootfs, cap_drop: ALL, no-new-privileges, external proxy network, Traefik TLS labels (Caddy alternative in docs). Token store on /data volume; writes off by default. Pulls the published image (FAMILIAR_IMAGE) with local build as fallback.
  • .env.example, .dockerignore.

CI (mirrors AegisGitea-MCP)

  • .gitea/workflows/docker.yml — lint → test → build → smoke-test → push to git.hiddenden.cafe/hiddenden/familiar. main → :latest + :main, dev → :dev, every build also :sha-<12>. Push gated to main/dev, authenticated with the REGISTRY_TOKEN secret; scoped cleanup.

Docs / tests

  • docs/deployment.md, README + CLAUDE.md notes.
  • Tests for settings parsing, fail-closed validation, and the bearer verifier. Full suite green (173 passed, 91% coverage); image builds and smoke-tests verified locally (non-root, /healthz 200, MCP 401 without token / 200 with token).

Login still happens once on a host with a browser; the token store is mounted into the container (multi-character supported). Fully web-based multi-account login (OAuth callback on the public domain) is a possible follow-up.

Adds an opt-in HTTP transport so familiar can be reached from the home lab over HTTPS behind a TLS reverse proxy, plus a hardened Docker image published to the Gitea registry. ## Serving - `config.http_settings()` — env-driven transport/host/port/path/token (no I/O). - `serve.run()` — dispatch stdio (default) vs http; **fails closed** on a non-loopback bind without a bearer token (`FAMILIAR_ALLOW_INSECURE_BIND` escape hatch). - `server.build_http_auth()` — FastMCP `StaticTokenVerifier` bearer gate (ignored by stdio); unauthenticated `/healthz` route for container/proxy probes. - `familiar serve` CLI alias; clean exit on `InsecureBind`. ## Docker - `docker/Dockerfile` — multi-stage, non-root (uid 1000), healthcheck on `/healthz`. - `docker-compose.yml` — read-only rootfs, `cap_drop: ALL`, `no-new-privileges`, external `proxy` network, Traefik TLS labels (Caddy alternative in docs). Token store on `/data` volume; writes off by default. Pulls the published image (`FAMILIAR_IMAGE`) with local build as fallback. - `.env.example`, `.dockerignore`. ## CI (mirrors AegisGitea-MCP) - `.gitea/workflows/docker.yml` — lint → test → build → smoke-test → push to `git.hiddenden.cafe/hiddenden/familiar`. `main` → `:latest` + `:main`, `dev` → `:dev`, every build also `:sha-<12>`. Push gated to main/dev, authenticated with the `REGISTRY_TOKEN` secret; scoped cleanup. ## Docs / tests - `docs/deployment.md`, README + CLAUDE.md notes. - Tests for settings parsing, fail-closed validation, and the bearer verifier. Full suite green (173 passed, 91% coverage); image builds and smoke-tests verified locally (non-root, `/healthz` 200, MCP 401 without token / 200 with token). Login still happens once on a host with a browser; the token store is mounted into the container (multi-character supported). Fully web-based multi-account login (OAuth callback on the public domain) is a possible follow-up.
Latte added 2 commits 2026-07-03 18:19:27 +00:00
Add an opt-in streamable-HTTP transport alongside the default stdio, so
familiar can be reached from a home lab over HTTPS behind a TLS reverse proxy.

- config.http_settings(): env-driven transport/host/port/path/token settings.
- serve.run(): dispatch stdio vs http; fail closed on a non-loopback bind
  without a bearer token (FAMILIAR_ALLOW_INSECURE_BIND escape hatch).
- server.build_http_auth(): FastMCP StaticTokenVerifier bearer gate; add an
  unauthenticated /healthz route for container/proxy probes.
- `familiar serve` CLI alias; clean exit on InsecureBind.
- docker/Dockerfile (multi-stage, non-root) + hardened docker-compose.yml
  (read-only rootfs, cap_drop ALL, no-new-privileges, Traefik TLS labels),
  .dockerignore, .env.example. Token store mounts at /data; writes off by default.
- .gitea/workflows/docker.yml: build + smoke-test, opt-in image publish.
- docs/deployment.md + README/CLAUDE.md notes; tests for settings, fail-closed
  validation, and the bearer verifier.
ci(docker): publish image to Gitea registry (dev + main), mirror AegisGitea-MCP
docker / lint (push) Successful in 50s
docker / test (push) Successful in 58s
CI / lint (pull_request) Successful in 50s
CI / test (pull_request) Successful in 1m1s
CI / publish (pull_request) Has been skipped
docker / lint (pull_request) Successful in 52s
docker / docker (push) Successful in 43s
docker / test (pull_request) Successful in 59s
docker / docker (pull_request) Successful in 37s
904e96a559
Replace the opt-in docker workflow with the AegisGitea-MCP-style pipeline:
lint -> test -> build -> smoke-test -> push to git.hiddenden.cafe.

- Image git.hiddenden.cafe/hiddenden/familiar; main -> :latest + :main, dev ->
  :dev, every build also tagged :sha-<12>. Push gated to main/dev push events,
  authenticated with the REGISTRY_TOKEN secret; scoped cleanup after each run.
- docker-compose.yml pulls the published image (FAMILIAR_IMAGE, default :latest)
  with local build as a fallback; .env.example + docs/deployment.md document the
  channels and the pull-based deploy.

Closing in favour of #63 as the HTTP-transport base.

Both PRs add the same core (the familiar serve subcommand, the StaticTokenVerifier bearer gate, writes-off-by-default over HTTP) and both add tests/test_serve.py, so they can't both land against dev (add/add on the test file, overlapping edits in __main__.py/config.py/server.py). #63 has the cleaner, newer core, keeps serve inline instead of a separate module, adds the headless login --no-browser flow needed to provision multiple characters on a headless host, and correctly bumps the fastmcp pin + refreshes uv.lock (this branch uses StaticTokenVerifier without touching pyproject.toml/uv.lock, which under-pins the dependency).

The deployment layer here is worth keeping and is not in #63. Branch feat/http-docker stays, so salvage these into a focused follow-up on top of #63:

  • docker/Dockerfile (multi-stage, non-root, healthcheck)
  • docker-compose.yml (read-only rootfs, cap_drop ALL, no-new-privileges, Traefik labels)
  • .gitea/workflows/docker.yml (build + push to the Gitea registry)
  • .dockerignore, .env.example, docs/deployment.md
  • the unauthenticated /healthz probe route
  • the fail-closed guard on a non-loopback bind (FAMILIAR_ALLOW_INSECURE_BIND escape hatch)

Drop this branch's serve.py/config.py/server.py/test_serve.py edits when salvaging, since #63 already provides them.

Closing in favour of #63 as the HTTP-transport base. Both PRs add the same core (the `familiar serve` subcommand, the `StaticTokenVerifier` bearer gate, writes-off-by-default over HTTP) and both add `tests/test_serve.py`, so they can't both land against `dev` (add/add on the test file, overlapping edits in `__main__.py`/`config.py`/`server.py`). #63 has the cleaner, newer core, keeps `serve` inline instead of a separate module, adds the headless `login --no-browser` flow needed to provision multiple characters on a headless host, and correctly bumps the fastmcp pin + refreshes `uv.lock` (this branch uses `StaticTokenVerifier` without touching `pyproject.toml`/`uv.lock`, which under-pins the dependency). The deployment layer here is worth keeping and is not in #63. Branch `feat/http-docker` stays, so salvage these into a focused follow-up on top of #63: - `docker/Dockerfile` (multi-stage, non-root, healthcheck) - `docker-compose.yml` (read-only rootfs, cap_drop ALL, no-new-privileges, Traefik labels) - `.gitea/workflows/docker.yml` (build + push to the Gitea registry) - `.dockerignore`, `.env.example`, `docs/deployment.md` - the unauthenticated `/healthz` probe route - the fail-closed guard on a non-loopback bind (`FAMILIAR_ALLOW_INSECURE_BIND` escape hatch) Drop this branch's `serve.py`/`config.py`/`server.py`/`test_serve.py` edits when salvaging, since #63 already provides them.
Ghost closed this pull request 2026-07-04 16:45:10 +00:00

Pull request closed

This pull request cannot be reopened because the branch was deleted.
Sign in to join this conversation.
No Reviewers
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Hiddenden/familiar#62