cloud-sync now ships a real Qt UI alongside the tkinter fallback.
Architecture:
- HeadlessProgress: --no-gui path, plain stdout
- TkProgressWindow: stdlib fallback when Qt isn't installed
- QtProgressWindow: preferred path; supports both PySide6 and PyQt6
(interchangeable APIs for our subset)
The factory in ui.py picks Qt → tkinter → headless. Tk stays so the
zipapp still works on bare Python with no extras.
Threading: QApplication runs on the main thread (started by run_with
via QDialog.exec). The restic worker runs on a daemon threading.Thread.
Cross-thread UI updates go via a Signal on a bridge QObject so Qt
auto-marshals them onto the main thread via a queued connection.
Cancellation: WM close + Cancel button both set a flag. sync.pull/push
pass ui.is_cancelled as restic.run's cancel_check; the subprocess gets
killed and returns -1 → exit 1.
Theme: Fusion style + Prism's dark palette (RGB values copied as facts
from PrismLauncher's DarkTheme.cpp). Override with PRISM_THEME=off.
Pyz size went 20 KB → 36 KB (added ui.py + ui_qt.py).
33 tests still green.
cloud-sync
Per-user Minecraft state sync via restic. Single Python zipapp drops into Prism / MMC / ATLauncher pre-launch and post-exit hooks alongside packwiz-installer-bootstrap. Part of the automc platform.
See DESIGN.md for the full architecture (restic backend, two-port cloud-svc control plane, etc.).
Status
Working skeleton + sync logic. 33 tests pass. E2E verified against a local restic-rest-server (pull empty → push initial → delete local → pull restores → modify+push creates second snapshot → client forget --prune correctly blocked by --append-only).
Install / build
Requires Python ≥ 3.10. No runtime deps (stdlib only).
# build single-file zipapp
make build # → cloud-sync.pyz (~53 KB)
# or pip-install
make install # pip install -e .
Usage in Prism (or MMC / ATLauncher)
Instance Settings → Custom commands:
Pre-launch:
python /path/to/cloud-sync.pyz pull --url=https://cloud.tm.center --pack-folder=$INST_MC_DIR
Post-exit:
python /path/to/cloud-sync.pyz push --url=https://cloud.tm.center --pack-folder=$INST_MC_DIR
Player needs Python 3.10+ on PATH. Token file (<INST_MC_DIR>/.cloud-sync/token) gets the discord_id:password credentials from their /register Discord DM.
CLI
python cloud-sync.pyz {pull,push} \
--url URL cloud-svc data plane URL (required)
--pack-folder PATH Minecraft instance directory (default: cwd)
--token-file PATH override default <pack-folder>/.cloud-sync/token
--restic-binary PATH skip auto-discovery
--no-download fail if no usable restic; don't fetch from upstream
-g, --no-gui headless mode
Programmatic API (for frazclient)
from pathlib import Path
import cloud_sync
cloud_sync.pull(cloud_sync.Args(
url="https://cloud.tm.center",
pack_folder=Path("/srv/mc/instance"),
token_file=Path("/srv/mc/instance/.cloud-sync/token"),
restic_binary=None, # auto-discover
allow_download=True,
headless=True,
))
frazclient's client.py consumes this directly via import cloud_sync instead of subprocessing the pyz.
On-disk layout
Per-instance state under <pack-folder>/.cloud-sync/:
.cloud-sync/
token # discord_id:password (mode 0600)
scope.json # optional; defaults baked in if missing
restic-<RESTIC_VERSION> # auto-downloaded binary
files-from.txt # restic --files-from
exclude-from.txt # restic --exclude-from
Auto-excluded from sync. Multiple MC instances = multiple .cloud-sync/ dirs with independent credentials.
Why Python (not a JAR)
- Antivirus. Unsigned JARs that auto-download binaries + upload files are textbook Windows Defender false-positive triggers. Python invoked by code-signed
python.exemostly sidesteps that. - Future Qt UI. PySide6 opens a path to a real Qt UI (matching Prism's look) if richer surfaces are wanted later. JVM Qt bindings are abandoned.
- frazclient already needs Python. Inlining as an import is zero overhead; the same package serves Prism via the pyz.
Cost: players using Prism must have Python 3.10+ installed. Most Linux/Mac systems already do; Windows users install once from the Microsoft Store or python.org.
Where the data lives
| Component | Role | Repo |
|---|---|---|
cloud-sync (this) |
Player-side. Subprocess restic for pull/push. | Timemachine/cloud-sync |
cloud-svc |
Operator-side control plane (provisioning + admin). | Timemachine/cloud-svc |
restic-rest-server (existing) |
Data plane. Player's restic hits it directly with their password. | upstream |
discord-bot |
Calls cloud-svc on /register to provision a player's cloud account. |
Timemachine/discord-bot |
License
MIT.