- commit
- ff86b4e2c3c0ec9a7e3d85a25b73e54ce0381996
- parent
- d297ffebebcdf94a90596928552cc489959130c1
- Author
- Tobias Bengfort <tobias.bengfort@posteo.de>
- Date
- 2024-06-15 16:27
init
Diffstat
| A | rules/audio | 2 | ++ |
| A | rules/bin | 4 | ++++ |
| A | rules/graphics | 3 | +++ |
| A | rules/gtk | 4 | ++++ |
| A | rules/network | 3 | +++ |
| A | xiwrap.py | 174 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
6 files changed, 190 insertions, 0 deletions
diff --git a/rules/audio b/rules/audio
@@ -0,0 +1,2 @@ -1 1 env XDG_RUNTIME_DIR -1 2 ro-bind $XDG_RUNTIME_DIR/pulse
diff --git a/rules/bin b/rules/bin
@@ -0,0 +1,4 @@ -1 1 ro-bind /bin -1 2 ro-bind /lib -1 3 ro-bind /lib64 -1 4 ro-bind /usr
diff --git a/rules/graphics b/rules/graphics
@@ -0,0 +1,3 @@ -1 1 env WAYLAND_DISPLAY -1 2 env XDG_RUNTIME_DIR -1 3 ro-bind $XDG_RUNTIME_DIR/$WAYLAND_DISPLAY
diff --git a/rules/gtk b/rules/gtk
@@ -0,0 +1,4 @@ -1 1 import graphics -1 2 env HOME -1 3 ro-bind $HOME/.themes -1 4 ro-bind $HOME/.config/dconf
diff --git a/rules/network b/rules/network
@@ -0,0 +1,3 @@ -1 1 share-net -1 2 ro-bind /etc/resolv.conf -1 3 ro-bind /etc/ssl/certs/ca-certificates.crt
diff --git a/xiwrap.py b/xiwrap.py
@@ -0,0 +1,174 @@
-1 1 import os
-1 2 import sys
-1 3 from os.path import expandvars
-1 4 from pathlib import Path
-1 5
-1 6 USER_CONFIG = Path(os.getenv('XDG_CONFIG_HOME', '~/.config')) / 'xiwrap'
-1 7 SYSTEM_CONFIG = Path('/etc') / 'xiwrap'
-1 8
-1 9 USAGE = """Usage: xiwrap [OPTION]... -- [BWRAP_OPTIONS]... CMD
-1 10
-1 11 Example: xiwrap --import bin --env TERM -- --chdir /tmp bash
-1 12
-1 13 The following options are available:
-1 14
-1 15 -h, --help Print this message and exit
-1 16 --debug Print the bwrap command instead of executing it.
-1 17 --env VAR [VALUE] Set an environment variable. If VALUE is not provided,
-1 18 the value from the current environment is kept.
-1 19 --bind DEST [SRC] Bind mount the host path SRC on DEST. If SRC is not
-1 20 provided, it is the same as DEST.
-1 21 --ro-bind DEST [SRC] Bind mount the host path SRC readonly on DEST. If SRC
-1 22 is not provided, it is the same as DEST.
-1 23 --proc DEST Mount new procfs on DEST.
-1 24 --dev DEST Mount new dev on DEST.
-1 25 --tmpfs DEST Mount new tmpfs on DEST.
-1 26 --share-net Do not create new network namespace.
-1 27 --import FILE Load additional options from FILE. FILE can be an
-1 28 absolute path or relative to the current directory,
-1 29 $XDG_CONFIG_HOME/xiwrap/ or /etc/xiwrap/. FILE must
-1 30 contain one option per line, without the leading --.
-1 31 Empty lines or lines starting with # are ignored.
-1 32 """
-1 33
-1 34
-1 35 class RuleError(ValueError):
-1 36 def __init__(self, key, args):
-1 37 rule = ' '.join([key, *args])
-1 38 super().__init__(f'Invalid rule: {rule}')
-1 39
-1 40
-1 41 class RuleSet:
-1 42 def __init__(self):
-1 43 self.env = {}
-1 44 self.paths = {
-1 45 '/tmp': ('tmpfs', None),
-1 46 '/dev': ('dev', None),
-1 47 '/proc': ('proc', None),
-1 48 }
-1 49 self.dbus = set() # TODO
-1 50 self.share_net = False
-1 51 self.debug = False
-1 52 self.usage = False
-1 53
-1 54 def find_config_file(self, name, cwd):
-1 55 if name.startswith('/'):
-1 56 return Path(name)
-1 57 elif name.startswith('~'):
-1 58 return Path(name).expanduser()
-1 59 for base in [cwd, USER_CONFIG, SYSTEM_CONFIG]:
-1 60 path = base / name
-1 61 if path.exists():
-1 62 return path
-1 63 raise FileNotFoundError(name)
-1 64
-1 65 def parse_env(self, key, args):
-1 66 if len(args) == 2:
-1 67 return args[0], args[1]
-1 68 elif len(args) == 1:
-1 69 return args[0], os.getenv(args[0])
-1 70 else:
-1 71 raise RuleError(key, args)
-1 72
-1 73 def parse_path(self, key, args):
-1 74 if len(args) == 2:
-1 75 return args[0], args[1]
-1 76 elif len(args) == 1:
-1 77 return args[0], args[0]
-1 78 else:
-1 79 raise RuleError(key, args)
-1 80
-1 81 def push_rule(self, key, args, *, cwd):
-1 82 if key == 'import':
-1 83 if len(args) != 1:
-1 84 raise RuleError(key, args)
-1 85 path = self.find_config_file(args[0], cwd)
-1 86 self.read_config_file(path)
-1 87 elif key == 'share-net':
-1 88 if len(args) != 0:
-1 89 raise RuleError(key, args)
-1 90 self.share_net = True
-1 91 elif key == 'env':
-1 92 var, value = self.parse_env(key, args)
-1 93 self.env[var] = value
-1 94 elif key in ['ro-bind', 'bind']:
-1 95 src, target = self.parse_path(key, args)
-1 96 self.paths[expandvars(target)] = (key, expandvars(src))
-1 97 elif key in ['tmpfs', 'dev', 'proc']:
-1 98 if len(args) != 1:
-1 99 raise RuleError(key, args)
-1 100 self.paths[expandvars(args[0])] = (key, None)
-1 101 else:
-1 102 raise RuleError(key, args)
-1 103
-1 104 def read_config_file(self, path):
-1 105 with open(path) as fh:
-1 106 for lineno, line in enumerate(fh, start=1):
-1 107 line = line.strip()
-1 108 if not line or line.startswith('#'):
-1 109 continue
-1 110 try:
-1 111 parts = line.split()
-1 112 self.push_rule(parts[0], parts[1:], cwd=path.parent)
-1 113 except RuleError as e:
-1 114 raise SyntaxError(str(e), (path, lineno, 1, line)) from e
-1 115
-1 116 def read_argv(self, argv):
-1 117 key = None
-1 118 args = []
-1 119 for i, token in enumerate(argv):
-1 120 if token == '--':
-1 121 if key is not None:
-1 122 self.push_rule(key, args, cwd=Path.cwd())
-1 123 return argv[i + 1:]
-1 124 elif token in ['-h', '--help']:
-1 125 self.usage = True
-1 126 elif token == '--debug':
-1 127 self.debug = True
-1 128 elif token.startswith('--'):
-1 129 if key is not None:
-1 130 self.push_rule(key, args, cwd=Path.cwd())
-1 131 key = token.removeprefix('--')
-1 132 args = []
-1 133 else:
-1 134 args.append(token)
-1 135 raise ValueError('--')
-1 136
-1 137 def build(self, bwrap_args):
-1 138 cmd = [
-1 139 'bwrap',
-1 140 '--die-with-parent',
-1 141 '--clearenv',
-1 142 '--unshare-pid',
-1 143 ]
-1 144 if not self.share_net:
-1 145 cmd += ['--unshare-net']
-1 146 for key, value in self.env.items():
-1 147 if value is not None:
-1 148 cmd += ['--setenv', key, value]
-1 149 for target, value in sorted(self.paths.items()):
-1 150 typ, src = value
-1 151 if src is None:
-1 152 cmd += [f'--{typ}', target]
-1 153 else:
-1 154 cmd += [f'--{typ}', src, target]
-1 155 return cmd + bwrap_args
-1 156
-1 157
-1 158 if __name__ == '__main__':
-1 159 rules = RuleSet()
-1 160 try:
-1 161 tail = rules.read_argv(sys.argv)
-1 162 except SyntaxError as e:
-1 163 print(f'{e.filename}:{e.lineno}: {e.msg}', file=sys.stderr)
-1 164 sys.exit(1)
-1 165 except ValueError:
-1 166 print(USAGE)
-1 167 sys.exit(1)
-1 168 cmd = rules.build(tail)
-1 169 if rules.usage:
-1 170 print(USAGE)
-1 171 elif rules.debug:
-1 172 print(' '.join(cmd))
-1 173 else:
-1 174 os.execvp('/usr/bin/bwrap', cmd)