Metadata-Version: 2.4
Name: parbaked
Version: 1.3.0a1
Summary: A par-baked starter for PoC apps: signup-with-approval auth, rate limiting, and deploy scaffolding for fly.io
Project-URL: Homepage, https://github.com/saml7n/parbaked
Project-URL: Repository, https://github.com/saml7n/parbaked
Project-URL: Issues, https://github.com/saml7n/parbaked/issues
Author: Sam Leighton
License: MIT
License-File: LICENSE
Keywords: auth,fastapi,fly.io,poc,scaffold,starter
Classifier: Development Status :: 3 - Alpha
Classifier: Framework :: FastAPI
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
Requires-Python: >=3.11
Requires-Dist: bcrypt>=4.1
Requires-Dist: email-validator>=2.1
Requires-Dist: fastapi>=0.110
Requires-Dist: jinja2>=3.1
Requires-Dist: pydantic-settings>=2.1
Requires-Dist: pydantic>=2.5
Requires-Dist: pyjwt[crypto]>=2.8
Requires-Dist: python-multipart>=0.0.9
Requires-Dist: slowapi>=0.1.9
Requires-Dist: sqlmodel>=0.0.16
Requires-Dist: typer>=0.12
Requires-Dist: uvicorn[standard]>=0.30
Provides-Extra: dev
Requires-Dist: httpx>=0.27; extra == 'dev'
Requires-Dist: mcp>=1.0; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.4; extra == 'dev'
Provides-Extra: mcp
Requires-Dist: mcp>=1.0; extra == 'mcp'
Description-Content-Type: text/markdown

# parbaked

**A par-baked starter for FastAPI PoCs.** Signup with admin approval, rate limiting, an admin dashboard, and a [published off-boarding contract](docs/data-format-guarantees.md) — set up in two commands.

> If you ship PoCs on fly.io, two things eventually bite you: anyone on the internet can spin up accounts in a loop (and run up your bill), and you write the same auth boilerplate every time. parbaked is the slice between "I have an idea" and "this is safe to put online."

## 30-second quickstart

```bash
uvx parbaked new myapp
cd myapp
parbaked dev
```

Open `http://localhost:8000/auth/admin` and log in as `admin` with the password from the terminal banner. The admin dashboard, signup/login REST API, and rate-limit middleware are already wired. Drop more `.py` files in `routes/` and they auto-mount.

You now have a working PoC with:

- Signup + login + JWT sessions
- Admin-approval gate (nobody gets in until you click approve)
- Built-in admin dashboard with pending/active/rejected queues
- Per-IP rate limiting (5/min signup, 10/min login by default)
- SQLite DB on a fly volume (persists across machine restarts)
- Secrets auto-generated on first run, never committed

## What `parbaked new` scaffolds

```
myapp/
├── parbaked.toml          # config
├── routes/
│   └── index.py           # add more .py files, they auto-mount
├── models.py              # your SQLModel tables go here
├── pyproject.toml
├── .env.example
├── .gitignore
└── README.md
```

No `main.py`. No `Dockerfile`. No `fly.toml`. The kernel runtime owns the entrypoint; `parbaked deploy` generates the deploy targets from `parbaked.toml`. If you ever want to take ownership, `parbaked eject` hands you everything (see below).

## Adding routes

Drop a `.py` file in `routes/` with a module-level `router`:

```python
# routes/notes.py
from fastapi import APIRouter, Depends
from parbaked import current_user

router = APIRouter()

@router.get("/")
def list_notes(user = Depends(current_user)):
    return {"user": user.email, "notes": []}
```

Restart the dev server (or wait for hot reload) and `GET /notes/` returns your handler's response. The auto-discovery rule: file path becomes URL prefix. `routes/api/users.py` mounts at `/api/users`. `routes/index.py` mounts at `/`.

## Shipping to fly.io

```bash
parbaked deploy
```

That's it. parbaked generates a `Dockerfile` + `fly.toml` into `.parbaked/build/` from your `parbaked.toml`, creates the SQLite volume if it doesn't exist, and runs `fly deploy`. The generated `fly.toml` ships with cost-protection defaults — `auto_stop_machines = "stop"`, `min_machines_running = 0`, `max_machines_running = 2` — so an idle deploy costs nothing and a traffic spike can't autoscale you into a bill.

For first-time setup:

