Self-hostable, Git-backed publishing and sharing of documents, static sites, and HTML drafts as shareable links — with an agent-friendly CLI for non-interactive publishing.
Patra (ಪತ್ರ / पत्र) is Sanskrit/Kannada for "page," "leaf," or "letter" — a fitting name for a place to keep and share your documents. The logo is a leaf whose veins read as lines of text.
Patra hosts, versions, previews, and shares self-contained HTML artifacts and multi-file static sites with real access control. It is built for two audiences:
- Teams that want a simpler, HTML-specialized "Drive" with a Git backbone, link sharing, team access, and an audit trail.
- AI coding agents (Claude Code, Codex, etc.) that generate reports, dashboards, mockups, and plans and need to push a file and get back a clean, shareable URL — non-interactively, with token auth and deterministic output.
It is a Bun/Turbo monorepo: a Hono API server, a React/Vite web app, a
patra CLI, and a shared types package.
- Draft HTML hosting — publish a single standalone
.htmlfile to a private, authenticated viewer URL, served through short-lived signed content URLs on a separate content origin. - Git-backed repos + multi-file sites — every user and team owns one bare Git repo; upload folders or push over Git smart-HTTP. Linked pages, CSS, and assets resolve by relative path without rewriting.
- Flexible sharing — public links, email shares, and team shares, with
optional password protection, expiry, and organization/email-domain
gating (
publicvsorglink access). - Analytics + audit log — per-share and per-draft view metrics (total views, unique visitors, last viewed, referrers) plus an actor activity audit log.
- Versioning — repos are Git-backed; restore any file (or the whole tree) from history as a new commit, and duplicate files or drafts.
- Scoped API tokens — least-privilege tokens (
repo:*,share:*,team:*,draft:*,git:*,site-data:*,webhook:*,audit:read,user:*, or*) enforced on every authenticated endpoint. - GitHub sync — one-way import via a GitHub App (selected-repository access) or a personal access token fallback; pick a whole repo, folder, or single file before syncing.
- Teams — full team CRUD with
owner/admin/member/viewerroles and email invitations. - Form / site-data collection — opt-in, per-collection form ingestion from hosted pages, public and rate-limited, storing only hashed visitor identifiers.
- Outbound webhooks — user-configurable, HMAC-signed event deliveries
(
share.created,share.revoked,github_sync.completed) with SSRF guards (private/loopback hosts rejected). - First-run setup + admin — a setup wizard and a sysadmin-only setup checklist;
sysadmins are derived from
SYSADMIN_EMAILS. - Docs — a hosted product/agent/API reference, an OpenAPI 3.1 spec at
GET /openapi.json, and a machine-readableGET /llms.txt. - Rate limiting — in-memory fixed-window limiter on public and auth endpoints, tunable via env.
- Docker self-hosting — single app container, SQLite + Git repos on a volume.
bun install
cp .env.example .env
bun run devThe web app runs on http://localhost:5173 and proxies API traffic to the server
on http://localhost:3000.
For local dev login, set ENABLE_DEV_LOGIN=true in .env, then sign in with any
email and password dev.
cp .env.production.example .env.production
# Edit .env.production: public URLs, OAuth credentials, and 32+ char secrets.
docker compose up --buildThe compose setup runs one app container on port 3000 and stores SQLite, bare
Git repositories, and extracted worktrees in the persistent data volume.
Minimum production settings:
NODE_ENV=productionAPP_URL=https://your-domainAPI_URL=https://your-domainCONTENT_ORIGIN=https://content.your-domain(a separate host for sandboxed draft HTML)DEPLOYMENT_NAME="Your Company Docs"for visible deployment branding (defaults toPatra)SYSADMIN_EMAILS=admin@your-domainfor setup/admin accessGOOGLE_REDIRECT_URI=https://your-domain/api/auth/google/callbackSESSION_SECRET,DRAFT_CONTENT_SECRET,HOOK_SECRET, andGITHUB_TOKEN_SECRET— each a unique 32+ character random valueENABLE_DEV_LOGIN=false
If you terminate TLS at a reverse proxy, forward traffic to the container on port
3000. For full details and platform-specific notes, see
docs/self-hosting.md and
docs/deployment.md.
The command-line client is published as the patra binary. Authenticate
with an API token created in Settings → API Tokens (tokens are prefixed
pat_ and shown once):
patra login --token pat_...
patra draft ./plan.html # publish one HTML file, print its URL
patra push ./site --to personal --message "Publish site"
patra teamsDrafts published with patra draft appear in the authenticated web app under
Drafts, where owners can open, copy, search, and delete their private draft
URLs.
Bun workspaces + Turborepo monorepo:
packages/server— Hono API, SQLite/Drizzle storage, Git smart-HTTP, file extraction, share/draft/webhook routes, OpenAPI + llms.txt.packages/web— React/Vite/Tailwind web app.packages/cli— thepatracommand-line client.packages/shared— shared TypeScript types and Zod validation schemas.
- In-app docs are served at
/docs. - Markdown sources live in
docs/: Product Guide, Agent Guide, API Reference, Self-Hosting, Deployment. - New here?
HANDOFF.mdexplains the architecture, feature status, and gotchas.
GitHub Pages serves the web app's public UI — the marketing home page and the
styled /docs guides, exactly as they render in the app — as a static site. The
authenticated app (login, setup, dashboard, sharing) is stripped out for the
static build, since there is no server on Pages.
The .github/workflows/docs-pages.yml
workflow builds and deploys the site with GitHub Actions: it builds
packages/web with VITE_PUBLIC_SITE=true (public routes only) and
PAGES_BASE=/<repo>/ (so assets and routes resolve under the repo subpath),
writes a 404.html SPA fallback for deep links, and publishes the dist/ output.
To enable it:
- In the repo, go to Settings → Pages.
- Under Build and deployment, set Source to "GitHub Actions".
The site publishes at https://<user>.github.io/<repo>/. The base path is derived
from the repo name at build time, so it keeps working whether the repository stays
named docs-share or is later renamed. Pushes to main that touch
packages/web/**, packages/shared/**, docs/**, or the workflow file
redeploy automatically; you can also trigger a manual run from the Actions tab.
The /docs content is the same Markdown in docs/, imported at build
time and rendered by the app's docs pages — editing those files updates both the
running app and the published site.
Patra ships with authentication (Google OAuth sessions + scoped pat_ API
tokens), sandboxed content serving (untrusted draft HTML runs from a separate
CONTENT_ORIGIN behind short-lived signed URLs and a sandbox CSP),
SSRF-guarded outbound webhooks, and rate limiting. Operators should set strong,
unique 32+ character secrets and terminate TLS (HTTPS) in front of the app. See
SECURITY.md.
See CONTRIBUTING.md.
Apache-2.0. See LICENSE.


