Code: Select all
#!/usr/bin/env python3
# Porteus VirtualBox builder
# Authors: Hamza & Brokenman
# Python port: Blaze
import os, sys, subprocess, re, tempfile, shutil, signal, time, fnmatch, tarfile, stat
import urllib.request, threading
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GLib, GdkPixbuf
VBOX = 'https://download.virtualbox.org/virtualbox'
IMGS = '/mnt/live/memory/images'
PKGDIR = '/var/lib/pkgtools/packages'
ICON = '/usr/share/pixmaps/porteus/vbox.png'
T = {
'en': {
'title': 'VirtualBox builder for Porteus', 'host': 'VirtualBox Host (for running VMs)',
'guest': 'Guest Additions (inside VM)',
'desc': 'Select a file or download a version.\nThe file will be converted to a Porteus module.',
'file:': 'Select file:', 'dl': 'Download latest version', 'browse': 'Browse',
'cancel': 'Cancel', 'ok': 'OK', 'close': 'Close', '_cancel': '_Cancel', '_open': '_Open',
'no_opt': 'Select file or download!', 'no_file': 'File not found',
'all': 'All files', 'f_iso': 'ISO images (*.iso)', 'f_vbox': 'VirtualBox files (*.run, *.iso)',
'no_ver': 'Could not determine version from filename.\nRename file to include version, e.g.:\n VBoxGuestAdditions_7.2.14.iso\n VirtualBox-7.2.14-Linux_amd64.run',
'no_link': 'Could not get link',
'downloading': 'Downloading', 'building': 'Building', 'preparing': 'Preparing environment...',
'installing': 'Installing VirtualBox...', 'copying': 'Copying files...',
'finalizing': 'Finalizing...', 'mkmod': 'Creating XZM module...',
'mnt_fail': 'Failed to mount ISO', 'no_additions': 'VBoxLinuxAdditions.run not found in ISO',
'ready': 'Module ready:', 'fail': 'Build failed',
'mv_act': 'Move and activate', 'open_dir': 'Open folder',
'moved': 'moved to', 'act_ok': 'successfully activated', 'act_ng': 'activation failed',
'mv_fail': 'Move error', 'act_fail': 'Activation failed',
'action': 'Choose action:', 'more': 'You can build more or close.',
'no_moddir': 'Modules folder not found', 'low_space': 'Low space in',
'need': 'Need', 'free': 'Free', 'root': 'Root required!', 'x11': 'X session required!',
'sel_ver': 'Select version', 'version': 'Version',
'installed': 'Installed VirtualBox version', 'na': 'n/a',
'dl_fail': 'Download failed', 'show_all': 'Show all versions',
'no_src': 'Kernel sources not found.\nPlease get and activate module:\n 06-crippled_sources-*.xzm\n(available on Porteus server or USB)',
'no_tools': 'Build tools not found (gcc/make).\nPlease get and activate module:\n 05-devel-*.xzm\n(available on Porteus server or USB)',
'stage_fail': 'Staging build failed. The live system was not modified.',
'extracting': 'Extracting installer...', 'compiling': 'Compiling kernel modules...',
'staging': 'Staging files...',
'reboot_guest': 'Recommended: reboot or log out/in.',
'reboot_host': 'Recommended: reboot or run init script.',
},
'ru': {
'title': 'Сборка VirtualBox для Porteus', 'host': 'Хост VirtualBox (для запуска ВМ)',
'guest': 'Гостевые дополнения (внутри ВМ)',
'desc': 'Выберите файл или загрузите версию.\nФайл будет преобразован в модуль Porteus.',
'file:': 'Выберите файл:', 'dl': 'Загрузить последнюю версию', 'browse': 'Указать файл',
'cancel': 'Отмена', 'ok': 'ОК', 'close': 'Закрыть', '_cancel': '_Отмена', '_open': '_Открыть',
'no_opt': 'Выберите файл или загрузку!', 'no_file': 'Файл не найден',
'all': 'Все файлы', 'f_iso': 'ISO-образы (*.iso)', 'f_vbox': 'Файлы VirtualBox (*.run, *.iso)',
'no_ver': 'Не удалось определить версию из имени файла.\nПереименуйте файл, включив версию, например:\n VBoxGuestAdditions_7.2.14.iso\n VirtualBox-7.2.14-Linux_amd64.run',
'no_link': 'Не удалось получить ссылку',
'downloading': 'Загрузка', 'building': 'Сборка', 'preparing': 'Подготовка окружения...',
'installing': 'Установка VirtualBox...', 'copying': 'Копирование файлов...',
'finalizing': 'Финализация...', 'mkmod': 'Создание модуля XZM...',
'mnt_fail': 'Не удалось смонтировать ISO', 'no_additions': 'VBoxLinuxAdditions.run не найден в ISO',
'ready': 'Модуль готов:', 'fail': 'Сборка не удалась',
'mv_act': 'Переместить и активировать', 'open_dir': 'Открыть папку',
'moved': 'перемещён в', 'act_ok': 'успешно активирован', 'act_ng': 'активация не удалась',
'mv_fail': 'Ошибка перемещения', 'act_fail': 'Ошибка активации',
'action': 'Выберите действие:', 'more': 'Можно собрать ещё или закрыть.',
'no_moddir': 'Папка модулей не найдена', 'low_space': 'Мало места в',
'need': 'Нужно', 'free': 'Свободно', 'root': 'Нужен root!', 'x11': 'Нужна X-сессия!',
'sel_ver': 'Выбор версии', 'version': 'Версия',
'installed': 'Установленная версия VirtualBox', 'na': 'н/д',
'dl_fail': 'Ошибка загрузки', 'show_all': 'Показывать все версии',
'no_src': 'Исходники ядра не найдены.\nПолучите и активируйте модуль:\n 06-crippled_sources-*.xzm\n(доступен на сервере Porteus или USB)',
'no_tools': 'Инструменты сборки не найдены (gcc/make).\nПолучите и активируйте модуль:\n 05-devel-*.xzm\n(доступен на сервере Porteus или USB)',
'stage_fail': 'Изолированная сборка не удалась. Живая система не была изменена.',
'extracting': 'Извлечение установщика...', 'compiling': 'Компиляция модулей ядра...',
'staging': 'Подготовка файлов...',
'reboot_guest': 'Рекомендуется перезагрузка или повторный вход.',
'reboot_host': 'Рекомендуется перезагрузка или запуск init-скрипта.',
}
}
def _(k): return T.get(os.environ.get('LANG', 'en')[:2], T['en']).get(k, k)
def safe_rm(p):
try: os.remove(p)
except: pass
def cmd(c, sh=False, t=60, env=None, input_data=None):
try:
kw = dict(shell=sh, capture_output=True, text=True, errors='replace', timeout=t, env=env)
if input_data is None:
kw['stdin'] = subprocess.DEVNULL
else:
kw['input'] = input_data
return subprocess.run(c, **kw)
except subprocess.TimeoutExpired:
return subprocess.CompletedProcess(c, 124, '', f'Timeout after {t}s')
except Exception as e:
return subprocess.CompletedProcess(c, 1, '', str(e))
def load_icon(size=48):
try: return GdkPixbuf.Pixbuf.new_from_file_at_size(ICON, size, size)
except: return None
def pver():
try:
m = re.search(r'(\d+\.\d+(?:\.\d+)?)', open('/etc/porteus-version').read().strip())
return f'v{m.group(1)}' if m else 'unknown'
except: return 'unknown'
def is_block(p):
try: return stat.S_ISBLK(os.stat(p).st_mode)
except: return False
def is_vbox_file(f, kind=None):
f = os.path.basename(f)
if not f.endswith('.xzm'): return False
if kind == 'guest': return f.startswith('vbox-guest-')
if kind == 'host': return f.startswith('VirtualBox-')
return f.startswith(('VirtualBox-', 'vbox-guest-'))
def find_vbox_mods(kind=None):
r = []
for d in (IMGS, os.environ.get('MODDIR', '')):
if d and os.path.isdir(d):
try: r.extend(os.path.join(d, f) for f in os.listdir(d) if is_vbox_file(f, kind))
except: pass
return r
def vbox_in_images(kind=None):
try: return os.path.isdir(IMGS) and any(is_vbox_file(f, kind) for f in os.listdir(IMGS))
except: return False
def deactivate_silent(m):
if os.path.isfile(m): cmd(['deactivate', m, '-q'], t=30)
def deactivate_all_wait(kind=None):
for m in find_vbox_mods(kind): deactivate_silent(m)
for _ in range(15):
if not vbox_in_images(kind): break
time.sleep(1)
def pkg_ver():
if not os.path.isdir(PKGDIR): return None, None
try:
for f in os.listdir(PKGDIR):
m = re.match(r'(?:VirtualBox|vbox-guest)-(\d+\.\d+\.\d+)', f)
if m: return m.group(1), f
except: pass
return None, None
def space_ok(p, mb=800):
try:
s = os.statvfs(p); free = s.f_bavail * s.f_frsize // (1024 * 1024)
return free >= mb, free
except: return True, 0
def is_vm():
try:
for p in ('/sys/class/dmi/id/product_name', '/sys/class/dmi/id/sys_vendor'):
if os.path.exists(p) and any(x in open(p).read() for x in ('VirtualBox', 'Oracle', 'innotek')):
return True
except: pass
return 'VirtualBox' in cmd(['/usr/sbin/dmidecode', '-s', 'system-product-name'], t=5).stdout
def fm_open(path):
try:
subprocess.Popen(['xdg-open', path], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
stdin=subprocess.DEVNULL, start_new_session=True)
return True
except: return False
def mod_dir():
d = os.environ.get('MODDIR', '')
if d and os.path.isdir(d): return d
for p in (IMGS, '/porteus/modules', '/mnt/sda1/porteus/modules'):
if os.path.isdir(p): return p
return None
def _unescape(s):
try: return s.encode('utf-8').decode('unicode_escape').encode('latin-1').decode('utf-8')
except: return s
def _mount_point_of(dev):
r = cmd(['findmnt', '-rn', '-o', 'TARGET', '-S', dev], t=5)
if r.returncode == 0 and r.stdout.strip():
return _unescape(r.stdout.strip().split('\n')[0])
try:
for line in open('/proc/mounts'):
parts = line.split()
if len(parts) >= 2 and os.path.realpath(parts[0]) == dev:
return _unescape(parts[1])
except: pass
return None
def find_ga_cd():
try:
by_label = '/dev/disk/by-label'
if os.path.isdir(by_label):
for name in sorted(os.listdir(by_label)):
if not name.startswith('VBox_GAs_'): continue
dev = os.path.realpath(os.path.join(by_label, name))
label = _unescape(name)
mp = _mount_point_of(dev)
if mp:
if os.path.exists(os.path.join(mp, 'VBoxLinuxAdditions.run')):
return mp, label
continue
if os.path.exists(dev): return dev, label
except: pass
try:
for d in sorted(os.listdir('/mnt')):
if d.startswith('VBox_GAs_'):
base = os.path.join('/mnt', d)
if os.path.exists(os.path.join(base, 'VBoxLinuxAdditions.run')):
return base, d
except: pass
try:
r = cmd(['blkid'], t=10)
if r.returncode == 0:
for line in r.stdout.splitlines():
if 'LABEL="VBox_GAs_' not in line: continue
dev = line.split(':')[0].strip()
m = re.search(r'LABEL="([^"]+)"', line)
label = m.group(1) if m else 'VBox_GAs'
mp = _mount_point_of(dev)
if mp:
if os.path.exists(os.path.join(mp, 'VBoxLinuxAdditions.run')):
return mp, label
continue
if os.path.exists(dev): return dev, label
except: pass
return None, None
def vbox_version_lists(mode='host', arch='x86_64'):
try:
vs = sorted(re.findall(r'href="(\d+\.\d+\.\d+)/"',
urllib.request.urlopen(f'{VBOX}/', timeout=15).read().decode()),
key=lambda v: [int(x) for x in v.split('.')], reverse=True)
# 32-bit hosts: max 5.2.44
max32 = (5, 2, 44) if mode == 'host' and arch not in ('x86_64', 'amd64') else None
all_out, latest, seen = [], [], set()
for v in vs:
try: parts = tuple(int(x) for x in v.split('.'))
except: continue
if max32 and parts > max32: continue
all_out.append(v)
key = '.'.join(v.split('.')[:2])
if key not in seen: seen.add(key); latest.append(v)
return latest, all_out
except: return [], []
def vbox_url(v, a, dt):
tg = 'amd64' if a == 'x86_64' else 'x86'
if dt == 'vbox':
try:
for l in re.findall(r'href="([^"]+\.run)"',
urllib.request.urlopen(f'{VBOX}/{v}/', timeout=10).read().decode()):
if tg in l: return f'{VBOX}/{v}/{l}'
except: pass
return f'{VBOX}/{v}/VirtualBox-{v}-Linux_{tg}.run'
return f'{VBOX}/{v}/VBoxGuestAdditions_{v}.iso'
def vbox_extpack_url(v):
try:
for l in re.findall(r'href="([^"]+\.vbox-extpack)"',
urllib.request.urlopen(f'{VBOX}/{v}/', timeout=10).read().decode()):
return f'{VBOX}/{v}/{l}'
except: pass
return None
def mount_iso(p, mnt):
args = ['mount', '-o', 'loop', p, mnt] if not is_block(p) else ['mount', p, mnt]
r = cmd(args, t=30)
return r.returncode == 0 and os.path.ismount(mnt)
def inst_ver():
try:
r = cmd(['VBoxManage', '--version'], t=5)
if r.returncode == 0:
m = re.match(r'(\d+\.\d+\.\d+)', r.stdout.strip())
if m: return m.group(0)
except: pass
v, _ = pkg_ver(); return v
def inst_short():
f = inst_ver(); m = re.match(r'(\d+\.\d+)', f) if f else None
return m.group(1) if m else None
def clean_tmp_files():
for f in ('/tmp/.message', '/tmp/.cdload', '/tmp/vboxguest-Module.symvers'): safe_rm(f)
def find_file(root, name):
for dp, dn, fn in os.walk(root):
if name in fn: return os.path.join(dp, name)
return None
def find_files_pattern(root, pattern):
return [os.path.join(dp, f) for dp, dn, fn in os.walk(root)
for f in fn if fnmatch.fnmatch(f.lower(), pattern.lower())]
def extract_tar_compat(tar, path):
try: tar.extractall(path, filter='data')
except TypeError: tar.extractall(path)
def normalize_perms(path):
# chmod the root itself too (mkdtemp leaves 0700)
try: os.chmod(path, 0o755)
except: pass
for root, dirs, files in os.walk(path):
for d in dirs:
try: os.chmod(os.path.join(root, d), 0o755)
except: pass
for f in files:
p = os.path.join(root, f)
try: os.chmod(p, 0o755 if os.stat(p).st_mode & 0o111 else 0o644)
except: pass
def strip_unwanted(path):
bad_dirs = {'sdk', 'doc', 'docs', 'manual', 'src', 'source', 'sources', 'dtrace',
'additions', 'unattendedtemplates', 'init'}
bad_files = ('*.cmd', '*.o', '*.mod', '*.mod.c', '*.order', '*.symvers', '*.c', '*.h',
'*.cpp', '*.cc', '*.s', '*.S', 'Makefile', 'Makefile.*', 'Kbuild', '*.debug',
'*.pdb', '*.sym', '*.map', '*.a', '*.la', '*.spec', 'UserManual.pdf',
'UserManual.qch', 'UserManual.qhc', 'postinst-common.sh', 'prerm-common.sh',
'routines.sh', 'check_module_dependencies.sh', 'VBox.sh', 'uninstall.sh',
'.autoreg', 'vboxshell.py', 'VBoxPython2.so', 'VBoxPython2_7.so', 'VBoxDTrace')
old_drv = ('vboxmouse_drv_13.so', 'vboxmouse_drv_14.so', 'vboxmouse_drv_15.so',
'vboxmouse_drv_16.so', 'vboxmouse_drv_70.so', 'vboxmouse_drv_71.so',
'vboxvideo_drv_13.so', 'vboxvideo_drv_14.so', 'vboxvideo_drv_15.so',
'vboxvideo_drv_16.so', 'vboxvideo_drv_17.so', 'vboxvideo_drv_18.so',
'vboxvideo_drv_19.so', 'vboxvideo_drv_70.so', 'vboxvideo_drv_71.so',
'vboxvideo_drv_110.so', 'vboxvideo_drv_111.so', 'vboxvideo_drv_112.so',
'vboxvideo_drv_113.so', 'vboxvideo_drv_114.so', 'vboxvideo_drv_115.so',
'vboxvideo_drv_116.so', 'vboxvideo_drv_117.so', 'vboxvideo_drv_118.so',
'x11config.sh', '98vboxadd-xclient', 'pam_vbox.so', 'vbox_accel.pp',
'vbox_x11.pp', 'VBoxAudioTest')
for root, dirs, files in os.walk(path, topdown=False):
for d in list(dirs):
if d.lower() in bad_dirs:
shutil.rmtree(os.path.join(root, d), ignore_errors=True); dirs.remove(d)
for f in files:
lf = f.lower()
if any(fnmatch.fnmatch(lf, p.lower()) for p in bad_files) or f in old_drv:
safe_rm(os.path.join(root, f))
for root, dirs, files in os.walk(path, topdown=False):
try:
if not os.listdir(root): os.rmdir(root)
except: pass
class Prog(Gtk.Dialog):
def __init__(self, par, t, m):
super().__init__(title=t, transient_for=par, modal=True)
self.set_deletable(False)
self.set_default_size(340, 110)
self.set_position(Gtk.WindowPosition.CENTER_ON_PARENT)
b = self.get_content_area(); b.set_spacing(12)
b.set_margin_top(20); b.set_margin_bottom(20)
try: b.set_margin_start(24); b.set_margin_end(24)
except: b.set_margin_left(24); b.set_margin_right(24)
self.lbl = Gtk.Label(label=m); self.lbl.set_line_wrap(True)
self.bar = Gtk.ProgressBar(); self.bar.set_pulse_step(0.08)
b.pack_start(self.lbl, False, False, 0)
b.pack_start(self.bar, False, False, 0)
self.show_all()
self._tid = GLib.timeout_add(80, self._tk)
self._d = False
def _tk(self):
if not self._d: self.bar.pulse(); return True
return False
def msg(self, m):
if not self._d: self.lbl.set_text(m)
def close(self):
if not self._d:
self._d = True
if self._tid: GLib.source_remove(self._tid)
self.destroy()
class VerDlg(Gtk.Dialog):
def __init__(self, par, latest, all_vs, iv):
super().__init__(title=_('sel_ver'), transient_for=par, modal=True)
self.set_default_size(360, 430)
self.set_position(Gtk.WindowPosition.CENTER_ON_PARENT)
b = self.get_content_area(); b.set_spacing(8)
b.set_margin_top(12); b.set_margin_bottom(12)
try: b.set_margin_start(16); b.set_margin_end(16)
except: b.set_margin_left(16); b.set_margin_right(16)
il = Gtk.Label(); il.set_markup(f'<b>{_("installed")}: {iv or _("na")}</b>')
il.set_halign(Gtk.Align.START); b.pack_start(il, False, False, 0)
self.latest, self.all = latest, all_vs
self.chk_all = Gtk.CheckButton(label=_('show_all'))
self.chk_all.connect('toggled', self._fill)
b.pack_start(self.chk_all, False, False, 0)
sw = Gtk.ScrolledWindow(); sw.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
self.st = Gtk.ListStore(str)
self.tv = Gtk.TreeView(model=self.st)
self.tv.append_column(Gtk.TreeViewColumn(_('version'), Gtk.CellRendererText(), text=0))
self.tv.set_headers_visible(True)
self.tv.get_selection().set_mode(Gtk.SelectionMode.SINGLE)
sw.add(self.tv); b.pack_start(sw, True, True, 0)
self._fill()
self.add_button(_('_cancel'), Gtk.ResponseType.CANCEL)
self.add_button(_('ok'), Gtk.ResponseType.OK)
self.show_all()
def _fill(self, *a):
self.st.clear()
for v in (self.all if self.chk_all.get_active() else self.latest):
self.st.append([v])
if len(self.st): self.tv.get_selection().select_path(Gtk.TreePath.new_first())
def chosen(self):
_, it = self.tv.get_selection().get_selected()
return self.st[it][0] if it else None
class App(Gtk.Window):
def __init__(self, cli_mode=None, cli_file=None):
super().__init__(title=_('title'))
self._icon = load_icon(48)
if self._icon: self.set_icon(self._icon)
self.set_border_width(12); self.set_resizable(True)
self.set_default_size(640, 360)
self.set_position(Gtk.WindowPosition.CENTER)
self.connect('destroy', Gtk.main_quit)
raw = os.uname().machine
self.arch = 'i586' if raw in ('i386', 'i486', 'i586', 'i686') else raw
self.lib = 'lib64' if self.arch == 'x86_64' else 'lib'
self.mode, self._prog = None, None
self.cd_src, self.cd_label = None, None
self.lang_codes = self._get_lang_codes()
self._ui()
if cli_mode == 'guest': self.rg.set_active(True); self.mode = 'guest'
elif cli_mode == 'host': self.rh.set_active(True); self.mode = 'host'
else:
self.mode = 'guest' if is_vm() else 'host'
if self.mode == 'guest': self.rg.set_active(True)
else: self.rh.set_active(True)
if self.mode == 'guest':
self.cd_src, self.cd_label = find_ga_cd()
if self.cd_src: self._set_path(self.cd_src)
if cli_file and os.path.isfile(cli_file): self._set_path(cli_file)
def _get_lang_codes(self):
codes = []
for var in ('LANGUAGE', 'LC_ALL', 'LC_MESSAGES', 'LANG'):
for item in os.environ.get(var, '').split(':'):
item = item.split('.')[0]
if item and item not in ('C', 'POSIX'):
codes.append(item)
if '_' in item: codes.append(item.split('_')[0])
return list(dict.fromkeys(codes))
def _ui(self):
vb = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12); self.add(vb)
hb = Gtk.Box(spacing=12)
if self._icon: hb.pack_start(Gtk.Image.new_from_pixbuf(self._icon), False, False, 0)
t = Gtk.Label(); t.set_markup(f'<span size="x-large" weight="bold">{_("title")}</span>')
hb.pack_start(t, True, True, 0)
vb.pack_start(hb, False, False, 0)
vb.pack_start(Gtk.Separator(), False, False, 0)
d = Gtk.Label(label=_('desc')); d.set_line_wrap(True); d.set_max_width_chars(80)
d.set_halign(Gtk.Align.START); vb.pack_start(d, False, False, 0)
rb = Gtk.Box(spacing=20)
self.rh = Gtk.RadioButton.new_with_label_from_widget(None, _('host'))
self.rh.connect('toggled', self._sm, 'host')
self.rg = Gtk.RadioButton.new_with_label_from_widget(self.rh, _('guest'))
self.rg.connect('toggled', self._sm, 'guest')
rb.pack_start(self.rh, False, False, 0)
rb.pack_start(self.rg, False, False, 0)
vb.pack_start(rb, False, False, 0)
fh = Gtk.Box(spacing=6)
fh.pack_start(Gtk.Label(label=_('file:')), False, False, 0)
self.ent = Gtk.Entry(); self.ent.set_editable(False); self.ent.set_width_chars(52)
fh.pack_start(self.ent, True, True, 0)
bb = Gtk.Button(label=_('browse')); bb.connect('clicked', self._br)
fh.pack_start(bb, False, False, 0)
vb.pack_start(fh, False, False, 0)
self.chk = Gtk.CheckButton(label=_('dl'))
self.chk.connect('toggled', lambda w: self.ent.set_sensitive(not w.get_active()))
vb.pack_start(self.chk, False, False, 0)
vb.pack_start(Gtk.Separator(), False, False, 4)
bh = Gtk.Box(spacing=8, halign=Gtk.Align.END)
bc = Gtk.Button(label=_('cancel')); bc.connect('clicked', lambda w: Gtk.main_quit())
bo = Gtk.Button(label=_('ok')); bo.connect('clicked', self._ok)
bh.pack_end(bo, False, False, 0)
bh.pack_end(bc, False, False, 0)
vb.pack_end(bh, False, False, 0)
self.show_all()
def _set_path(self, p):
p = p or ''
self.ent.set_text(p); self.ent.set_tooltip_text(p)
def _sm(self, b, m):
if b.get_active():
self.mode = m
self.ent.set_text(''); self.ent.set_tooltip_text('')
def _br(self, w):
d = Gtk.FileChooserDialog(title=_('browse'), parent=self, action=Gtk.FileChooserAction.OPEN)
d.add_buttons(_('_cancel'), Gtk.ResponseType.CANCEL, _('_open'), Gtk.ResponseType.OK)
fa = Gtk.FileFilter(); fa.set_name(_('all')); fa.add_pattern('*'); d.add_filter(fa)
pt, nm = ('*.iso', _('f_iso')) if self.mode == 'guest' else ('*.run;*.iso', _('f_vbox'))
ff = Gtk.FileFilter(); ff.set_name(nm)
for p in pt.split(';'): ff.add_pattern(p)
d.add_filter(ff); d.set_filter(ff); d.set_current_folder('/mnt')
if d.run() == Gtk.ResponseType.OK:
self._set_path(d.get_filename()); self.chk.set_active(False)
d.destroy()
def _ok(self, w):
if self.chk.get_active(): return self._pick()
p = self.ent.get_text().strip()
if not p: self._err(_('no_opt'))
elif not os.path.exists(p): self._err(_('no_file'))
else: self._set_path(p); self._build(p, False)
def _msg(self, mt, tx):
def f():
bt = Gtk.ButtonsType.OK if mt == Gtk.MessageType.INFO else Gtk.ButtonsType.CLOSE
d = Gtk.MessageDialog(transient_for=self, modal=True, message_type=mt, buttons=bt, text=tx)
d.run(); d.destroy(); return False
GLib.idle_add(f)
def _err(self, m): self._msg(Gtk.MessageType.ERROR, m)
def _info(self, m): self._msg(Gtk.MessageType.INFO, m)
def _po(self, t, m):
def f():
if self._prog: self._prog.close()
self._prog = Prog(self, t, m); return False
GLib.idle_add(f)
def _pm(self, m): GLib.idle_add(lambda: (self._prog and self._prog.msg(m)) or False)
def _pc(self):
def f():
if self._prog: self._prog.close(); self._prog = None
return False
GLib.idle_add(f)
def _thread(self, fn, *args):
threading.Thread(target=fn, args=args, daemon=True).start()
def _pick(self):
self._po(_('downloading'), _('downloading'))
def w():
latest, all_vs = vbox_version_lists(self.mode, self.arch)
iv = inst_short()
GLib.idle_add(self._after_pick, latest, all_vs, iv)
self._thread(w)
def _after_pick(self, latest, all_vs, iv):
self._pc()
if latest: self._verdlg(latest, all_vs, iv)
else: self._err(_('no_link'))
def _verdlg(self, latest, all_vs, iv):
d = VerDlg(self, latest, all_vs, iv)
r = d.run(); v = d.chosen(); d.destroy()
if r == Gtk.ResponseType.OK and v: self._dlver(v)
def _dlver(self, ver):
self._po(_('downloading'), _('downloading'))
def w():
url = vbox_url(ver, self.arch, 'guest' if self.mode == 'guest' else 'vbox')
if not url:
GLib.idle_add(self._err, _('no_link')); GLib.idle_add(self._pc); return
fn = os.path.basename(url)
dest = os.path.join('/tmp', fn)
dl = False
if not (os.path.exists(dest) and os.path.getsize(dest) > 1048576):
self._pm(f'{_("downloading")} {fn}...')
try:
with urllib.request.urlopen(url, timeout=600) as r, open(dest, 'wb') as f:
shutil.copyfileobj(r, f)
dl = True
except Exception as e:
safe_rm(dest)
GLib.idle_add(self._err, f'{_("dl_fail")}: {e}')
GLib.idle_add(self._pc); return
GLib.idle_add(self._pc)
GLib.idle_add(self._set_path, dest)
GLib.idle_add(self._build, dest, dl)
self._thread(w)
def _build(self, fp, dl=False):
self._po(_('building'), _('preparing'))
def w():
try: self._do(fp, dl)
except Exception as e:
GLib.idle_add(self._pc)
GLib.idle_add(self._err, f'{_("fail")}: {e}')
self._thread(w)
def _file_version(self, fn):
m = re.search(r'(?:VBoxGuestAdditions[_-]|VirtualBox-|VBox_GAs_)(\d+\.\d+\.\d+(?:[-_.][^\s/\\]+)?)', fn)
if m:
clean = re.search(r'(\d+\.\d+\.\d+)', m.group(1))
if clean: return clean.group(1)
m = re.search(r'(\d+\.\d+\.\d+)', fn)
return m.group(1) if m else None
def _mki(self, bd):
if self.mode == 'guest':
ip, ln = os.path.join(bd, 'etc/rc.d/init.d/rc.vbox-add'), 'rc.vbox-add'
body = '#!/bin/sh\n/sbin/modprobe vboxguest 2>/dev/null\n/sbin/modprobe vboxsf 2>/dev/null\n/sbin/modprobe vboxvideo 2>/dev/null\n/usr/sbin/VBoxService\n'
else:
ip, ln = os.path.join(bd, 'etc/rc.d/init.d/rc.virtualbox'), 'rc.virtualbox'
body = (
'#!/bin/sh\n'
'grep -q "^vboxusers:" /etc/group || groupadd -g 1001 vboxusers\n'
'id guest >/dev/null 2>&1 && usermod -aG vboxusers guest 2>/dev/null\n'
'/sbin/depmod -a\n'
'/sbin/modprobe vboxdrv\n'
'/sbin/modprobe vboxnetadp\n'
'/sbin/modprobe vboxnetflt\n'
'mkdir -p /dev/vboxusb\n'
'chmod 0750 /dev/vboxusb\n'
'chown root:vboxusers /dev/vboxusb\n'
'chmod 0755 /opt/VirtualBox/VBoxCreateUSBNode.sh 2>/dev/null\n'
'chmod -R a+rX /opt/VirtualBox/ExtensionPacks 2>/dev/null\n'
'udevadm control --reload-rules 2>/dev/null\n'
'udevadm trigger 2>/dev/null\n'
)
# _write_text creates parent dirs
self._write_text(ip, body, 0o755)
os.makedirs(os.path.join(bd, 'etc/rc.d/rc4.d'), exist_ok=True)
cmd(['ln', '-sf', f'/etc/rc.d/init.d/{ln}',
os.path.join(bd, 'etc/rc.d/rc4.d', 'S99' + ln.replace('rc.', ''))])
def _link(self, bd, target, link_path):
if not target or not os.path.exists(target): return
link_abs = os.path.join(bd, link_path.lstrip('/'))
os.makedirs(os.path.dirname(link_abs), exist_ok=True)
try:
if os.path.lexists(link_abs): os.remove(link_abs)
os.symlink('/' + os.path.relpath(target, bd), link_abs)
except: pass
def _write_text(self, path, text, mode=0o755):
os.makedirs(os.path.dirname(path), exist_ok=True)
open(path, 'w').write(text); os.chmod(path, mode)
def _copy_to(self, src, dst):
if src and os.path.exists(src):
os.makedirs(dst, exist_ok=True)
try: shutil.copy2(src, dst)
except: pass
def _payload_root(self, src, markers):
for m in markers:
marker = find_file(src, m)
if marker:
d = os.path.dirname(marker)
return os.path.dirname(d) if d != src and os.path.basename(d).lower() in ('bin', 'sbin') else d
return src
def _strip_nls(self, opt):
nls = os.path.join(opt, 'nls')
if not os.path.isdir(nls): return
for f in os.listdir(nls):
if not f.endswith('.qm'): continue
base = f[:-3]
if not (base.startswith(('VirtualBox_en', 'qt_en')) or
any(base == f'VirtualBox_{l}' or base == f'qt_{l}' for l in self.lang_codes)):
safe_rm(os.path.join(nls, f))
def _extract_tar(self, tp, dest):
try:
with tarfile.open(tp, 'r:*') as tar: extract_tar_compat(tar, dest)
return True
except Exception as e:
print(f'[vbox-builder] tarfile failed for {os.path.basename(tp)}: {e}')
r = cmd(['tar', '-xf', tp, '-C', dest], t=600)
if r.returncode == 0: return True
print(f'[vbox-builder] system tar failed rc={r.returncode}: {(r.stderr or "")[:300]}')
return False
def _extract_tarballs(self, src):
tballs = find_files_pattern(src, '*.tar.bz2') + find_files_pattern(src, '*.tar.gz')
print(f'[vbox-builder] tarballs found: {[os.path.basename(t) for t in tballs]}')
want = 'amd64' if self.arch == 'x86_64' else 'x86'
tballs.sort(key=lambda t: 0 if want in os.path.basename(t).lower() else 1)
for tf in tballs:
td = os.path.join(src, f'ex_{os.path.basename(tf)}')
os.makedirs(td, exist_ok=True)
if self._extract_tar(tf, td): return td
return src
def _find_mod_src(self, root, mod):
ml = mod.lower()
for dp, dn, fn in os.walk(root):
if 'Makefile' in fn and os.path.basename(dp).lower().startswith(ml): return dp
for dp, dn, fn in os.walk(root):
if 'Makefile' in fn and any(x.lower().startswith(ml) and x.lower().endswith('.c') for x in fn):
return dp
return None
def _make_dir(self, d, src, chk):
kver = os.uname().release
env = os.environ.copy()
env.update({'PATH': '/usr/sbin:/usr/bin:/sbin:/bin',
'KERN_DIR': '/usr/src/linux', 'KERN_VER': kver, 'VBOX_GUEST_SRC': d})
parent = os.path.dirname(d)
attempts = [
['make', '-C', d, f'KERN_DIR=/usr/src/linux', f'KERN_VER={kver}'],
['make', '-C', d],
['make', '-C', parent, f'KERN_DIR=/usr/src/linux', f'KERN_VER={kver}'],
['make', '-C', parent],
['make', '-C', '/usr/src/linux', f'M={d}', 'modules'],
]
last = None
for a in attempts:
r = cmd(a, t=1800, env=env); last = r
if chk and (find_file(src, chk) or find_files_pattern(src, chk + '*')): return True
if last is not None:
print(f'[vbox-builder] make failed for {d}\n last rc={last.returncode}\n'
f' stdout tail: {(last.stdout or "")[-600:]}\n'
f' stderr tail: {(last.stderr or "")[-600:]}')
return False
def _compile_guest(self, src, bd):
kver = os.uname().release
dest = os.path.join(bd, 'lib/modules', kver, 'misc')
os.makedirs(dest, exist_ok=True)
for mod in ('vboxguest', 'vboxsf', 'vboxvideo'):
d = self._find_mod_src(src, mod)
if not d:
if mod != 'vboxvideo': return False
continue
if self._make_dir(d, src, f'{mod}.ko'):
for ko in find_files_pattern(src, f'{mod}.ko*'):
self._copy_to(ko, dest)
elif mod != 'vboxvideo':
return False
else:
print(f'[vbox-builder] NOTE: {mod} not built (kernel {kver} newer than VBox DRM '
f'support); skipped - graphics via VMSVGA/VBoxDRMClient')
return all(find_files_pattern(dest, f'{m}.ko*') for m in ('vboxguest', 'vboxsf'))
def _compile_host(self, src, bd):
kver = os.uname().release
dest = os.path.join(bd, 'lib/modules', kver, 'misc')
os.makedirs(dest, exist_ok=True)
d = None
for dp, dn, fn in os.walk(src):
if 'Makefile' in fn and 'vboxhost' in os.path.basename(dp).lower():
d = dp; break
if not d:
for dp, dn, fn in os.walk(src):
if 'Makefile' in fn and any('vboxdrv' in x.lower() for x in fn):
d = dp; break
print(f'[vbox-builder] vboxhost src dir: {d}')
if not d: return False
self._make_dir(d, src, 'vboxdrv.ko')
for ko in find_files_pattern(src, '*.ko*'):
self._copy_to(ko, dest)
return bool(find_files_pattern(dest, 'vboxdrv.ko*'))
def _install_extpack(self, bd, fv):
url = vbox_extpack_url(fv)
if not url:
print('[vbox-builder] Extension Pack URL not found, skipping')
return
self._pm(_('downloading'))
fn = os.path.basename(url)
print(f'[vbox-builder] downloading Extension Pack: {fn}')
dest = os.path.join(tempfile.gettempdir(), fn)
tmp_ep = tempfile.mkdtemp(prefix='vbox-extpack-')
try:
try:
with urllib.request.urlopen(url, timeout=600) as r, open(dest, 'wb') as f:
shutil.copyfileobj(r, f)
except Exception as e:
print(f'[vbox-builder] Extension Pack download failed: {e}')
return
if not self._extract_tar(dest, tmp_ep):
print('[vbox-builder] Extension Pack extraction failed')
return
# handle archives wrapped in a top-level dir
root_ep = tmp_ep
if not os.path.exists(os.path.join(root_ep, 'ExtPack.xml')):
for sub in sorted(os.listdir(root_ep)):
p = os.path.join(root_ep, sub)
if os.path.isdir(p) and os.path.exists(os.path.join(p, 'ExtPack.xml')):
root_ep = p; break
else:
print('[vbox-builder] ExtPack.xml not found, invalid package')
return
# dir name from ExtPack.xml (spaces -> underscores)
name = 'Oracle VirtualBox Extension Pack'
try:
m = re.search(r'<Name>\s*([^<]+?)\s*</Name>',
open(os.path.join(root_ep, 'ExtPack.xml')).read())
if m: name = m.group(1)
except Exception as e:
print(f'[vbox-builder] cannot read ExtPack.xml name: {e}')
ep_dir = os.path.join(bd, 'opt', 'VirtualBox', 'ExtensionPacks', name.replace(' ', '_'))
shutil.rmtree(ep_dir, ignore_errors=True)
os.makedirs(os.path.dirname(ep_dir), exist_ok=True)
shutil.move(root_ep, ep_dir)
# drop foreign platforms/arches
ep_arch = 'amd64' if self.arch == 'x86_64' else 'x86'
for sub in os.listdir(ep_dir):
sp = os.path.join(ep_dir, sub)
if not os.path.isdir(sp): continue
if sub.startswith(('win.', 'darwin.', 'solaris.', 'os2.')) or \
(sub.startswith('linux.') and sub != f'linux.{ep_arch}'):
shutil.rmtree(sp, ignore_errors=True)
# fixes VERR_ACCESS_DENIED
normalize_perms(ep_dir)
print(f'[vbox-builder] Extension Pack installed to {ep_dir}')
finally:
safe_rm(dest)
shutil.rmtree(tmp_ep, ignore_errors=True)
def _stage_guest(self, src, bd, fv):
self._pm(_('compiling'))
src = self._extract_tarballs(src)
if not self._compile_guest(src, bd): return False
self._pm(_('staging'))
opt = os.path.join(bd, 'opt', f'VBoxGuestAdditions-{fv}')
try: shutil.copytree(self._payload_root(src, ['VBoxService']), opt, symlinks=True, dirs_exist_ok=True)
except: return False
strip_unwanted(opt); strip_unwanted(bd)
normalize_perms(opt)
self._link(bd, find_file(opt, 'VBoxService'), '/usr/sbin/VBoxService')
self._link(bd, find_file(opt, 'VBoxClient'), '/usr/bin/VBoxClient')
self._link(bd, find_file(opt, 'VBoxControl'), '/usr/bin/VBoxControl')
self._link(bd, find_file(opt, 'mount.vboxsf'), '/sbin/mount.vboxsf')
# VBoxClient --all removed in 7.x
vboxclient_all = (
'#!/bin/sh\n'
'VBoxClient --clipboard\n'
'VBoxClient --seamless\n'
'VBoxClient --draganddrop\n'
'VBoxClient --display\n'
'VBoxClient --vmsvga-session\n'
)
self._write_text(os.path.join(bd, 'usr/bin/VBoxClient-all'), vboxclient_all, 0o755)
drv = find_file(opt, 'vboxvideo_drv.so')
if drv: self._copy_to(drv, os.path.join(bd, 'usr', self.lib, 'xorg/modules/drivers'))
dri = find_file(opt, 'vboxvideo_dri.so')
if dri:
self._copy_to(dri, os.path.join(bd, 'usr', self.lib, 'dri'))
self._copy_to(dri, os.path.join(bd, 'usr', self.lib, 'xorg/modules/dri'))
for ogl in find_files_pattern(opt, 'VBoxOGL*.so*'):
self._copy_to(ogl, os.path.join(bd, 'usr', self.lib))
dtxt = '[Desktop Entry]\nType=Application\nName=VirtualBox Guest Additions\nComment=VirtualBox Guest Additions\nExec=/usr/bin/VBoxClient-all\nTerminal=false\nNoDisplay=true\nX-GNOME-Autostart-enabled=true\n'
self._write_text(os.path.join(bd, 'etc/xdg/autostart/vboxclient.desktop'), dtxt, 0o644)
self._write_text(os.path.join(bd, 'usr/share/autostart/vboxclient.desktop'), dtxt, 0o644)
lxqt_dir = os.path.join(bd, 'etc/xdg/lxqt')
os.makedirs(lxqt_dir, exist_ok=True)
self._write_text(os.path.join(lxqt_dir, 'autostart'),
'[Autostart]\nVBoxClient-all=/usr/bin/VBoxClient-all\n', 0o644)
self._write_text(os.path.join(bd, 'etc/profile.d/vboxclient.sh'),
'#!/bin/sh\n[ -n "$DISPLAY" ] && /usr/bin/VBoxClient-all 2>/dev/null &\n', 0o755)
udev_rules_dir = os.path.join(bd, 'etc/udev/rules.d')
os.makedirs(udev_rules_dir, exist_ok=True)
udev_rule = (
'KERNEL=="vboxuser", MODE="0666"\n'
'KERNEL=="vboxguest", MODE="0666"\n'
)
self._write_text(os.path.join(udev_rules_dir, '60-vboxguest.rules'), udev_rule, 0o644)
self._mki(bd)
return True
def _stage_host(self, src, bd, fv):
self._pm(_('compiling'))
src = self._extract_tarballs(src)
if not self._compile_host(src, bd): return False
self._pm(_('staging'))
opt = os.path.join(bd, 'opt', 'VirtualBox')
try: shutil.copytree(self._payload_root(src, ['VirtualBox', 'VBoxManage']), opt,
symlinks=True, dirs_exist_ok=True)
except: return False
strip_unwanted(opt); self._strip_nls(opt); strip_unwanted(bd)
# before setuid chmods below, so they survive
normalize_perms(opt)
vbox_create_usb = find_file(src, 'VBoxCreateUSBNode.sh')
if vbox_create_usb:
self._copy_to(vbox_create_usb, opt)
print(f'[vbox-builder] VBoxCreateUSBNode.sh copied to {opt}')
else:
print('[vbox-builder] WARNING: VBoxCreateUSBNode.sh NOT found - USB passthrough may not work')
for b in ('VirtualBox', 'VirtualBoxVM', 'VBoxManage', 'VBoxHeadless', 'VBoxSDL',
'VBoxNetAdpCtl', 'VBoxNetDHCP', 'VBoxNetNAT', 'VBoxVolInfo', 'rdesktop-vrdp'):
self._link(bd, find_file(opt, b), f'/usr/bin/{b}')
vm, vb = os.path.join(opt, 'VirtualBoxVM'), os.path.join(opt, 'VirtualBox')
if os.path.exists(vm): os.chmod(vm, 0o4755)
if os.path.exists(vb): os.chmod(vb, 0o755)
self._write_text(os.path.join(bd, 'usr/share/applications/virtualbox.desktop'),
'[Desktop Entry]\n'
'Name=Oracle VM VirtualBox\n'
'GenericName=Virtual Machine\n'
'GenericName[ru]=Виртуальная машина\n'
'GenericName[de]=Virtuelle Maschine\n'
'GenericName[fr]=Machine virtuelle\n'
'GenericName[es]=Máquina virtual\n'
'GenericName[it]=Macchina virtuale\n'
'GenericName[pt_BR]=Máquina Virtual\n'
'GenericName[pl]=Maszyna wirtualna\n'
'GenericName[uk]=Віртуальна машина\n'
'GenericName[zh_CN]=虚拟机\n'
'GenericName[ja]=仮想マシン\n'
'GenericName[tr]=Sanal Makine\n'
'Type=Application\n'
'Exec=/usr/bin/VirtualBox %U\n'
'TryExec=/usr/bin/VirtualBox\n'
'Icon=virtualbox\n'
'Categories=Virtualization;Emulator;System;Utility;\n'
'Comment=Run several virtual systems on a single host computer\n'
'Comment[ru]=Запуск виртуальных систем на одном компьютере\n'
'Comment[de]=Virtuelle Systeme auf einem Host-Rechner ausführen\n'
'Comment[fr]=Exécuter des systèmes virtuels sur un ordinateur hôte\n'
'Comment[es]=Ejecutar sistemas virtuales en un equipo anfitrión\n'
'Comment[it]=Esegui sistemi virtuali su un computer host\n'
'Comment[pt_BR]=Executar sistemas virtuais em um computador host\n'
'Comment[pl]=Uruchamianie systemów wirtualnych na jednym komputerze\n'
"Comment[uk]=Запуск віртуальних систем на одному комп'ютері\n"
'Comment[zh_CN]=在一台主机上运行多个虚拟系统\n'
'Comment[ja]=ホストコンピュータで仮想システムを実行\n'
'Comment[tr]=Ana bilgisayar üzerinde sanal sistemler çalıştırın\n'
'StartupNotify=true\n'
'MimeType=application/x-virtualbox-vbox;application/x-virtualbox-vbox-extpack;application/x-virtualbox-ova;application/x-virtualbox-ovf;\n',
0o644)
icon_src = find_file(opt, 'virtualbox.png') or find_file(opt, 'VBox.png')
if icon_src:
icons_dir = os.path.join(bd, 'usr/share/pixmaps')
os.makedirs(icons_dir, exist_ok=True)
try: shutil.copy2(icon_src, os.path.join(icons_dir, 'virtualbox.png'))
except: pass
udev_rules_dir = os.path.join(bd, 'etc/udev/rules.d')
os.makedirs(udev_rules_dir, exist_ok=True)
udev_rule = (
'KERNEL=="vboxdrv", OWNER="root", GROUP="vboxusers", MODE="0660"\n'
'KERNEL=="vboxdrvu", OWNER="root", GROUP="root", MODE="0666"\n'
'KERNEL=="vboxnetctl", OWNER="root", GROUP="vboxusers", MODE="0660"\n'
'SUBSYSTEM=="usb_device", ACTION=="add", RUN+="/opt/VirtualBox/VBoxCreateUSBNode.sh $major $minor $attr{bDeviceClass} vboxusers"\n'
'SUBSYSTEM=="usb", ACTION=="add", ENV{DEVTYPE}=="usb_device", RUN+="/opt/VirtualBox/VBoxCreateUSBNode.sh $major $minor $attr{bDeviceClass} vboxusers"\n'
'SUBSYSTEM=="usb_device", ACTION=="remove", RUN+="/opt/VirtualBox/VBoxCreateUSBNode.sh --remove $major $minor"\n'
'SUBSYSTEM=="usb", ACTION=="remove", ENV{DEVTYPE}=="usb_device", RUN+="/opt/VirtualBox/VBoxCreateUSBNode.sh --remove $major $minor"\n'
)
self._write_text(os.path.join(udev_rules_dir, '10-vboxdrv.rules'), udev_rule, 0o644)
self._install_extpack(bd, fv)
self._mki(bd)
return True
def _do(self, fp, dl=False):
ok, free = space_ok('/tmp', 800)
if not ok:
self._pc(); self._err(f'{_("low_space")} /tmp\n{_("need")}: 800 MB, {_("free")}: {free} MB'); return
if not os.path.exists('/usr/src/linux'):
self._pc(); self._err(_('no_src')); return
if not shutil.which('gcc') or not shutil.which('make'):
self._pc(); self._err(_('no_tools')); return
fv = self._file_version(os.path.basename(fp))
if not fv and fp == self.cd_src and self.cd_label:
fv = self._file_version(self.cd_label)
if not fv:
self._pc(); self._err(_('no_ver')); return
self._do_staging(fp, dl, fv)
def _do_staging(self, fp, dl, fv):
with tempfile.TemporaryDirectory(prefix='vbox-stage-') as td:
src, bd, iso = os.path.join(td, 'src'), os.path.join(td, 'build'), os.path.join(td, 'iso')
for d in (src, bd, iso): os.makedirs(d, exist_ok=True)
mounted, inst = False, fp
try:
self._pm(_('extracting'))
if self.mode == 'host' and (fp.lower().endswith('.iso') or is_block(fp)):
self._pc(); self._err(_('no_file')); return
run_src = None
if self.mode == 'guest':
if fp.lower().endswith('.iso') or is_block(fp):
if not mount_iso(fp, iso):
self._pc(); self._err(_('mnt_fail')); return
mounted = True
run_src = os.path.join(iso, 'VBoxLinuxAdditions.run')
elif os.path.isdir(fp):
run_src = os.path.join(fp, 'VBoxLinuxAdditions.run')
if run_src:
if not os.path.exists(run_src):
self._pc(); self._err(_('no_additions')); return
inst = os.path.join(td, 'VBoxLinuxAdditions.run')
shutil.copy2(run_src, inst); os.chmod(inst, 0o755)
env = os.environ.copy()
env.update({'TERM': 'dumb', 'PATH': '/usr/sbin:/usr/bin:/sbin:/bin', 'SETUP_NOCHECK': '1'})
ok_ex = False
for args in ([['sh', inst, '--quiet', '--accept', '--noexec', '--keep',
'--noprogress', '--nox11', '--nochown', '--nodiskspace', '--target', src],
['sh', inst, '--accept', '--noexec', '--keep', '--target', src],
['sh', inst, '--noexec', '--keep', '--target', src],
['sh', inst, '--noexec', '--target', src]]):
r = cmd(args, t=900, env=env, input_data='yes\n')
print(f'[vbox-builder] extract rc={r.returncode}: {" ".join(args)}')
if r.returncode != 0:
print(f' stdout: {(r.stdout or "")[:300]}\n stderr: {(r.stderr or "")[:300]}')
if os.path.isdir(src) and os.listdir(src):
ok_ex = True; break
if not ok_ex:
self._pc(); self._err(_('stage_fail')); return
ok_st = self._stage_guest(src, bd, fv) if self.mode == 'guest' else self._stage_host(src, bd, fv)
if not ok_st:
self._pc(); self._err(_('stage_fail')); return
self._package(bd, fv, dl, fp)
finally:
if mounted and os.path.ismount(iso): cmd(['umount', iso], t=20)
clean_tmp_files()
def _build_ok(self, bd):
if self.mode == 'guest':
return (os.path.exists(os.path.join(bd, 'etc/rc.d/init.d/rc.vbox-add')) and
bool(find_file(bd, 'VBoxService')) and
bool(find_files_pattern(bd, 'vboxguest.ko*')))
return (os.path.exists(os.path.join(bd, 'etc/rc.d/init.d/rc.virtualbox')) and
bool(find_file(bd, 'VirtualBox')) and
bool(find_files_pattern(bd, 'vboxdrv.ko*')))
def _package(self, bd, fv, dl=False, fp=None):
if not self._build_ok(bd):
self._pc(); self._err(_('fail')); return False
self._pm(_('mkmod'))
pn = 'vbox-guest' if self.mode == 'guest' else 'VirtualBox'
out = f'/tmp/{pn}-{fv}-porteus-{pver()}-{self.arch}-1prt.xzm'
safe_rm(out)
self._mkpkg(bd, pn, fv)
r = cmd(['dir2xzm', bd, out], t=600)
if r.returncode != 0:
err_msg = (r.stderr or '').strip() or (r.stdout or '').strip() or _('fail')
self._pc(); self._err(f'{_("fail")}: {err_msg}'); return False
self._pc()
if dl and fp: safe_rm(fp)
GLib.idle_add(self._result, out)
return True
def _mkpkg(self, bd, pn, ver):
pd = os.path.join(bd, 'var/lib/pkgtools/packages')
os.makedirs(pd, exist_ok=True)
nm = f'{pn}-{ver}-porteus-{pver()}-{self.arch}-1prt'
with open(os.path.join(pd, nm), 'w') as f:
f.write(f'PACKAGE NAME: {nm}\nPACKAGE DESCRIPTION:\n')
for l in [f'{pn}: {pn} (virtualization software)', f'{pn}:',
f'{pn}: Oracle VM VirtualBox is a cross-platform virtualization',
f'{pn}: application that allows you to run multiple operating systems',
f'{pn}: simultaneously on a single physical computer.', f'{pn}:',
f'{pn}: https://www.virtualbox.org/', f'{pn}:', 'FILE LIST:']:
f.write(l + '\n')
for root, _, files in os.walk(bd):
for fn in files:
rel = os.path.relpath(os.path.join(root, fn), bd)
if not rel.startswith('var/lib/pkgtools/packages'):
f.write(rel + '\n')
def _result(self, out):
d = Gtk.MessageDialog(transient_for=self, modal=True, message_type=Gtk.MessageType.INFO,
buttons=Gtk.ButtonsType.NONE, text=f'{_("ready")}\n{out}')
d.set_property('secondary-text', f'{_("action")}\n{_("more")}')
d.add_button(_('mv_act'), 1)
d.add_button(_('open_dir'), 2)
d.add_button(_('close'), 3)
r = d.run(); d.destroy()
if r == 1: self._thread(self._move_act, out)
elif r == 2: fm_open('/tmp')
else: Gtk.main_quit()
return False
def _move_act(self, out):
md = mod_dir()
if not md:
GLib.idle_add(self._err, _('no_moddir')); return
try:
mn, dest = os.path.basename(out), os.path.join(md, os.path.basename(out))
deactivate_all_wait(self.mode)
if not os.path.isfile(out):
GLib.idle_add(self._err, f'{_("mv_fail")}: {out}'); return
GLib.idle_add(self._po, _('building'), _('mv_act'))
shutil.move(out, dest); os.sync()
r = cmd(['activate', dest], t=60)
GLib.idle_add(self._pc)
extra = _('reboot_guest') if self.mode == 'guest' else _('reboot_host')
if r.returncode == 0:
GLib.idle_add(self._info, f'{mn}\n{_("moved")} {md}\n{_("act_ok")}\n\n{extra}')
else:
GLib.idle_add(self._err, f'{_("act_fail")}\n{(r.stderr or "").strip() or (r.stdout or "").strip() or _("act_ng")}')
except Exception as e:
GLib.idle_add(self._pc)
GLib.idle_add(self._err, f'{_("mv_fail")}: {e}')
def parse_args():
mode = fp = None
if len(sys.argv) > 1:
a = sys.argv[1].lower()
if a in ('vbox', 'host'): mode = 'host'
elif a in ('vboxguest', 'guest'): mode = 'guest'
if len(sys.argv) > 2 and os.path.isfile(sys.argv[2]): fp = sys.argv[2]
return mode, fp
if __name__ == '__main__':
if os.getuid() != 0: print(_('root')); sys.exit(1)
if not os.environ.get('DISPLAY'): print(_('x11')); sys.exit(1)
pix = load_icon(48)
if pix:
try: Gtk.Window.set_default_icon(pix)
except: pass
signal.signal(signal.SIGINT, lambda *_: Gtk.main_quit())
GLib.set_prgname('vbox-builder')
GLib.set_application_name(_('title'))
cm, cf = parse_args()
App(cm, cf).show_all()
Gtk.main()