Public endpoint via Cloudflare Tunnel
TinyS3 can expose a running server through a Cloudflare Tunnel, giving it a public https://… URL without opening a firewall port. It is delivered as an optional plugin (tiny-s3-cloudflared) that drives a native libcloudflared library through the Java Project Panama (FFM) API.
PUBLIC EXPOSURE
Opening a tunnel makes the server reachable from the public internet. Enable TLS and configure explicit (non–auto-generated) root credentials before sharing the URL. The server logs a prominent warning at startup, and an extra warning when TLS is disabled and the root credentials were auto-generated.
Enabling
The tunnel is opt-in and only activates with an explicit, deliberately-named flag — it is never enabled implicitly (for example by an environment variable):
bash
java -jar tinys3-all.jar --config config.yaml --allow-cloudflared-public-endpointOr activate it from the YAML config under the experimental: section (it maps to the same flag):
yaml
experimental:
cloudflared:
enabled: true # shorthand: `cloudflared: true`It lives under experimental: — not plugins: — because opening a public tunnel is an effect-heavy, less-stable feature that should read as deliberate.
- Quick tunnel (default): with no
TUNNEL_TOKENset, a randomhttps://<name>.trycloudflare.comURL is minted and printed in the startup banner. - Named tunnel: set
TUNNEL_TOKEN=<token>to attach a pre-configured Cloudflare tunnel. The token is read only after the flag has activated the plugin, and is used solely to choose named vs quick mode. It is never logged or persisted.
Native library resolution
The native libcloudflared library is resolved, in order, from:
- the JVM loader /
java.library.path(System.loadLibrarysemantics), which is seeded fromLD_LIBRARY_PATH(Linux) /DYLD_LIBRARY_PATH(macOS) /PATH(Windows); - a
libcloudflared.<ext>sitting next to thetinys3single-file binary — the launcher detects it and prepends its directory to that search path for you (the "drop it in and go" path, see below); - on linux/x86-64 only, a gzip-compressed copy bundled in the jar — extracted to a private per-run directory and checksum-verified before load. So the tunnel works out of the box on the most common server platform with no download and no flags; every other platform uses 1–2.
For safety the loader never searches the working directory or walks parent directories. The launcher's sibling lookup (2) is scoped to the directory of the executable itself — operator-placed, not an ambient CWD search. To pin a specific library yourself, put its directory on LD_LIBRARY_PATH (or the platform equivalent). The shipping fat jars already declare Enable-Native-Access: ALL-UNNAMED in their manifest.
Download a prebuilt library (releases)
linux/x86-64 needs nothing — the library is bundled in the jar for that platform and loads automatically. For every other platform the library is published as a separate per-platform asset on each GitHub release (it is ~30 MB, so it is not bundled for all platforms). Download the one for your platform into the same folder as the tinys3 binary and name it libcloudflared.so (.dylib on macOS); the launcher picks it up automatically on the next start.
bash
# Linux x86-64 — next to the tinys3 binary:
curl -fSL -o libcloudflared.so \
https://github.com/devdilson/tiny-s3/releases/download/v1.4.0/libcloudflared-linux-amd64.so
# Linux ARM64:
curl -fSL -o libcloudflared.so \
https://github.com/devdilson/tiny-s3/releases/download/v1.4.0/libcloudflared-linux-arm64.so
# verify against the release checksums, then run as usual:
curl -fSL https://github.com/devdilson/tiny-s3/releases/download/v1.4.0/SHA256SUMS-cloudflared -o SHA256SUMS-cloudflared
sha256sum -c SHA256SUMS-cloudflared --ignore-missing
./tinys3 server /data --allow-cloudflared-public-endpointIf you run the fat jar directly instead of the single-file binary, put the library's directory on the dynamic-library search path: LD_LIBRARY_PATH=$PWD java -jar tinys3-all.jar … (Linux; DYLD_LIBRARY_PATH on macOS, PATH on Windows).
Plugin allowlist
Server plugins are discovered via ServiceLoader. As a defense-in-depth trust boundary, an optional allowlist restricts which plugins may activate:
bash
# comma-separated plugin names; only these may activate when set
TINY_S3_PLUGINS=cloudflared java -jar tinys3-all.jar \
--config config.yaml --allow-cloudflared-public-endpoint
# or: -Dtinys3.plugins=cloudflaredWhen unset (the default), all discovered plugins are eligible and gate themselves via their own enablement flag.
Building the native library
Requires the cloudflared git submodule and a Go toolchain (cgo):
bash
./gradlew :tiny-s3-cloudflared:buildCloudflaredSharedLibThe native build is skipped automatically when the submodule or Go toolchain is absent, so ./gradlew build still succeeds without them — only the tunnel runtime is unavailable.
Cross-building the release assets
The per-platform libraries published on releases are cross-compiled from one machine with zig as the cgo C toolchain (no Docker or per-target JDK needed):
bash
cd integrations/cloudflared/cloudflared
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 \
CC="zig cc -target x86_64-linux-gnu" CXX="zig c++ -target x86_64-linux-gnu" \
go build -buildmode=c-shared -ldflags "-s -w" -o libcloudflared-linux-amd64.so ./cmd/libcloudflared
CGO_ENABLED=1 GOOS=linux GOARCH=arm64 \
CC="zig cc -target aarch64-linux-gnu" CXX="zig c++ -target aarch64-linux-gnu" \
go build -buildmode=c-shared -ldflags "-s -w" -o libcloudflared-linux-arm64.so ./cmd/libcloudflaredRegenerating the bundled library (libcloudflared.so.gz)
The linux/x86-64 library is committed gzip-compressed at integrations/cloudflared/src/main/resources/native/linux-x86-64/libcloudflared.so.gz (~9 MB) so the tunnel works from the jar with no download. The loader pins the SHA-256 of the decompressed library, so whenever you rebuild it you must update that pin in two places or the guard test fails. Prerequisites: the cloudflared submodule, Go, and zig.
bash
# 1. Build the stripped linux/x86-64 c-shared library (from the repo root):
cd integrations/cloudflared/cloudflared
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 \
CC="zig cc -target x86_64-linux-gnu" CXX="zig c++ -target x86_64-linux-gnu" \
go build -buildmode=c-shared -ldflags "-s -w" -o /tmp/libcloudflared.so ./cmd/libcloudflared
cd -
# 2. Compress it into the resource path:
gzip -9 -c /tmp/libcloudflared.so \
> integrations/cloudflared/src/main/resources/native/linux-x86-64/libcloudflared.so.gz
# 3. Compute the SHA-256 of the *decompressed* library:
shasum -a 256 /tmp/libcloudflared.soPaste that hash into both:
BUNDLED_SHA256inNativeLibraryLoader.java, andEXPECTED_SHA256inBundledNativeLibraryTest.java.
Then verify — the guard test confirms the committed resource decompresses to the pinned hash:
bash
./gradlew :tiny-s3-cloudflared:test --tests '*BundledNativeLibraryTest*'The pinned hash and the compressed resource must always move together; the test exists to catch a resource swap or a stale pin at build time rather than as a runtime checksum failure on a user's machine.