init: Allow re-initializing while container daemon is running

* Make the container service always expose the initializer object on DBus
* Add `waydroid init --client` to always bring up the GTK initializer client
This commit is contained in:
Alessandro Astone
2025-11-10 14:59:21 +01:00
parent fa2b74bae2
commit 5d6f413c40
7 changed files with 105 additions and 129 deletions

View File

@@ -4,10 +4,14 @@ Name=Waydroid
Exec=waydroid
Icon=waydroid
Categories=X-WayDroid-App;Utility;
Actions=stop;
Actions=stop;initialize;
X-Purism-FormFactor=Workstation;Mobile;
[Desktop Action stop]
Name=Stop Waydroid
Exec=waydroid session stop
Icon=waydroid
[Desktop Action initialize]
Name=Initialize Waydroid
Exec=waydroid init --client

View File

@@ -4,7 +4,7 @@ Description=Waydroid Container
[Service]
UMask=0022
BusName=id.waydro.Container
ExecStart=/usr/bin/waydroid -w container start
ExecStart=/usr/bin/waydroid container start
[Install]
WantedBy=multi-user.target

View File

@@ -43,27 +43,16 @@ def main():
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
dbus.mainloop.glib.threads_init()
dbus_name_scope = None
if not actions.initializer.is_initialized(args) and \
args.action and args.action not in ("init", "first-launch", "log"):
if args.wait_for_init:
try:
dbus_name_scope = dbus.service.BusName("id.waydro.Container", dbus.SystemBus(), do_not_queue=True)
actions.wait_for_init(args)
except dbus.exceptions.NameExistsException:
print('ERROR: WayDroid service is already awaiting initialization')
return 1
else:
print('ERROR: WayDroid is not initialized, run "waydroid init"')
return 0
if args.action is None:
args.action = "first-launch"
if args.action == "init":
actionNeedRoot(args.action)
actions.init(args)
if args.client:
actions.remote_init_client(args)
else:
actionNeedRoot(args.action)
actions.init(args)
elif args.action == "upgrade":
actionNeedRoot(args.action)
actions.upgrade(args)
@@ -78,12 +67,6 @@ def main():
elif args.action == "container":
actionNeedRoot(args.action)
if args.subaction == "start":
if dbus_name_scope is None:
try:
dbus_name_scope = dbus.service.BusName("id.waydro.Container", dbus.SystemBus(), do_not_queue=True)
except dbus.exceptions.NameExistsException:
print('ERROR: WayDroid container service is already running')
return 1
actions.container_manager.start(args)
elif args.subaction == "stop":
actions.container_manager.stop(args)
@@ -127,7 +110,8 @@ def main():
elif args.action == "show-full-ui":
actions.app_manager.showFullUI(args)
elif args.action == "first-launch":
actions.remote_init_client(args)
if not actions.initializer.is_initialized(args):
actions.remote_init_client(args)
if actions.initializer.is_initialized(args):
actions.app_manager.showFullUI(args)
elif args.action == "status":

View File

@@ -1,6 +1,6 @@
# Copyright 2021 Erfan Abdi
# SPDX-License-Identifier: GPL-3.0-or-later
from tools.actions.initializer import init, wait_for_init, remote_init_client
from tools.actions.initializer import init, remote_init_client
from tools.actions.upgrader import upgrade
from tools.actions.session_manager import start, stop
from tools.actions.container_manager import start, stop, freeze, unfreeze

View File

