A personal photo and video album whose storage is a GitHub repository, managed through GitDb.
Visitor’s access to PIXEL_REPO |
What they can do in pixel |
|---|---|
| No access (private repo, not a collaborator) | Nothing — they are told the album is private |
Read (pull), or anyone if the repo is public |
Browse, view, play and download |
Write (push) |
…plus create albums, upload and delete |
| Admin | …plus read the visitor audit log |
Visitors sign in by pasting a GitHub token (a fine-grained personal access token scoped to the data repository with Contents: read — or read and write for uploaders). The token stays on the server in an in-memory session (HttpOnly, SameSite=Strict cookie); it is never stored in the repo and never sent back to the browser. Permissions are re-checked with GitHub every five minutes, so removing a collaborator or making the repo private takes effect without restarting pixel.
To share with family: make the data repository private and invite them as collaborators with Read access. To share with everyone: make it public.
data/albums/… GitDb documents: title, description, cover
data/media/… GitDb documents: album, filename, type, size, paths
data/_index/media/album.json
media/<album>/<id>.<ext> the original photo or video
media/<album>/<id>.thumb.jpg
The audit log is stored in data/audit/… of PIXEL_AUDIT_REPO, which must be a
separate repository (pixel refuses to start otherwise) so album viewers cannot
read visitors’ IP addresses.
charles2ke/pixel-data),
initialised with a README so the main branch exists.charles2ke/pixel-audit)
and a fine-grained token with Contents: read and write on it only. Audit
entries include visitor IP addresses and user agents; pixel logs a warning
at startup if the audit repository is public.Install and run (Python 3.9+):
python -m venv .venv && . .venv/bin/activate
pip install -r requirements.txt # or: pip install -e .
cp .env.example .env # edit, then export the variables
uvicorn pixel.app:app --host 0.0.0.0 --port 8000
Serve it over HTTPS (for example behind Caddy or nginx, with
PIXEL_TRUST_PROXY=1) — installation on phones, service workers and
secure cookies all require it. Run a single worker process: sessions are
in memory and writes are serialised in-process.
GitHub Pages only serves static files, so it cannot run pixel. The
Pages workflow publishes this README as the
project page on every push to main. To turn it on, go to Settings → Pages
and set Source to GitHub Actions.
Run the app itself on any host that runs containers (Render, Fly.io, Railway,
a VPS, …). The
container image workflow
publishes a ready-made image for linux/amd64 and linux/arm64 to GitHub
Packages:
docker run -p 8000:8000 --env-file .env ghcr.io/charles2ke/pixel:latest
| Tag | Built from |
|---|---|
latest, main |
The newest commit on main |
v* |
A v* git tag created after this workflow was added (pushed, or created with a new GitHub release) |
sha-<commit> |
A single commit (short SHA) |
A new package starts out private, even when the repository is public. To let anyone pull it without signing in, open it from the repository’s Packages list, choose Package settings, and under Danger Zone use Change visibility → Public (a public package cannot be made private again).
Or build the image yourself from the included Dockerfile:
docker build -t pixel .
docker run -p 8000:8000 --env-file .env pixel
The container listens on $PORT (default 8000) with a single worker. Put it
behind HTTPS and set PIXEL_TRUST_PROXY=1 when a proxy terminates TLS.
| Variable | Default | Meaning |
|---|---|---|
PIXEL_REPO |
(required) | owner/name of the data repository |
PIXEL_BRANCH |
main |
Branch to read and write |
PIXEL_DATA_ROOT |
data |
GitDb root folder |
PIXEL_MEDIA_ROOT |
media |
Folder for photo/video files |
PIXEL_MAX_UPLOAD_MB |
50 |
Largest accepted upload (GitHub’s hard limit is 100 MB per file) |
PIXEL_AUDIT_REPO |
(required with a token) | Private repository for the visitor audit log; must differ from PIXEL_REPO |
PIXEL_AUDIT_BRANCH |
PIXEL_BRANCH |
Branch for the audit log |
PIXEL_AUDIT_TOKEN |
(none) | Token used to write audit entries; without it events only go to the server log |
PIXEL_AUDIT_FLUSH_SECONDS |
10 |
How often buffered audit events are committed |
PIXEL_TRUST_PROXY |
0 |
Use X-Forwarded-For / X-Forwarded-Proto from a reverse proxy |
PIXEL_COOKIE_SECURE |
auto | Force the Secure cookie flag on/off (auto = on for HTTPS) |
PIXEL_API_URL, PIXEL_RAW_URL |
GitHub | Override GitHub endpoints (used by the tests) |
The installed app opens full-screen. The service worker caches only the app shell; photos, videos and API responses are never stored by it. (The browser may keep a photo it already showed in its private HTTP cache for up to five minutes.) Revoking access on GitHub therefore also applies to installed devices.
JPEG, PNG, GIF, WebP, HEIC/HEIF, AVIF photos and MP4, MOV, M4V, WebM, 3GP videos. The type is detected from the file contents, not the name; anything else (including SVG and HTML) is rejected. Thumbnails are generated in the browser at upload time. HEIC photos and some MOV videos only display in browsers that support them (for example Safari).
raw.githubusercontent.com, which can lag a few minutes behind new uploads.pip install -e ".[dev]"
python -m playwright install chromium
ruff check . && ruff format --check .
pytest -q # API tests + Playwright end-to-end tests
The tests run against an in-process fake GitHub (tests/fake_github.py) that
enforces private/public repositories and collaborator permissions. The
Playwright tests save screenshots to test-results/screenshots
(override with PIXEL_SCREENSHOTS); CI uploads them as the
playwright-screenshots artifact.