Skip to content

Instantly share code, notes, and snippets.

@gasinvein
Last active March 10, 2020 17:51
Show Gist options
  • Save gasinvein/06923f3b7e32aba4e38566ba42756314 to your computer and use it in GitHub Desktop.
Save gasinvein/06923f3b7e32aba4e38566ba42756314 to your computer and use it in GitHub Desktop.
This script generates arguments for a container system (e.g. Podman) to run GUI apps within a container.
#!/usr/bin/python3 -u
import os
import subprocess
import glob
import re
import pwd
GUEST_GL_DIR = "/run/host/gl"
GUEST_XAUTHORITY = "/run/host/Xauthority"
GUEST_PA_SOCKET = "/run/host/pulse/socket"
class ContainerRunner(object):
def mount_args(self, host_path, guest_path, kind=None, dev=False, readonly=False):
raise NotImplementedError()
def env_args(self, env_var, env_value):
raise NotImplementedError()
def get_gl_args(self, system):
args = []
gl_dirs = []
for arch in system.GL.keys():
guest_mp = f"{GUEST_GL_DIR}.{arch}"
gl_dirs.append(guest_mp)
for abs_p, rel_p in system.get_gl_paths(arch):
if os.path.islink(abs_p):
kind = "symlink"
else:
kind = "file"
args += self.mount_args(
abs_p, os.path.join(guest_mp, rel_p), kind=kind, readonly=True
)
args += self.env_args("LD_LIBRARY_PATH", os.pathsep.join(gl_dirs))
return args
def get_xorg_args(self):
args = []
args += self.mount_args(
"/tmp/.X11-unix", "/tmp/.X11-unix",
kind="dir", readonly=True
)
args += self.mount_args(
os.environ["XAUTHORITY"], GUEST_XAUTHORITY,
kind="file", readonly=True
)
args += self.env_args(
"XAUTHORITY", GUEST_XAUTHORITY
)
args += self.env_args(
"DISPLAY", os.getenv("DISPLAY")
)
return args
def get_dri_args(self):
args = self.mount_args("/dev/dri", "/dev/dri", kind="dir", dev=True, readonly=False)
for nv_dev in glob.glob("/dev/nvidia*"):
args += self.mount_args(nv_dev, nv_dev, kind="file", dev=True, readonly=False)
return args
def get_pulseaudio_args(self):
xdg_runtime_dir = os.getenv("XDG_RUNTIME_DIR",
os.path.join("/run", "user", str(os.getuid()))
)
pulse_server = os.getenv("PULSE_SERVER")
if pulse_server is not None:
if pulse_server.startswith("unix:"):
pulse_socket = pulse_server.split(":", 1)[1]
else:
return self.env_args("PULSE_SERVER", pulse_server)
else:
pulse_socket = os.path.join(xdg_runtime_dir, "pulse", "native")
return self.mount_args(pulse_socket, GUEST_PA_SOCKET) + \
self.env_args("PULSE_SERVER", f"unix:{GUEST_PA_SOCKET}")
class Podman(ContainerRunner):
def mount_args(self, host_path, guest_path, kind=None, dev=False, readonly=False):
mode = "ro" if readonly else "rw"
return ["-v", f"{host_path}:{guest_path}:{mode}"]
def env_args(self, env_var, env_value):
return ["-e", f"{env_var}={env_value}"]
class Bubblewrap(ContainerRunner):
def mount_args(self, host_path, guest_path, kind=None, dev=False, readonly=False):
src_path = host_path
if kind == "symlink":
opt = "--symlink"
src_path = os.readlink(host_path)
elif dev:
opt = "--dev-bind"
elif readonly:
opt = "--ro-bind"
else:
opt = "--bind"
return [opt, src_path, guest_path]
def env_args(self, env_var, env_value):
return ["--setenv", env_var, env_value]
class Flatpak(ContainerRunner):
def mount_args(self, host_path, guest_path=None, readonly=False, *args, **kwargs):
mode = "ro" if readonly else "rw"
return [f"--filesystem={host_path}:{mode}"]
def env_args(self, env_var, env_value):
return [f"--env={env_var}={env_value}"]
def get_gl_args(self, *args, **kwargs):
return []
def get_xorg_args(self):
return ["--socket=x11", "--share=ipc"]
def get_dri_args(self):
return ["--device=dri"]
class LinuxSystem(object):
PKG_LIST_CMD = None
GL = {
"x86_64": {
"libdir": None,
"pkgs": None,
}
}
def list_pkg(self, pkg):
pkgman_proc = subprocess.run(
self.PKG_LIST_CMD + [pkg],
stdout=subprocess.PIPE, check=True, text=True,
)
return pkgman_proc.stdout.splitlines()
def get_gl_paths(self, arch):
libdir = os.path.abspath(self.GL[arch]["libdir"])
regex = re.compile(r'^{}.*\.so\.*'.format(libdir))
for pkg in self.GL[arch]["pkgs"]:
for p in self.list_pkg(pkg):
if regex.match(p):
yield (p, os.path.relpath(p, libdir))
def get_container_args(self, container, gl=False):
args = container.get_gl_args(system=self) if gl else []
args += container.get_xorg_args()
args += container.get_dri_args()
args += container.get_pulseaudio_args()
return args
class Fedora(LinuxSystem):
PKG_LIST_CMD = ["rpm", "-ql"]
GL = {
"x86_64": {
"libdir": "/usr/lib64",
"pkgs": [
"xorg-x11-drv-nvidia-libs.x86_64",
"xorg-x11-drv-nvidia-cuda-libs.x86_64"
]
},
"i386": {
"libdir": "/usr/lib",
"pkgs": [
"xorg-x11-drv-nvidia-libs.i686",
"xorg-x11-drv-nvidia-cuda-libs.i686"
]
}
}
CONATINER = {
"podman": Podman,
"bwrap": Bubblewrap,
"flatpak": Flatpak,
}
SYSTEM = {
"fedora": Fedora
}
def read_env_file(env_file):
regex = re.compile(r'^(\w+)="?(.*?)"?$', re.MULTILINE)
with open(env_file, "r") as e:
env_text = e.read()
return dict(regex.findall(env_text))
def get_system():
os_release = read_env_file("/etc/os-release")
return SYSTEM[os_release["ID"]]()
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("-n", "--no-gl", action="store_true", help="Disable graphics libraries mount")
parser.add_argument("-s", "--system", help="OS family", choices=SYSTEM.keys())
parser.add_argument("container", help="ContainerRunner type (one of {})", choices=CONATINER.keys())
args = parser.parse_args()
if args.system:
system = SYSTEM[args.system]()
else:
system = get_system()
c_cls = CONATINER[args.container]
for i in system.get_container_args(c_cls(), gl=not args.no_gl):
print(i)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment