# Cellar ![Cellar logo](cellar.jpg) **Author:** Matteo Benedetto `Cellar` is a minimal self-hosted prototype for archiving exported [Bottles](https://usebottles.com/) 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 `FastAPI` server - a local `SQLite` database for archive metadata - local file storage for uploaded bottle backups - a minimal Python CLI client - a small `GTK4` desktop client for browsing and installing archived bottles - a `Docker Compose` setup 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 ` - 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](Dockerfile) — container image for the API - [docker-compose.yml](docker-compose.yml) — local deployment - [requirements.txt](requirements.txt) — Python dependencies - [client.py](client.py) — minimal CLI client - [cellar-gtk.py](cellar-gtk.py) — GTK4 desktop client - [app/main.py](app/main.py) — API routes - [app/models.py](app/models.py) — SQLAlchemy model - [app/schemas.py](app/schemas.py) — API response schemas - [app/database.py](app/database.py) — database setup - [app/storage.py](app/storage.py) — file storage helpers - [app/config.py](app/config.py) — environment/config handling - [data/](data/) — SQLite database location - [storage/](storage/) — uploaded archive files --- ## Architecture ### Server The server is built with `FastAPI` and stores metadata in `SQLite`. Each uploaded archive creates: 1. a physical file in [storage/](storage/) 2. a metadata record in the SQLite database in [data/](data/) 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: 1. finds the archive by bottle name 2. downloads it 3. extracts it safely 4. 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`, or `install` 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: - [net.enne2.Cellar.yaml](net.enne2.Cellar.yaml) Flatpak packaging assets are stored in [flatpak/](flatpak/): - desktop entry - AppStream metadata - launcher script - icon generated from [cellar.jpg](cellar.jpg) Typical local build flow: 1. install `flatpak-builder` 2. install the required runtimes: - `org.gnome.Platform//47` - `org.gnome.Sdk//47` 3. 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 publish - `dist/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_URL` - `STORAGE_DIR` Current defaults in [docker-compose.yml](docker-compose.yml): - database: `sqlite:////app/data/bottle_archive.db` - storage: `/app/storage` Host directories are mounted as volumes: - [data/](data/) → `/app/data` - [storage/](storage/) → `/app/storage` --- ## 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: - `file` - `name` - `bottle_name` - `description` - `tags` - `arch` - `runner` - `windows_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-gobject` and `gtk4` - Debian/Ubuntu: `python3-gi` and `gir1.2-gtk-4.0` --- ## Example workflow ### Backup and upload 1. Start the server: - `docker compose up --build` 2. Scan local Bottles: - `python client.py scan-local` 3. Start the wizard: - `python client.py wizard-upload` 4. Choose one of the local bottles 5. Confirm archive metadata 6. The client creates a temporary `.tar.gz` backup and uploads it ### Restore on another machine 1. Start the same server or connect to a reachable one 2. Install the bottle by name: - `python client.py install Empire-Earth-Gold` 3. 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: - `name` - `description` - `tags` - `runner` - `windows_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` - `users` table + relation from archive rows to owners Minimum target for this phase: - `POST /auth/register` - `POST /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: - `name` - `bottle_name` - `description` - `tags` - `arch` - `runner` - `windows_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 ` 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: 1. secure access 2. allow metadata editing 3. 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.