diff --git a/app-tray/backone-tray.py b/app-tray/backone-tray.py index 3826865..8b3849d 100755 --- a/app-tray/backone-tray.py +++ b/app-tray/backone-tray.py @@ -5,6 +5,7 @@ import signal import subprocess import sys import threading +import time import urllib.request import gi @@ -21,11 +22,13 @@ from gi.repository import GLib, Gtk APP_URL = os.environ.get('BACKONE_APP_URL', 'http://127.0.0.1:9993/app/') HEALTH_URL = os.environ.get('BACKONE_HEALTH_URL', 'http://127.0.0.1:9993/health') ICON_NAME = 'network-workgroup' +SERVICE_WAIT_SECONDS = int(os.environ.get('BACKONE_SERVICE_WAIT', '45')) class BackOneTray: def __init__(self): self._windows = [] + self._service_ready = False self.indicator = AppIndicator3.Indicator.new( 'backone-tray', ICON_NAME, @@ -39,6 +42,7 @@ class BackOneTray: pass self._update_status() GLib.timeout_add_seconds(30, self._on_timer) + GLib.idle_add(self._bootstrap_service) def _build_menu(self): menu = Gtk.Menu() @@ -67,15 +71,94 @@ class BackOneTray: except Exception: return False + def _systemd_active(self): + try: + result = subprocess.run( + ['systemctl', 'is-active', 'backone'], + capture_output=True, + text=True, + timeout=5, + ) + return result.returncode == 0 and result.stdout.strip() == 'active' + except (OSError, subprocess.TimeoutExpired): + return False + + def _start_service(self): + for cmd in ( + ['systemctl', 'start', 'backone'], + ['pkexec', 'systemctl', 'start', 'backone'], + ): + try: + result = subprocess.run( + cmd, + capture_output=True, + timeout=30, + ) + if result.returncode == 0: + return True + except (OSError, subprocess.TimeoutExpired): + continue + return False + + def _wait_for_service(self, timeout=SERVICE_WAIT_SECONDS): + deadline = time.time() + timeout + while time.time() < deadline: + if self._service_ok(): + self._service_ready = True + return True + time.sleep(0.5) + return False + + def _ensure_service(self): + if self._service_ok(): + self._service_ready = True + return True + if not self._systemd_active(): + self._start_service() + return self._wait_for_service() + + def _bootstrap_service(self): + def worker(): + ready = self._ensure_service() + GLib.idle_add(self._on_bootstrap_done, ready) + threading.Thread(target=worker, daemon=True).start() + return False + + def _on_bootstrap_done(self, ready): + self._service_ready = ready + self._update_status() + return False + def _update_status(self): - title = 'BackOne (online)' if self._service_ok() else 'BackOne (offline)' + online = self._service_ok() + self._service_ready = online + title = 'BackOne (online)' if online else 'BackOne (offline)' self.indicator.set_title(title) def _on_timer(self): self._update_status() return True + def _show_offline_dialog(self): + dialog = Gtk.MessageDialog( + transient_for=None, + flags=0, + message_type=Gtk.MessageType.ERROR, + buttons=Gtk.ButtonsType.OK, + text='BackOne service is not running', + ) + dialog.format_secondary_text( + 'Could not reach the BackOne web server on 127.0.0.1:9993.\n\n' + 'Use "Restart BackOne Service" from the tray menu, or run:\n' + ' sudo systemctl start backone' + ) + dialog.run() + dialog.destroy() + def _open_url(self, url): + if not self._ensure_service(): + self._show_offline_dialog() + return if self._try_webkit(url): return for cmd in ( @@ -115,6 +198,10 @@ class BackOneTray: stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, ) + def worker(): + ready = self._wait_for_service(timeout=30) + GLib.idle_add(self._on_bootstrap_done, ready) + threading.Thread(target=worker, daemon=True).start() def main(): diff --git a/debian/backone-app-tray.postinst b/debian/backone-app-tray.postinst index 1379578..d90d17f 100644 --- a/debian/backone-app-tray.postinst +++ b/debian/backone-app-tray.postinst @@ -6,6 +6,14 @@ case "$1" in elif [ -x /usr/lib/backone/addons/updater/rebuild-addon-manifest.sh ]; then BACKONE_HOME=/var/lib/backone /usr/lib/backone/addons/updater/rebuild-addon-manifest.sh || true fi + if [ -x /usr/lib/backone/addons/updater/merge-local-conf.sh ]; then + BACKONE_HOME=/var/lib/backone /usr/lib/backone/addons/updater/merge-local-conf.sh '{"settings":{"enableWebServer":true}}' || true + fi + if [ -d /run/systemd/system ]; then + systemctl daemon-reload 2>/dev/null || true + systemctl enable backone.service 2>/dev/null || true + systemctl start backone.service 2>/dev/null || true + fi ;; esac #DEBHELPER# diff --git a/debian/backone.service b/debian/backone.service index e7bac18..3618c2d 100644 --- a/debian/backone.service +++ b/debian/backone.service @@ -6,6 +6,7 @@ Wants=network-online.target [Service] ExecStart=/usr/sbin/backone Restart=always +RestartSec=5 KillMode=process [Install] diff --git a/debian/postinst b/debian/postinst index a1c49c7..26c59be 100644 --- a/debian/postinst +++ b/debian/postinst @@ -20,6 +20,11 @@ case "$1" in rm -f /etc/systemd/system/backone.service systemctl daemon-reload 2>/dev/null || true fi + # backone must run as root; older packages shipped User=backone which prevents startup + if [ -f /usr/lib/systemd/system/backone.service ] && grep -q '^User=backone' /usr/lib/systemd/system/backone.service 2>/dev/null; then + sed -i '/^User=backone$/d; /^Group=backone$/d; /^StateDirectory=backone$/d' /usr/lib/systemd/system/backone.service + systemctl daemon-reload 2>/dev/null || true + fi if [ -x /usr/lib/backone/addons/updater/sync-authtoken.sh ]; then /usr/lib/backone/addons/updater/sync-authtoken.sh || true elif [ -x /usr/share/backone/sync-authtoken.sh ]; then