Skip to content

nexusriot/s3duck

Repository files navigation

S3Duck πŸ¦†

Simple cross-platform GUI client for S3-compatible object storage (AWS S3, MinIO, Ceph, and others).

ListBuckets Screenshot


Features

  • 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-read ACL 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

Requirements

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)

Running from Source

Quick start (system packages, Debian/Ubuntu):

sudo apt install python3-boto3 python3-cryptography python3-pyqt6
python3 s3duck.py

Recommended β€” virtualenv:

python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
python3 s3duck.py

Building

Debian / Ubuntu package

sudo apt-get install git devscripts build-essential lintian upx-ucl
./build_deb.sh              # auto-detects amd64 / arm64
./build_deb.sh arm64        # explicit architecture

Output: build/s3duck_<version>_<arch>.deb

Linux binary (PyInstaller)

./build_linux_bin.sh

macOS binary + DMG

./build_macos_bin.sh                  # native arch
./build_macos_bin.sh universal2       # fat binary (x86_64 + arm64)
./build_dmg.sh

Windows binary

build_win.cmd

Pre-built releases are available on the GitHub releases page.


Project Architecture

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

Layers

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  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        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Key components

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

Data flow

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)

Region / endpoint auto-retry flow

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

Credential storage

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

License

See LICENSE.

Author

Vladislav Ananev Β© 2022–2026

About

Opensource crossplatform S3 client

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors