#!/usr/bin/env python3 import os import sys import yaml import time import glob import getpass import fileinput import subprocess def get_containers(): container_list = subprocess.run(['sudo', '-u', user, 'podman', 'ps', '-a', '--no-trunc', '--sort=created', '--format', '{{.Names}}'], stdout=subprocess.PIPE).stdout.decode('utf-8').strip().split('\n') try: with open(os.path.expanduser('~/.config/blend/config.yaml')) as config_file: data = yaml.safe_load(config_file) order = data['container_order'].copy() order.reverse() container_list.reverse() for i in container_list: if i.strip() not in order: order.insert(0, i) for i, o in enumerate(order): if o not in container_list: del order[i] return order except: return container_list def list_use_container_bin(): try: with open(os.path.expanduser('~/.config/blend/config.yaml')) as config_file: data = yaml.safe_load(config_file) return data['use_container_bins'] except: return [] def check_if_present(attr, desktop_str): for l in desktop_str: if l.startswith(attr + '='): return True return False def which(bin): results = [] for dir in os.environ.get('PATH').split(':'): if os.path.isdir(dir): if os.path.basename(bin) in os.listdir(dir): results.append(os.path.join(dir, os.path.basename(bin))) if results == []: return None return results def create_container_binaries(): _binaries = [] remove_binaries = [] for c in _list: c = c.strip() for i in con_get_output(c, '''find /usr/bin -type f -printf "%P\n" 2>/dev/null; find /usr/local/bin -type f -printf "%P\n" 2>/dev/null;''').split('\n'): i = i.strip() os.makedirs(os.path.expanduser( f'~/.local/bin/blend_{c}'), exist_ok=True) i_present = False orig_which_out = which(os.path.basename(i)) which_out = None if orig_which_out != None: which_out = orig_which_out.copy() try: which_out.remove(os.path.expanduser( f'~/.local/bin/blend_bin/{os.path.basename(i)}')) except ValueError: pass if which_out == []: which_out = None if which_out != None and os.path.basename(i) not in _exceptions: i_present = True if os.path.basename(i) != 'host-spawn' and i != '' and not i_present: with open(os.path.expanduser(f'~/.local/bin/blend_{c}/{os.path.basename(i)}.tmp'), 'w') as f: f.write('#!/bin/bash\n') f.write(f'# blend container: {c};{i}\n') if os.path.basename(i) in _exceptions: f.write(f'# EXCEPTION\n') f.write('[ -f /run/.containerenv ] && { if [[ -e "/usr/bin/' + os.path.basename(i) + '" ]] || [[ -e "/usr/local/bin/' + os.path.basename(i) + '" ]]; then if [[ -e "/usr/bin/' + os.path.basename(i) + '" ]]; then /usr/bin/' + os.path.basename( i) + ' "$@"; elif [[ -e "/usr/local/bin/' + os.path.basename(i) + '" ]]; then /usr/local/bin/' + os.path.basename(i) + ' "$@"; fi; exit $?; else echo "This command can be accessed from the host, or from the container \'' + c + '\'."; exit 127; fi } || :\n') f.write( f'BLEND_ALLOW_ROOT= BLEND_NO_CHECK= blend enter -cn {c} -- {os.path.basename(i)} "$@"\n') # XXX: make this bit fully atomic os.chmod(os.path.expanduser( f'~/.local/bin/blend_{c}/{os.path.basename(i)}.tmp'), 0o775) subprocess.call(['mv', os.path.expanduser(f'~/.local/bin/blend_{c}/{os.path.basename(i)}.tmp'), os.path.expanduser(f'~/.local/bin/blend_{c}/{os.path.basename(i)}')]) _binaries.append((c, os.path.basename(os.path.basename(i)))) os.makedirs(os.path.expanduser(f'~/.local/bin/blend_bin'), exist_ok=True) for c, i in _binaries: try: os.symlink(os.path.expanduser( f'~/.local/bin/blend_{c}/{i}'), os.path.expanduser(f'~/.local/bin/blend_bin/{i}')) except FileExistsError: if subprocess.call(['grep', '-q', f'^# container: {c};{i}$', os.path.expanduser(f'~/.local/bin/blend_bin/{i}')], shell=False): os.remove(os.path.expanduser(f'~/.local/bin/blend_bin/{i}')) os.symlink(os.path.expanduser( f'~/.local/bin/blend_{c}/{i}'), os.path.expanduser(f'~/.local/bin/blend_bin/{i}')) for i in remove_binaries: try: os.remove(i) except: pass for b in os.listdir(os.path.expanduser(f'~/.local/bin/blend_bin')): if [_b for _b in _binaries if _b[1] == b] == []: os.remove(os.path.join(os.path.expanduser( f'~/.local/bin/blend_bin'), b)) for b_dir in glob.glob(os.path.expanduser(f'~/.local/bin/blend_*')): if os.path.basename(b_dir) != 'blend_bin' and os.path.basename(b_dir)[6:] not in _list: subprocess.call(['rm', '-rf', b_dir], shell=False) def create_container_applications(): _apps = [] os.makedirs(os.path.expanduser( f'~/.local/share/applications'), exist_ok=True) for c in _list: c = c.strip() for i in con_get_output(c, 'find /usr/share/applications -type f 2>/dev/null; find /usr/local/share/applications -type f 2>/dev/null').split('\n'): orig_path = i.strip() i = os.path.basename(orig_path) i_present = (os.path.isfile(f'/usr/share/applications/{i}') or os.path.isfile(f'/usr/local/share/applications/{i}') or os.path.isfile(os.path.expanduser(f'~/.local/share/applications/{i}'))) if i != '' and not i_present: with open(os.path.expanduser(f'~/.local/share/applications/blend;{i}'), 'w') as f: _ = con_get_output( c, f"sudo sed -i '/^DBusActivatable=/d' {orig_path}") _ = con_get_output( c, f"sudo sed -i '/^TryExec=/d' {orig_path}") contents = con_get_output(c, f'cat {orig_path}') f.write(contents) for line in fileinput.input(os.path.expanduser(f'~/.local/share/applications/blend;{i}'), inplace=True): if line.strip().startswith('Exec='): line = f'Exec=env BLEND_NO_CHECK= blend enter -cn {c} -- {line[5:]}\n' elif line.strip().startswith('Icon='): if '/' in line: line = line.strip() _ = con_get_output( c, f"mkdir -p ~/.local/share/blend/icons/file/\"{c}_{i}\"; cp {line[5:]} ~/.local/share/blend/icons/file/\"{c}_{i}\"") line = f'Icon={os.path.expanduser("~/.local/share/blend/icons/file/" + c + "_" + i + "/" + os.path.basename(line[5:]))}\n' else: line = line.strip() icons = con_get_output(c, f'''find /usr/share/icons /usr/share/pixmaps /var/lib/flatpak/exports/share/icons \\ -type f -iname "*{line[5:]}*" 2> /dev/null | sort''').split('\r\n') _ = con_get_output( c, f"mkdir -p ~/.local/share/blend/icons/\"{c}_{i}\"; cp {icons[0]} ~/.local/share/blend/icons/\"{c}_{i}\"") line = f'Icon={os.path.expanduser("~/.local/share/blend/icons/" + c + "_" + i + "/" + os.path.basename(icons[0]))}\n' sys.stdout.write(line) os.chmod(os.path.expanduser( f'~/.local/share/applications/blend;{i}'), 0o775) _apps.append((c, i)) del _ for a in os.listdir(os.path.expanduser(f'~/.local/share/applications')): if a.startswith('blend;'): a = a.removeprefix('blend;') if [_a for _a in _apps if _a[1] == a] == []: os.remove(os.path.expanduser( f'~/.local/share/applications/blend;{a}')) def create_container_sessions(type='xsessions'): session_dir = f'/usr/share/{type}' os.makedirs('/usr/share/xsessions', exist_ok=True) for session in os.listdir(session_dir): if session.startswith(os.path.join(session_dir, 'blend-')): os.remove(os.path.join(session_dir, session)) for c in _list: c = c.strip() for i in con_get_output(c, f'find {session_dir} -type f 2>/dev/null').split('\n'): orig_path = i.strip() i = os.path.basename(orig_path) if i != '': with open(os.path.expanduser(f'{session_dir}/blend-{c};{i}'), 'w') as f: contents = con_get_output(c, f'cat {orig_path}') f.write(contents) for line in fileinput.input(os.path.expanduser(f'/{session_dir}/blend-{c};{i}'), inplace=True): if line.strip().startswith('Name'): name = line.split('=')[1] line = f'Name=Container {c}: {name}' elif line.strip().startswith('Exec='): line = f'Exec=blend enter -cn {c} -- {line[5:]}' elif line.strip().startswith('TryExec='): continue sys.stdout.write(line) os.chmod(os.path.expanduser( f'{session_dir}/blend-{c};{i}'), 0o775) def con_get_output(name, cmd): try: return subprocess.run(['sudo', '-u', user, 'podman', 'exec', '--user', getpass.getuser(), '-it', name, 'bash', '-c', cmd], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, timeout=5).stdout.decode('UTF-8').strip() except subprocess.TimeoutExpired: return '' user = getpass.getuser() try: user = sys.argv[2] except: pass for c in get_containers(): c = c.strip() subprocess.call(['podman', 'start', c]) while True: _list = get_containers() _exceptions = list_use_container_bin() create_container_binaries() create_container_applications() time.sleep(1)