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
         
     |