~gitpocket/gitpocket

7

docs(api): document the api, token scopes and the gist uploader

commit
afe9eeb7545ead1afb02033900068b2c60de3277 browse tree
parent
ed739427b244519d73d7c9408eac31978772e23a
author
~james <354576047+[email protected]>
signature
Signed by: ~james SHA256:KlzJ0P0gvJcnOYpaxD5yCUw52ImHttTKRjVAEnnYnKg
date
2026-11-14 14:26 UTC

2 files changed, +25 −8

INSTALL.md modified

@@ -64,7 +64,7 @@ ln -s ../sites-available/gitpocket /etc/nginx/sites-enabled/
6464 nginx -t && systemctl reload nginx
6565 ```
6666
67-In the Cloudflare zone the records for `git.example.org` and `*.git.example.org` are proxied with the SSL mode set to Full, which lets the edge hold the public certificates while the origin keeps a long lived self signed pair that never needs renewing. A Cloudflare origin certificate can replace that pair when the zone runs in Full (strict) mode instead. The wildcard also covers `gist.git.example.org`, the cookie-less origin for raw gist files, and since `gist` is a reserved name it can never belong to a user site. SSH cannot pass through the proxy, so publish an unproxied `ssh.git.example.org` record and start the server with `-ssh-host ssh.git.example.org` to show that name in clone URLs while accepting that this one record reveals the address.
67+In the Cloudflare zone the records for `git.example.org` and `*.git.example.org` are proxied with the SSL mode set to Full, which lets the edge hold the public certificates while the origin keeps a long lived self signed pair that never needs renewing. A Cloudflare origin certificate can replace that pair when the zone runs in Full (strict) mode instead. The wildcard also covers `gist.git.example.org`, the cookie-less origin for raw gist files, and since `gist` is a reserved name it can never belong to a user site. Cloudflare only caches common static extensions by default, so a Cache Rule matching the host `gist.git.example.org` and the path wildcard `/raw/*/*/*`, set to eligible for cache with the origin's TTLs, makes pinned raw files load from the edge while unpinned ones still pass through. SSH cannot pass through the proxy, so publish an unproxied `ssh.git.example.org` record and start the server with `-ssh-host ssh.git.example.org` to show that name in clone URLs while accepting that this one record reveals the address.
6868
6969 Custom domains reach the origin through Cloudflare for SaaS, where each verified domain becomes a custom hostname whose certificate Cloudflare issues once the owner points a CNAME at their subdomain. The script below runs every five minutes, writes the verified domains into `/etc/nginx/conf.d/gitpocket-domains.conf` and keeps the custom hostnames of the zone in step with them, reading `CF_TOKEN` and `CF_ZONE` from `/etc/gitpocket/cloudflare`, where the token needs the SSL and Certificates edit permission and `CF_SKIP` may list domains that are already proxied by a zone of your own.
7070

README.md modified

@@ -25,13 +25,14 @@ The web interface is then on `127.0.0.1:3200` and wants a reverse proxy with TLS
2525 - Pull mirrors that fetch a source on a schedule and stay read only and push mirrors that copy every branch and tag to another host after each change
2626 - Pull requests opened by pushing to `refs/for/<branch>` with no fork needed
2727 - Static sites served from `<user>.<host>` or a domain of your own, built from a `pages` repository or the `pages` branch of any repository, private ones included
28-- Gists of one or more flat files with public or secret visibility, revisions shown as diffs, stars, forks, comments, iframe embeds and a JSON API for uploads from the command line
28+- Gists of one or more flat files with public or secret visibility, revisions shown as diffs, stars, forks, comments, iframe embeds and a command line uploader
29+- A JSON API that mirrors the URL of every page under `/api`, with tokens scoped to everything, reads only or gists only
2930 - Reports to admins and per account blocking, with hourly limits on new issues, pull requests and repositories
3031 - SSH and PGP commit verification, TOTP, access tokens and Atom feeds for commits, issues and pull requests
31-- Site search over repositories and people with language facets
32+- Site search over repositories, people and public gists with language facets
3233 - Light and dark themes, light by default, with an option to follow the device
3334 - A notification bell for activity on pull requests and issues you take part in
34-- Open, invite only or closed registration and an `/admin` page for accounts, invites, the front page and storage
35+- Open, invite only or closed registration and an `/admin` page for accounts, invites, the front page, storage and gists
3536
3637 There is no wiki and no CI and neither is planned.
3738
@@ -71,14 +72,30 @@ A gist is a bare repository at `~you/.gists/<id>.git` holding a single `main` br
7172
7273 Secret gists are unlisted rather than private, which means anyone holding the link can read, clone or fork them while neither the gist index nor a profile ever lists one. Creating a gist needs an account at least a day old and shares the hourly upload limit, and anyone signed in may report a gist to the admins, who can delete it from its page.
7374
74-Raw files and embeds are served from `gist.git.example.org`, a separate origin that never sees a cookie, answers only `GET` and sends every file as `text/plain` or an image under a sandboxed content security policy, so a hostile file cannot run script against the main site. A pinned revision such as `/raw/<id>/<commit>/<name>` is cached as immutable, while the embed page at `/embed/<id>` may be framed by any site.
75+Raw files and embeds are served from `gist.git.example.org`, a separate origin that never sees a cookie, answers only `GET` and sends every file as `text/plain` or an image under a sandboxed content security policy, so a hostile file cannot run script against the main site. A pinned revision such as `/raw/<id>/<commit>/<name>` is immutable for browsers but kept for at most an hour at the Cloudflare edge, so a gist that an admin deletes stops being served within the hour, while the embed page at `/embed/<id>` may be framed by any site.
76+
77+`contrib/gist` uploads files or standard input with nothing beyond `curl`, reading the token from `GITPOCKET_TOKEN` and the site from `GITPOCKET_URL`, and prints the address of the new gist. A token scoped `gist` is enough for it and cannot touch your repositories.
7578
7679 ```
77-curl -u you:TOKEN --data-binary @notes.txt 'https://git.example.org/api/gists?file=notes.txt&public=1'
78-curl -u you:TOKEN -H 'Content-Type: application/json' -d '{"description":"demo","public":false,"files":{"a.ml":{"content":"let x = 1"}}}' https://git.example.org/api/gists
80+install -m 755 contrib/gist ~/.local/bin/gist
81+gist -p -d "closure conversion" closure.ml README.md
82+make 2>&1 | gist -n build.log
7983 ```
8084
81-The first form prints the address of the new gist and the second answers with the same JSON that `GET /api/gists/<id>` returns, in the shape of the GitHub gist API, while uploads without `public` default to secret.
85+### API
86+
87+Every page has a JSON twin at the same path under `/api`, so `/~you/repo/pulls/3` is read from `/api/~you/repo/pulls/3`, and `/api` itself lists every route with the scope it needs, as JSON for programs and as a table for a browser. Accounts appear as `~name` handles and repositories as `~owner/name` paths, lists answer `{items, total, next}` with `?from=` and `?limit=` and every error is `{status, error}` with the HTTP status repeated in the body.
88+
89+```
90+curl -H "Authorization: Bearer $TOKEN" https://git.example.org/api/me
91+curl -H "Authorization: Bearer $TOKEN" -d '{"title":"crash on empty input"}' https://git.example.org/api/~you/repo/issues
92+curl -H "Authorization: Bearer $TOKEN" -X PUT -d '{"content":"hello\n","message":"greet"}' https://git.example.org/api/~you/repo/tree/main/hello.txt
93+curl -H "Authorization: Bearer $TOKEN" -d '{"strategy":"squash"}' https://git.example.org/api/~you/repo/pulls/3/merge
94+```
95+
96+Tokens are sent as a bearer or as the password of HTTP basic auth and carry a scope of `all`, `read` or `gist`, where a `read` token cannot push or change anything and a `gist` token adds writes to your own gists alone. Minting a token through `POST /api/me/tokens` needs the password and an account with two-factor authentication refuses the password everywhere in the API, so a leaked token can neither create another nor outlive its revocation. Session cookies only count when the request comes from the site itself, which lets any page call the API with a token through CORS while a cross-site form can never act with your session.
97+
98+The API calls the same functions as the web forms, which means archived repositories, blocks, branch protection, the daily thread budget and the hourly creation limits apply identically, and writes are further capped at one a second with a burst of 120 per account.
8299
83100 ### Mirrors
84101