SeaweedFS CLI tools
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
benato.denis96@gmail.com 6b7a784d5e
Some checks failed
Rust / build (push) Has been cancelled
Chore: update dependencies to latest compatible versions
Runs cargo update, picking the newest versions compatible with the
toolchain's MSRV (1.94). Notable bumps: h2 0.4.14 -> 0.4.20, hyper
1.11 -> 1.12, tokio stack, rustls stack, rand 0.10.2 -> 0.10.3, and
reqx 0.1.27 -> 0.1.34 (newer reqx requires Rust 1.96).

This clears 8 of the 10 cargo-audit advisories that had accumulated
since the last lockfile refresh. The two that remain (RUSTSEC-2026-0194
and RUSTSEC-2026-0195) are both in quick-xml 0.39.4, pulled in
transitively by s3 0.1.27: the fixes land in quick-xml 0.41, which s3
only depends on from s3 0.1.38, which requires Rust 1.99.1 and is thus
unreachable on the current toolchain.

cargo build/test/clippy (-D warnings, all features) pass.
2026-10-26 10:39:10 +01:00
.github/workflows Fix: resolve cargo-audit advisories and pin dependency versions 2026-10-26 08:37:25 +01:00
rpm Chore: add CI 2026-03-12 11:44:21 +01:00
src Fix: drop the no-op u64::MAX clamp when truncating 2026-10-26 09:21:26 +01:00
tests Test: check the workflow from the README 2026-10-26 08:37:25 +01:00
.gitignore Feat: initial features implementation 2026-03-12 08:45:23 +01:00
Cargo.lock Chore: update dependencies to latest compatible versions 2026-10-26 10:39:10 +01:00
Cargo.toml Feat: upload large objects as a multipart upload 2026-10-26 08:37:25 +01:00
LICENSE.md Chore: add CI 2026-03-12 11:44:21 +01:00
README.md Docs: describe the object layout and the git support 2026-10-26 08:37:25 +01:00

swfss3

swfss3 is a small CLI for performing filesystem-like operations (list/read/write/delete) against a SeaweedFS S3-compatible API endpoint using the Rust s3 crate.

Build

cargo build --release

Binary will be at target/release/swfss3.

Authentication and configuration

You must provide:

  • Endpoint: --endpoint (custom domain / S3 API URL)
  • Bucket: --bucket
  • Region: --region (defaults to us-east-1)

Authentication (choose one):

  • Flags: --access-key-id and --secret-access-key (optional --session-token)
  • Environment variables: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN
  • Anonymous: --anonymous (unsigned requests)

If you see invalid config: missing AWS_ACCESS_KEY_ID, it means you did not pass credentials flags and swfss3 could not find the AWS environment variables. Fix it by using one of:

  • Provide keys via flags:
swfss3 --endpoint http://localhost:8333 --bucket mybucket --path-style \
  --access-key-id YOUR_KEY --secret-access-key YOUR_SECRET \
  list --prefix some/path/
  • Export keys via environment variables:
export AWS_ACCESS_KEY_ID=YOUR_KEY
export AWS_SECRET_ACCESS_KEY=YOUR_SECRET
# optional (temporary credentials)
export AWS_SESSION_TOKEN=YOUR_TOKEN
  • Use unsigned requests (only works if the bucket/object is publicly readable/writable):
swfss3 --endpoint http://localhost:8333 --bucket mybucket --path-style --anonymous \
  list --prefix some/path/

Addressing style (optional, but often important for S3-compatible servers):

  • --path-style (recommended for many self-hosted endpoints)
  • --virtual-hosted (bucket as a subdomain)
  • default is automatic selection

Commands

All commands share the common flags above and then take their own arguments.

List objects

List a prefix (like listing a directory):

swfss3 --endpoint http://localhost:8333 --bucket mybucket --path-style \
  list --prefix some/path/

Recursive listing (no delimiter grouping):

swfss3 --endpoint http://localhost:8333 --bucket mybucket --path-style \
  list --prefix some/path/ --recursive

Fetch all pages:

swfss3 --endpoint http://localhost:8333 --bucket mybucket --path-style \
  list --prefix some/path/ --all

Read an object

To stdout:

swfss3 --endpoint http://localhost:8333 --bucket mybucket --path-style \
  read some/path/file.txt

To a local file:

swfss3 --endpoint http://localhost:8333 --bucket mybucket --path-style \
  read some/path/file.txt --out ./file.txt

Write an object

Upload a local file to an object key:

swfss3 --endpoint http://localhost:8333 --bucket mybucket --path-style \
  write ./local.bin some/path/local.bin

Optionally set Content-Type:

swfss3 --endpoint http://localhost:8333 --bucket mybucket --path-style \
  write ./index.html some/path/index.html --content-type text/html

Delete an object

swfss3 --endpoint http://localhost:8333 --bucket mybucket --path-style \
  delete some/path/local.bin

Stat (HEAD) an object

