Simple cross-platform GUI client for S3-compatible object storage (AWS S3, MinIO, Ceph, and others).
- Multi-profile management β create, edit, copy, and delete named connection profiles; credentials are encrypted at rest using Fernet symmetric encryption
- Bucket browser β list, create, and delete buckets; recursive delete with confirmation
- Object browser β navigate prefixes as a virtual folder tree with sorting by name, size, and modified date
- Upload β single/multiple files via dialog or drag-and-drop from the OS file manager
- Download β single files or entire folder prefixes, recreating the directory tree locally
- Delete β objects and folder prefixes (recursive)
- Create folder β creates an S3 prefix placeholder
- Object properties β key, size, ETag, and public URL
- Presigned URL β copy a temporary share link (1 hour expiry)
- Make public β set
public-readACL and copy direct URL - Bucket usage stats β total size, breakdown by file category (Documents / Media / Other) with a pie chart, and top folder groups
- Runtime profile switch β switch S3 accounts without restarting the app
- Automatic region/endpoint detection β when an operation fails due to a region or endpoint mismatch the app probes the server for the correct region, rebuilds the client, and retries transparently; applies to bucket open, listing, upload, download, and delete
- S3-compatible storage β path-style addressing option for MinIO and similar backends
- Cross-platform β Linux, macOS, Windows
| Dependency | Version | Purpose |
|---|---|---|
| Python | β₯ 3.10 | Runtime |
| PyQt6 | β₯ 6.7 | GUI framework |
| boto3 | β₯ 1.42 | AWS / S3-compatible SDK |
| cryptography | β₯ 46.0 | Fernet credential encryption |
| pyinstaller | β₯ 6.18 | Binary packaging (optional) |
Quick start (system packages, Debian/Ubuntu):
sudo apt install python3-boto3 python3-cryptography python3-pyqt6
python3 s3duck.pyRecommended β virtualenv:
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
python3 s3duck.pysudo apt-get install git devscripts build-essential lintian upx-ucl
./build_deb.sh # auto-detects amd64 / arm64
./build_deb.sh arm64 # explicit architectureOutput: build/s3duck_<version>_<arch>.deb
./build_linux_bin.sh./build_macos_bin.sh # native arch
./build_macos_bin.sh universal2 # fat binary (x86_64 + arm64)
./build_dmg.shbuild_win.cmdPre-built releases are available on the GitHub releases page.
s3duck/
βββ s3duck.py Entry point β QApplication bootstrap, Profiles dialog
βββ main_window.py Main window β file browser, toolbar, async workers
βββ model.py S3/data layer β all boto3 operations, region retry logic
βββ settings.py Profile create/edit dialog
βββ properties_window.py Object properties dialog
βββ profile_switcher.py Runtime profile-switch dialog
βββ utils.py Shared helpers (str_to_bool)
β
βββ icons/ 24 px SVG icons for toolbar and context menus
βββ resources/ App icon (ico/icns/png), screenshots, .desktop file
βββ DEBIAN/ Debian package metadata (control, postinst, prerm)
β
βββ requirements.txt Python dependencies
βββ s3duck.spec PyInstaller build spec
βββ build_deb.sh Build .deb package
βββ build_linux_bin.sh Build Linux self-contained binary
βββ build_macos_bin.sh Build macOS self-contained binary
βββ build_dmg.sh Pack macOS binary into .dmg
βββ build_win.cmd Build Windows self-contained binary
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Entry / Profile layer s3duck.py β
β Profiles dialog, Crypto (Fernet), SettingsItem β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β UI layer main_window.py β
β MainWindow, Tree, UpTopProxyModel, PieWidget, β
β BucketUsageDialog, ListItem β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Worker / Threading layer main_window.py β
β NavigationWorker BucketEnterWorker Worker UsageWorker β
β (each runs in a QThread, communicates via pyqtSignal) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Dialog layer β
β SettingsWindow PropertiesWindow ProfileSwitchWindow β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Data / S3 layer model.py β
β Model β boto3 wrapper, adaptive region/endpoint probing β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
| Component | File | Responsibility |
|---|---|---|
Profiles |
s3duck.py | CRUD for connection profiles; launches MainWindow |
Crypto |
s3duck.py | Fernet encrypt/decrypt of stored credentials |
MainWindow |
main_window.py | Root window β toolbar, splitter (tree + log), statusbar |
Tree |
main_window.py | Drag-and-drop QTreeView; hands drops to upload worker |
UpTopProxyModel |
main_window.py | Proxy that pins [..] to top and sorts BUCKET < FOLDER < FILE |
NavigationWorker |
main_window.py | Off-thread bucket/prefix listing; uses a private Model clone to avoid client races |
BucketEnterWorker |
main_window.py | Off-thread bucket entry with hints-based region/endpoint retry |
Worker |
main_window.py | Off-thread upload / download / delete with byte-level progress and cancellation |
UsageWorker |
main_window.py | Off-thread bucket size aggregation by file category |
PieWidget |
main_window.py | Custom QPainter pie chart for usage breakdown |
Model |
model.py | All boto3 calls; _try_bind_bucket probes addressing styles; rebind_bucket auto-corrects region mid-session |
SettingsWindow |
settings.py | Profile form (name, URL, region, bucket, keys, flags) |
PropertiesWindow |
properties_window.py | Object metadata: key, size, ETag, public URL |
ProfileSwitchWindow |
profile_switcher.py | Runtime profile switch without app restart |
User action
β
βΌ
MainWindow ββspawnβββΊ QThread + Worker/NavigationWorker
β (private Model clone or shared Model)
β
βΌ
Model.method()
β boto3 S3 API call
βΌ
AWS S3 / MinIO / Ceph β¦
β
pyqtSignal (progress / finished / error)
β
βΌ
MainWindow ββupdateβββΊ UI (tree, log, progress bar)
Operation fails (AuthorizationHeaderMalformed | PermanentRedirect)
β
βΌ
get_bucket_hints() HEAD Bucket β x-amz-bucket-region header
β
βΌ
build_region_swapped_endpoint() rewrite AWS endpoint for new region
β
βΌ
rebind_bucket() swap endpoint + region β enter_bucket() β validate
β
βΌ
retry original operation transparent to the caller
New profile
β access_key, secret_key
βΌ
Crypto.encrypt() (Fernet, key stored in QSettings "common/key")
β encrypted bytes
βΌ
QSettings β ~/.config/s3duck/s3duck.ini
Launch profile
β encrypted bytes from QSettings
βΌ
Crypto.decrypt() β plaintext creds β boto3.Session
See LICENSE.
Vladislav Ananev Β© 2022β2026

