ybox 0.9.10__py3-none-any.whl → 0.9.11__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ybox/__init__.py +1 -1
- ybox/conf/profiles/apps.ini +10 -5
- ybox/conf/profiles/basic.ini +23 -11
- ybox/conf/profiles/dev.ini +4 -0
- ybox/conf/resources/entrypoint-cp.sh +1 -1
- ybox/conf/resources/entrypoint-root.sh +3 -3
- ybox/conf/resources/entrypoint.sh +2 -9
- ybox/conf/resources/run-in-dir +26 -16
- ybox/conf/resources/ybox-systemd.template +7 -5
- ybox/env.py +18 -7
- ybox/migrate/{0.9.0-0.9.7:0.9.8.py → 0.9.0-0.9.10:0.9.11.py} +6 -5
- ybox/pkg/inst.py +22 -10
- ybox/pkg/mark.py +1 -1
- ybox/run/control.py +17 -2
- ybox/run/create.py +98 -45
- ybox/run/destroy.py +32 -10
- ybox/run/graphics.py +37 -17
- ybox/run/pkg.py +3 -3
- {ybox-0.9.10.dist-info → ybox-0.9.11.dist-info}/METADATA +30 -19
- {ybox-0.9.10.dist-info → ybox-0.9.11.dist-info}/RECORD +24 -24
- {ybox-0.9.10.dist-info → ybox-0.9.11.dist-info}/WHEEL +1 -1
- {ybox-0.9.10.dist-info → ybox-0.9.11.dist-info}/entry_points.txt +0 -0
- {ybox-0.9.10.dist-info → ybox-0.9.11.dist-info/licenses}/LICENSE +0 -0
- {ybox-0.9.10.dist-info → ybox-0.9.11.dist-info}/top_level.txt +0 -0
ybox/__init__.py
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
"""`ybox` is a tool to easily manage linux distributions in containers"""
|
2
|
-
__version__ = "0.9.
|
2
|
+
__version__ = "0.9.11"
|
ybox/conf/profiles/apps.ini
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
[base]
|
2
2
|
name = Profile for CLI and GUI apps
|
3
3
|
includes = basic.ini
|
4
|
+
ssh_agent = on
|
4
5
|
|
5
6
|
[security]
|
6
7
|
# SYS_PTRACE may be required by mesa which is invoked indirectly by both firefox and chromium.
|
@@ -9,6 +10,9 @@ includes = basic.ini
|
|
9
10
|
caps_add = SYS_PTRACE
|
10
11
|
|
11
12
|
[mounts]
|
13
|
+
# export the host's ssh keys for use by ssh-agent in the container as required ("ro" mode
|
14
|
+
# implies that known_hosts and other files within ~/.ssh cannot be changed)
|
15
|
+
ssh = $HOME/.ssh:$TARGET_HOME/.ssh:ro
|
12
16
|
music = $HOME/Music:$TARGET_HOME/Music:ro
|
13
17
|
pictures = $HOME/Pictures:$TARGET_HOME/Pictures:ro
|
14
18
|
videos = $HOME/Videos:$TARGET_HOME/Videos:ro
|
@@ -19,8 +23,9 @@ videos = $HOME/Videos:$TARGET_HOME/Videos:ro
|
|
19
23
|
|
20
24
|
[app_flags]
|
21
25
|
# These flags will be added to Exec line of google-chrome.desktop when it is copied to host.
|
22
|
-
|
23
|
-
#
|
24
|
-
|
25
|
-
google-chrome
|
26
|
-
google-chrome-
|
26
|
+
|
27
|
+
# the --disable-dev-shm-usage flag in chrome/chromium based browsers disables use of /dev/shm
|
28
|
+
# which can reduce memory footprint at the cost of performance and increased disk activity
|
29
|
+
#google-chrome = !p --disable-dev-shm-usage !a
|
30
|
+
#google-chrome-beta = !p --disable-dev-shm-usage !a
|
31
|
+
#google-chrome-unstable = !p --disable-dev-shm-usage !a
|
ybox/conf/profiles/basic.ini
CHANGED
@@ -20,8 +20,8 @@
|
|
20
20
|
# - YBOX_SYS_CONF_DIR: path to system configuration directory where configuration directory
|
21
21
|
# shipped with ybox is installed (or the string form of
|
22
22
|
# the directory if it is not on filesystem like an egg or similar)
|
23
|
-
# - TARGET_HOME: set to the home directory of the container user
|
24
|
-
# (
|
23
|
+
# - TARGET_HOME: set to the home directory of the container user in the container
|
24
|
+
# (which is same as the host user's $HOME for podman and /root for docker)
|
25
25
|
# Additionally a special notation can be used for current date+time with this notation:
|
26
26
|
# ${NOW:<fmt>}. The <fmt> uses the format supported by python strftime
|
27
27
|
# (https://docs.python.org/3/library/datetime.html#datetime.datetime.strftime)
|
@@ -67,7 +67,7 @@ includes =
|
|
67
67
|
# to freely create as many containers as desired to achieve best isolation without worrying
|
68
68
|
# about dramatic increase in disk and/or memory usage.
|
69
69
|
shared_root = $HOME/.local/share/ybox/SHARED_ROOTS/$YBOX_DISTRIBUTION_NAME
|
70
|
-
# Bind mount the container $HOME to this local path
|
70
|
+
# Bind mount the container $HOME to this local path. This makes it
|
71
71
|
# easier for backup software and otherwise to read useful container data.
|
72
72
|
# If not provided then you should explicitly mount required directories in the [mounts]
|
73
73
|
# section otherwise home will remain completely ephemeral which is not recommended.
|
@@ -100,6 +100,16 @@ pulseaudio = on
|
|
100
100
|
dbus = on
|
101
101
|
# If enabled then the system dbus from the host is available to the container.
|
102
102
|
dbus_sys = off
|
103
|
+
# If enabled then the socket for SSH agent, if present, is made available to the container.
|
104
|
+
# The $SSH_AUTH_SOCK environment variables must be set in the host environment for this to work.
|
105
|
+
# You can also mount $HOME/.ssh with appropriate flags ("ro" if possible) in the [mounts]
|
106
|
+
# section to enable the container use the host's ssh keys.
|
107
|
+
ssh_agent = off
|
108
|
+
# If enabled then the socket for GPG agent, if present, is made available to the container.
|
109
|
+
# The $GPG_AGENT_INFO environment variable must be set in the host environment for this to work.
|
110
|
+
# You can also mount $HOME/.gnupg with appropriate flags ("ro" if possible) in the [mounts]
|
111
|
+
# section to enable the container use the host's gpg keys.
|
112
|
+
gpg_agent = off
|
103
113
|
# If enabled then Direct Rendering Infrastructure for accelerated graphics is available to
|
104
114
|
# the container.
|
105
115
|
dri = on
|
@@ -123,8 +133,8 @@ nvidia = off
|
|
123
133
|
#
|
124
134
|
# This will take precedence if both "nvidia" and "nvidia_ctk" are enabled.
|
125
135
|
nvidia_ctk = off
|
126
|
-
# default podman/docker shm-size is 64m which can be insufficient for many apps
|
127
|
-
shm_size =
|
136
|
+
# default podman/docker shm-size is only 64m which can be insufficient for many apps
|
137
|
+
shm_size = 2g
|
128
138
|
# Limit the maximum number of processes in the container (to avoid stuff like fork bombs).
|
129
139
|
pids_limit = 2048
|
130
140
|
# Logging driver to use. Default for podman/docker is to use journald in modern Linux
|
@@ -137,6 +147,9 @@ log_driver = json-file
|
|
137
147
|
# Example for docker that does not support `path`
|
138
148
|
log_opts = max-size=10m,max-file=3
|
139
149
|
|
150
|
+
# Comma separated list of additional devices that should be made available to the container using
|
151
|
+
# the --device option to podman/docker run. Example: devices = /dev/video0,/dev/ttyUSB0
|
152
|
+
devices =
|
140
153
|
|
141
154
|
# The security-opt and other security options passed to podman/docker.
|
142
155
|
# You should restrict these as required.
|
@@ -227,9 +240,10 @@ documents = $HOME/Documents:$TARGET_HOME/Documents:ro
|
|
227
240
|
# in the [base] section.
|
228
241
|
#
|
229
242
|
# Note: The LHS should typically have a path having $HOME while RHS will be relative to the
|
230
|
-
# target's home inside the container. Do not use $TARGET_HOME on RHS
|
231
|
-
#
|
243
|
+
# target's home inside the container. Do not use $TARGET_HOME on RHS since path the assumed
|
244
|
+
# to be a relative one and $TARGET_HOME already inserted as required.
|
232
245
|
[configs]
|
246
|
+
env_conf = $HOME/.config/environment.d -> .config/environment.d
|
233
247
|
bashrc = $HOME/.bashrc -> .bashrc
|
234
248
|
starship = $HOME/.config/starship.toml -> .config/starship.toml
|
235
249
|
# replicate fish configuration directory with copy of fish_variables but symlinks for the rest
|
@@ -303,14 +317,12 @@ XMODIFIERS
|
|
303
317
|
[app_flags]
|
304
318
|
# These flags/arguments will be added to Exec line of chromium.desktop when it is copied to
|
305
319
|
# host as well as in the wrapper chromium executable created on the host.
|
306
|
-
# You can use "!p" here for the first argument in the 'Exec='
|
320
|
+
# You can use "!p" here for the first argument in the 'Exec=' line in the desktop
|
307
321
|
# file and '!a' for rest of the arguments. When linking to an executable program, '!p' will
|
308
322
|
# refer to the full path of the executable while '!a' will be replaced by "$@" in the shell
|
309
323
|
# script. Use '!!p' for a literal '!p' and '!!a' for a literal '!a'.
|
310
324
|
|
311
|
-
|
312
|
-
# /dev/shm in read-write mode which is quite insecure.
|
313
|
-
chromium = !p --disable-dev-shm-usage --enable-chrome-browser-cloud-management !a
|
325
|
+
chromium = !p --enable-chrome-browser-cloud-management !a
|
314
326
|
|
315
327
|
|
316
328
|
# Startup programs you want to run when starting the container. These are run using
|
ybox/conf/profiles/dev.ini
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
[base]
|
2
2
|
name = Profile for creating development environment
|
3
3
|
includes = basic.ini
|
4
|
+
ssh_agent = on
|
4
5
|
|
5
6
|
[security]
|
6
7
|
# SYS_PTRACE is required by mesa and without this, the following warning can be seen:
|
@@ -8,6 +9,9 @@ includes = basic.ini
|
|
8
9
|
caps_add = SYS_PTRACE
|
9
10
|
|
10
11
|
[mounts]
|
12
|
+
# export the host's ssh keys for use by ssh-agent in the container as required ("ro" mode
|
13
|
+
# implies that known_hosts and other files within ~/.ssh cannot be changed)
|
14
|
+
ssh = $HOME/.ssh:$TARGET_HOME/.ssh:ro
|
11
15
|
# add your projects and other directories having source code
|
12
16
|
#projects = $HOME/projects:$TARGET_HOME/projects
|
13
17
|
#pyenv = $HOME/.pyenv:$TARGET_HOME/.pyenv:ro
|
@@ -28,5 +28,5 @@ echo_color "$fg_purple" "Copying data from container to shared root mounted on '
|
|
28
28
|
IFS="," read -ra shared_dirs_arr <<< "$shared_dirs"
|
29
29
|
for dir in "${shared_dirs_arr[@]}"; do
|
30
30
|
echo_color "$fg_orange" "Copying $dir to $shared_bind$dir"
|
31
|
-
cp -
|
31
|
+
cp -an "$dir" "$shared_bind$dir"
|
32
32
|
done
|
@@ -10,9 +10,9 @@ source "$SCRIPT_DIR/entrypoint-common.sh"
|
|
10
10
|
|
11
11
|
export HOME=/root
|
12
12
|
echo_color "$fg_cyan" "Copying prime-run, run-in-dir and run-user-bash-cmd" >> $status_file
|
13
|
-
cp -
|
14
|
-
cp -
|
15
|
-
cp -
|
13
|
+
cp -af "$SCRIPT_DIR/prime-run" /usr/local/bin/prime-run
|
14
|
+
cp -af "$SCRIPT_DIR/run-in-dir" /usr/local/bin/run-in-dir
|
15
|
+
cp -af "$SCRIPT_DIR/run-user-bash-cmd" /usr/local/bin/run-user-bash-cmd
|
16
16
|
chmod 0755 /usr/local/bin/prime-run /usr/local/bin/run-in-dir /usr/local/bin/run-user-bash-cmd
|
17
17
|
|
18
18
|
# invoke the NVIDIA setup script if present
|
@@ -57,19 +57,12 @@ function replicate_config_files() {
|
|
57
57
|
home_file="$HOME/${BASH_REMATCH[2]}"
|
58
58
|
dest_file="$config_dir/${BASH_REMATCH[2]}"
|
59
59
|
# only replace the file if it is already a link (assuming the link target may
|
60
|
-
# have changed in the config_list file), or a directory containing links
|
60
|
+
# have changed in the config_list file), or a directory containing only links
|
61
61
|
if [ -e "$dest_file" ]; then
|
62
62
|
if [ -L "$home_file" ]; then
|
63
63
|
rm -f "$home_file"
|
64
64
|
elif [ -d "$home_file" ]; then
|
65
|
-
|
66
|
-
for f in "$home_file"/*; do
|
67
|
-
if [ -e "$f" -a ! -L "$f" ]; then
|
68
|
-
do_rmdir=false
|
69
|
-
break
|
70
|
-
fi
|
71
|
-
done
|
72
|
-
if [ "$do_rmdir" = true ]; then
|
65
|
+
if [ -z $(find "$home_file" -type f -print -quit) ]; then
|
73
66
|
rm -rf "$home_file"
|
74
67
|
fi
|
75
68
|
fi
|
ybox/conf/resources/run-in-dir
CHANGED
@@ -9,24 +9,34 @@ if [ -n "$dir" -a -d "$dir" ]; then
|
|
9
9
|
cd "$dir"
|
10
10
|
fi
|
11
11
|
|
12
|
-
# XAUTHORITY
|
13
|
-
# by podman/docker exec in the mount point of its parent directory
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
12
|
+
# XAUTHORITY, SSH_AUTH_SOCK and GPG_AGENT_INFO files can change after a re-login or a restart,
|
13
|
+
# so search for the passed one by podman/docker exec in the mount point of its parent directory
|
14
|
+
for env_var in XAUTHORITY SSH_AUTH_SOCK GPG_AGENT_INFO; do
|
15
|
+
env_var_orig=${env_var}_ORIG
|
16
|
+
var_val=${!env_var}
|
17
|
+
var_val_orig=${!env_var_orig}
|
18
|
+
if [ -n "$var_val" -a -n "$var_val_orig" ]; then
|
19
|
+
if [ ! -r "$var_val" ]; then
|
20
|
+
# the value should be in /run/user/<uid> or in /tmp, or else the parent directory is used
|
21
|
+
run_dir="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}"
|
22
|
+
if [[ "$var_val" == $run_dir/* ]]; then
|
23
|
+
host_dir="$run_dir"
|
24
|
+
elif [[ "$var_val" == /tmp/* ]]; then
|
25
|
+
host_dir=/tmp
|
26
|
+
else
|
27
|
+
host_dir="$(dirname "$var_val")"
|
28
|
+
fi
|
29
|
+
new_val="${var_val/#$host_dir/${host_dir}-host}" # replace $host_dir by ${host_dir}-host
|
30
|
+
if [ ! -r "$new_val" ]; then
|
31
|
+
new_val="$var_val_orig"
|
32
|
+
fi
|
33
|
+
export $env_var="$new_val"
|
34
|
+
fi
|
21
35
|
else
|
22
|
-
|
36
|
+
# remove unset variable in the container else apps can misbehave
|
37
|
+
unset $env_var
|
23
38
|
fi
|
24
|
-
|
25
|
-
if [ ! -r "$XAUTHORITY" ]; then
|
26
|
-
XAUTHORITY="$XAUTHORITY_ORIG"
|
27
|
-
fi
|
28
|
-
export XAUTHORITY
|
29
|
-
fi
|
39
|
+
done
|
30
40
|
|
31
41
|
# In case NVIDIA driver has been updated, the updated libraries and other files may need to be
|
32
42
|
# linked again, so check for a missing library file and invoke the setup script if present
|
@@ -8,15 +8,17 @@ Wants=network-online.target
|
|
8
8
|
After=network-online.target
|
9
9
|
{docker_requires}
|
10
10
|
[Service]
|
11
|
-
|
12
|
-
|
11
|
+
Environment=PATH={sys_path}:{ybox_bin_dir}
|
12
|
+
EnvironmentFile=%h/.config/systemd/user/{env_file}
|
13
|
+
Type=notify
|
14
|
+
NotifyAccess=all
|
13
15
|
Restart=on-failure
|
14
|
-
TimeoutStopSec=70
|
15
16
|
# sleep to allow for initialization of the user's login/graphical environment
|
16
17
|
ExecStartPre=/usr/bin/sleep $SLEEP_SECS
|
17
|
-
ExecStart=/bin/sh -c 'ybox-control start {name}'
|
18
|
+
ExecStart=/bin/sh -c 'ybox-control start {name} && systemd-notify --ready && exec ybox-control wait {name}'
|
18
19
|
ExecStop=/bin/sh -c 'ybox-control stop -t 20 --ignore-stopped {name}'
|
19
20
|
ExecStopPost=/bin/sh -c 'ybox-control stop -t 20 --ignore-stopped {name}'
|
20
|
-
|
21
|
+
TimeoutStopSec=60
|
22
|
+
|
21
23
|
[Install]
|
22
24
|
WantedBy=default.target
|
ybox/env.py
CHANGED
@@ -60,34 +60,41 @@ class Environ:
|
|
60
60
|
:param home_dir: if a non-default user home directory has to be set
|
61
61
|
"""
|
62
62
|
self._home_dir = home_dir or os.path.expanduser("~")
|
63
|
+
self._home_dir = self._home_dir.rstrip("/")
|
63
64
|
self._docker_cmd = docker_cmd or get_docker_command()
|
64
65
|
cmd_version = subprocess.check_output([self._docker_cmd, "--version"])
|
65
66
|
self._uses_podman = "podman" in cmd_version.decode("utf-8").lower()
|
66
67
|
# local user home might be in a different location than /home but target user in the
|
67
68
|
# container will always be in /home with podman else /root for the root user with docker
|
68
69
|
# as ensured by entrypoint-base.sh script
|
69
|
-
|
70
|
+
current_user = getpass.getuser()
|
71
|
+
current_uid = pwd.getpwnam(current_user).pw_uid
|
70
72
|
if self._uses_podman:
|
71
|
-
self._target_user =
|
72
|
-
target_uid =
|
73
|
+
self._target_user = current_user
|
74
|
+
target_uid = current_uid
|
73
75
|
self._target_home = f"/home/{self._target_user}"
|
74
76
|
else:
|
75
77
|
self._target_user = "root"
|
78
|
+
target_uid = 0
|
76
79
|
self._target_home = "/root"
|
77
80
|
# confirm that docker is being used in rootless mode (not required for podman because
|
78
81
|
# it runs as rootless when run by a non-root user in any case without explicit sudo
|
79
82
|
# which the ybox tools don't use)
|
80
83
|
if (docker_ctx := subprocess.check_output(
|
81
84
|
[self._docker_cmd, "context", "show"]).decode("utf-8")).strip() != "rootless":
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
+
# check for DOCKER_HOST environment variable
|
86
|
+
expected_docker_host = f"unix:///run/user/{current_uid}/docker.sock"
|
87
|
+
if not docker_ctx or os.environ.get("DOCKER_HOST", "") != expected_docker_host:
|
88
|
+
raise NotSupportedError("docker should use the rootless mode (see "
|
89
|
+
"https://docs.docker.com/engine/security/rootless/) "
|
90
|
+
f"but the current context is '{docker_ctx}' and "
|
91
|
+
f"$DOCKER_HOST is not set to '{expected_docker_host}'")
|
85
92
|
os.environ["TARGET_HOME"] = self._target_home
|
86
93
|
self._user_base = user_base = site.getuserbase()
|
87
94
|
target_user_base = f"{self._target_home}/.local"
|
88
95
|
self._data_dir = f"{user_base}/share/ybox"
|
89
96
|
self._target_data_dir = f"{target_user_base}/share/ybox"
|
90
|
-
self._xdg_rt_dir = os.environ.get("XDG_RUNTIME_DIR", "")
|
97
|
+
self._xdg_rt_dir = os.environ.get("XDG_RUNTIME_DIR", "").rstrip("/")
|
91
98
|
# the container user's one can be different because it is the root user for docker
|
92
99
|
self._target_xdg_rt_dir = f"/run/user/{target_uid}"
|
93
100
|
self._now = datetime.now()
|
@@ -148,6 +155,10 @@ class Environ:
|
|
148
155
|
"""if podman is the container manager being used"""
|
149
156
|
return self._uses_podman
|
150
157
|
|
158
|
+
def systemd_user_conf_dir(self) -> str:
|
159
|
+
"""standard configuration directory location of user specific systemd services"""
|
160
|
+
return f"{self._home_dir}/.config/systemd/user"
|
161
|
+
|
151
162
|
@property
|
152
163
|
def target_user(self) -> str:
|
153
164
|
"""username of the container user (which is the same as the current user for podman
|
@@ -20,11 +20,12 @@ copy_ybox_scripts_to_container(static_conf, distro_conf)
|
|
20
20
|
|
21
21
|
# rename PKGMGR_CLEANUP to PKGMGR_CLEAN in pkgmgr.conf
|
22
22
|
scripts_dir = static_conf.scripts_dir
|
23
|
-
pkgmgr_conf = f"{scripts_dir}/pkgmgr.conf"
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
23
|
+
pkgmgr_conf = Path(f"{scripts_dir}/pkgmgr.conf")
|
24
|
+
if pkgmgr_conf.exists():
|
25
|
+
with pkgmgr_conf.open("r", encoding="utf-8") as pkgmgr_file:
|
26
|
+
pkgmgr_data = pkgmgr_file.read()
|
27
|
+
with pkgmgr_conf.open("w", encoding="utf-8") as pkgmgr_file:
|
28
|
+
pkgmgr_file.write(pkgmgr_data.replace("PKGMGR_CLEANUP", "PKGMGR_CLEAN"))
|
28
29
|
# run entrypoint-root.sh again to refresh scripts and configuration
|
29
30
|
subprocess.run([static_conf.env.docker_cmd, "exec", "-it", static_conf.box_name, "/usr/bin/sudo",
|
30
31
|
"/bin/bash", f"{static_conf.target_scripts_dir}/entrypoint-root.sh"])
|
ybox/pkg/inst.py
CHANGED
@@ -24,11 +24,16 @@ from ybox.state import (CopyType, DependencyType, RuntimeConfiguration,
|
|
24
24
|
from ybox.util import check_package, ini_file_reader, select_item_from_menu
|
25
25
|
|
26
26
|
# match both "Exec=" and "TryExec=" lines (don't capture trailing newline)
|
27
|
-
|
27
|
+
_EXEC_PATTERN = r"\s*((Try)?Exec\s*=\s*)(\S+)\s*(.*?)\s*"
|
28
|
+
# pattern to match icons with absolute paths and change them to just names
|
29
|
+
_ICON_PATH_PATTERN = r"\s*Icon\s*=\s*(/usr/share/(icons|pixmaps)/\S+)\s*"
|
30
|
+
# regex to match either of the two above
|
31
|
+
_EXEC_ICON_RE = re.compile(f"^{_EXEC_PATTERN}|{_ICON_PATH_PATTERN}$")
|
28
32
|
# match !p and !a to replace executable program (third group above) and arguments respectively
|
29
33
|
_FLAGS_RE = re.compile("![ap]")
|
30
34
|
# environment variables passed through from host environment to podman/docker executable
|
31
35
|
_PASSTHROUGH_ENVVARS = ("XAUTHORITY", "DISPLAY", "WAYLAND_DISPLAY", "FREETYPE_PROPERTIES",
|
36
|
+
"SSH_AUTH_SOCK", "GPG_AGENT_INFO",
|
32
37
|
"__NV_PRIME_RENDER_OFFLOAD", "__GLX_VENDOR_LIBRARY_NAME",
|
33
38
|
"__VK_LAYER_NV_optimus", "VK_ICD_FILES", "VK_ICD_FILENAMES")
|
34
39
|
|
@@ -156,7 +161,8 @@ def _install_package(package: str, args: argparse.Namespace, install_cmd: str, l
|
|
156
161
|
if not skip_desktop_files:
|
157
162
|
copy_type |= CopyType.DESKTOP
|
158
163
|
if not skip_executables:
|
159
|
-
resp = input("Create application executable(s)
|
164
|
+
resp = input("Create wrapper(s) for application executable(s) of package "
|
165
|
+
f"'{package}'? (Y/n) ") if quiet == 0 else "Y"
|
160
166
|
if resp.strip().lower() != "n":
|
161
167
|
copy_type |= CopyType.EXECUTABLE
|
162
168
|
# TODO: wrappers for newly installed required dependencies should also be created;
|
@@ -436,9 +442,9 @@ def docker_cp_action(docker_cmd: str, box_name: str, src: str,
|
|
436
442
|
def _wrap_desktop_file(filename: str, file: str, docker_cmd: str, conf: StaticConfiguration,
|
437
443
|
app_flags: dict[str, str], wrapper_files: list[str]) -> None:
|
438
444
|
"""
|
439
|
-
For a desktop file, add "podman/docker exec ..." to its Exec
|
440
|
-
|
441
|
-
|
445
|
+
For a desktop file, add "podman/docker exec ..." to its `Exec` lines. Also read the additional
|
446
|
+
flags for the command passed in `app_flags` and add them to an appropriate position in the
|
447
|
+
`Exec` lines. Also removes the `TryExec` lines that do not work in some desktop environments.
|
442
448
|
|
443
449
|
:param filename: name of the desktop file being wrapped
|
444
450
|
:param file: full path of the desktop file being wrapped
|
@@ -451,7 +457,13 @@ def _wrap_desktop_file(filename: str, file: str, docker_cmd: str, conf: StaticCo
|
|
451
457
|
# container name is added to desktop file to make it unique
|
452
458
|
wrapper_name = f"ybox.{conf.box_name}.{filename}"
|
453
459
|
|
454
|
-
def
|
460
|
+
def replace_exec_icon(match: re.Match[str]) -> str:
|
461
|
+
"""replace Exec, TryExec and Icon lines appropriately for the host system"""
|
462
|
+
if not (exec_word := match.group(1)): # check for the case of `Icon=/usr/...`
|
463
|
+
return f"Icon={os.path.basename(match.group(5))}\n"
|
464
|
+
# remove TryExec lines that don't work in some desktop environments (like KDE plasma 5)
|
465
|
+
if match.group(2):
|
466
|
+
return ""
|
455
467
|
program = match.group(3)
|
456
468
|
args = match.group(4)
|
457
469
|
# check for additional flags to be added
|
@@ -464,7 +476,7 @@ def _wrap_desktop_file(filename: str, file: str, docker_cmd: str, conf: StaticCo
|
|
464
476
|
full_cmd = program
|
465
477
|
# pseudo-tty cannot be allocated with rootless docker outside of a terminal app
|
466
478
|
env_vars = " -e=".join(_PASSTHROUGH_ENVVARS)
|
467
|
-
return (f'{
|
479
|
+
return (f'{exec_word}{docker_cmd} exec -e={env_vars} {conf.box_name} '
|
468
480
|
f'/usr/local/bin/run-in-dir "" {full_cmd}\n')
|
469
481
|
|
470
482
|
# the destination will be $HOME/.local/share/applications
|
@@ -476,7 +488,7 @@ def _wrap_desktop_file(filename: str, file: str, docker_cmd: str, conf: StaticCo
|
|
476
488
|
def write_desktop_file(src: str) -> None:
|
477
489
|
with open(wrapper_file, "w", encoding="utf-8") as wrapper_fd:
|
478
490
|
with open(src, "r", encoding="utf-8") as src_fd:
|
479
|
-
wrapper_fd.writelines(
|
491
|
+
wrapper_fd.writelines(_EXEC_ICON_RE.sub(replace_exec_icon, line) for line in src_fd)
|
480
492
|
if docker_cp_action(docker_cmd, conf.box_name, file, write_desktop_file) == 0:
|
481
493
|
wrapper_files.append(wrapper_file)
|
482
494
|
|
@@ -545,8 +557,8 @@ def _copy_app_icons(selected_icons: dict[str, tuple[float, str]], docker_cmd: st
|
|
545
557
|
# copy from temporary file over the existing one, if any, to overwrite rather than move
|
546
558
|
# (which will preserve all of its hard links, for example)
|
547
559
|
exists = os.path.exists(target_icon_path)
|
548
|
-
if docker_cp_action(docker_cmd, conf.box_name, icon_path,
|
549
|
-
|
560
|
+
if docker_cp_action(docker_cmd, conf.box_name, icon_path, lambda src, dest=target_icon_path:
|
561
|
+
None if shutil.copy2(src, dest) else None) == 0: # "if" for pyright
|
550
562
|
# skip registration of icon file it already existed and was overwritten so that
|
551
563
|
# it is not removed on package uninstall
|
552
564
|
if not exists:
|
ybox/pkg/mark.py
CHANGED
@@ -30,7 +30,7 @@ def mark_package(args: argparse.Namespace, pkgmgr: SectionProxy, docker_cmd: str
|
|
30
30
|
mark_explicit: bool = args.explicit
|
31
31
|
mark_dependency_of: str = args.dependency_of or ""
|
32
32
|
if not mark_explicit ^ bool(mark_dependency_of):
|
33
|
-
print_error("ybox-pkg mark: exactly one of -e or -
|
33
|
+
print_error("ybox-pkg mark: exactly one of -e or -d option must be specified "
|
34
34
|
f"(explicit={mark_explicit}, dependency-of={mark_dependency_of})")
|
35
35
|
return 1
|
36
36
|
# check that the package(s) are installed and replace with actual installed name
|
ybox/run/control.py
CHANGED
@@ -32,7 +32,7 @@ def start_container(docker_cmd: str, args: argparse.Namespace):
|
|
32
32
|
container_name = args.container
|
33
33
|
if status := get_ybox_state(docker_cmd, container_name, (), exit_on_error=False):
|
34
34
|
if status[0] == "running":
|
35
|
-
print_color(f"
|
35
|
+
print_color(f"ybox container '{container_name}' already active", fg=fgcolor.cyan)
|
36
36
|
else:
|
37
37
|
print_color(f"Starting ybox container '{container_name}'", fg=fgcolor.cyan)
|
38
38
|
run_command([docker_cmd, "container", "start", container_name],
|
@@ -107,6 +107,17 @@ def show_container_status(docker_cmd: str, args: argparse.Namespace) -> None:
|
|
107
107
|
print_error(f"No ybox container '{container_name}' found")
|
108
108
|
|
109
109
|
|
110
|
+
def wait_for_container_stop(docker_cmd: str, args: argparse.Namespace) -> None:
|
111
|
+
"""
|
112
|
+
Wait for an active container to stop.
|
113
|
+
|
114
|
+
:param docker_cmd: the podman/docker executable to use
|
115
|
+
:param args: arguments having all attributes passed by the user
|
116
|
+
"""
|
117
|
+
while check_active_ybox(docker_cmd, args.container):
|
118
|
+
time.sleep(2)
|
119
|
+
|
120
|
+
|
110
121
|
def main_argv(argv: list[str]) -> None:
|
111
122
|
"""
|
112
123
|
Main entrypoint of `ybox-control` that takes a list of arguments which are usually the
|
@@ -138,7 +149,7 @@ def parse_args(argv: list[str]) -> argparse.Namespace:
|
|
138
149
|
stop = operations.add_parser("stop", help="stop a ybox container")
|
139
150
|
_add_subparser_args(stop, 10,
|
140
151
|
"time in seconds to wait for a container to stop before killing it")
|
141
|
-
stop.add_argument("--ignore-stopped", action="store_true",
|
152
|
+
stop.add_argument("-I", "--ignore-stopped", action="store_true",
|
142
153
|
help="don't fail on an already stopped container")
|
143
154
|
stop.set_defaults(func=stop_container)
|
144
155
|
|
@@ -150,6 +161,10 @@ def parse_args(argv: list[str]) -> argparse.Namespace:
|
|
150
161
|
_add_subparser_args(status, 0, "")
|
151
162
|
status.set_defaults(func=show_container_status)
|
152
163
|
|
164
|
+
wait = operations.add_parser("wait", help="wait for an active ybox container to stop")
|
165
|
+
_add_subparser_args(wait, 0, "")
|
166
|
+
wait.set_defaults(func=wait_for_container_stop)
|
167
|
+
|
153
168
|
parser_version_check(parser, argv)
|
154
169
|
return parser.parse_args(argv)
|
155
170
|
|
ybox/run/create.py
CHANGED
@@ -27,9 +27,11 @@ from ybox.filelock import FileLock
|
|
27
27
|
from ybox.pkg.inst import install_package, wrap_container_files
|
28
28
|
from ybox.print import (bgcolor, fgcolor, print_color, print_error, print_info,
|
29
29
|
print_notice, print_warn)
|
30
|
-
from ybox.run.destroy import get_all_containers, remove_orphans_from_db
|
30
|
+
from ybox.run.destroy import (get_all_containers, remove_orphans_from_db,
|
31
|
+
ybox_systemd_service_prefix)
|
31
32
|
from ybox.run.graphics import (add_env_option, add_mount_option, enable_dri,
|
32
|
-
enable_nvidia, enable_wayland, enable_x11
|
33
|
+
enable_nvidia, enable_wayland, enable_x11,
|
34
|
+
handle_variable_mount)
|
33
35
|
from ybox.run.pkg import parse_args as pkg_parse_args
|
34
36
|
from ybox.state import RuntimeConfiguration, YboxStateManagement
|
35
37
|
from ybox.util import (EnvInterpolation, config_reader,
|
@@ -195,10 +197,16 @@ def main_argv(argv: list[str]) -> None:
|
|
195
197
|
Path(f"{conf.scripts_dir}/{Consts.entrypoint_init_done_file()}").touch(mode=0o644)
|
196
198
|
wait_msg = ("Waiting for the container to be ready "
|
197
199
|
f"(see ybox-logs -f {box_name}' for detailed progress)")
|
198
|
-
if args.
|
199
|
-
systemctl := shutil.which("systemctl", path=sys_path))
|
200
|
+
if not args.skip_systemd_service and (sys_path := os.pathsep.join(Consts.sys_bin_dirs())) and (
|
201
|
+
systemctl := shutil.which("systemctl", path=sys_path)) and run_command(
|
202
|
+
[systemctl, "--user", "--quiet", "is-enabled", "default.target"],
|
203
|
+
exit_on_error=False) == 0:
|
200
204
|
create_and_start_service(box_name, env, systemctl, sys_path, wait_msg)
|
201
205
|
else:
|
206
|
+
if not args.skip_systemd_service:
|
207
|
+
print_warn("Skipping user systemd service generation due to missing systemctl in "
|
208
|
+
f"PATH={os.pathsep.join(Consts.sys_bin_dirs())} or failure in "
|
209
|
+
"'systemctl --user is-enabled default.target'")
|
202
210
|
start_container(docker_cmd, conf)
|
203
211
|
print_info(wait_msg)
|
204
212
|
wait_for_ybox_container(docker_cmd, conf, 120)
|
@@ -268,9 +276,12 @@ def parse_args(argv: list[str]) -> argparse.Namespace:
|
|
268
276
|
parser.add_argument("-n", "--name", type=str,
|
269
277
|
help="name of the ybox; default is ybox-<distribution>_<profile> "
|
270
278
|
"if not provided (removing the .ini suffix from <profile> file)")
|
271
|
-
parser.add_argument("-S", "--systemd-service", action="store_true",
|
272
|
-
help="
|
273
|
-
"
|
279
|
+
parser.add_argument("-S", "--skip-systemd-service", action="store_true",
|
280
|
+
help="skip creation of user systemd service file for the ybox container; "
|
281
|
+
"by default a user systemd service file is created and enabled in "
|
282
|
+
"~/.config/systemd/user with the name 'ybox-<name>.service' if the "
|
283
|
+
"<name> does not begin with 'ybox-' prefix else '<name>.service' if "
|
284
|
+
"it already has 'ybox-' prefix")
|
274
285
|
parser.add_argument("-F", "--force-own-orphans", action="store_true",
|
275
286
|
help="force ownership of orphan packages on the same shared root even "
|
276
287
|
"if container configuration does not match, meaning the packages "
|
@@ -607,6 +618,12 @@ def process_base_section(base_section: SectionProxy, profile: PathName, conf: St
|
|
607
618
|
elif key == "dbus":
|
608
619
|
if _get_boolean(val):
|
609
620
|
enable_dbus(docker_args, base_section.getboolean("dbus_sys", fallback=False), env)
|
621
|
+
elif key == "ssh_agent":
|
622
|
+
if _get_boolean(val):
|
623
|
+
enable_ssh_agent(docker_args, env)
|
624
|
+
elif key == "gpg_agent":
|
625
|
+
if _get_boolean(val):
|
626
|
+
enable_gpg_agent(docker_args, env)
|
610
627
|
elif key == "dri":
|
611
628
|
dri = _get_boolean(val)
|
612
629
|
elif key == "nvidia":
|
@@ -629,6 +646,9 @@ def process_base_section(base_section: SectionProxy, profile: PathName, conf: St
|
|
629
646
|
(re.match("^--log-opt=path=(.*)/.*$", path) for path in docker_args) if mt]
|
630
647
|
for log_dir in log_dirs:
|
631
648
|
os.makedirs(log_dir, mode=Consts.default_directory_mode(), exist_ok=True)
|
649
|
+
elif key == "devices":
|
650
|
+
if val:
|
651
|
+
add_multi_opt(docker_args, "device", val)
|
632
652
|
elif key not in ("name", "dbus_sys", "includes"):
|
633
653
|
raise NotSupportedError(f"Unknown key '{key}' in the [base] of {profile} "
|
634
654
|
"or its includes")
|
@@ -685,21 +705,51 @@ def enable_dbus(docker_args: list[str], sys_enable: bool, env: Environ) -> None:
|
|
685
705
|
to the user dbus message bus
|
686
706
|
:param env: an instance of the current :class:`Environ`
|
687
707
|
"""
|
688
|
-
def replace_target_dir(src: str) -> str:
|
689
|
-
return src.replace(f"{env.xdg_rt_dir}/", f"{env.target_xdg_rt_dir}/")
|
690
708
|
if dbus_session := os.environ.get("DBUS_SESSION_BUS_ADDRESS"):
|
691
709
|
dbus_user = dbus_session[dbus_session.find("=") + 1:]
|
692
710
|
if (dbus_opts_idx := dbus_user.find(",")) != -1:
|
693
711
|
dbus_user = dbus_user[:dbus_opts_idx]
|
694
|
-
add_mount_option(docker_args, dbus_user,
|
695
|
-
add_env_option(docker_args, "DBUS_SESSION_BUS_ADDRESS",
|
712
|
+
add_mount_option(docker_args, dbus_user, _replace_xdg_rt_dir(dbus_user, env))
|
713
|
+
add_env_option(docker_args, "DBUS_SESSION_BUS_ADDRESS",
|
714
|
+
_replace_xdg_rt_dir(dbus_session, env))
|
696
715
|
if sys_enable:
|
697
|
-
dbus_sys
|
698
|
-
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
|
716
|
+
for dbus_sys in ("/run/dbus/system_bus_socket", "/var/run/dbus/system_bus_socket"):
|
717
|
+
if os.access(dbus_sys, os.W_OK):
|
718
|
+
add_mount_option(docker_args, dbus_sys, dbus_sys)
|
719
|
+
break
|
720
|
+
|
721
|
+
|
722
|
+
def enable_ssh_agent(docker_args: list[str], env: Environ) -> None:
|
723
|
+
"""
|
724
|
+
Append options to podman/docker arguments to share host machine's ssh agent socket
|
725
|
+
with the new ybox container.
|
726
|
+
|
727
|
+
:param docker_args: list of podman/docker arguments to which the options have to be appended
|
728
|
+
:param env: an instance of the current :class:`Environ`
|
729
|
+
"""
|
730
|
+
if ssh_auth_sock := os.environ.get("SSH_AUTH_SOCK"):
|
731
|
+
target_ssh_auth_sock = handle_variable_mount(docker_args, env, ssh_auth_sock)
|
732
|
+
add_env_option(docker_args, "SSH_AUTH_SOCK", target_ssh_auth_sock)
|
733
|
+
add_env_option(docker_args, "SSH_AUTH_SOCK_ORIG", target_ssh_auth_sock)
|
734
|
+
|
735
|
+
|
736
|
+
def enable_gpg_agent(docker_args: list[str], env: Environ) -> None:
|
737
|
+
"""
|
738
|
+
Append options to podman/docker arguments to share host machine's gpg agent sockets
|
739
|
+
with the new ybox container.
|
740
|
+
|
741
|
+
:param docker_args: list of podman/docker arguments to which the options have to be appended
|
742
|
+
:param env: an instance of the current :class:`Environ`
|
743
|
+
"""
|
744
|
+
if gpg_agent_info := os.environ.get("GPG_AGENT_INFO"):
|
745
|
+
target_gpg_agent_info = handle_variable_mount(docker_args, env, gpg_agent_info)
|
746
|
+
add_env_option(docker_args, "GPG_AGENT_INFO", target_gpg_agent_info)
|
747
|
+
add_env_option(docker_args, "GPG_AGENT_INFO_ORIG", target_gpg_agent_info)
|
748
|
+
|
749
|
+
|
750
|
+
def _replace_xdg_rt_dir(src: str, env: Environ) -> str:
|
751
|
+
"""replace host's $XDG_RUNTIME_DIR in `src` with that of container user's $XDG_RUNTIME_DIR"""
|
752
|
+
return src.replace(env.xdg_rt_dir + "/", env.target_xdg_rt_dir + "/")
|
703
753
|
|
704
754
|
|
705
755
|
def add_multi_opt(docker_args: list[str], opt: str, val: Optional[str]) -> None:
|
@@ -712,7 +762,7 @@ def add_multi_opt(docker_args: list[str], opt: str, val: Optional[str]) -> None:
|
|
712
762
|
"""
|
713
763
|
if val:
|
714
764
|
for opt_val in val.split(","):
|
715
|
-
docker_args.append(f"--{opt}={opt_val}")
|
765
|
+
docker_args.append(f"--{opt}={opt_val.strip()}")
|
716
766
|
|
717
767
|
|
718
768
|
def process_security_section(sec_section: SectionProxy, profile: PathName,
|
@@ -790,8 +840,7 @@ def process_configs_section(configs_section: SectionProxy, config_hardlinks: boo
|
|
790
840
|
# this is refreshed on every container start
|
791
841
|
|
792
842
|
# always recreate the directory to pick up any changes
|
793
|
-
|
794
|
-
shutil.rmtree(conf.configs_dir)
|
843
|
+
shutil.rmtree(conf.configs_dir, ignore_errors=True)
|
795
844
|
os.makedirs(conf.configs_dir, mode=Consts.default_directory_mode(), exist_ok=True)
|
796
845
|
if config_hardlinks:
|
797
846
|
print_info("Creating hard links to paths specified in [configs] ...")
|
@@ -812,10 +861,7 @@ def process_configs_section(configs_section: SectionProxy, config_hardlinks: boo
|
|
812
861
|
dest_path = f"{conf.configs_dir}/{dest_rel_path}"
|
813
862
|
if os.access(src_path, os.R_OK):
|
814
863
|
if os.path.exists(dest_path):
|
815
|
-
|
816
|
-
shutil.rmtree(dest_path)
|
817
|
-
else:
|
818
|
-
os.unlink(dest_path)
|
864
|
+
shutil.rmtree(dest_path, ignore_errors=True)
|
819
865
|
else:
|
820
866
|
os.makedirs(os.path.dirname(dest_path),
|
821
867
|
mode=Consts.default_directory_mode(), exist_ok=True)
|
@@ -842,7 +888,7 @@ def process_configs_section(configs_section: SectionProxy, config_hardlinks: boo
|
|
842
888
|
print_warn(f"Skipping inaccessible configuration path '{src_path}'")
|
843
889
|
print_info("DONE.")
|
844
890
|
# finally mount the configs directory to corresponding directory in the target container
|
845
|
-
add_mount_option(docker_args, conf.configs_dir, conf.target_configs_dir
|
891
|
+
add_mount_option(docker_args, conf.configs_dir, conf.target_configs_dir)
|
846
892
|
|
847
893
|
|
848
894
|
def process_env_section(env_section: SectionProxy, docker_args: list[str]) -> None:
|
@@ -927,13 +973,16 @@ def copytree(src_path: str, dest: str, hardlink: bool = False,
|
|
927
973
|
Note: this function only handles regular files and directories (and hard/symbolic links to
|
928
974
|
them) and will skip special files like device files, fifos etc.
|
929
975
|
|
930
|
-
:param src_path: the
|
931
|
-
|
976
|
+
:param src_path: the source directory to be copied (should have been resolved using
|
977
|
+
`os.path.realpath` or `Path.resolve` if `src_root` argument is not supplied)
|
978
|
+
:param dest: the destination directory which should not already exist (but its parent should)
|
932
979
|
:param hardlink: if True then create hard links to the files in the source (so it should
|
933
980
|
be in the same filesystem) else copy the files, defaults to False
|
934
|
-
:param src_root: the resolved root source directory (same as `src_path` if `None`
|
981
|
+
:param src_root: the resolved root source directory (same as `src_path` if `None` which is
|
982
|
+
assumed to have been resolved using `os.path.realpath` or `Path.resolve`)
|
935
983
|
"""
|
936
984
|
src_root = src_root or src_path
|
985
|
+
src_root = src_root.rstrip("/")
|
937
986
|
os.mkdir(dest, mode=stat.S_IMODE(os.stat(src_path).st_mode))
|
938
987
|
# follow symlink if it leads to outside the "src" tree, else copy as a symlink which
|
939
988
|
# ensures that all destination files are always accessible regardless of source going
|
@@ -951,7 +1000,7 @@ def copytree(src_path: str, dest: str, hardlink: bool = False,
|
|
951
1000
|
os.symlink(l_name, dest_path)
|
952
1001
|
continue
|
953
1002
|
entry_path = os.path.realpath(entry.path)
|
954
|
-
if entry_path.startswith(src_root
|
1003
|
+
if entry_path.startswith(src_root + "/"):
|
955
1004
|
rpath = entry_path[len(src_root) + 1:]
|
956
1005
|
os.symlink(("../" * rpath.count("/")) + rpath, dest_path)
|
957
1006
|
continue
|
@@ -974,6 +1023,8 @@ def copytree(src_path: str, dest: str, hardlink: bool = False,
|
|
974
1023
|
except OSError as err:
|
975
1024
|
# ignore permission and related errors and continue
|
976
1025
|
print_warn(f"Skipping copy/link of '{entry_path}' due to error: {err}")
|
1026
|
+
# TODO: SW: check for success in all copytree's else return False, then check at caller
|
1027
|
+
# to print a bold warning
|
977
1028
|
|
978
1029
|
|
979
1030
|
def setup_ybox_scripts(conf: StaticConfiguration, distro_config: ConfigParser) -> None:
|
@@ -1138,8 +1189,8 @@ def run_container(docker_full_cmd: list[str], current_user: str, shared_root: st
|
|
1138
1189
|
programs from less secure containers; the `ybox-pkg` tool provided a convenient high-level
|
1139
1190
|
package manager that users should use for managing packages in the containers which will
|
1140
1191
|
help in exposing packages only in designated containers
|
1141
|
-
* systemd user service file
|
1142
|
-
automatically on user login
|
1192
|
+
* systemd user service file is generated for podman/docker to start the container
|
1193
|
+
automatically on user login (in absence of -S/--skip-systemd-service option)
|
1143
1194
|
|
1144
1195
|
:param docker_full_cmd: the `docker`/`podman run -itd` command with all the options filled
|
1145
1196
|
in from the container profile specification as a list of string
|
@@ -1207,26 +1258,28 @@ def create_and_start_service(box_name: str, env: Environ, systemctl: str, sys_pa
|
|
1207
1258
|
svc_file = env.search_config_path("resources/ybox-systemd.template", only_sys_conf=True)
|
1208
1259
|
with svc_file.open("r", encoding="utf-8") as svc_fd:
|
1209
1260
|
svc_tmpl = svc_fd.read()
|
1210
|
-
pid_file = ""
|
1211
1261
|
if env.uses_podman:
|
1212
1262
|
manager_name = "Podman"
|
1213
1263
|
docker_requires = ""
|
1214
|
-
res = run_command([env.docker_cmd, "container", "inspect", "--format={{.ConmonPidFile}}",
|
1215
|
-
box_name], capture_output=True, exit_on_error=False)
|
1216
|
-
if isinstance(res, str):
|
1217
|
-
pid_file = f"PIDFile={res}"
|
1218
1264
|
else:
|
1219
1265
|
manager_name = "Docker"
|
1220
1266
|
docker_requires = "After=docker.service\nRequires=docker.service\n"
|
1221
|
-
systemd_dir =
|
1222
|
-
|
1223
|
-
|
1267
|
+
systemd_dir = env.systemd_user_conf_dir()
|
1268
|
+
ybox_svc_prefix = ybox_systemd_service_prefix(box_name)
|
1269
|
+
ybox_svc = f"{ybox_svc_prefix}.service"
|
1270
|
+
ybox_env = f".{ybox_svc_prefix}.env"
|
1224
1271
|
formatted_now = env.now.astimezone().strftime("%a %d %b %Y %H:%M:%S %Z")
|
1272
|
+
# get the path of ybox-control and replace $HOME by %h to keep it generic
|
1273
|
+
if ybox_ctrl_path := shutil.which("ybox-control"):
|
1274
|
+
ybox_bin_dir = os.path.dirname(ybox_ctrl_path)
|
1275
|
+
if ybox_bin_dir.startswith(env.home + "/"):
|
1276
|
+
ybox_bin_dir = f"%h{ybox_bin_dir[len(env.home):]}"
|
1277
|
+
else:
|
1278
|
+
ybox_bin_dir = "%h/.local/bin"
|
1225
1279
|
svc_content = svc_tmpl.format(name=box_name, version=product_version, date=formatted_now,
|
1226
1280
|
manager_name=manager_name, docker_requires=docker_requires,
|
1227
|
-
|
1281
|
+
sys_path=sys_path, ybox_bin_dir=ybox_bin_dir, env_file=ybox_env)
|
1228
1282
|
env_content = f"""
|
1229
|
-
PATH={sys_path}:{env.home}/.local/bin
|
1230
1283
|
SLEEP_SECS={{sleep_secs}}
|
1231
1284
|
# set the container manager to the one configured during ybox-create
|
1232
1285
|
YBOX_CONTAINER_MANAGER={env.docker_cmd}
|
@@ -1235,15 +1288,15 @@ def create_and_start_service(box_name: str, env: Environ, systemctl: str, sys_pa
|
|
1235
1288
|
print_color(f"Generating user systemd service '{ybox_svc}' and reloading daemon", fgcolor.cyan)
|
1236
1289
|
with open(f"{systemd_dir}/{ybox_svc}", "w", encoding="utf-8") as svc_fd:
|
1237
1290
|
svc_fd.write(svc_content)
|
1238
|
-
with open(ybox_env, "w", encoding="utf-8") as env_fd:
|
1291
|
+
with open(f"{systemd_dir}/{ybox_env}", "w", encoding="utf-8") as env_fd:
|
1239
1292
|
env_fd.write(dedent(env_content.format(sleep_secs=0))) # don't sleep for the start below
|
1240
1293
|
run_command([systemctl, "--user", "daemon-reload"], exit_on_error=False)
|
1241
1294
|
run_command([systemctl, "--user", "enable", ybox_svc], exit_on_error=True)
|
1242
1295
|
print_info(wait_msg)
|
1243
1296
|
run_command([systemctl, "--user", "start", ybox_svc], exit_on_error=True)
|
1244
|
-
# change SLEEP_SECS to
|
1245
|
-
with open(ybox_env, "w", encoding="utf-8") as env_fd:
|
1246
|
-
env_fd.write(dedent(env_content.format(sleep_secs=
|
1297
|
+
# change SLEEP_SECS to 5 for subsequent starts
|
1298
|
+
with open(f"{systemd_dir}/{ybox_env}", "w", encoding="utf-8") as env_fd:
|
1299
|
+
env_fd.write(dedent(env_content.format(sleep_secs=5)))
|
1247
1300
|
|
1248
1301
|
|
1249
1302
|
def start_container(docker_cmd: str, conf: StaticConfiguration) -> None:
|
ybox/run/destroy.py
CHANGED
@@ -5,6 +5,7 @@ Code for the `ybox-destroy` script that is used to destroy an active or stopped
|
|
5
5
|
import argparse
|
6
6
|
import os
|
7
7
|
import shutil
|
8
|
+
import subprocess
|
8
9
|
import sys
|
9
10
|
|
10
11
|
from ybox.cmd import check_ybox_exists, parser_version_check, run_command
|
@@ -36,15 +37,12 @@ def main_argv(argv: list[str]) -> None:
|
|
36
37
|
check_ybox_exists(docker_cmd, container_name, exit_on_error=True)
|
37
38
|
print_color(f"Stopping ybox container '{container_name}'", fg=fgcolor.cyan)
|
38
39
|
# check if there is a systemd service for the container
|
39
|
-
|
40
|
-
ybox_svc = f"
|
41
|
-
|
42
|
-
if (systemctl := shutil.which("systemctl", path=os.pathsep.join(Consts.sys_bin_dirs()))) and \
|
43
|
-
not os.access(ybox_svc_path := f"{systemd_dir}/{ybox_svc}", os.R_OK):
|
44
|
-
ybox_svc_path = ""
|
40
|
+
ybox_svc_prefix = ybox_systemd_service_prefix(container_name)
|
41
|
+
ybox_svc = f"{ybox_svc_prefix}.service"
|
42
|
+
systemctl = check_systemd_service_present(ybox_svc)
|
45
43
|
|
46
44
|
# continue even if this fails since the container may already be in stopped state
|
47
|
-
if systemctl
|
45
|
+
if systemctl:
|
48
46
|
run_command([systemctl, "--user", "stop", ybox_svc],
|
49
47
|
exit_on_error=False, error_msg=f"stopping '{container_name}'")
|
50
48
|
else:
|
@@ -59,12 +57,16 @@ def main_argv(argv: list[str]) -> None:
|
|
59
57
|
run_command(rm_args, error_msg=f"removing '{container_name}'")
|
60
58
|
|
61
59
|
# remove systemd service file and reload daemon
|
62
|
-
if systemctl
|
60
|
+
if systemctl:
|
63
61
|
print_color(f"Removing systemd service '{ybox_svc}' and reloading daemon", fg=fgcolor.cyan)
|
64
62
|
run_command([systemctl, "--user", "disable", ybox_svc], exit_on_error=False)
|
65
|
-
|
63
|
+
systemd_dir = env.systemd_user_conf_dir()
|
64
|
+
try:
|
65
|
+
os.unlink(f"{systemd_dir}/{ybox_svc}")
|
66
|
+
except OSError:
|
67
|
+
pass
|
66
68
|
try:
|
67
|
-
os.unlink(f"{systemd_dir}/.
|
69
|
+
os.unlink(f"{systemd_dir}/.{ybox_svc_prefix}.env")
|
68
70
|
except OSError:
|
69
71
|
pass
|
70
72
|
run_command([systemctl, "--user", "daemon-reload"], exit_on_error=False)
|
@@ -97,6 +99,26 @@ def parse_args(argv: list[str]) -> argparse.Namespace:
|
|
97
99
|
return parser.parse_args(argv)
|
98
100
|
|
99
101
|
|
102
|
+
def ybox_systemd_service_prefix(container_name: str) -> str:
|
103
|
+
"""systemd service name prefix for given ybox container name"""
|
104
|
+
return container_name if container_name.startswith("ybox-") else f"ybox-{container_name}"
|
105
|
+
|
106
|
+
|
107
|
+
def check_systemd_service_present(user_svc: str) -> str:
|
108
|
+
"""
|
109
|
+
Check if the given user systemd service is present and return the PATH of system installed
|
110
|
+
`systemctl` tool if true, else return empty string.
|
111
|
+
|
112
|
+
:param user_svc: name the user systemd service file
|
113
|
+
:return: full path of `systemctl` if installed and user systemd service is available else empty
|
114
|
+
"""
|
115
|
+
if (systemctl := shutil.which("systemctl", path=os.pathsep.join(Consts.sys_bin_dirs()))) and \
|
116
|
+
subprocess.run([systemctl, "--user", "--quiet", "list-unit-files", user_svc],
|
117
|
+
check=False, capture_output=True).returncode == 0:
|
118
|
+
return systemctl
|
119
|
+
return ""
|
120
|
+
|
121
|
+
|
100
122
|
def get_all_containers(docker_cmd: str) -> list[str]:
|
101
123
|
"""
|
102
124
|
Get all the valid containers as known to the container manager.
|
ybox/run/graphics.py
CHANGED
@@ -20,7 +20,8 @@ _STD_LIB_DIR_PATTERNS = ["&/usr/lib/*-linux-gnu", "&/lib/*-linux-gnu", "&/usr/li
|
|
20
20
|
"&/lib64/*-linux-gnu", "&/usr/lib32/*-linux-gnu", "&/lib32/*-linux-gnu"]
|
21
21
|
_STD_LD_LIB_PATH_VARS = ["LD_LIBRARY_PATH", "LD_LIBRARY_PATH_64", "LD_LIBRARY_PATH_32"]
|
22
22
|
_NVIDIA_LIB_PATTERNS = ["*nvidia*.so*", "*NVIDIA*.so*", "libcuda*.so*", "libnvcuvid*.so*",
|
23
|
-
"libnvoptix*.so*", "gbm/*nvidia*.so*", "vdpau/*nvidia*.so*"
|
23
|
+
"libnvoptix*.so*", "gbm/*nvidia*.so*", "vdpau/*nvidia*.so*",
|
24
|
+
"libXNVCtrl.so*"]
|
24
25
|
_NVIDIA_BIN_PATTERNS = ["nvidia-smi", "nvidia-cuda*", "nvidia-debug*", "nvidia-bug*"]
|
25
26
|
# note that the code below assumes that file name pattern below is always of the form *nvidia*
|
26
27
|
# (while others are directories), so if that changes then update _process_nvidia_data_files
|
@@ -45,7 +46,8 @@ def add_env_option(docker_args: list[str], env_var: str, env_val: Optional[str]
|
|
45
46
|
docker_args.append(f"-e={env_var}={env_val}")
|
46
47
|
|
47
48
|
|
48
|
-
def add_mount_option(docker_args: list[str], src: str, dest: str, flags: str = ""
|
49
|
+
def add_mount_option(docker_args: list[str], src: str, dest: str, flags: str = "",
|
50
|
+
check_exists: bool = False) -> None:
|
49
51
|
"""
|
50
52
|
Add option to the list of podman/docker arguments to bind mount a source directory to
|
51
53
|
given destination directory.
|
@@ -54,11 +56,40 @@ def add_mount_option(docker_args: list[str], src: str, dest: str, flags: str = "
|
|
54
56
|
:param src: the source directory in the host system
|
55
57
|
:param dest: the destination directory in the container
|
56
58
|
:param flags: any additional flags to be passed to `-v` podman/docker argument, defaults to ""
|
59
|
+
:param check_exists: check if the bind mount was already added (and skip if so)
|
57
60
|
"""
|
58
|
-
if flags:
|
59
|
-
|
61
|
+
mount_arg = f"-v={src}:{dest}:{flags}" if flags else f"-v={src}:{dest}"
|
62
|
+
if not check_exists or mount_arg not in docker_args:
|
63
|
+
docker_args.append(mount_arg)
|
64
|
+
|
65
|
+
|
66
|
+
def handle_variable_mount(docker_args: list[str], env: Environ, mount_path: str) -> str:
|
67
|
+
"""
|
68
|
+
Handle the case where a mount point may change in different starts or even within the same
|
69
|
+
started container instance. In these cases the "base" directory of the mount point is
|
70
|
+
mounted instead which should normally be `/tmp` or `$XDG_RUNTIME_DIR`. The variable values
|
71
|
+
are assumed to lie between these two, or the parent directory of the mount point if it does
|
72
|
+
not lie within these two base directories. The actual passing of the required environment
|
73
|
+
variable (that can change) is handled by the `run-in-dir` script that will adjust the variable
|
74
|
+
value to reflect that mount point added by this method.
|
75
|
+
|
76
|
+
:param docker_args: list of podman/docker arguments to which the options have to be appended
|
77
|
+
:param env: an instance of the current :class:`Environ`
|
78
|
+
:param mount_path: the variable path which is usually the value of an environment variable
|
79
|
+
:return: the result mount point inside the container for the `mount_path`
|
80
|
+
"""
|
81
|
+
base_dir = os.path.dirname(mount_path)
|
82
|
+
# check if parent_dir is in $XDG_RUNTIME_DIR or /tmp
|
83
|
+
if not env.xdg_rt_dir:
|
84
|
+
base_dirs = {base_dir, "/tmp"}
|
85
|
+
elif mount_path.startswith(env.xdg_rt_dir + "/") or mount_path.startswith("/tmp/"):
|
86
|
+
base_dirs = (env.xdg_rt_dir, "/tmp")
|
87
|
+
base_dir = "/tmp" if base_dir.startswith("/tmp") else env.xdg_rt_dir
|
60
88
|
else:
|
61
|
-
|
89
|
+
base_dirs = (base_dir, env.xdg_rt_dir, "/tmp")
|
90
|
+
for b_dir in base_dirs:
|
91
|
+
add_mount_option(docker_args, b_dir, f"{b_dir}-host", "ro", check_exists=True)
|
92
|
+
return mount_path.replace(base_dir, f"{base_dir}-host")
|
62
93
|
|
63
94
|
|
64
95
|
def enable_x11(docker_args: list[str], env: Environ) -> None:
|
@@ -82,18 +113,7 @@ def enable_x11(docker_args: list[str], env: Environ) -> None:
|
|
82
113
|
# parent can cause trouble if one changes the display manager, for example, which
|
83
114
|
# uses an entirely different mount point (e.g. gdm uses /run/user/... while sddm
|
84
115
|
# uses /tmp)
|
85
|
-
|
86
|
-
# check if parent_dir is in $XDG_RUNTIME_DIR or /tmp
|
87
|
-
if not env.xdg_rt_dir:
|
88
|
-
parent_dirs = {parent_dir, "/tmp"}
|
89
|
-
elif xauth.startswith(f"{env.xdg_rt_dir}/") or xauth.startswith("/tmp/"):
|
90
|
-
parent_dirs = (env.xdg_rt_dir, "/tmp")
|
91
|
-
parent_dir = "/tmp" if parent_dir.startswith("/tmp") else env.xdg_rt_dir
|
92
|
-
else:
|
93
|
-
parent_dirs = (parent_dir, env.xdg_rt_dir, "/tmp")
|
94
|
-
for p_dir in parent_dirs:
|
95
|
-
add_mount_option(docker_args, p_dir, f"{p_dir}-host", "ro")
|
96
|
-
target_xauth = xauth.replace(parent_dir, f"{parent_dir}-host")
|
116
|
+
target_xauth = handle_variable_mount(docker_args, env, xauth)
|
97
117
|
add_env_option(docker_args, "XAUTHORITY", target_xauth)
|
98
118
|
add_env_option(docker_args, "XAUTHORITY_ORIG", target_xauth)
|
99
119
|
|
ybox/run/pkg.py
CHANGED
@@ -392,11 +392,11 @@ def add_mark(subparser: argparse.ArgumentParser) -> None:
|
|
392
392
|
subparser.add_argument("-e", "--explicit", action="store_true",
|
393
393
|
help="mark the package as explicitly installed; the package will "
|
394
394
|
"henceforth be managed by `ybox-pkg` if not already; "
|
395
|
-
"exactly one of -e or -
|
396
|
-
subparser.add_argument("-
|
395
|
+
"exactly one of -e or -d option must be specified")
|
396
|
+
subparser.add_argument("-d", "--dependency-of", type=str,
|
397
397
|
help="mark the package as a dependency of given package; both the "
|
398
398
|
"packages will henceforth be managed by `ybox-pkg` if not "
|
399
|
-
"already; exactly one of -e or -
|
399
|
+
"already; exactly one of -e or -d option must be specified")
|
400
400
|
subparser.add_argument("package", type=str, help="the package to be marked")
|
401
401
|
subparser.set_defaults(func=mark_package)
|
402
402
|
|
@@ -1,6 +1,6 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.4
|
2
2
|
Name: ybox
|
3
|
-
Version: 0.9.
|
3
|
+
Version: 0.9.11
|
4
4
|
Summary: Securely run Linux distribution inside a container
|
5
5
|
Author-email: Sumedh Wale <sumwale@yahoo.com>, Vishal Rao <vishalrao@gmail.com>
|
6
6
|
License: Copyright (c) 2024-2025 Sumedh Wale and contributors
|
@@ -25,7 +25,7 @@ License: Copyright (c) 2024-2025 Sumedh Wale and contributors
|
|
25
25
|
|
26
26
|
Project-URL: Homepage, https://github.com/sumwale/ybox
|
27
27
|
Project-URL: Issues, https://github.com/sumwale/ybox/issues
|
28
|
-
Keywords: Linux in container,toolbox
|
28
|
+
Keywords: Linux in container,toolbox,distrobox
|
29
29
|
Classifier: Development Status :: 4 - Beta
|
30
30
|
Classifier: Intended Audience :: End Users/Desktop
|
31
31
|
Classifier: License :: OSI Approved :: MIT License
|
@@ -42,6 +42,7 @@ License-File: LICENSE
|
|
42
42
|
Requires-Dist: packaging
|
43
43
|
Requires-Dist: simple-term-menu
|
44
44
|
Requires-Dist: tabulate>=0.9.0
|
45
|
+
Dynamic: license-file
|
45
46
|
|
46
47
|
## Introduction
|
47
48
|
|
@@ -99,7 +100,7 @@ So, for example, if you want to run the latest and greatest Intellij IDEA commun
|
|
99
100
|
to do is:
|
100
101
|
|
101
102
|
```sh
|
102
|
-
# create an Arch Linux based container
|
103
|
+
# create an Arch Linux based container and generate systemd service file (if possible)
|
103
104
|
ybox-create arch
|
104
105
|
# then select an appropriate built-in profile e.g. "dev.ini" from the menu
|
105
106
|
|
@@ -197,6 +198,8 @@ to point to the full path of the podman or docker executable.
|
|
197
198
|
ybox-create
|
198
199
|
```
|
199
200
|
|
201
|
+
By default this will also generate a user systemd service if possible (add `-S` or
|
202
|
+
`--skip-systemd-service` option to skip creation of a user systemd service).
|
200
203
|
This will allow choosing from supported distributions, then from the available profiles.
|
201
204
|
You can start with the Arch Linux distribution and `apps.ini` profile to try it out. The container
|
202
205
|
will have a name like `ybox-<distribution>_<profile>` by default like `ybox-arch_apps` for the
|
@@ -269,7 +272,7 @@ ybox-pkg list -o
|
|
269
272
|
```
|
270
273
|
To show more details of the packages (combine with -a/-o as required):
|
271
274
|
```sh
|
272
|
-
ybox-pkg list -
|
275
|
+
ybox-pkg list -v
|
273
276
|
```
|
274
277
|
|
275
278
|
List all the files installed by the package:
|
@@ -309,6 +312,8 @@ Clean package cache, temporary downloads etc:
|
|
309
312
|
```sh
|
310
313
|
ybox-pkg clean
|
311
314
|
```
|
315
|
+
Add `-q` option to answer yes for any questions automatically if all your containers use
|
316
|
+
the same shared root.
|
312
317
|
|
313
318
|
Mark a package as explicitly installed (also registers with `ybox-pkg` if not present):
|
314
319
|
```sh
|
@@ -317,7 +322,7 @@ ybox-pkg mark firefox -e
|
|
317
322
|
|
318
323
|
Mark a package as a dependency of another (also registers with `ybox-pkg` if not present):
|
319
324
|
```sh
|
320
|
-
ybox-pkg mark qt5ct -
|
325
|
+
ybox-pkg mark qt5ct -d zoom # mark qt5ct as an optional dependency of zoom
|
321
326
|
```
|
322
327
|
|
323
328
|
Repair package installation after a failure or interrupt:
|
@@ -437,26 +442,32 @@ for a ybox container. See the full set of options with `ybox-control -h/--help`.
|
|
437
442
|
### Auto-starting containers
|
438
443
|
|
439
444
|
Containers can be auto-started as per the usual way for rootless podman/docker services.
|
440
|
-
This is triggered by systemd on user login which is exactly what
|
445
|
+
This is triggered by systemd on user login which is exactly what is required for ybox
|
441
446
|
containers so that the container applications are available on login and are stopped on
|
442
|
-
session logout.
|
447
|
+
session logout. All the tested Linux distributions support this and provide for user
|
448
|
+
systemd daemon on user login.
|
443
449
|
|
444
|
-
|
445
|
-
|
446
|
-
|
450
|
+
The `ybox-create` command autogenerates the systemd service file (in absence of `-S` or
|
451
|
+
`--skip-systemd-service` option) which is also removed by `ybox-destroy` automatically.
|
452
|
+
The name of the generated service is `ybox-<NAME>` where `<NAME>` is the name of the
|
453
|
+
container if `<NAME>` does not start with `ybox-` prefix, else it is just `<NAME>`.
|
447
454
|
|
448
|
-
|
449
|
-
|
450
|
-
For podman you will need to explicitly generate systemd service file for each container and
|
451
|
-
copy to your systemd configuration directory since podman does not use a background daemon.
|
452
|
-
For the `ybox-arch_apps` container in the examples before:
|
455
|
+
With a user service installed, the `systemctl` commands can be used to control the
|
456
|
+
ybox container (`<SERVICE_NAME>` is `ybox-<NAME>/<NAME>` mentioned above):
|
453
457
|
|
454
458
|
```sh
|
455
|
-
|
456
|
-
|
457
|
-
systemctl --user
|
459
|
+
systemctl --user status <SERVICE_NAME> # show status of the service
|
460
|
+
systemctl --user stop <SERVICE_NAME> # stop the service
|
461
|
+
systemctl --user start <SERVICE_NAME> # start the service
|
458
462
|
```
|
459
463
|
|
464
|
+
If your Linux distribution does not use systemd, then the autostart has to be handled
|
465
|
+
manually as per the distribution's preferred way. For instance an appropriate desktop
|
466
|
+
file can be added to `~/.config/autostart` directory to start a ybox container on
|
467
|
+
graphical login, though performing a clean stop can be hard with this approach.
|
468
|
+
Note that the preferred way to start/stop a ybox container is using the `ybox-control`
|
469
|
+
command rather than directly using podman/docker.
|
470
|
+
|
460
471
|
|
461
472
|
## Development
|
462
473
|
|
@@ -1,7 +1,7 @@
|
|
1
|
-
ybox/__init__.py,sha256=
|
1
|
+
ybox/__init__.py,sha256=DJW4aoiXY2arxFg1eBQPfjHm19Ur_tXdLu332z66Esw,97
|
2
2
|
ybox/cmd.py,sha256=RaNZ7LBqUNwpqQkitR29WLoItjkMfZmaFEeryLTR_tM,14962
|
3
3
|
ybox/config.py,sha256=inmuUhlAZb6EKLGYWdymsqoHggtiLd58kc125l25ACA,9943
|
4
|
-
ybox/env.py,sha256=
|
4
|
+
ybox/env.py,sha256=6o0tJ163KWhVZHnhDRWw_16nMMYoy4-2yAHSJYBSL-0,9420
|
5
5
|
ybox/filelock.py,sha256=nWBp3jvbtrNziRzNcWm6FVVA_lhMccLwgLCVT2IDK5c,3185
|
6
6
|
ybox/print.py,sha256=hAQjTb6JmtjWh0sF4GdZHcKRph5iMKP5x23s8LE85q4,4343
|
7
7
|
ybox/state.py,sha256=QSAfa4LGy7oDZVe6muBXFIylKB7CMalv3OgEQ3VtmAo,50174
|
@@ -27,27 +27,27 @@ ybox/conf/distros/deb-oldstable/distro.ini,sha256=lr7140ftndEvs3Liavf3hg3v0qHHWA
|
|
27
27
|
ybox/conf/distros/deb-stable/distro.ini,sha256=xyQ31mLOpj3Z1MK-UYuc_9NfEkb7Gy10MdDKXMgnqDo,1100
|
28
28
|
ybox/conf/distros/ubuntu2204/distro.ini,sha256=3Pw1Q30USyKMUcHp_cvlqhwyU0FPo8O2AHWkgd0cdE4,1105
|
29
29
|
ybox/conf/distros/ubuntu2404/distro.ini,sha256=ra4_EteDsHrw9UcehP4qLGTn98gAHc4JCb56CDzz1Wo,1102
|
30
|
-
ybox/conf/profiles/apps.ini,sha256=
|
31
|
-
ybox/conf/profiles/basic.ini,sha256=
|
32
|
-
ybox/conf/profiles/dev.ini,sha256=
|
30
|
+
ybox/conf/profiles/apps.ini,sha256=vxVVy9oUw31to8CaagNZj9MQpGHIK2xARN4nzHy4vZ0,1270
|
31
|
+
ybox/conf/profiles/basic.ini,sha256=FGSDOooI4meXhQlwFpPo_RxZ62it3bvdjqWkpVVDcA4,19713
|
32
|
+
ybox/conf/profiles/dev.ini,sha256=Qicas_96ZoG3nsm1MSE1urarlBzIj9kBGmUqrk_cD3g,976
|
33
33
|
ybox/conf/profiles/games.ini,sha256=FiBILNFOMpjKhrIR9nPbPj9QDUeI5h9wZaNXIb6Z7dc,1792
|
34
34
|
ybox/conf/resources/entrypoint-base.sh,sha256=hcW8ZLHM-jlHx7McoKTo8HIFz_VBBPD0I3WqlX7LjHs,4890
|
35
35
|
ybox/conf/resources/entrypoint-common.sh,sha256=fMopKBLeGuVV0ukANXh_ZHGTR1yGq108CTN_M2MNPyQ,461
|
36
|
-
ybox/conf/resources/entrypoint-cp.sh,sha256=
|
37
|
-
ybox/conf/resources/entrypoint-root.sh,sha256=
|
36
|
+
ybox/conf/resources/entrypoint-cp.sh,sha256=jemWFCqfme9blVtfVxqBzCsCp7b-bN2aFLajruF0GGQ,814
|
37
|
+
ybox/conf/resources/entrypoint-root.sh,sha256=58xcDX58ZtEFSr7ZSUFmzKt1OyGU29OrxjOmDiR6V8Q,706
|
38
38
|
ybox/conf/resources/entrypoint-user.sh,sha256=Tps_UyQGyGn30LbgKm3gpzZtjOHnJpnlKx-px2GAd7A,698
|
39
|
-
ybox/conf/resources/entrypoint.sh,sha256=
|
39
|
+
ybox/conf/resources/entrypoint.sh,sha256=jbfFfUprU7ycp0moZLXNdnxk_bhtDxAk6byEiPMNz-s,8560
|
40
40
|
ybox/conf/resources/prime-run,sha256=en8wEspc2Hzod9rq8KKnPQrjIPTLjUk44TqmtX-g0sw,339
|
41
|
-
ybox/conf/resources/run-in-dir,sha256
|
41
|
+
ybox/conf/resources/run-in-dir,sha256=-Xnwp4n6TK_2yPeBhGX6agenztyOtZ0316-vUlfOIag,2299
|
42
42
|
ybox/conf/resources/run-user-bash-cmd,sha256=LMlPoHtzYNDcOI885ouBha9xGRnQ6AWCFVsSu2dxy10,1065
|
43
|
-
ybox/conf/resources/ybox-systemd.template,sha256
|
44
|
-
ybox/migrate/0.9.0-0.9.
|
43
|
+
ybox/conf/resources/ybox-systemd.template,sha256=bA-bJOmc07Be-mQYtoFzl0yq4SJACv-FkAoKscPTh3g,779
|
44
|
+
ybox/migrate/0.9.0-0.9.10:0.9.11.py,sha256=WHvIWvUhXnruzSYwWYnv2zPNOlkXfAyaaJ_nZwG2wwE,1627
|
45
45
|
ybox/pkg/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
46
46
|
ybox/pkg/clean.py,sha256=UlsrJLfZOyKg-7BE8s9xPvAyuUXSwvU5x-E7MMjGJSE,1219
|
47
47
|
ybox/pkg/info.py,sha256=HoEsiAi-iPEnAOWBMy9SsA4TOfyqHonRURU8k5EeAWA,1607
|
48
|
-
ybox/pkg/inst.py,sha256=
|
48
|
+
ybox/pkg/inst.py,sha256=wxFJo1GNrUGyjIqtEtOc_44Ty7zdZKj-qHcs9RdZudQ,36912
|
49
49
|
ybox/pkg/list.py,sha256=Sk5THAmF132HKEZxJVDlqLLR8Z2w1wRA_E-gBsxlesQ,10097
|
50
|
-
ybox/pkg/mark.py,sha256=
|
50
|
+
ybox/pkg/mark.py,sha256=Hb1FaowdBx8x3GFcakXsq4ERNqI60Gjljr24qXsDGR8,3748
|
51
51
|
ybox/pkg/repair.py,sha256=rCwXXJbilJYU73feIIVWGFdbkh6SDcquxgiEnYV6pNs,8080
|
52
52
|
ybox/pkg/repo.py,sha256=25gQgGMPeHgX83iqqWXyG6V6rIZ4i_KEnIbNG-CrxTk,13677
|
53
53
|
ybox/pkg/search.py,sha256=GKfonxCLHtH-kojZpRJlRfe0qf768wUehVv20nM4lKY,2526
|
@@ -55,13 +55,13 @@ ybox/pkg/uninst.py,sha256=ndqZ_WYr9HE5jVL1tQ_tTmSPyTFM6OJRCTLLmG-Qm4w,5338
|
|
55
55
|
ybox/pkg/update.py,sha256=M-1MC8oh-baTHdSPWUUSTU_AhN3eu2nTCSge3bb6GmI,3269
|
56
56
|
ybox/run/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
57
57
|
ybox/run/cmd.py,sha256=a77RMC14Vovx2NV3LgdAzXVZXStC1TucgjcHPz4x03A,2105
|
58
|
-
ybox/run/control.py,sha256=
|
59
|
-
ybox/run/create.py,sha256=
|
60
|
-
ybox/run/destroy.py,sha256=
|
61
|
-
ybox/run/graphics.py,sha256=
|
58
|
+
ybox/run/control.py,sha256=Lvew2tqtWy5xcZMPXuh5sTrM2K92MykXfdMfBuKsIDc,7540
|
59
|
+
ybox/run/create.py,sha256=N2ULSoBsMWamBcm_JMLeZQdvMPhilhzFeM1aMkPDw7s,73062
|
60
|
+
ybox/run/destroy.py,sha256=i3N2zRCgrQM_lGA6W28O6Y-D1NIwBP64PA1e6vhOkL4,6315
|
61
|
+
ybox/run/graphics.py,sha256=mKQFU0la83Rv2V5l9TS25KIbqYmMnZjGQgGghLlfQp8,20309
|
62
62
|
ybox/run/logs.py,sha256=pIdMWgNBNl-MgixArbMryUuBNNbi5JvDFP62IZ7jwr8,2050
|
63
63
|
ybox/run/ls.py,sha256=7ylyxOOYEsVWK8baM0GaZcUlVQBwpdGiF7EhU09xf2s,2787
|
64
|
-
ybox/run/pkg.py,sha256=
|
64
|
+
ybox/run/pkg.py,sha256=6pes_wpVmPDiFYGfr8568GpyMByzor4dK5DSWaYmdsE,27219
|
65
65
|
ybox/schema/0.9.1-added.sql,sha256=1rGp2DczZmmC_xwjmheeZNPSbDpFzasu6LO3tpTy3zI,1049
|
66
66
|
ybox/schema/0.9.6-added.sql,sha256=Qcho6dP5OUpPUW3IBWl_kv88agMPHzueUAKqnZPnt3U,809
|
67
67
|
ybox/schema/init.sql,sha256=fei8lPvjb-EIjm5zuA_XkEdjsIE3vtROhgRPt7QMlSs,1599
|
@@ -69,9 +69,9 @@ ybox/schema/migrate/0.9.0:0.9.1.sql,sha256=e9JGwrjFZXdWKGv2JQZlKcWz8DmOuUARpToSs
|
|
69
69
|
ybox/schema/migrate/0.9.1:0.9.2.sql,sha256=X5J3unDS0eLeVvYKxQgx-iUBoAOk9T2suO34pWlQ-lE,362
|
70
70
|
ybox/schema/migrate/0.9.2:0.9.3.sql,sha256=Y7GeBSuEEs7Hs9hh-KYDARgeeMgwQwercvTB5P_-k6I,102
|
71
71
|
ybox/schema/migrate/0.9.5:0.9.6.sql,sha256=wqYhmputlUQzBI5zfP7O5kqIFWAbZQ05kolyHK4714A,70
|
72
|
-
ybox-0.9.
|
73
|
-
ybox-0.9.
|
74
|
-
ybox-0.9.
|
75
|
-
ybox-0.9.
|
76
|
-
ybox-0.9.
|
77
|
-
ybox-0.9.
|
72
|
+
ybox-0.9.11.dist-info/licenses/LICENSE,sha256=7GbFgERMXSwD1VyLA5bo_XHvJipmNEUhDW5Sy51TFeY,1077
|
73
|
+
ybox-0.9.11.dist-info/METADATA,sha256=64TAyPzQLQVg4KBazcBTshFP8OPYS5D1npAgIvu9QDQ,25772
|
74
|
+
ybox-0.9.11.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
75
|
+
ybox-0.9.11.dist-info/entry_points.txt,sha256=xDlI_84Hl3ytYO_ERyt0rkJ4ioUF8Z1r49Hx1xL28Rk,243
|
76
|
+
ybox-0.9.11.dist-info/top_level.txt,sha256=DYX7jvndHcBaJXLJ8vDyKrq0_KWoSeXXFq8r0d5L6Nk,5
|
77
|
+
ybox-0.9.11.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|