#!/usr/bin/env python3 from subprocess import call, check_call, check_output from time import sleep import os import sys import json CONTAINER_NAME = "otherworld" IMAGE_NAME = "debian:stable" def create_x11_mapping (): home = os.environ["HOME"] xdg_dir = os.environ["XDG_RUNTIME_DIR"] display = os.environ["DISPLAY"] inner_user = home Xauthority = os.environ["XAUTHORITY"] return ["-e", f"DISPLAY={display}", "-v", "/tmp/.X11-unix/:/tmp/.X11-unix", "-v", "/dev/snd:/dev/snd", "-v", f"{xdg_dir}/pulse:/run/pulse:ro", "-v", f"{home}/.gtkrc-2.0:{inner_user}/.gtkrc-2.0", "-v", f"{home}/.config/gtk-3.0/:{inner_user}/.config/gtk-3.0", "-v", f"{home}/.guix-profile/share/fonts/truetype:/user/share/fonts/truetype-guix", "-v", f"{home}/.guix-profile/share/fonts/opentype:/usr/share/fonts/opentype-guix", "-v", f"{Xauthority}:{inner_user}/.Xauthority", "-w", inner_user, "--shm-size", "2g", "--privileged"] DOCKER_CREATE_OPTIONS = ["-t"] + create_x11_mapping() USER_VOLUMES = ["~/otherworld"] BACKGROUND_COMMAND = None DEFAULT_COMMAND = ["bash"] def docker_inspect (container): return json.loads(check_output(["docker", "inspect", container])) def docker_create (image, container, options, command): docker_command = ["docker", "create", "--name", container] + options + [image] if command: docker_command = docker_command + command call(docker_command) def docker_get_user (container, user): return check_output(["docker", "exec", container, "groups", user]) def docker_create_user (container, user, uid=None): command = ["docker", "exec", container, "adduser", user, "--gecos", ",,,", "--disabled-password"] if uid is not None: command = command + ["--uid", str(uid)] call(command) call(["docker", "exec", container, "chown", user, f"/home/{user}"]) def docker_start (container): call(["docker", "start", container]) def docker_exec (container, command, user): call(["docker", "exec", "--user", user, "-ti", container] + command) def docker_rm (container): call(["docker", "rm", "-f", container]) def docker_build (build_path, options, image_name): check_call(["docker", "build"] + options + ["-t", image_name, build_path]) return image_name def docker_pull (image_name): check_call(["docker", "pull", image_name]) def resolve_build_path (build_path): if build_path is not None and build_path.startswith("~/"): build_path = f"{os.environ['HOME']}/{build_path[1:]}" return build_path def expand_user_volumes (volumes): outer_home = os.environ["HOME"] inner_home = outer_home mappings = [] for volume in volumes: source = volume target = volume if ":" in source: (source, target) = source.split(":") if source.startswith("~/"): source = f"{outer_home}/{source[1:]}" if source.startswith(outer_home) and not os.path.exists(source): os.makedirs(source) if target.startswith("~/"): target = f"{inner_home}/{target[1:]}" mappings = mappings + ["-v", f"{source}:{target}"] return mappings user = os.environ["USER"] uid = int(check_output(["id", "-u"]).strip()) image_name = os.environ.get("OW_IMAGE", IMAGE_NAME) container_name = os.environ.get("OW_CONTAINER", f"{CONTAINER_NAME}_{user}") build_path = None command = sys.argv[1:] command_user = user user_volumes = USER_VOLUMES pull = False quiet = False while len(command) > 0: arg = command[0] if arg == "--quiet": command = command[1:] quiet = True elif arg == "--rm": if not quiet: print(f">> Removing container: {container_name}") docker_rm(container_name) sys.exit(0) elif arg == "--sudo": command_user = "root" command = command[1:] elif arg.startswith("--container="): container_name = arg[len("--container="):] command = command[1:] elif arg.startswith("--image="): image_name = arg[len("--image="):] command = command[1:] elif arg.startswith("--build="): build_path = arg[len("--build="):] command = command[1:] elif arg.startswith("--volume="): user_volumes.append(arg[len("--volume="):]) command = command[1:] elif arg == "--pull": command = command[1:] pull = True else: break if len(command) == 0: command = DEFAULT_COMMAND container = None try: container = docker_inspect(container_name)[0] except Exception: build_path = resolve_build_path(build_path) if build_path is not None: image_name = docker_build(build_path, ["--pull"] if pull else [], f"image_{container_name}") elif pull: docker_pull(image_name) if not quiet: print(f">> Creating container: {container_name} (using image: {image_name})") docker_create( image_name, container_name, DOCKER_CREATE_OPTIONS + expand_user_volumes(user_volumes), BACKGROUND_COMMAND ) container = docker_inspect(container_name)[0] if not container["State"]["Running"]: if not quiet: print(f">> Starting container: {container_name}") docker_start(container_name) container = docker_inspect(container_name)[0] try: docker_get_user(container_name, user) except Exception: if not quiet: print(f">> Creating container user: {user}") docker_create_user(container_name, user, uid) if not quiet: print(f">> Welcome to {container_name} (IP {container['NetworkSettings']['IPAddress']})") docker_exec(container_name, command, command_user)