@@ -8,6 +8,7 @@ import signal
import tools.config
from tools import helpers
from tools import services
from tools import actions
import dbus
import dbus.service
import dbus.exceptions
@@ -36,14 +37,20 @@ class DbusContainerManager(dbus.service.Object):
@dbus.service.method("id.waydro.ContainerManager", in_signature='', out_signature='')
def Freeze(self):
if not actions.initializer.is_initialized(self.args):
raise RuntimeError("Waydroid is not initialized")
freeze(self.args)
@dbus.service.method("id.waydro.ContainerManager", in_signature='', out_signature='')
def Unfreeze(self):
if not actions.initializer.is_initialized(self.args):
raise RuntimeError("Waydroid is not initialized")
unfreeze(self.args)
@dbus.service.method("id.waydro.ContainerManager", in_signature='', out_signature='a{ss}')
def GetSession(self):
if not actions.initializer.is_initialized(self.args):
raise RuntimeError("Waydroid is not initialized")
try:
session = self.args.session
session["state"] = helpers.lxc.status(self.args)
@@ -52,7 +59,8 @@ class DbusContainerManager(dbus.service.Object):
return {}
def service(args, looper):
dbus_obj = DbusContainerManager(looper, dbus.SystemBus(), '/ContainerManager', args)
initializer = actions.initializer.DbusInitializer(looper, dbus.SystemBus(), '/Initializer', args)
runner = DbusContainerManager(looper, dbus.SystemBus(), '/ContainerManager', args)
looper.run()
def set_permissions(args, perm_list=None, mode="777"):
@@ -101,37 +109,48 @@ def start(args):
logging.error("Container service is already running")
return
status = helpers.lxc.status(args)
if status == "STOPPED":
# Load binder and ashmem drivers
cfg = tools.config.load(args)
if cfg["waydroid"]["vendor_type"] == "MAINLINE":
if helpers.drivers.probeBinderDriver(args) != 0:
logging.error("Failed to load Binder driver")
helpers.drivers.probeAshmemDriver(args)
helpers.drivers.loadBinderNodes(args)
set_permissions(args, [
"/dev/" + args.BINDER_DRIVER,
"/dev/" + args.VNDBINDER_DRIVER,
"/dev/" + args.HWBINDER_DRIVER
], "666")
mainloop = GLib.MainLoop()
mainloop = GLib.MainLoop()
def sigint_handler(data):
def sigint_handler(data):
try:
stop(args)
mainloop.quit()
except:
pass
mainloop.quit()
GLib.unix_signal_add(GLib.PRIORITY_HIGH, signal.SIGINT, sigint_handler, None)
GLib.unix_signal_add(GLib.PRIORITY_HIGH, signal.SIGTERM, sigint_handler, None)
service(args, mainloop)
else:
logging.error("WayDroid container is {}".format(status))
GLib.unix_signal_add(GLib.PRIORITY_HIGH, signal.SIGINT, sigint_handler, None)
GLib.unix_signal_add(GLib.PRIORITY_HIGH, signal.SIGTERM, sigint_handler, None)
service(args, mainloop)
prepared_drivers = False
def prepare_drivers_once(args):
global prepared_drivers
if prepared_drivers:
return
# Load binder and ashmem drivers
cfg = tools.config.load(args)
if cfg["waydroid"]["vendor_type"] == "MAINLINE":
if helpers.drivers.probeBinderDriver(args) != 0:
logging.error("Failed to load Binder driver")
helpers.drivers.probeAshmemDriver(args)
helpers.drivers.loadBinderNodes(args)
set_permissions(args, [
"/dev/" + args.BINDER_DRIVER,
"/dev/" + args.VNDBINDER_DRIVER,
"/dev/" + args.HWBINDER_DRIVER
], "666")
prepared_drivers = True
def do_start(args, session):
if not actions.initializer.is_initialized(args):
raise RuntimeError("Waydroid is not initialized")
if "session" in args:
raise RuntimeError("Already tracking a session")
prepare_drivers_once(args)
logging.info("Starting up container for a new session")
# Networking
@@ -194,6 +213,9 @@ def do_start(args, session):
args.session = session
def stop(args, quit_session=True):
if not actions.initializer.is_initialized(args):
raise RuntimeError("Waydroid is not initialized")
logging.info("Stopping container")
try:

View File

