xiwrap

slightly higher-level container setup utility
git clone https://git.ce9e.org/xiwrap.git

commit
7a0d3fbe59a245b0ec17a6101aa70ec542f9c12a
parent
f5be3d47dc7f82732fe345c68ad16e3a936c9984
Author
Tobias Bengfort <tobias.bengfort@posteo.de>
Date
2024-06-16 21:06
improve expandvars

- support xdg-base-dirs
- resolve variables in correct context

Diffstat

M rules/base 1 +
M rules/gui 13 +++++++------
M xiwrap.py 68 ++++++++++++++++++++++++++++++++++++++++++++-----------------

3 files changed, 57 insertions, 25 deletions


diff --git a/rules/base b/rules/base

@@ -2,6 +2,7 @@ import env-lang
    2     2 setenv TERM
    3     3 setenv USER
    4     4 setenv HOME
   -1     5 setenv XDG_RUNTIME_DIR
    5     6 
    6     7 tmpfs /tmp
    7     8 dev /dev

diff --git a/rules/gui b/rules/gui

@@ -1,5 +1,6 @@
    1    -1 setenv HOME
    2     1 setenv XDG_RUNTIME_DIR
   -1     2 setenv XDG_DATA_HOME
   -1     3 setenv XDG_CACHE_HOME
    3     4 
    4     5 setenv DISPLAY
    5     6 setenv WAYLAND_DISPLAY
@@ -10,11 +11,11 @@ ro-bind-try /etc/fonts
   10    11 ro-bind-try /usr/share/fonts
   11    12 ro-bind-try /usr/share/icons
   12    13 ro-bind-try /usr/share/themes/
   13    -1 ro-bind-try $HOME/.local/share/fonts
   14    -1 ro-bind-try $HOME/.local/share/icons
   15    -1 ro-bind-try $HOME/.local/share/themes
   16    -1 bind-try $HOME/.cache/fontconfig
   17    -1 bind-try $HOME/.cache/thumbnails
   -1    14 ro-bind-try $XDG_DATA_HOME/fonts
   -1    15 ro-bind-try $XDG_DATA_HOME/icons
   -1    16 ro-bind-try $XDG_DATA_HOME/themes
   -1    17 bind-try $XDG_CACHE_HOME/fontconfig
   -1    18 bind-try $XDG_CACHE_HOME/thumbnails
   18    19 
   19    20 dbus-talk org.a11y.Bus
   20    21 

diff --git a/xiwrap.py b/xiwrap.py

@@ -1,17 +1,8 @@
    1     1 import os
    2     2 import subprocess
    3     3 import sys
    4    -1 from os.path import expandvars
    5     4 from pathlib import Path
    6     5 
    7    -1 XDG_RUNTIME_DIR = Path(os.getenv('XDG_RUNTIME_DIR'))
    8    -1 XDG_CONFIG_HOME = Path(os.getenv('XDG_CONFIG_HOME', '~/.config')).expanduser()
    9    -1 
   10    -1 USER_CONFIG = XDG_CONFIG_HOME / 'xiwrap'
   11    -1 SYSTEM_CONFIG = Path('/etc') / 'xiwrap'
   12    -1 
   13    -1 DBUS_PROXY_PATH = XDG_RUNTIME_DIR / f'dbus-proxy-{os.getpid()}'
   14    -1 
   15     6 USAGE = """Usage: xiwrap [OPTION]... -- CMD
   16     7 
   17     8 Example: xiwrap --import host-os --setenv TERM -- bash
@@ -48,6 +39,9 @@ The following options are available:
   48    39                         Empty lines or lines starting with # are ignored.
   49    40 """
   50    41 
   -1    42 DBUS_SRC = f'{os.environ["XDG_RUNTIME_DIR"]}/dbus-proxy-{os.getpid()}'
   -1    43 DBUS_DEST = '$XDG_RUNTIME_DIR/bus'
   -1    44 
   51    45 
   52    46 class RuleError(ValueError):
   53    47     def __init__(self, key, args):
@@ -55,6 +49,38 @@ class RuleError(ValueError):
   55    49         super().__init__(f'Invalid rule: {rule}')
   56    50 
   57    51 
   -1    52 def expandvars(path, env):
   -1    53     # https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
   -1    54 
   -1    55     if not path.startswith('$'):
   -1    56         return path
   -1    57 
   -1    58     for var, default in [
   -1    59         ('HOME', None),
   -1    60         ('XDG_DATA_HOME', '.local/share'),
   -1    61         ('XDG_CONFIG_HOME', '.config'),
   -1    62         ('XDG_STATE_HOME', '.local/state'),
   -1    63         ('XDG_CACHE_HOME', '.cache'),
   -1    64         ('XDG_RUNTIME_DIR', None),
   -1    65     ]:
   -1    66         if path.startswith(f'${var}'):
   -1    67             if env.get(var):
   -1    68                 head = Path(env.get(var))
   -1    69             elif default is not None:
   -1    70                 head = Path(env.get('HOME')) / default
   -1    71             else:
   -1    72                 raise ValueError(
   -1    73                     f'Invalid path {path}: {var} is not defined in this context.'
   -1    74                 )
   -1    75             if '/' in path:
   -1    76                 tail = path.removeprefix(f'${var}/')
   -1    77                 return str(head / tail)
   -1    78             else:
   -1    79                 return str(head)
   -1    80 
   -1    81     raise ValueError(path)
   -1    82 
   -1    83 
   58    84 class RuleSet:
   59    85     def __init__(self):
   60    86         self.env = {}
