How trueno's spec is designed¶
The README (in English) covers setup and how to use the API. Here I briefly explain, in Japanese, "why it is shaped the way it is."
Premise: run where only CGI exists¶
The target is a shared rental server where Apache CGI is the only way to execute anything, with no resident processes and no pip. To work there, it sticks to the following.
- Python 3.6 standard library only. It doesn't depend on the
cgimodule either (it was removed in 3.13) - No DB. All state is the files under
files/and their mtimes - Serving is Apache's static file serving. CGI runs only for writes and the listing
The URL tells the lifetime¶
At upload you choose a retention period (bucket). 1h / 1d / 1w are cleaned up by cron based on mtime; keep is never cleaned up.
| bucket | URL shape | ID |
|---|---|---|
1h 1d 1w |
/<bucket>/<id>.<ext> |
4 characters (base62) |
keep |
/<id>.<ext> |
6 characters |
Looking at the URL tells you "how long it will be around." The inversion where the shorter one lives longer is deliberate: only permanent things get the short, privileged URL. On collision a 4-character ID is retried, and it is created atomically with O_EXCL.
Sidecars stand in for a DB¶
The information needed for the listing and deletion (original filename, comment, delete key, time) lives in <id>.<ext>.meta (JSON) next to the file.
- Apache does not serve
.meta(denied in.htaccess) - Only the sha256 hash of the delete key is stored. Even if
.metaleaks, the key doesn't - The file and its
.metashare the same mtime, so cron cleans up both at once. No orphan-hunting is needed - Old files without a sidecar show up in the listing with their ID as the name
- The listing links the ID and shows the original filename alongside it. If the original name is just
_(_.jpgetc.), that's taken as an intent to hide it, and it isn't shown
Two levels of authorization¶
- bearer token: one token per line in
~/.trueno-key. Each token can be restricted to certain buckets (no-keep,buckets:1h,1d). Any token can delete (operator privilege) - anonymous: tokenless uploads are accepted only when the operator lists buckets for it. Opening
keepto anonymous users isn't intended. Deletion uses the delete key you chose yourself at upload
The default is 1 only. Turn on 2 when you want to recreate "the old-school uploader anyone can post to."
Endpoints¶
| Role | State | |
|---|---|---|
POST /upload |
Save. time / comment / delete_key |
Write |
GET /list |
Listing. Scans files/ and merges in .meta, newest first, paginated |
Read |
POST /delete |
Deletes the file and .meta with the delete key or bearer |
Write |
GET /info |
Limits, extensions, buckets open to anonymous users | Read |
GET /<...> |
The file itself. Apache static serving | — |
The listing is public. Since the URLs are already public, the listing reveals no new secrets.
Thumbnails¶
The image listing is a dedicated page (thumbs.html). The server doesn't make reduced versions (CGI hosts have no image library). The browser loads the original image and shrinks it for display. It works as-is on old files too.
What isn't built¶
Captcha, IP restrictions, download counts, accounts. Counting downloads would require serving through CGI, which breaks the "serving is static" premise.
Deployment shape¶
Drop in the docroot as-is. Configuration is a few lines in make.local, and make deploy bakes the public URL, site name, and anonymous buckets into each file. Some hosts ignore SetEnv in .htaccess, so it doesn't rely on environment variables.
Created 2026-10-07 by lamutara · Updated 2026-10-07 by lamutara · 6 revisions · Machine-translated from the Japanese original.