@@ -117,69 +117,56 @@ def setup_config(args):
return True
def init(args):
if not is_initialized(args) or args.force:
initializer_service = None
try:
initializer_service = tools.helpers.ipc.DBusContainerService("/Initializer", "id.waydro.Initializer")
except dbus.DBusException:
pass
if not setup_config(args):
return
status = "STOPPED"
if os.path.exists(tools.config.defaults["lxc"] + "/waydroid"):
status = helpers.lxc.status(args)
if status != "STOPPED":
if is_initialized(args) and not args.force:
logging.info("Already initialized")
if not setup_config(args):
return
status = "STOPPED"
session = None
if os.path.exists(tools.config.defaults["lxc"] + "/waydroid"):
status = helpers.lxc.status(args)
if status != "STOPPED":
if "running_init_in_service" in args:
session = args.session
tools.actions.container_manager.stop(args, False)
else:
logging.info("Stopping container")
try:
container = tools.helpers.ipc.DBusContainerService()
args.session = container.GetSession()
session = container.GetSession()
container.Stop(False)
except Exception as e:
logging.debug(e)
tools.actions.container_manager.stop(args)
if args.images_path not in tools.config.defaults["preinstalled_images_paths"]:
helpers.images.get(args)
else:
helpers.images.remove_overlay(args)
if not os.path.isdir(tools.config.defaults["rootfs"]):
os.mkdir(tools.config.defaults["rootfs"])
if not os.path.isdir(tools.config.defaults["overlay"]):
os.mkdir(tools.config.defaults["overlay"])
os.mkdir(tools.config.defaults["overlay"]+"/vendor")
if not os.path.isdir(tools.config.defaults["overlay_rw"]):
os.mkdir(tools.config.defaults["overlay_rw"])
os.mkdir(tools.config.defaults["overlay_rw"]+"/system")
os.mkdir(tools.config.defaults["overlay_rw"]+"/vendor")
helpers.drivers.probeAshmemDriver(args)
helpers.lxc.setup_host_perms(args)
helpers.lxc.set_lxc_config(args)
helpers.lxc.make_base_props(args)
if status != "STOPPED":
logging.info("Starting container")
try:
container.Start(args.session)
except Exception as e:
logging.debug(e)
logging.error("Failed to restart container. Please do so manually.")
if "running_init_in_service" not in args or not args.running_init_in_service:
try:
if initializer_service:
initializer_service.Done()
except dbus.DBusException:
pass
tools.actions.container_manager.stop(args, False)
if args.images_path not in tools.config.defaults["preinstalled_images_paths"]:
helpers.images.get(args)
else:
logging.info("Already initialized")
def wait_for_init(args):
helpers.ipc.create_channel("remote_init_output")
mainloop = GLib.MainLoop()
dbus_obj = DbusInitializer(mainloop, dbus.SystemBus(), '/Initializer', args)
mainloop.run()
# After init
dbus_obj.remove_from_connection()
helpers.images.remove_overlay(args)
if not os.path.isdir(tools.config.defaults["rootfs"]):
os.mkdir(tools.config.defaults["rootfs"])
if not os.path.isdir(tools.config.defaults["overlay"]):
os.mkdir(tools.config.defaults["overlay"])
os.mkdir(tools.config.defaults["overlay"]+"/vendor")
if not os.path.isdir(tools.config.defaults["overlay_rw"]):
os.mkdir(tools.config.defaults["overlay_rw"])
os.mkdir(tools.config.defaults["overlay_rw"]+"/system")
os.mkdir(tools.config.defaults["overlay_rw"]+"/vendor")
helpers.drivers.probeAshmemDriver(args)
helpers.lxc.setup_host_perms(args)
helpers.lxc.set_lxc_config(args)
helpers.lxc.make_base_props(args)
if status != "STOPPED":
try:
if "running_init_in_service" in args:
tools.actions.container_manager.do_start(args, session)
else:
logging.info("Starting container")
container.Start(session)
except Exception as e:
logging.debug(e)
logging.error("Failed to restart container. Please do so manually.")
class DbusInitializer(dbus.service.Object):
def __init__(self, looper, bus, object_path, args):
@@ -197,11 +184,6 @@ class DbusInitializer(dbus.service.Object):
else:
raise PermissionError("Polkit: Authentication failed")
@dbus.service.method("id.waydro.Initializer", in_signature='', out_signature='')
def Done(self):
if is_initialized(self.args):
self.looper.quit()
def ensure_polkit_auth(sender, conn, privilege):
dbus_info = dbus.Interface(conn.get_object("org.freedesktop.DBus", "/org/freedesktop/DBus/Bus", False), "org.freedesktop.DBus")
pid = dbus_info.GetConnectionUnixProcessID(sender)
@@ -289,21 +271,6 @@ def remote_init_client(args):
bus = dbus.SystemBus()
if is_initialized(args):
try:
tools.helpers.ipc.DBusContainerService("/Initializer", "id.waydro.Initializer").Done()
except dbus.DBusException:
pass
return
def notify_and_quit(caller):
if is_initialized(args):
try:
tools.helpers.ipc.DBusContainerService("/Initializer", "id.waydro.Initializer").Done()
except dbus.DBusException:
pass
GLib.idle_add(Gtk.main_quit)
class WaydroidInitWindow(Gtk.Window):
def __init__(self):
super().__init__(title="Initialize Waydroid")
@@ -440,7 +407,7 @@ def remote_init_client(args):
GLib.set_prgname("Waydroid")
win = WaydroidInitWindow()
win.connect("destroy", notify_and_quit)
win.connect("destroy", Gtk.main_quit)
win.show_all()
win.outTextView.hide()

View File

@@ -33,6 +33,7 @@ def arguments_init(subparser):
help="rom type (options: \"lineage\", \"bliss\" or OTA channel URL; default is LineageOS)")
ret.add_argument("-s", "--system_type",
help="system type (options: VANILLA, FOSS or GAPPS; default is VANILLA)")
ret.add_argument("--client", help="run as user mode, connecting to the remote initializer service", action="store_true")
return ret
def arguments_status(subparser):
@@ -152,8 +153,6 @@ def arguments():
" logfiles (this may reduce performance)")
parser.add_argument("-q", "--quiet", dest="quiet", action="store_true",
help="do not output any log messages")
parser.add_argument("-w", "--wait", dest="wait_for_init", action="store_true",
help="wait for init before running")
# Actions
sub = parser.add_subparsers(title="action", dest="action")