CONTRIBUTING.md
Rama main · réplica de lectura del repositorio original.
# Contributing to PrivaTools
Thanks for your interest in improving PrivaTools — a suite of free,
open-source, privacy-first file tools. This guide covers how to get set up,
test your changes, and open a pull request.
## Ground rules
- **Privacy first.** The product promise is that files are processed
privately and never sent to third parties: server tools keep uploads in
temporary files that response cleanup deletes, with a background sweep for
leftovers, and many tools never upload at all. Don't add third-party
uploads, trackers, or client-side calls to external services without an
explicit, disclosed reason. In-tool copy must match what the code actually
does.
- **MIT licensed.** By contributing you agree your contribution is licensed
under the [MIT License](LICENSE).
- **Be respectful.** Assume good faith; keep discussion technical and kind.
## Getting set up
### Run the whole app (Docker — easiest)
```bash
git clone https://github.com/ethereaglehq/privatools.git
cd privatools
docker compose up --build
# open http://localhost:8000
```
### Frontend (Vite + React)
```bash
cd frontend
npm ci
npm run dev # dev server
npm run gen:llms # regenerate the committed content files
npm run build # production build (also runs gen:llms)
```
`gen:llms` writes the crawler indexes (`llms.txt`, `llms-full.txt`), sitemap,
feed and content JSON in `frontend/public/`, plus
`frontend/src/data/tool-blog-links.json`, from `frontend/src/data/`, and
those files are committed. After changing a registry or other content there,
run it and commit what it changes: CI regenerates the files and fails the PR
when the committed copies differ.
### Backend (FastAPI)
The backend shells out to native binaries and links native libraries, so the
test suite needs more than `pip install`. On Debian/Ubuntu (what production
uses), install:
```bash
sudo apt-get install -y \
tesseract-ocr tesseract-ocr-eng poppler-utils ffmpeg qpdf libzbar0 \
libcairo2 libpango-1.0-0 libpangocairo-1.0-0 libgdk-pixbuf-2.0-0 \
libreoffice-writer-nogui libreoffice-calc-nogui libreoffice-impress-nogui libreoffice-math-nogui
# From the repo root: Python 3.12 virtual environment from the hashed lock
# (needs uv). CI installs the same lock with
# `pip install --require-hashes -r requirements-dev.txt`.
npm run setup:backend
```
(On macOS, the equivalents come from Homebrew: `brew install qpdf pango cairo
gdk-pixbuf zbar poppler tesseract ffmpeg`.)
## Testing — required before a PR
CI runs these on every pull request (`.github/workflows/test.yml`). Run them
locally first:
```bash
# Backend (run from the repo root). Build the frontend first, as CI does:
# some tests read frontend/dist and skip or fail without it.
npm --prefix frontend run build
.venv/bin/python -m pytest backend/tests -q
# Frontend (run from frontend/)
npm run gen:llms # then commit anything it changed
npx tsc --noEmit -p tsconfig.app.json
npm run lint -- --max-warnings 0
npm run test:content
npm test
git fetch origin && npm run check:review-dates
```
- Every new feature or bug fix needs a test. New tools need a backend test
under `backend/tests/` and must be registered in the frontend tool registry
(`frontend/src/data/`) with a `seoTitle` (40–60 characters), a
`metaDescription` (120–160 characters) and a `lastReviewed` date, which
`frontend/src/test/tool-registry.test.ts` checks. They also need the
endpoint map (`frontend/src/lib/tool-endpoints.ts`) and steps and FAQ in
`backend/app/tool_content.py`; after editing that file, run
`.venv/bin/python scripts/seo/export-tool-guides.py` and commit
`frontend/src/data/tool-guide/`. Then run `npm run gen:llms` in
`frontend/`: the backend's SEO metadata and sitemap read the tool manifest
it writes, so neither needs a per-tool entry. A page that runs an AI model
or a bring-your-own-key provider also needs its path in the CSP sets in
`backend/app/main.py`. The full list of places a new slug touches is kept
in [CLAUDE.md](CLAUDE.md) under "Registering a tool slug". The suite
enforces these consistency rules.
- Heavy work in a route handler (rendering, OCR, ffmpeg, LibreOffice, rembg,
compression) goes through `run_bounded` from
`backend/app/utils/concurrency.py`, which caps concurrent heavy jobs and
runs them in their own thread pool. Offload light blocking I/O with
`await asyncio.to_thread(...)`, and run external programs with
`asyncio.create_subprocess_exec`, so nothing blocks the event loop.
## Security
Found a vulnerability? **Do not open a public issue.** Follow
[SECURITY.md](SECURITY.md) — email `hello@privatools.me` with the `[Security]`
subject prefix, or use GitHub's private vulnerability reporting. We aim to
acknowledge within 72 hours.
For changes that touch the file-processing surface, keep the existing
defenses intact: SSRF validation on server-side URL fetches, argv-list
subprocess calls (never `shell=True`), archive-extraction byte caps, and
guaranteed temp-file cleanup on every code path.
## Pull requests
1. Branch off `main`.
2. Keep PRs focused; write a clear description of *what* and *why*.
3. Make sure the test suites and type-check pass.
4. A maintainer reviews and merges.
Branch protection on `main` requires five checks:
| Check | Workflow | What it runs |
| --- | --- | --- |
| Backend tests (pytest) | `test.yml` | The backend suite, plus the API starter build and tests |
| Frontend tests (vitest) | `test.yml` | Generated-content check, type-check, lint, content tests, Vitest, review-date check |
| Frontend audit and build | `security.yml` | `npm audit --audit-level=high`, type-check, production build, bundle budget |
| Python dependency audit | `security.yml` | `pip-audit` on the hashed runtime lock, `requirements.txt` |
| Image builds and serves (docker) | `test.yml` | Builds the image and boots it with `scripts/ci/probe-image.py` |
Two steps inside "Frontend tests (vitest)" fail it on their own:
- **Check generated content is current** reruns `npm run gen:llms` and fails
when the committed output differs.
- **Check tool review dates** (pull requests only) runs
`npm run check:review-dates`. It fails when a tool's `seoTitle`,
`metaDescription`, `longDescription` or `description` changed without its
`lastReviewed` moving, or when more than 25 dates move unless the PR title
or a commit message line starts with `[bulk-review]`.
[scripts/README.md](scripts/README.md#tool-review-dates) explains the rules.
CodeQL, dependency review, the Trivy scans, OpenSSF Scorecard and the
`Frontend quality` job (`ci.yml`) also run on pull requests but are not
required. Separately, `monitor.yml` probes privatools.me every 30 minutes and,
when a check fails, opens a tracking issue or comments on the open one.
## Releases & deploys
Production deploys only signed `v*` release tags (see `deploy/README.md`).
Pushing a tag runs `release.yml`: the whole `test.yml` suite, then an arm64
image that is pushed to GHCR, scanned and signed with cosign, and finally a
GitHub Release. Merging to `main` does not deploy on
its own — a maintainer cuts a release tag when a set of changes is ready.
Deploys start the new release beside the running one, so for about a minute
both share the accounts database. Keep schema migrations additive, quick and
compatible with the previous release; `backend/app/store.py` has the details.