swfss3 --endpoint http://localhost:8333 --bucket mybucket --path-style \
  stat some/path/file.txt

Copy / move (rename) an object

Server-side copy:

swfss3 --endpoint http://localhost:8333 --bucket mybucket --path-style \
  cp some/path/file.txt some/path/file-copy.txt

Move (copy then delete):

swfss3 --endpoint http://localhost:8333 --bucket mybucket --path-style \
  mv some/path/file.txt some/path/renamed.txt

Delete a directory

swfss3 --endpoint http://localhost:8333 --bucket mybucket --path-style \
  rmdir photos/2025

Create a directory

swfss3 --endpoint http://localhost:8333 --bucket mybucket --path-style \
  mkdir photos/2025

Presigned URLs

Create a temporary download URL (default 900s expiry):

swfss3 --endpoint http://localhost:8333 --bucket mybucket --path-style \
  presign-get some/path/file.txt --expires 300

Create a temporary upload URL:

swfss3 --endpoint http://localhost:8333 --bucket mybucket --path-style \
  presign-put some/path/upload.bin --expires 300

Delete everything under a prefix

Dry-run (prints keys that would be deleted):

swfss3 --endpoint http://localhost:8333 --bucket mybucket --path-style \
  rm-prefix some/path/

Actually delete:

swfss3 --endpoint http://localhost:8333 --bucket mybucket --path-style \
  rm-prefix some/path/ --yes

Mount bucket via FUSE3 (Linux)

Mount the whole bucket read-only:

mkdir -p /tmp/s3mnt
swfss3 --endpoint http://localhost:8333 --bucket mybucket --path-style \
  mount /tmp/s3mnt

Mount only a prefix (the prefix becomes the filesystem root):

swfss3 --endpoint http://localhost:8333 --bucket mybucket --path-style \
  mount /tmp/s3mnt --prefix photos/2025/

Enable writes (buffered uploads on flush/close):

swfss3 --endpoint http://localhost:8333 --bucket mybucket --path-style \
  mount /tmp/s3mnt --read-write

Allow access by other users (requires user_allow_other in /etc/fuse.conf):

swfss3 --endpoint http://localhost:8333 --bucket mybucket --path-style \
  mount /tmp/s3mnt --allow-other

How objects are laid out

The filesystem maps a directory tree onto the flat key space of an object store. Two conventions make that mapping unambiguous and keep the bucket usable with other tools:

Kind Key Example
File the plain name photos/2025/cat.jpg
Directory the name with a trailing /, stored as an empty object photos/2025/

A directory is therefore stored as a directory marker: an empty object whose key ends with /. The marker is what makes an empty directory survive a round trip, and it is what mkdir and rmdir create and remove.

A directory also exists implicitly when other objects live below it, so photos/2025/cat.jpg implies that photos/2025/ is a directory even when no marker was ever written. Both forms are recognised.

The mkdir and rmdir commands create and remove markers directly:

swfss3 --endpoint http://localhost:8333 --bucket mybucket --path-style \
  mkdir photos/2025

A trailing / is optional: photos/2025 and photos/2025/ name the same directory.

Using it with git

A git repository can live in a bucket. Mount the bucket read-write and point git at it:

mkdir -p /tmp/s3mnt
swfss3 --endpoint http://localhost:8333 --bucket mybucket --path-style \
  mount /tmp/s3mnt --read-write

cd /tmp/s3mnt
git init myrepo
cd myrepo
echo hello > README.md
git add README.md
git commit -m "initial"

Git needs a few operations that a plain object store does not provide, and they are implemented: creating and removing directories, truncating files, setting modification times, flushing and renaming.

Two things are worth knowing:

  • A rename is a copy followed by a delete. The object store has no atomic rename, so there is a short window in which both objects exist. A crash in that window leaves the copy behind; the original is only removed once the copy has succeeded.
  • The page cache of the kernel is bypassed. Every read goes to the server, so what is read is always what is stored. This costs some performance but avoids showing stale data.

Git writes loose objects and a packfile into .git/objects, and keeps its references in .git/refs. Both are ordinary files as far as the filesystem is concerned.

Troubleshooting

NoSuchBucket but the bucket “exists”

If you get an error like code=NoSuchBucket, double-check:

  • You are hitting the S3 gateway endpoint, not the filer UI or another reverse-proxied service.
  • The bucket exists in the same endpoint/cluster. Buckets are endpoint-scoped.
  • Addressing style: if one style doesn’t work, try the other:
    • --path-style (common for self-hosted)
    • --virtual-hosted (requires bucket DNS/wildcard)

Help

swfss3 --help
swfss3 list --help
swfss3 read --help
swfss3 write --help
swfss3 delete --help
swfss3 stat --help
swfss3 cp --help
swfss3 mv --help
swfss3 mkdir --help
swfss3 rmdir --help
swfss3 presign-get --help
swfss3 presign-put --help
swfss3 rm-prefix --help
swfss3 mount --help