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.
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.
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-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.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
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_BINDescape hatch).server.build_http_auth()— FastMCPStaticTokenVerifierbearer gate (ignored by stdio); unauthenticated/healthzroute for container/proxy probes.familiar serveCLI alias; clean exit onInsecureBind.Docker
docker/Dockerfile— multi-stage, non-root (uid 1000), healthcheck on/healthz.docker-compose.yml— read-only rootfs,cap_drop: ALL,no-new-privileges, externalproxynetwork, Traefik TLS labels (Caddy alternative in docs). Token store on/datavolume; 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 togit.hiddenden.cafe/hiddenden/familiar.main→:latest+:main,dev→:dev, every build also:sha-<12>. Push gated to main/dev, authenticated with theREGISTRY_TOKENsecret; scoped cleanup.Docs / tests
docs/deployment.md, README + CLAUDE.md notes./healthz200, 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.
Closing in favour of #63 as the HTTP-transport base.
Both PRs add the same core (the
familiar servesubcommand, theStaticTokenVerifierbearer gate, writes-off-by-default over HTTP) and both addtests/test_serve.py, so they can't both land againstdev(add/add on the test file, overlapping edits in__main__.py/config.py/server.py). #63 has the cleaner, newer core, keepsserveinline instead of a separate module, adds the headlesslogin --no-browserflow needed to provision multiple characters on a headless host, and correctly bumps the fastmcp pin + refreshesuv.lock(this branch usesStaticTokenVerifierwithout touchingpyproject.toml/uv.lock, which under-pins the dependency).The deployment layer here is worth keeping and is not in #63. Branch
feat/http-dockerstays, 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/healthzprobe routeFAMILIAR_ALLOW_INSECURE_BINDescape hatch)Drop this branch's
serve.py/config.py/server.py/test_serve.pyedits when salvaging, since #63 already provides them.Pull request closed