Uploads (arbitrary files)¶
The uploads stream carries arbitrary files across the diode with no package ecosystem behind them — an installer, a dataset, a PDF, a model file. Pick a folder name, upload one or more files, and the high side serves them at /uploads/<folder>/<name>, lists them on its dashboard under Uploads, and — uniquely among the streams — lets the operator delete a file there again.
The two deliberate exceptions
Uploads bend two rules every other stream follows. Export dedup is never consulted — every upload ships in full, so a file deleted on the high side comes back by simply uploading it again (an "already forwarded" skip would silently withhold it). And the uploads/ tree is mutable: re-uploading a name replaces the file, and the high side's delete endpoint can remove one. Everything else ArtiGate serves stays immutable.
How it works¶
folder name + files (multipart upload)
│
▼
stream each file to disk, SHA-256 it (nothing buffered in memory)
│
▼
signed ArtiGate bundle ══ diode ══▶ high side import
│
▼
serve verbatim at /uploads/<folder>/<name>
(dashboard listing, per-file delete)
Files are verified like every other stream's artifacts — Ed25519 manifest signature, per-file SHA-256 — and served exactly as verified; there is no index to regenerate.
Low side: input¶
POST /admin/uploads/collect — multipart/form-data, not JSON (add ?stream=1 for streamed progress, ?dry_run=1 for an estimate):
curl -fsS -X POST \
-F "folder=tools" \
-F "file=@installer.run" \
-F "file=@README.pdf" \
https://artigate-low.local/admin/uploads/collect
| Form field | Meaning |
|---|---|
folder |
Required. The folder the files land in (one path segment, ≤ 128 chars, no leading ., no / or \, no control characters) |
file |
One or more file parts. Names are reduced to their base name and validated by the same rules; the same name twice in one upload is rejected |
Files are streamed to disk while being hashed — nothing is buffered in memory, so multi-gigabyte files are fine (use ?stream=1 for live progress on those). Staging imposes no size limit, but a single file must still fit one diode bundle — 64 GiB by default; check Limitations before uploading anything near that. One upload may carry up to 10,000 files. The dashboard's Uploads card wraps the same endpoint with a folder field and a file picker.
There is no force field — uploads always behave as if forced (full bundle, dedup bypassed), and prior_files is always 0.
Uploads cannot be scheduled
There is no upstream to re-pull, so the uploads stream has no watch support — the scheduler rejects it with uploads cannot be scheduled; upload again when the content changes.
High side: serving, listing, deleting¶
| Route | Method | Response |
|---|---|---|
/uploads/<folder>/<name> |
GET/HEAD | The file bytes (range and conditional requests supported; Content-Type by extension) |
/admin/uploads |
GET | JSON listing: folders with their files' names, sizes, and modification times |
/admin/uploads/delete |
POST | Delete one file: JSON body {"folder": "tools", "name": "installer.run"} → {"status": "ok"} (404 if absent) |
Deleting a folder's last file removes the folder from the listing — folders live only through their files. The delete endpoint is the only mutation of served content on the whole high side, and it answers loopback callers only unless ARTIGATE_HIGH_ALLOW_REMOTE_ADMIN=on (set that when a published-port or reverse-proxy hop makes local admin appear non-loopback, and keep the listener restricted at the host). The read-only listing is not restricted.
Client usage¶
The high-side dashboard's Uploads tree shows every folder and file with size, modification time, SHA-256, and a download link.
Limitations¶
- No scheduling — upload again when the content changes.
- No dedup, ever: identical re-uploads still produce (and transfer) a full bundle. That is the price of "delete on the high side, restore by re-uploading".
- Replace semantics: re-uploading a name overwrites the served file on import.
- Flat namespace rules: folder and file names are single path segments (≤ 128 chars, no leading dot, no separators); browser-style paths in a file part are reduced to their base name.
- A single file must fit one bundle. The upload as a whole is unbounded — content beyond the per-bundle transport limit ships as consecutive sequenced bundles — but one file can never split across bundles: if its estimated archive (raw size plus just under 1% packing overhead) exceeds the limit, the collect fails at export, after the file has been streamed and hashed (cleanly — no sequence number is burned). The limit is 64 GiB, and a small UDP-pitcher block geometry can lower it; the refusal then says which pitcher knob to raise.
- Bounds: the folder field is capped at 4 KiB and one upload at 10,000 parts; staging imposes no per-file size cap (files stream to disk).
Related pages¶
- Low side — operating the exporter
- High side — operating the read-only mirror
- Security & trust — the signing/verification chain
- HTTP API reference — the exact request/response contracts