s3s

module
v0.7.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 11, 2026 License: MIT

README ΒΆ

s3s

A fast, keyboard-driven terminal browser for S3-compatible object storage β€” think k9s, but for your buckets.

Release CI Go Report Card Go version

Point it at Ceph RGW or MinIO, switch clusters like kubectl contexts, and walk millions of keys, inspect metadata, and preview files without leaving the terminal. Read-only by default β€” safe to point at production. Writes are an explicit, loudly-badged opt-in, and a per-context readonly flag keeps protected environments untouchable.

Why s3s

s3cmd and mc answer one question per invocation. s3s keeps you in the storage β€” browsing, inspecting, and answering operator questions interactively, without hammering the cluster to do it:

  • πŸ›‘οΈ Cluster-safe by design. Hovering a bucket shows its size and object count from a budget-capped background scan (default 20 000 objects) β€” an honest β‰₯ lower bound for anything bigger. The uncapped scan runs only on an explicit keystroke (A), streams progress, is cancellable, and partial progress is cached β€” never thrown away. A 100M-object production bucket is safe to browse.
  • 🩺 Operator health card (H) β€” one screen answers "what is this bucket made of": age and size histograms, the storage-class spread (computed from the same scan pass β€” zero extra requests), incomplete multipart uploads (count, size, oldest age β€” the classic hidden cost), and a small-object index-pressure warning. Denied or unsupported probes say so explicitly β€” never rendered as a clean zero.
  • πŸ”— Copy & share anything (Y) β€” the S3 URI, a style-aware HTTPS URL, a ready-to-run aws s3api command, or a presigned GET link (15m/1h/24h/7d) minted entirely client-side and never logged. Export usage/health reports to CSV/JSON. Clipboard works over SSH (OSC52), and every value can be shown full-screen for manual copy.
  • πŸ” Payload-aware previews β€” JSON/NDJSON pretty-printed (toggle raw with p), gzip transparently decompressed (bomb-safe), binaries hex-dumped, images rendered in-terminal β€” bounded to the first 5 MiB, no full downloads.
  • πŸ—‚οΈ kubectl-style contexts β€” clusters, users, and contexts in one YAML file; switch live (no restart) or jump by number (1–9). Secrets live in the OS keychain or come from an external command (pass, Vault, 1Password…) β€” never on disk in plaintext.

Features

  • Rich inline metadata β€” the details pane shows object metadata in named groups (identity & content / security & governance / delivery): version, encryption + KMS key, replication, object-lock & legal-hold, lifecycle expiration β€” with relative + exact dates, a multipart-ETag explanation, and every field copyable in full. a expands a ranked largest-first usage breakdown, object tags, or the bucket configuration.
  • Tree navigation at any scale β€” walk the key namespace by delimiter with on-demand pagination; never loads a whole bucket up front. Per-session cache, manual refresh (r).
  • Fast filter & search β€” instant bucket-name filter; server-side prefix search within a level (debounced, complete results).
  • Sortable lists β€” by name, size, or last-modified (s/S); persists across navigation.
  • Download & bulk operations β€” pull objects to disk (a read β€” works in read-only mode); multi-select (space) for bulk download/delete/copy with truthful per-item summaries.
  • Opt-in writes with a safety model β€” arm write at runtime (w, with confirmation); a loud [RW] badge shows on every screen while armed. Destructive actions need a Ctrl chord plus a confirmation that scales with blast radius (binary y/N β†’ typed name for recursive/bucket deletes). A CI guard structurally confines mutations to the storage layer.
  • Non-blocking UI β€” every backend call runs off the event loop; superseded loads are cancelled; long operations show progress and cancel cleanly.
  • Secrets never leak β€” credentials are redacted everywhere; presigned URLs are never written to logs; logs go to a file only (the TUI owns the terminal).

Installation

Method Command
Homebrew (macOS) brew install danchupin/tap/s3s
Scoop (Windows) scoop bucket add danchupin https://github.com/danchupin/scoop-bucket && scoop install s3s
Debian / Ubuntu sudo dpkg -i s3s_*_linux_amd64.deb (Releases)
RHEL / Fedora sudo rpm -i s3s_*_linux_amd64.rpm
Alpine sudo apk add --allow-untrusted s3s_*_linux_amd64.apk
Go go install github.com/danchupin/s3s/cmd/s3s@latest
Binaries grab a .tar.gz/.zip from the Releases page
Source git clone https://github.com/danchupin/s3s && cd s3s && make build (Go 1.25+)

Quick start

s3s config init     # interactive wizard: endpoint, addressing, credentials, context
s3s                 # browse (read-only by default)

No cluster handy? Spin up a local MinIO:

docker run -p 9000:9000 -p 9001:9001 \
  -e MINIO_ROOT_USER=admin -e MINIO_ROOT_PASSWORD=password \
  minio/minio server /data --console-address ":9001"