@@ -64,13 +90,15 @@ class RuleSet:
   64    90         self.sync_fds = None
   65    91         self.debug = False
   66    92         self.usage = False
   -1    93         self.userconfig = Path(expandvars('$XDG_CONFIG_HOME/xiwrap', os.environ))
   -1    94         self.sysconfig = Path('/etc/xiwrap')
   67    95 
   68    96     def find_config_file(self, name, cwd):
   69    97         if name.startswith('/'):
   70    98             return Path(name)
   71    99         elif name.startswith('~'):
   72   100             return Path(name).expanduser()
   73    -1         for base in [cwd, USER_CONFIG, SYSTEM_CONFIG]:
   -1   101         for base in [cwd, self.userconfig, self.sysconfig]:
   74   102             path = base / name
   75   103             if path.exists():
   76   104                 return path
@@ -96,9 +124,7 @@ class RuleSet:
   96   124         if self.sync_fds is not None:
   97   125             return
   98   126         self.sync_fds = os.pipe2(0)
   99    -1         bus = str(XDG_RUNTIME_DIR / 'bus')
  100    -1         self.paths[bus] = ('ro-bind', str(DBUS_PROXY_PATH))
  101    -1         self.env['DBUS_SESSION_BUS_ADDRESS'] = f'unix:path={bus}'
   -1   127         self.push_rule('ro-bind', [DBUS_SRC, DBUS_DEST], cwd=None)
  102   128 
  103   129     def push_rule(self, key, args, *, cwd):
  104   130         if key == 'import':
@@ -129,11 +155,11 @@ class RuleSet:
  129   155             'ro-bind-try',
  130   156         ]:
  131   157             src, target = self.parse_path(key, args)
  132    -1             self.paths[expandvars(target)] = (key, expandvars(src))
   -1   158             self.paths[target] = (key, src)
  133   159         elif key in ['tmpfs', 'dev', 'proc', 'mqueue', 'dir']:
  134   160             if len(args) != 1:
  135   161                 raise RuleError(key, args)
  136    -1             self.paths[expandvars(args[0])] = (key, None)
   -1   162             self.paths[args[0]] = (key, None)
  137   163         else:
  138   164             raise RuleError(key, args)
  139   165 
@@ -178,18 +204,22 @@ class RuleSet:
  178   204         ]
  179   205         if self.sync_fds is not None:
  180   206             cmd += ['--sync-fd', str(self.sync_fds[0])]
   -1   207             bus = expandvars(DBUS_DEST, self.env)
   -1   208             cmd += ['--setenv', 'DBUS_SESSION_BUS_ADDRESS', f'unix:path={bus}']
  181   209         for key in ['share-ipc', 'share-pid', 'share-net']:
  182   210             if not self.share.get(key):
  183   211                 cmd.append(f'--un{key}')
  184   212         for key, value in self.env.items():
  185   213             if value is not None:
  186   214                 cmd += ['--setenv', key, value]
  187    -1         for target, value in sorted(self.paths.items()):
  188    -1             typ, src = value
   -1   215         for target, typ, src in sorted(
   -1   216             (expandvars(target, self.env), *value)
   -1   217             for target, value in self.paths.items()
   -1   218         ):
  189   219             if src is None:
  190   220                 cmd += [f'--{typ}', target]
  191   221             else:
  192    -1                 cmd += [f'--{typ}', src, target]
   -1   222                 cmd += [f'--{typ}', expandvars(src, os.environ), target]
  193   223         return cmd + bwrap_args
  194   224 
  195   225     def build_dbus(self):
@@ -199,7 +229,7 @@ class RuleSet:
  199   229             'xdg-dbus-proxy',
  200   230             f'--fd={self.sync_fds[1]}',
  201   231             os.getenv('DBUS_SESSION_BUS_ADDRESS'),
  202    -1             str(DBUS_PROXY_PATH),
   -1   232             DBUS_SRC,
  203   233             '--filter',
  204   234         ]
  205   235         for value, typ in sorted(self.dbus.items()):