diff --git a/app/sovran_systemsos_web/server.py b/app/sovran_systemsos_web/server.py index 5f587d7..67c71ff 100644 --- a/app/sovran_systemsos_web/server.py +++ b/app/sovran_systemsos_web/server.py @@ -398,6 +398,27 @@ SERVICE_DESCRIPTIONS: dict[str, str] = { "Your system account credentials. These are the keys to your Sovran_SystemsOS machine — " "root access, user accounts, and SSH passphrases. Keep them safe." ), + "sparrow-autoconnect.service": ( + "Sparrow Wallet is a privacy-focused Bitcoin desktop wallet for sending, receiving, " + "and managing your Bitcoin. Sovran_SystemsOS automatically connects it to your local " + "Electrs server on first boot — your address lookups, balances, and transactions " + "never touch a third-party server. Full privacy, zero configuration." + ), + "bisq-autoconnect.service": ( + "Bisq is a decentralized, peer-to-peer Bitcoin exchange — buy and sell Bitcoin " + "with no KYC and no middleman. Sovran_SystemsOS automatically connects it to your " + "local Bitcoin node on first boot, routing all traffic through Tor. Your trades are " + "verified by your own node, keeping you fully sovereign." + ), +} + +SERVICE_DESKTOP_LINKS: dict[str, list[dict[str, str]]] = { + "sparrow-autoconnect.service": [ + {"label": "Open Sparrow Wallet", "desktop_file": "sparrow-desktop.desktop"}, + ], + "bisq-autoconnect.service": [ + {"label": "Open Bisq", "desktop_file": "Bisq.desktop"}, + ], } # ── App setup ──────────────────────────────────────────────────── @@ -2151,9 +2172,36 @@ async def api_service_detail(unit: str, icon: str | None = None): btc_ver = _format_bitcoin_version(raw_ver, icon=icon) service_detail["bitcoin_version"] = btc_ver # backwards compat service_detail["version"] = btc_ver + desktop_links = SERVICE_DESKTOP_LINKS.get(unit, []) + if desktop_links: + service_detail["desktop_links"] = desktop_links return service_detail +@app.post("/api/desktop/launch/{desktop_file}") +async def api_desktop_launch(desktop_file: str): + """Launch a desktop application via gtk-launch on the local GNOME session.""" + import re as _re + if not _re.match(r'^[a-zA-Z0-9_.-]+\.desktop$', desktop_file): + raise HTTPException(status_code=400, detail="Invalid desktop file name") + + try: + env = dict(os.environ) + env["DISPLAY"] = ":0" + result = subprocess.run( + ["gtk-launch", desktop_file], + capture_output=True, text=True, timeout=10, env=env, + ) + if result.returncode != 0: + raise HTTPException(status_code=500, detail=f"Failed to launch: {result.stderr.strip()}") + except FileNotFoundError: + raise HTTPException(status_code=500, detail="gtk-launch not found on this system") + except subprocess.TimeoutExpired: + raise HTTPException(status_code=500, detail="Launch command timed out") + + return {"ok": True, "launched": desktop_file} + + @app.get("/api/network") async def api_network(): loop = asyncio.get_event_loop() diff --git a/app/sovran_systemsos_web/static/css/tiles.css b/app/sovran_systemsos_web/static/css/tiles.css index 7651f02..9acd4c7 100644 --- a/app/sovran_systemsos_web/static/css/tiles.css +++ b/app/sovran_systemsos_web/static/css/tiles.css @@ -343,3 +343,18 @@ color: var(--yellow); font-weight: 600; } + + +/* ── Desktop launch buttons ──────────────────────────────────────── */ + +.svc-detail-launch-row { + display: flex; + gap: 10px; + flex-wrap: wrap; +} + +.svc-detail-launch-btn { + font-size: 0.85rem; + padding: 8px 18px; + cursor: pointer; +} diff --git a/app/sovran_systemsos_web/static/js/service-detail.js b/app/sovran_systemsos_web/static/js/service-detail.js index 6b0e928..faf869c 100644 --- a/app/sovran_systemsos_web/static/js/service-detail.js +++ b/app/sovran_systemsos_web/static/js/service-detail.js @@ -72,6 +72,21 @@ async function openServiceDetailModal(unit, name, icon) { ''; } + // Section: Desktop Launch (only for services with desktop apps) + if (data.desktop_links && data.desktop_links.length > 0) { + var launchBtns = ''; + data.desktop_links.forEach(function(link) { + launchBtns += ''; + }); + html += '
If you are accessing this machine locally on the GNOME desktop, click below to launch the app directly.
' + + '