3.7 KiB
13ft — OTM's fork
A self-hosted "read the article" fetcher (Python/Flask + gunicorn, ~200 lines
in app/ (portable.py is the entrypoint)): it refetches a URL as Googlebot and serves the page
without the paywall/ad cruft. Forked 2026-10-14 from
wasi-master/13ft (the original project
was taken down once already — wasm-fyi → wasi-master — which is WHY we hold a
fork, not a dependency on GitHub/Docker Hub).
- Remotes:
gitea=tonym/13fton git.poweredbyotm.com (ours, push here);upstream= GitHub wasi-master/13ft (pull only:git fetch upstream && git merge upstream/main). Token for Gitea lives in~/Projects/CLAUDE.md. - Running today (since 2026-10-19, v0.4.0): https://13ft.poweredbyotm.com on
anvil as a FIRST-PARTY APP built from this repo (
platform-13ft:latest, catalogue entry in otm-admin 0.148.0) behind Sign in with OTM — grantapp:13fton /users. The old stock-image + Caddy basic-auth stack (platform/infra/13ft/setup-13ft-anvil.sh, platform#91) is gone; the script is marked SUPERSEDED. Deploy = push →build_app 13ft→deploy_app 13ft.
Where this is going (the reason for the fork)
Make it a proper OTM first-party app per
platform/docs/first-party-app-guide.md (house rules are CI-enforced by
platform/test/first-party-house-rules.test.ts):
- Build from this repo — catalogue entry in
platform/apps/otm-admin/lib/first-party-apps.ts(id: "13ft", repotonym/13ft, imageplatform-13ft:latest, port 5000,hostname: 13ft.poweredbyotm.com), thenbuild_app 13ft/deploy_app 13ftvia the platform MCP. First: destroy or rename the hand-provisioned13ftstack (same slug + hostname + container name) or the two will fight over the Caddy site and the container name. - Sign in with OTM replaces Caddy basic auth:
needsAuthSecret→OTM_SSO_SECRET; add/api/auth/otm-sso(verify the HS256 ticket for audapp:13ft— portpickup/src/sso.js, or in Python: HMAC-SHA256 overheader.payload, base64url, checkexp/aud/jti, single-use) and a session cookie; gate every fetch route on it. Grant-only (NOTpublicAccess) — a stranger's OTM account must not get a free fetch proxy. - tides (
needsAnalytics) — the beacon<script defer src="${TIDES_HOST}/t.js" data-site="${TIDES_SITE_ID}">in the page template. Ideas (needsSuggestions) — a lightbulb that files Gitea issues (seepickup/src/ideas.jsfor the plain-HTTP shape). Version +/changelog+/api/healthreporting the version + footer "Powered by OTM". - Keep the Dockerfile's non-root user (
thirteener) and gunicorn; add aHEALTHCHECK.
Status 2026-10-18 (v0.4.0): steps 1–4 are built. app/otm.py holds the ticket
verifier + session cookie + health/changelog/footer/Ideas; portable.py mounts the
before_request gate (everything but otm.PUBLIC_PATHS needs a session; no secret =
503, never open) and the routes. Tests: cd app && ../.venv/bin/python -m unittest
(pure test_otm.py + Flask test-client test_app.py). The platform catalogue entry
landed in otm-admin 0.148.0. Deploy = stop the hand stack (cd /srv/otm/clients/13ft && docker compose down), then build_app 13ft + deploy_app 13ft; grant app:13ft
to each reader on /users. The in-memory jti guard assumes gunicorn's single worker.
Dev
python3 -m venv .venv && . .venv/bin/activate && pip install -r requirements.txt
cd app && python portable.py # http://127.0.0.1:5000 (the app lives in app/)
docker build -t 13ft-dev . && docker run --rm -p 5000:5000 13ft-dev
Upstream README (below in this repo's README.md) covers the rest. Keep
upstream's MIT license file.