|
|
2 weeks ago | |
|---|---|---|
| app | 2 weeks ago | |
| flatpak | 2 weeks ago | |
| scripts | 2 weeks ago | |
| .dockerignore | 2 weeks ago | |
| .gitignore | 2 weeks ago | |
| Dockerfile | 2 weeks ago | |
| Dockerfile.build | 2 weeks ago | |
| README.md | 2 weeks ago | |
| cellar | 2 weeks ago | |
| cellar-cli.py | 2 weeks ago | |
| cellar-cli.spec | 2 weeks ago | |
| cellar-gtk.py | 2 weeks ago | |
| cellar.jpg | 2 weeks ago | |
| cellar.spec | 2 weeks ago | |
| client.py | 2 weeks ago | |
| docker-compose.yml | 2 weeks ago | |
| net.enne2.Cellar.yaml | 2 weeks ago | |
| requirements.txt | 2 weeks ago | |
README.md
Cellar
Author: Matteo Benedetto
Cellar is a minimal self-hosted prototype for archiving exported Bottles environments and restoring them on another Linux system.
The name refers to a wine cellar — the place where bottles are stored and preserved.
The project currently provides:
- a
FastAPIserver - a local
SQLitedatabase for archive metadata - local file storage for uploaded bottle backups
- a minimal Python CLI client
- a small
GTK4desktop client for browsing and installing archived bottles - a
Docker Composesetup for running the server quickly
The current goal is simple: make it possible to back up a local Bottles bottle, upload it to a Cellar server, list what is stored, and reinstall a bottle by name.
Current status
This is an early MVP.
Implemented features:
- health endpoint
- archive upload
- archive listing
- archive detail lookup
- archive download
- archive deletion
- local Bottles scan from the client
- interactive backup + upload wizard from the client
- install a bottle from the server with a simple command:
python client.py install <bottle-name>
- browse server archives from a GTK4 window and install them with visual feedback
Not implemented yet:
- authentication
- permissions / multi-user separation
- editing metadata already stored in the database
- archive versioning
- deduplication
- tags normalization
- server-side search/filtering
- direct Bottles GUI integration
Project structure
- Dockerfile — container image for the API
- docker-compose.yml — local deployment
- requirements.txt — Python dependencies
- client.py — minimal CLI client
- cellar-gtk.py — GTK4 desktop client
- app/main.py — API routes
- app/models.py — SQLAlchemy model
- app/schemas.py — API response schemas
- app/database.py — database setup
- app/storage.py — file storage helpers
- app/config.py — environment/config handling
- data/ — SQLite database location
- storage/ — uploaded archive files
Architecture
Server
The server is built with FastAPI and stores metadata in SQLite.
Each uploaded archive creates:
Stored metadata includes:
- archive display name
- bottle name
- description
- tags
- architecture
- runner
- Windows version
- original file name
- stored file name
- content type
- file size
- SHA256 digest
- creation time
Client
The client is intentionally minimal and uses the Python standard library.
It can:
- list archives on the server
- upload an existing archive file
- download an archive by id
- scan local Bottles bottles
- run a wizard to choose a local bottle, create a backup, and upload it
- install a remote bottle by name
The GTK desktop client builds on the same install flow, but exposes it through a small Linux GUI:
- refresh the archive list from the server
- filter archives locally with a quick search field
- see archive metadata at a glance
- show when a bottle is already installed locally
- download and install a bottle locally with progress feedback
- open a separate configuration window to change the server URL and local Bottles path
Storage format
The current client creates a .tar.gz backup of the selected bottle directory.
The install flow:
- finds the archive by bottle name
- downloads it
- extracts it safely
- places it into the local Bottles directory
Because Bottles backups contain dosdevices symlinks, extraction uses a trusted tar extraction mode while still enforcing a path traversal safety check.
Requirements
Recommended local environment:
- Docker
- Docker Compose
- Python 3.12+
- Bottles installed locally if you want to use
scan-local,wizard-upload, orinstall
Expected Bottles path used by the client by default:
~/.var/app/com.usebottles.bottles/data/bottles/bottles
This matches the Flatpak Bottles installation layout.
Running the server
From bottle-archive-server run:
docker compose up --build
The API is exposed on:
http://127.0.0.1:8080
Interactive API docs are available at:
http://127.0.0.1:8080/docs
Flatpak package
The repository now includes a Flatpak manifest for the GTK client:
Flatpak packaging assets are stored in flatpak/:
- desktop entry
- AppStream metadata
- launcher script
- icon generated from cellar.jpg
Typical local build flow:
- install
flatpak-builder - install the required runtimes:
org.gnome.Platform//47org.gnome.Sdk//47
- run:
flatpak-builder --user --install --force-clean flatpak-build net.enne2.Cellar.yaml
Then launch it with:
flatpak run net.enne2.Cellar
To export a private Flatpak repository and a single-file bundle, use:
./scripts/build-flatpak-repo.sh
This creates:
flatpak-repo/— OSTree/Flatpak repository ready to publishdist/net.enne2.Cellar.flatpak— installable Flatpak bundle
Publish on brain.local
If brain.local serves static files, copy the generated repository there, for example:
./scripts/publish-flatpak-repo.sh brain.local /var/www/html/flatpak/cellar
Then clients can add the remote and install the app with:
flatpak remote-add --if-not-exists --user --no-gpg-verify brain-local http://brain.local/flatpak/cellar
flatpak install --user brain-local net.enne2.Cellar
Environment configuration
Current environment variables used by the API container:
DATABASE_URLSTORAGE_DIR
Current defaults in docker-compose.yml:
- database:
sqlite:////app/data/bottle_archive.db - storage:
/app/storage
Host directories are mounted as volumes:
API endpoints
GET /health
Simple health check.
GET /archives
Returns the list of archives ordered by creation time descending.
POST /archives
Uploads a new archive file and stores metadata.
Form fields supported:
filenamebottle_namedescriptiontagsarchrunnerwindows_version
GET /archives/{archive_id}
Returns one archive metadata entry.
GET /archives/{archive_id}/download
Downloads the archive file.
DELETE /archives/{archive_id}
Deletes the archive file and the corresponding metadata row.
Client commands
All commands below assume you are inside bottle-archive-server.
List remote archives
python client.py list
Upload an existing archive file
python client.py upload /path/to/archive.tar.gz --name "Empire Earth Gold" --bottle-name "Empire-Earth-Gold"
Optional extra metadata:
python client.py upload /path/to/archive.tar.gz --name "Empire Earth Gold" --bottle-name "Empire-Earth-Gold" --description "Backup from desktop" --tags "games,gog" --arch win32 --runner soda-9.0-1 --windows-version win7
Download an archive by id
python client.py download 1 ./downloads/
Scan local Bottles bottles
python client.py scan-local
JSON output:
python client.py scan-local --json
Interactive wizard: choose local bottle, back it up, upload it
python client.py wizard-upload
Install a bottle from the server by name
python client.py install Empire-Earth-Gold
Replace existing local bottle:
python client.py install Empire-Earth-Gold --replace
Use a custom Bottles directory:
python client.py install Empire-Earth-Gold --bottles-dir /custom/path
GTK4 desktop client
Run:
python cellar-gtk.py
The GUI lets you:
- list archives available on the server
- filter archives by name, bottle, runner, tags, and description
- immediately see whether a bottle is already installed on the local system
- download and install a selected bottle locally
- enable overwrite of existing local bottles
- open a separate configuration window to set the server address
The GTK client stores its local settings in:
~/.config/cellar/gtk_client.json
On Linux you need GTK4 bindings available in the system Python. Typical packages are:
- Fedora:
python3-gobjectandgtk4 - Debian/Ubuntu:
python3-giandgir1.2-gtk-4.0
Example workflow
Backup and upload
- Start the server:
docker compose up --build
- Scan local Bottles:
python client.py scan-local
- Start the wizard:
python client.py wizard-upload
- Choose one of the local bottles
- Confirm archive metadata
- The client creates a temporary
.tar.gzbackup and uploads it
Restore on another machine
- Start the same server or connect to a reachable one
- Install the bottle by name:
python client.py install Empire-Earth-Gold
- Check local Bottles:
python client.py scan-local
Known limitations
1. No authentication
At the moment, anyone who can reach the API can list, download, upload, and delete archives.
This is acceptable only for a local prototype or a trusted LAN environment.
2. No metadata editing
Once an archive is inserted in the database, there is currently no API or client command to update its metadata.
That means fields like:
namedescriptiontagsrunnerwindows_version
cannot yet be corrected or refined without direct database manipulation or deleting and re-uploading the entry.
3. No versions/history model
Multiple uploads of the same bottle are just separate rows. There is no version chain or “latest version” logic yet.
4. Local storage only
The server stores everything on local disk. There is no S3/MinIO/remote object storage integration yet.
5. Flatpak Bottles path assumption
The default client path targets a Flatpak Bottles installation. Native Bottles layouts may need --bottles-dir.
Next phases
The next development phases should focus on two critical areas.
Phase 1: Authentication system integration
This is the most important missing feature.
The project needs an authentication layer so the server can be used safely beyond a private local test.
Recommended additions:
- user accounts
- password hashing
- token-based authentication
- per-user archive ownership
- permission checks on download/delete/edit operations
- optional admin role
Possible implementation direction:
- FastAPI auth routes
- JWT access tokens or session tokens
- password hashing with a standard library such as
passlib/bcrypt userstable + relation from archive rows to owners
Minimum target for this phase:
POST /auth/registerPOST /auth/login- protected archive routes
- client support for login and storing a token locally
Phase 2: Database entry editing system
The second required feature is a proper metadata editing workflow.
Right now archive metadata is write-once. That is too limiting.
The project needs a system to edit database entries safely from the API and the client.
Recommended additions:
PATCH /archives/{id}endpoint- validation rules for editable fields
- audit fields like
updated_at - optional edit history
- client command such as:
python client.py edit 12 --name "..." --tags "..."
Editable fields should include at least:
namebottle_namedescriptiontagsarchrunnerwindows_version
This phase is important because real archives often need cleanup after upload.
Recommended roadmap
Short term
- add authentication
- add metadata editing
- add archive search/filter endpoints
- add better error messages in client install/upload flows
Medium term
- add versioning for the same bottle
- add per-user archive namespaces
- add pagination on
GET /archives - add import/export manifest support
- add
upload-local <bottle-name>non-interactive command
Long term
- replace local storage with S3/MinIO
- add a small web UI
- add full-text search on archive metadata
- add deduplication / retention policies
- integrate directly with Bottles workflows more tightly
Security note
Until authentication is implemented, do not expose this API publicly.
Use it only:
- on localhost
- on a trusted home LAN
- behind an authenticated reverse proxy if temporarily needed
Development notes
The project is currently intentionally simple.
That simplicity is useful because it makes the next steps clear:
- secure access
- allow metadata editing
- improve archive lifecycle management
Those two immediate next phases — authentication and database entry editing — should be considered required before treating this project as more than a local prototype.
