- Rust 100%
The help printed the placeholder name literally. Same replace pattern the base URL already used; a test now asserts no brace survives in --help or in a usage error. |
||
|---|---|---|
| src | ||
| tests | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| README.md | ||
nc-put (Rust)
Upload one file to a WebDAV share (Nextcloud) and prove the upload landed
completely: MKCOL the missing parent directories, stream the file with PUT, then
compare the remote getcontentlength against the local size.
Written for the git bundles in /home/pandi/backups/git/, which until now only
existed on the same disk as the repositories they backup.
Build and install
cargo build --release
install -m 755 target/release/nc-put ~/bin/nc-put
Usage
nc-put [--base-url URL] [--dry-run] [--quiet] QUELLDATEI REMOTE-PFAD
REMOTE-PFAD is relative to the user's WebDAV root, e.g.
hermes-backups/git/2026-10-30/OakSeerr.bundle. Path segments are
percent-encoded individually.
--base-url URL/NC_BASE_URL— WebDAV root of the user, e.g.https://host/remote.php/dav/files/USER/. A literal{user}in the value is replaced by the username from the env file, so the shell command needs no username.--dry-run— print target URL, directories and byte count; no requests.--quiet— suppress progress lines.
Credentials come from NEXTCLOUD_WEBDAV_USER / NEXTCLOUD_WEBDAV_PASS in the
env file (HERMES_ENV, default /home/pandi/.hermes/.env). The parser is
line-based: KEY=VALUE, # comments and blank lines ignored, = allowed in
the value, one layer of matching quotes stripped, nothing shell-interpreted.
The password is never printed, not even truncated.
Exit codes: 0 ok, 1 upload or verification failed, 2 bad invocation or
missing credentials. Failures name the HTTP status and a snippet of the server
body (413, 507, timeouts included).
Behaviour
- MKCOL each missing directory segment in order; HTTP 405 (already exists) is accepted.
- PUT the file with
Content-Lengthset and the file reader streamed straight into the request — the file never sits in memory as a whole. - PROPFIND
Depth: 0on the target and comparegetcontentlengthwith the local size; a mismatch exits 1.
User-Agent is curl/8.5.0, same as nc-inventar: the server rejects unknown
agents with 403.
Differences to a plain curl -T
- One command does directories, upload and size check, and fails loudly with the HTTP status when any step is short.
- The size check is a real PROPFIND against the server, not a guess from the PUT status code.
Tests
cargo test
- 4 unit tests: env parser (comments,
=in value, quotes,export, junk lines), missing key returns an error instead of panicking,getcontentlengthparsing regardless of namespace prefix, per-segment percent-encoding. - 2 integration tests against a local
std::net::TcpListenerstub (no network): (a) the three missing directories are created one by one with MKCOL, an existing directory answered with 405 is tolerated, and the PUT body is byte-identical to the file; (b) a reportedgetcontentlengthone byte short of the local size yields exit code 1 with a clear message.