Files
upl/README.md
T
ddidderr c072b93726 feat: write chunks directly to temp upload files
Completed uploads used to copy every staged chunk into a second file before
renaming the result into data/complete. That doubled write volume and required
peak disk space for both the chunk set and the final file.

Write each chunk directly into one private temp upload file at its final offset
instead. After a chunk write succeeds, record a tiny durable completion marker
for progress and resume scans. Completion now verifies the temp file length and
all markers, then renames the temp file into the completed upload directory.

Add UPL_TEMP_DIR and --temp-dir so operators can choose where upload metadata,
markers, and temp files live. The default remains data/staging, and docs call
out that the temp directory must be on the same filesystem as data/complete for
atomic promotion. The nginx example now aliases only the completed upload
directory, and the smoke test verifies that final-file alias.

This keeps the existing length-based validation model; it does not add per-chunk
hashing.

Test Plan:
- just check
- just nginx-smoke
- cargo clippy && cargo clippy --benches && cargo clippy --tests
- cargo +nightly fmt --all
- cargo clippy && cargo clippy --benches && cargo clippy --tests

Refs: none
2026-05-30 18:10:05 +02:00

90 lines
3.1 KiB
Markdown

# upl
`upl` is a small personal resumable upload service. The intended deployment is:
```text
browser -> nginx -> upl Rust server -> local filesystem
```
The server writes upload chunks directly into an inaccessible temp file at
their final offsets. Once every chunk is present, completion atomically renames
that temp file into the completed upload directory.
## Project Structure
```text
upl
Rust server
src/main.rs binary entrypoint and listener setup
src/app.rs Axum router, shared state, static file service
src/api.rs HTTP handlers and API error responses
src/model.rs JSON request, response, and metadata shapes
src/storage.rs local filesystem layout, offset writes, and final rename
src/lib.rs library surface used by integration tests
Browser UI
static/index.html upload tool markup
static/styles.css responsive tool styling
static/app.js upload scheduler, retries, and browser resume state
Deployment
deploy/nginx/ nginx reverse proxy example
scripts/ reusable local smoke tests
Validation
tests/ integration tests for server behavior
TESTS.md reusable manual and automated test checklist
```
## Configuration
- `--bind` sets the listen address. It overrides `UPL_BIND` and defaults to
`127.0.0.1:3000`.
- `--static-dir` sets the static asset directory. It overrides `UPL_STATIC_DIR`
and defaults to `static/` inside this repository.
- `--data-dir` sets the completed upload data root. Completed files land under
its `complete/` subdirectory. It overrides `UPL_DATA_DIR` and defaults to
`data/` inside this repository.
- `--temp-dir` sets the directory for upload metadata, completion markers, and
inaccessible temp upload files. It overrides `UPL_TEMP_DIR` and defaults to
`<data-dir>/staging`.
- `upl --help` prints the full argument help text.
- The server accepts request bodies up to 64 MiB, which leaves room for the
planned 16 MiB upload chunks and matches the nginx example in `PLAN.md`.
- Keep `UPL_TEMP_DIR` on the same filesystem as `<data-dir>/complete` so
completion can promote files with an atomic rename.
## Common Commands
Use the `justfile` for routine tasks:
```sh
just check
just run
```
`just check` also syntax-checks the static browser JavaScript with `node`.
## nginx
Run `upl` on localhost and put nginx in front of it for TLS and access control:
```sh
UPL_BIND=127.0.0.1:3000 \
UPL_DATA_DIR=/srv/upl/data \
UPL_TEMP_DIR=/srv/upl/data/staging \
upl
```
Use `deploy/nginx/upl.conf.example` as the starting point for the nginx site.
Before exposing the service, replace the certificate paths and add a protection
layer such as HTTP basic auth, an IP allowlist, or VPN-only access. The nginx
example aliases only `/srv/upl/data/complete`; do not expose `UPL_TEMP_DIR`.
For a local Docker-based reverse-proxy smoke test:
```sh
just nginx-smoke
```
The smoke test binds the Rust server to `0.0.0.0` so the nginx container can
reach it through Docker's host gateway. The production nginx example keeps the
server bound to localhost.