264 lines
8.2 KiB
Python
Executable File
264 lines
8.2 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
from subprocess import call, check_call, check_output
|
|
from time import sleep
|
|
from itertools import chain
|
|
import os
|
|
import sys
|
|
import json
|
|
|
|
CONTAINER_NAME = "otherworld"
|
|
DEFAULT_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"]
|
|
|
|
GENERATED_IMAGE_NAME_TEMPLATE = "image_{container_name}"
|
|
|
|
CREATE_USER_BOOTSTRAP_SCRIPT="""
|
|
chown {user} /home/{user}
|
|
chown {user} /home/{user}/.config
|
|
cd /home/{user}
|
|
cp -Rv /etc/skel .
|
|
chown -R {user} skel
|
|
mv skel/* skel/.* .
|
|
rm -rf skel
|
|
"""
|
|
|
|
def get_generated_image_name (container_name):
|
|
return GENERATED_IMAGE_NAME_TEMPLATE.format(container_name=container_name)
|
|
|
|
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, "sh", "-c", CREATE_USER_BOOTSTRAP_SCRIPT.format(user=user)])
|
|
|
|
def docker_start (container):
|
|
call(["docker", "start", container])
|
|
|
|
def docker_exec (container, command, user, env={}, work_dir=None):
|
|
call(["docker", "exec", "--user", user, "-ti"] + \
|
|
(["--workdir", work_dir] if work_dir else []) + \
|
|
list(chain.from_iterable([["--env", f"{kv[0]}={kv[1]}"] for kv in env.items()])) + \
|
|
[container] + command)
|
|
|
|
def docker_commit (container, image_name):
|
|
call(["docker", "commit", container, image_name])
|
|
|
|
def docker_rm (container):
|
|
call(["docker", "rm", "-f", container])
|
|
|
|
def docker_rmi (container):
|
|
call(["docker", "rmi", "-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[2:]}"
|
|
|
|
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[2:]}"
|
|
|
|
if source.startswith(outer_home) and not os.path.exists(source):
|
|
os.makedirs(source)
|
|
|
|
if target.startswith("~/"):
|
|
target = f"{inner_home}/{target[2:]}"
|
|
|
|
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", None)
|
|
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
|
|
|
|
actions = []
|
|
|
|
command_env = {}
|
|
command_workdir = None
|
|
|
|
quiet = False
|
|
while len(command) > 0:
|
|
arg = command[0]
|
|
if arg == "--quiet":
|
|
command = command[1:]
|
|
quiet = True
|
|
elif arg == "--rm":
|
|
actions.append("rm")
|
|
command = command[1:]
|
|
elif arg == "--rmi":
|
|
actions.append("rmi")
|
|
command = command[1:]
|
|
elif arg == "--commit":
|
|
actions.append("commit")
|
|
command = command[1:]
|
|
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.startswith("--env="):
|
|
env_pair = arg[len("--env="):]
|
|
(key, value) = env_pair.split("=", 1)
|
|
command_env[key] = value
|
|
command = command[1:]
|
|
elif arg == "--cd":
|
|
command_workdir = os.getcwd()
|
|
command = command[1:]
|
|
elif arg.startswith("--cd="):
|
|
command_workdir = arg[len("--cd="):]
|
|
command = command[1:]
|
|
elif arg == "--pull":
|
|
command = command[1:]
|
|
pull = True
|
|
else:
|
|
break
|
|
|
|
if command_workdir:
|
|
user_volumes.append(command_workdir)
|
|
command_workdir = resolve_build_path(command_workdir)
|
|
|
|
if actions:
|
|
if "commit" in actions:
|
|
target_image_name = get_generated_image_name(container_name)
|
|
if not quiet:
|
|
print(f">> Committing container: {container_name} to image: {target_image_name}")
|
|
docker_commit(container_name, target_image_name)
|
|
|
|
if "rm" in actions:
|
|
if not quiet:
|
|
print(f">> Removing container: {container_name}")
|
|
docker_rm(container_name)
|
|
|
|
if "rmi" in actions:
|
|
target_image_name = get_generated_image_name(container_name)
|
|
if not quiet:
|
|
print(f">> Removing image: {target_image_name}")
|
|
docker_rmi(target_image_name)
|
|
|
|
if not command:
|
|
sys.exit(0)
|
|
|
|
if not command:
|
|
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 [], get_generated_image_name(container_name))
|
|
else:
|
|
if image_name is None:
|
|
image_name = get_generated_image_name(container_name)
|
|
try:
|
|
docker_inspect(image_name)
|
|
if not quiet:
|
|
print(f">> Using default otherworld image {image_name}")
|
|
except Exception:
|
|
if not quiet:
|
|
print(f">> Default otherworld image {image_name} not found, falling back to {DEFAULT_IMAGE_NAME}")
|
|
image_name = DEFAULT_IMAGE_NAME
|
|
|
|
if 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, command_env, command_workdir)
|