```bash
fly launch --copy-config --no-deploy   # claims the app name
parbaked secrets                       # pushes .env to fly secrets
parbaked deploy
```

Tunnel for local sharing: `parbaked tunnel` spins up a `cloudflared` quick tunnel — a `*.trycloudflare.com` URL with no Cloudflare account needed.

## Off-boarding contract

Every byte parbaked owns is in a public, importable format:

- **Password hashes:** bcrypt 2b, cost 12 — verifiable with any bcrypt-compliant library.
- **Session tokens:** RFC 7519 JWT, HS256 — decodable on jwt.io or any stdlib.
- **Users table:** documented column-by-column in [`docs/data-format-guarantees.md`](docs/data-format-guarantees.md). Additive changes only between major versions.
- **Audit events:** one JSON object per stdout line — ships to any log aggregator.

When you outgrow parbaked, run `parbaked eject`. You get a `parbaked-export/` directory: PostgreSQL-compatible `schema.sql`, CSV data dumps, the generated `Dockerfile` + `fly.toml`, an `.env.example` listing the env-var contract, and a `MANIFEST.md` pointing back at the format docs. No vendor-specific decoders. No lobster trap.

## Configuration

Everything has sensible defaults. Set in `parbaked.toml` (non-secret) or via `PARBAKED_*` env vars (secrets):

| Env var | Default | What it does |
|---|---|---|
| `PARBAKED_JWT_SECRET` | *(auto-generated)* | Session-token signing key. Required in prod. |
| `PARBAKED_APPROVAL_TOKEN_SECRET` | *(auto-generated)* | Magic-link signing key. Required in prod. |
| `PARBAKED_ADMIN_PASSWORD` | *(auto-generated)* | Dashboard login password (user is always `admin`). |
| `PARBAKED_ADMIN_EMAIL` | unset | Where signup-approval emails go (dashboard works without this). |
| `PARBAKED_APP_NAME` | `"My App"` | Used in email subjects. |
| `PARBAKED_APP_URL` | `http://localhost:8000` | Public URL for magic links. |
| `PARBAKED_RESEND_KEY` | — | Set this to send real email via Resend. Unset → emails print to stdout. Get a free key (no card) at <https://resend.com/api-keys> or run `parbaked email setup`. |
| `PARBAKED_MAIL_FROM` | `onboarding@resend.dev` | From address. Default is Resend's sandbox. For real users, verify your domain at resend.com/domains. |
| `PARBAKED_RATELIMIT_SIGNUP` | `5/minute` | Per-IP signup limit. |
| `PARBAKED_RATELIMIT_LOGIN` | `10/minute` | Per-IP login limit. |
| `PARBAKED_DATABASE_URL` | `sqlite:///./parbaked.db` | SQLite-only. Standard SQLAlchemy URL. |

Auto-generated secrets get persisted to `.parbaked.json` (chmod 600). In production, set them as env vars instead.

## Security posture

What protects you from a bill:

- **Per-IP rate limits** on signup, login, and password reset (slowapi).
- **No email enumeration** — signup with an existing email and login with a wrong password return generic errors.
- **Approval gate** — even if someone gets past the rate limit, they can't do anything until you click approve.
- **bcrypt** for passwords, **HS256 JWT** for sessions, **audience-scoped JWT** for magic links so a session token can never be replayed as approval (and vice versa).
- **CSRF double-submit cookies** on every admin POST.
- **Production-mode tripwires** — `PARBAKED_ENV=production` refuses to boot if any required secret was auto-generated, warns on multi-instance setups, suppresses the admin password in the banner.

## Running under a different ASGI server

`parbaked dev` shells out to `uvicorn parbaked.runtime:create_app --factory`. For Gunicorn / Hypercorn / Granian, write a 2-line `wsgi.py`:

```python
from parbaked.runtime import create_app
app = create_app()
```

Then `gunicorn -k uvicorn.workers.UvicornWorker wsgi:app --workers 4`. See [AGENTS.md](AGENTS.md) for the full advanced section.

## Install

```bash
pip install parbaked
# or
uv add parbaked
```

Requires Python 3.11+.

## Develop / contribute

```bash
git clone https://github.com/saml7n/parbaked
cd parbaked
uv venv && source .venv/bin/activate
uv pip install -e ".[dev]"
pytest
```

Issues and PRs welcome.

## License

MIT — see [LICENSE](LICENSE).
