0.0
The project is in a healthy, maintained state
Simple, dependency-light client for talking to a My.S3 server via its JSON and multipart HTTP API.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Runtime

>= 2.6
>= 0.2.0
>= 0.11
 Project Readme

GitHub issues GitHub GitHub tag (latest by date) GitHub last commit

MyS3

MyS3 is a minimal, open-source object store inspired by AWS S3. It exposes a JSON-only HTTP API, stores every object directly on the filesystem, and is designed to run anywhere Puma and Sinatra are available.

Just rent a Storage VPS and run your own Simple-Storage-Service for cheap.

Features

  • JSON-over-HTTP API with API-key authentication only
  • Filesystem-backed storage with nested folders/prefixes
  • Thread-safe operations compatible with Puma multi-threading
  • Configurable upload limits, logging, timezone, and symlink policy
  • Zero external services: no database, queues, or UI
  • Lightweight landing page at / to confirm the daemon is live (no API key required)

Requirements

  • Ruby 3.1.2 (or newer 3.1.x)
  • Bundler
  • A writable directory for the storage root and log files

Installation

bundle install
cp config.example.yml config.yml
$EDITOR config.yml

Set MY_S3_CONFIG=/absolute/path/to/config.yml if the file lives outside the project root.

Configuration

All configuration lives in config.yml and is never exposed through the API. The file must define the following keys:

Key Description
api_key Shared secret required in the X-API-Key header.
storage_root Absolute or relative path used as the storage sandbox.
public_base_url Base URL used to build public/download URLs.
bind_host / port Interface and port Puma should bind to.
max_upload_size_mb Maximum accepted upload size (per file).
follow_symlinks Allow (true) or reject (false, default) symlinks inside the storage root.
puma_threads_min/max Puma thread pool size hints.
log_level, log_file Standard Ruby logger options.
timezone Sets ENV['TZ'] for consistent timestamps.
session_secret Optional secret used to sign the browser session cookie; defaults to a random value at boot.

Any relative paths are resolved against the configuration file directory. The application creates the storage root and log/ directory on boot when needed.

Running

MY_S3_CONFIG=/srv/my_s3/config.yml bundle exec puma \
	-t 4:16 \
	-b tcp://0.0.0.0:4567 \
	config.ru

Always point Puma at config.ru; it bootstraps the Rack app and pulls in app.rb. Run the command from the repository root so Bundler picks up the correct Gemfile (or export BUNDLE_GEMFILE=/abs/path/to/Gemfile if you insist on running it elsewhere). Use the same host/port and thread counts configured in your config.yml. Puma’s multi-threaded mode is required; every disk operation is wrapped in thread-safe primitives inside the app.

Browsing to / in a web browser now shows the Explorer login: submit the API key once and the session cookie unlocks the UI. All JSON endpoints still require the X-API-Key header for every request.

Browser Explorer

Point your browser at / and enter the API key once to unlock a lightweight explorer UI. The key is stored in an encrypted session cookie so you can click through folders, review file metadata, download assets, open files in a new tab, and delete files or folders without crafting curl commands. Use the breadcrumb navigation to move around and the “Sign out” button to clear the session; closing the browser tab also invalidates the session cookie when it expires.

API Overview

All endpoints:

  • Require X-API-Key: <your api_key>
  • Accept/return JSON (except upload.json, which uses multipart form data)
Endpoint Method Description
/list.json GET List files and folders inside path (default: root).
/create_folder.json POST Create folder_name inside path.
/delete_folder.json DELETE Delete a folder (and all children).
/rename_folder.json POST Rename a folder to new_name.
/upload.json POST Multipart upload for a single file.
/delete.json DELETE Delete filename inside path.
/delete_older_than.json POST Delete files older than an ISO 8601 timestamp.
/get_download_url.json POST Build a public download URL for a file.
/get_public_url.json POST Build a browser-friendly public URL for a file.

Example Calls

List everything at the root:

curl -H "X-API-Key: CHANGE_ME" \
  'http://127.0.0.1:4567/list.json?path='

Create a folder:

curl -X POST -H "Content-Type: application/json" \
	-H "X-API-Key: CHANGE_ME" \
	-d '{"path":"projects","folder_name":"images"}' \
	http://127.0.0.1:4567/create_folder.json

Upload a file (multipart):

curl -X POST -H "X-API-Key: CHANGE_ME" \
	-F path=projects/images \
	-F file=@./example.png \
	http://127.0.0.1:4567/upload.json

Delete files older than 30 days:

curl -X POST -H "Content-Type: application/json" \
	-H "X-API-Key: CHANGE_ME" \
	-d '{"path":"projects/images","older_than":"2025-01-01T00:00:00Z"}' \
	http://127.0.0.1:4567/delete_older_than.json

Generate a URL:

curl -X POST -H "Content-Type: application/json" \
	-H "X-API-Key: CHANGE_ME" \
	-d '{"path":"projects/images","filename":"example.png"}' \
	http://127.0.0.1:4567/get_public_url.json

Logging & Monitoring

  • Logs are written to the path defined by log_file (defaults to log/app.log).
  • Every unhandled exception is logged with a stack trace and surfaces as a 500 Internal Server Error JSON payload.
  • Add your own metrics/forwarding by tailing the log file, shipping to Loki, etc.

Production Checklist

  • Run behind a TLS-terminating reverse proxy (nginx, Traefik, Caddy).
  • Rotate the API key regularly and store it outside version control.
  • Mount the storage root on durable disks (e.g., attached volume, network share).
  • Create and monitor backups; objects live on disk only.
  • Configure log rotation to keep disk usage predictable.

Enjoy!