s3s --context prod        # explicit context (> S3S_CONTEXT env > current-context)
s3s --config ~/work.yaml  # alternate config (or S3S_CONFIG env)
s3s --write               # start with write armed (readonly contexts stay protected)

Configuration

Config lives at ~/.config/s3s/config.yaml (XDG honored; override with --config). Generate it with s3s config init, or write it by hand:

apiVersion: s3s/v1
clusters:
  - name: minio-local
    endpoint: http://127.0.0.1:9000
    region: us-east-1
    pathStyle: true          # false => virtual-host/domain-style addressing
users:
  - name: dev
    accessKeyId: admin
    keychain: true           # secret in the OS keystore: s3s cred set local
  - name: public
    anonymous: true
contexts:
  - name: local
    cluster: minio-local
    user: dev
current-context: local

# optional tuning
usageScanBudget: 20000        # ambient usage-scan cap in objects; 0 = explicit-only
healthSmallObjectKiB: 128     # health-card small-object threshold
healthSmallObjectShare: 0.5   # warning fires above this share of small objects
chmod 600 ~/.config/s3s/config.yaml
s3s cred set local            # store the secret in the OS keystore (no echo)

A non-anonymous user names exactly one secret source: keychain: true (macOS Keychain / Windows Credential Manager / Linux Secret Service; manage with s3s cred set|rotate|rm <context>) or cmd: "<command>" whose stdout is the secret (runs as argv, never a shell; requires an owner-only config; 10s timeout). The secret never lives on disk in plaintext.

Scoped credentials (pinned buckets) β€” keys that cannot ListBuckets

Some credentials reach specific buckets but cannot list all buckets β€” common with bucket-scoped RGW/MinIO keys and domain-style endpoints where only <bucket>.<host> resolves. Pin the reachable buckets on the cluster:

clusters:
  - name: scoped
    endpoint: https://bucket.example-rgw
    pathStyle: false
    buckets: [my-bucket, another-bucket]

s3s then skips ListBuckets and shows exactly those names; add more at runtime via the + add bucket row (persisted to the config).

External credential commands β€” ready recipes
vault kv get -field=secret s3/prod                  # HashiCorp Vault
op read "op://Private/s3-prod/secret"               # 1Password CLI
pass show s3/prod                                   # pass
sops -d --extract '["secret"]' creds.yaml           # sops
secret-tool lookup service s3s account prod         # libsecret
security find-generic-password -w -s s3s -a prod    # macOS
Multiple configs β€” work/personal, prod/staging

--config <path> or S3S_CONFIG applies to the TUI, s3s cred, and s3s config init. Keychain secrets are namespaced per config, so two configs that both define a prod context never share a secret.

Plugins

External capability providers β€” executables you declare that supply data the S3 protocol cannot: bucket discovery (e.g. a provisioning API listing the buckets you were granted, when credentials can't ListBuckets or the endpoint is domain-style-only) and object metadata (e.g. image-storage info keyed by an id encoded in the object key, shown as a From <plugin> group in the details pane). Strictly opt-in: no plugins: section, no plugin behavior.

plugins:
  - name: corp-discovery
    capability: bucket-discovery
    cmd: "s3s-corp-discovery --cluster prod"   # shlex argv, never a shell
    timeout: 5s                                # optional, default 5s
    connections: [prod-rgw]
  - name: image-storage-meta
    capability: object-metadata
    cmd: "~/bin/image-storage-meta.sh"
    match:
      connections: [prod-rgw]
      buckets: ["images-*"]                    # glob; empty = any
      keyPattern: "^[0-9a-f]{32}"              # RE2; empty = any

A plugin reads one JSON request on stdin and writes one JSON response on stdout (contract v1 β€” see docs/plugins/ for the exchange and two ready-to-copy stubs). Discovered names merge additively into the bucket list (pinned βˆͺ listed βˆͺ discovered); failures never degrade browsing β€” a transient notice points at the status surface (P / :plugins: per-plugin outcome, enable/disable persisted to config, retry).

Security model: commands run as argv (never sh -c), only while the config file is owner-only-writable (chmod 600) and owned by you; the request carries identity context only (accessKeyId is the public identifier) β€” the secret key is never passed in any field, env var, or argument; all plugin-supplied text is sanitized before rendering; one log record per invocation captures facts (plugin, capability, target, duration, outcome) and never payloads or argv.

Key bindings

Arrows are primary; vim aliases (h/j/k/l, g/G) work everywhere. The full keymap lives in the help overlay (?) and the always-visible command bar.

Key Action
↑/↓ Β· β†’/Enter Β· ←/Esc move Β· enter/open Β· back (also cancels an in-flight load)
/ filter buckets / search a level by prefix
Enter on an object metadata + content side-by-side
a more detail: usage breakdown Β· object tags Β· bucket config
A full usage scan (uncapped β€” the only unbounded enumeration, always explicit)
H health card: histograms Β· incomplete uploads Β· warnings
Y copy/share: URI Β· URL Β· command Β· presigned link Β· export CSV/JSON
p toggle pretty ↔ raw for JSON/NDJSON previews
d download the selected object / marked set (a read)
space Β· s/S Β· r Β· i multi-select Β· sort Β· refresh Β· reveal full identifier
w arm/disarm write (confirm to arm; instant to disarm)
P plugin status (shown only when plugins are declared): toggle Β· retry Β· error detail
c Β· 1–9 connections manager Β· jump to context by number
: command bar (:scan, :health, :copy, :detail, …)
? Β· q help Β· quit

Destructive actions (delete, move, recursive delete, bucket/connection delete) are never on a bare key β€” they require a Ctrl chord (Ctrl+x / Ctrl+o) plus a confirmation that scales with blast radius, up to typing the exact target name. Bucket delete requires an empty bucket; the active connection cannot be deleted.

Logs: $XDG_STATE_HOME/s3s/s3s.log (or ~/.local/state/s3s/s3s.log).

Roadmap

Planned work β€” syntax highlighting, bucket administration, versioning management, incomplete-multipart cleanup, and more β€” lives in ROADMAP.md.

Development

make test               # unit tests (fake storage) β€” no Docker needed
make test-integration   # + real MinIO via testcontainers (needs Docker)
make fmt vet lint       # formatting, go vet, golangci-lint
make check-readonly     # structural read-only guard
  • internal/storage β€” the Storage interface + aws-sdk-go-v2 impl (the only importer of service/s3) + an in-memory fake for unit tests.
  • internal/config β€” kubectl-style YAML loader, validation, config init.
  • internal/cache β€” per-session level cache (manual refresh only).
  • internal/preview β€” text/JSON/image/binary classification, pretty-print, gunzip, hexdump, image rendering.
  • internal/share β€” pure builders for copyable artifacts and report export.
  • internal/logging β€” file slog handler + a redacting Secret type.
  • internal/ui β€” Bubble Tea (v2) model; depends only on the storage interface.
  • cmd/s3s β€” wiring: load config β†’ build storage β†’ run the TUI.

Built with Bubble Tea, Lip Gloss, and aws-sdk-go-v2.

License

MIT Β© Daniil Chupin

Directories ΒΆ

Path Synopsis
cmd
s3s command
Command s3s is a read-only, keyboard-driven TUI for browsing S3-compatible object storage (Ceph RGW, MinIO).
Command s3s is a read-only, keyboard-driven TUI for browsing S3-compatible object storage (Ceph RGW, MinIO).
internal
cache
Package cache holds a per-session, TTL-free cache of loaded tree levels.
Package cache holds a per-session, TTL-free cache of loaded tree levels.
config
Package config loads and validates the kubectl-style YAML config (~/.config/s3s/config.yaml): clusters, users, contexts, and current-context.
Package config loads and validates the kubectl-style YAML config (~/.config/s3s/config.yaml): clusters, users, contexts, and current-context.
localfs
Package localfs is a small, UI-agnostic reader of the local filesystem used by the upload file browser.
Package localfs is a small, UI-agnostic reader of the local filesystem used by the upload file browser.
logging
Package logging provides a file-based slog logger and a redacting Secret type.
Package logging provides a file-based slog logger and a redacting Secret type.
plugin
Package plugin is the UI-agnostic core of the external capability-provider boundary: user-declared executables that supply data the storage protocol cannot (bucket discovery for listing-denied connections, object metadata from external systems).
Package plugin is the UI-agnostic core of the external capability-provider boundary: user-declared executables that supply data the storage protocol cannot (bucket discovery for listing-denied connections, object metadata from external systems).
preview
Package preview classifies and renders bounded object content for the preview pane: scrollable text, visual images, and a safe summary for binary.
Package preview classifies and renders bounded object content for the preview pane: scrollable text, visual images, and a safe summary for binary.
secret
Package secret resolves a context's S3 secret from one of two pluggable, secure sources (the OS keychain or an external command) so the secret never needs to live in the shell environment or on disk in plaintext (014).
Package secret resolves a context's S3 secret from one of two pluggable, secure sources (the OS keychain or an external command) so the secret never needs to live in the shell environment or on disk in plaintext (014).
share
Package share builds copyable artifacts from browse state: canonical URIs, addressing-style-aware HTTPS URLs, ready-to-run command snippets, and CSV/JSON report serializations (017 US3).
Package share builds copyable artifacts from browse state: canonical URIs, addressing-style-aware HTTPS URLs, ready-to-run command snippets, and CSV/JSON report serializations (017 US3).
storage
Package storage is the sole boundary between the UI and S3-compatible backends.
Package storage is the sole boundary between the UI and S3-compatible backends.
ui
Package ui is the Bubble Tea (v2) TUI layer.
Package ui is the Bubble Tea (v2) TUI layer.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL