В некоторых задачах Linux просто необходим. И самым ярким тому примером на сегодня является наличие системы WSL. Однако не везде ею можно пользоваться. Некоторые предприятия принципиально застревают на Win7. И их можно понять. Эта система не столь требовательна к железу (особенно к видео-подсистеме), не ломится чуть-что в интернет, да и в Ultimate варианте вообще не требует подключения к глобальной сети.
В большей части случаев можно обойтись родными для системы средствами разработки и сторонними инструментами. Но представьте себе, что для работы Вашего комплекса нужно собрать вместе более десятка не маленьких opensource проектов с перекрёстными зависимостями. Причём часть из них написана под python (и тут никаких проблем нет - виртуальная среда и всё ок), а часть собирается в бинарные исполняшки, от которых зависят другие модули. И тут может быть как минимум три решения:
Я не буду здесь писать о чём-то новом. На просторах Хабра всё, что будет описано ниже уже не раз встречалось. Но вот применительно к конкретной задаче маскировки работы виртуальной машины под работу обычной консольной программы Windows, текст будет интересен.
Я буду описывать весь процесс на примере Manjaro. Во первых, я её очень Люблю. Во вторых это Arch-дистрибутив с установленным из коробки pamac и AUR. Конечно при использовании чистого Arch Linux итоговый образ получился бы меньше, но не на много.
Идея заключается в том, что создаваемый комплекс должен вести некоторую обработку файлов и выдавать файловый же результат. То есть он должен вести себя, как консольная программа. Для Windows-пользователей также будет не лишним добавить диалог открытия файлов (а, если нужно, и ввода параметров), чтобы им не пришлось параметры в командную строку вбивать.
На хосте должны быть установлены пакеты из группы qemu-full. Образ установщика гостевой системы находится на https://manjaro.org/download/. В принципе можно брать любой. Потом всё равно нужно будет удалять лишние пакеты. Но вот беда: некоторые пакеты (например tesseract) ставят себе в зависимость пакеты окружения рабочего стола. Так что лучше сразу поставить что-то полегче (xfce, например), чтобы потом не жалко было его оставлять.
Создаём диск виртуальной машины:
qemu-img create -f qcow2 image.qcow2 32G
И запускаем её установку:
qemu-system-x86_64 \
-enable-kvm \
-cpu host \
-hda image.qcow2 \
-drive file=manjaro-xfce-21.0-210318-linux510.iso,media=cdrom \
-boot d \
-smp 4 \
-m 4G \
-display gtk \
-vga std \
-device virtio-net,netdev=vmnic -netdev user,id=vmnic
Советую собирать на единственном разделе. Либо вообще без свопа, либо со своп-файлом. Это нужно для того, чтобы образ в итоге занял меньше места и не имел тенденции к чрезмерному росту.
Из дополнительных пакетов нам понадобится samba для получения доступа к папке Windows-хоста.
Также далее будем считать, что пользователя гостевой системы зовут user, а его пароль pass.
После установки команда запуска поменяется на:
qemu-system-x86_64 \
-enable-kvm \
-cpu host \
-hda image.qcow2 \
-smp 4 \
-m 4G \
-display gtk \
-vga std \
-device virtio-net,netdev=vmnic -netdev user,id=vmnic
Мы изъяли установочный диск:
-drive file=manjaro-xfce-21.0-210318-linux510.iso,media=cdrom \
-boot d \
Теперь можно устанавливать и настраивать свой программный комплекс в приятной Linux-среде. Раз уж мы работаем в Manjaro, откроем сразу GUI pamac'а и в настройках разрешим использование AUR-репозитория, также изменив сборочную директорию на ~/AUR. Теперь собирать стало ещё проще. Найти что-то, чего нет в AUR - это не так-то просто.
Пришло время маскироваться под Windows-приложение. Для этого нам потребуется portable python. Забираем любой понравившийся с https://github.com/oskaritimperi/portablepython/releases. Я буду писать на примере python3. Естественно нужен QEMU с https://qemu.weilnetz.de/w64/.
Создаём папки SystemName/workdir/qemu. В workdir распаковываем содержимое папки python'а из архива и перемещаем образ image.qcow2. A в qemu копируем содержимое установленного QEMU (установить его можно и wine'ом).
Для теста системы на поддержку виртуализации нам потребуется cpuinfo в portable python'е на Windows, а для передачи параметров (да и мало ли ещё чего) через подключаемый iso-образ pycdlib:
# Опять-таки можно и через wine запустить.
python.exe -m pip install py-cpuinfo pycdlib
В папке SystemName создаём run.bat с простеньким содержимым:
cd workdir
python.exe run.py
cd ..
А в workdir создаём скрипт run.py, который и будет заниматься получением параметров запуска, запуском виртуальной машины и выводом лога работы программы на консоль:
import cpuinfo
import os
import sys
import subprocess
import time
import getpass
from io import BytesIO
import pycdlib
from tkinter import *
from tkinter import filedialog
user = getpass.getuser()
qemu_bin = 'qemu/qemu-system-x86_64.exe'
password = getpass.win_getpass(f'{user}, введите свой пароль: ')
# Проверяем на включенность виртуализации и предупреждаем пользователя, если что.
if len(set(cpuinfo.get_cpu_info()['flags']).intersection({'vmx', 'svm'})) > 0:
cpu = '-cpu host'
else:
cpu = '-cpu qemu64'
print('Если включить в BIOS поддержку Intel VT-x, программа будет работать быстрее.')
# Открываем диалог выбора папки и просим пользователя показать, где лежат данные.
# При необходимости сюда и окно с параметрами можно записать.
# Но спрашивать нужно именно папку, чтобы записать туда конфигурацию для программы
# на виртуальной машине.
base_root = Tk()
base_dialog = filedialog.Directory(base_root)
base_root.withdraw()
base = base_dialog.show().strip().replace('\\', '/') # Приводим слеши в порядок.
if base[-1] != '/':
base += '/'
# Пишем диск с конфигурацией (здесь только пользователь и пароль к разделяемой папке).
iso = pycdlib.PyCdlib()
iso.new()
conf = bytes(f'{user}\n{password}\n', 'utf-8')
iso.add_fp(BytesIO(conf), len(conf), '/CONFIG.;1')
iso.write_fp(BytesIO(conf))
iso.write('config.iso')
iso.close()
# Команда запуска виртуальной машины.
# Обратите внимание на параметр -display none.
# Это позволяет не показывать экран виртуальной машины.
run_qemu = f'{qemu_bin} {cpu} -hda ./image.qcow2 -smp {os.cpu_count()} \
-m 4G -display none -vga std -device virtio-net,netdev=vmnic \
-netdev user,id=vmnic -drive file=config.iso,media=cdrom'.replace('/', '\\')
open(f'{base}system.log', 'w') # Очищаем лог.
# Команда запуска черезчур сложна для subprocess.Popen, поэтому нужен скрипт.
open(f'./qemu.bat', 'w').write(run_qemu)
# На Windows-хосте нужно расшарить папку и только потом запускать виртуальную машину.
base_win = base.replace('/', '\\')[: -1]
os.system(f'net share vmshare={base_win} /GRANT:"{user},FULL"')
qemu = subprocess.Popen(['qemu.bat'])
# Начинаем читать лог.
print('Загрузка...')
lines_printed = 0
while qemu.poll() is None: # Пока виртуальная машина запущена.
log = open(f'{base}system.log', 'rb').read()
if b'\n' in log:
if log[-1] != b'\n':
log = log[: log.rfind(b'\n')]
log = log.split(b'\n')
if len(log[-1]) == 0:
log = log[: -1]
log = log[lines_printed:]
if len(log) > 0:
lines_printed += len(log)
for line in log:
try: # На случай битого вывода.
print(line.decode('utf-8'))
except:
pass
else:
time.sleep(1)
# Отключаем папку.
os.system('net share /delete vmshare')
print('Завершено')
Ключевыми особенностями нового скрипта запуска QEMU (в переменной run_qemu) являются два параметра:
# Не показывать пользователю экран виртуальной машины.
-display none
# Подключить сгенерированный CD с параметрами.
-drive file=config.iso,media=cdrom
Стоит отметить, что на подключаемый CD можно складывать всё, что угодно. Например, так удобно обновлять ПО внутри системы прямо на лету.
Теперь нужно, чтобы гостевая система запускалась, выполняла задачу и выключалась. Для этого нам понадобится ещё один файл на гостевой системе, который нужно прописать в автозапуск любым удобным для вас способом.
#!/bin/bash
# Пример скрипта запуска комплекса run.sh
# Весь полезный вывод перенаправляется в /mnt/system.log.
# Файл конфигурации все могут прочитать в config на подключаемом CD.
# Пусть в этой папке лежат все исполняемые файлы комплекса.
cd /home/user/SystemName
# Чтение параметров.
echo "pass" | sudo -S umount /dev/sr0
mkdir /home/user/SystemName/config
echo "pass" | sudo -S mount /dev/sr0 /home/user/SystemName/config
user=`sed -n "1p" < /home/user/SystemName/config`
pass=`sed -n "2p" < /home/user/SystemName/config`
echo "pass" | sudo -S umount /dev/sr0
# Монтирование рабочей директории.
echo "pass" | sudo -S mount -t cifs -o username=$user,password=$pass,workgroup=workgroup,iocharset=utf8,uid=user //10.0.2.2/vmshare /mnt
./it_works_all_the_time.py >> /mnt/system.log &
all_time_worker=$!
# Здесь все данные, с которыми комплекс должен работать.
base="/mnt/"
# То, что нельзя распараллелить.
./1_line.py >> /mnt/system.log
./2_line.py >> /mnt/system.log
./3_line.py >> /mnt/system.log
# То, что может быть выполнено параллельно.
./4_parallel.py >> /mnt/system.log &
pid_41=$!
-S ./4_parallel.py >> /mnt/system.log &
pid_42=$!
./4_parallel.py >> /mnt/system.log &
pid_43=$!
# Ожидание распараллеленных процессов.
wait $pid_41
wait $pid_42
wait $pid_34
echo "pass" | sudo -S ./5_end.py >> /mnt/system.log
# Завершение.
kill $all_time_worker
echo "pass" | sudo -S umount /mnt
echo "pass" | sudo -S poweroff
Всё! Виртуальная машина запускается, делает, что ей положено и выключается, передавая все строки вывода в файл, который читается скриптом на хосте.
После сборки всего необходимого мой образ весил 22 ГБ - неприлично много, но там точно около 13 ГБ "полезной" нагрузки. Нужно уменьшать. Опять открываем pamac на гостевой системе и в меню переходим в "Режим приложений". Удаляем всё ненужное. Теперь возвращаемся в обычный режим, в закладку "Установлены" и группу "Неиспользуемые". Итеративно удаляем всё, пока список не опустеет. И выключаем систему.
Теперь нужно по возможности уменьшить размер образа, удалив лишние файлы, дефрагментировав и обрезав его:
### В гостевой системе.
# Удаляем кэш пакетов. Устанавливать нам больше нечего.
pacman -Sc # Дважды соглашаемся.
rm -f /var/cache/pacman/pkg/*
rm -rf ~/AUR
### На хосте.
# Подключаем модуль ядра.
modprobe nbd max_part=8
# Монтируем файловую систему.
qemu-nbd --connect=/dev/nbd0 image.qcow2
mkdir /mnt/qcow2
mount /dev/nbd0p1 /mnt/qcow2
# Дефрагментируем. Всё свободное место скапливается
# в конце диска.
e4defrag /dev/nbd0p1 /mnt/qcow2
# Заполняем свободное место нулями.
# Будьте внимательны. В процессе диск раздуется до
# предельного размера.
dd if=/dev/zero of=/mnt/qcow2/tempfile
rm -f /mnt/qcow2/tempfile
# Отключаем файловую систему.
umount /mnt/qcow2
qemu-nbd --disconnect /dev/nbd0
rmmod nbd
# Пересобираем диск, чтобы он занимал минимум места.
mv image.qcow2 image.qcow2.old
qemu-img convert -O qcow2 image.qcow2.old image.qcow2
rm -f image.qcow2.old
Вот теперь 13 ГБ. Точно, как и ожидалось.
Пара слов о том, как понять, какой объём системы является "полезным":
import pacman
import locale
from ipywidgets import IntProgress
from IPython.display import display
from datetime import datetime
locale.setlocale(locale.LC_TIME, 'ru_RU.UTF-8')
print('Получение списка установленных пакетов.')
installed = pacman.get_installed()
installed = {pkg['id'] for pkg in installed}
print('Получение информации о пакетах.')
progress = IntProgress(min=0, max=len(installed))
display(progress)
specially_installed = []
all_installed = []
for pkg in installed:
info = pacman.get_info(pkg)
if info['Причина установки'] != 'Установлен как зависимость другого пакета':
specially_installed.append(info)
all_installed.append(info)
progress.value = progress.value + 1
installed_dict = {pkg['Название']: pkg for pkg in all_installed}
print('Получение пакетов, зависящих от пакета.')
progress = IntProgress(min=0, max=len(installed))
display(progress)
dependensies = dict()
for pkg in installed:
dependensies[pkg] = set()
try:
for depend in pacman.depends_for(pkg):
if depend != pkg and depend in installed:
dependensies[pkg].add(depend)
except:
dependensies.pop(pkg)
progress.value = progress.value + 1
# Ищем нужные пакеты и их зависимости.
needed_words = {
'В', 'этом', 'списке', 'должны', 'быть', 'солва', ',', 'являющиеся',
'базовыми', 'формами', 'имён', 'пакетов', ',', 'необходимых', 'Вашему',
'комплексу', '.',
'То', 'есть', ',', 'если', 'пакет', 'называется', 'python-pip', ',',
'то', 'в', 'списке', 'должен', 'быть', 'просто', 'pip', '.'}
needed = set()
for name in needed_words:
for pkg in installed:
if name in pkg:
needed.add(pkg)
new_needed = set()
for pkg in list(needed):
if pkg in dependensies:
for dep in dependensies:
if dep not in needed:
needed.add(dep)
new_needed.add(dep)
print(f'Добавлено {len(new_needed)} пакетов. Всего {len(needed)} пакетов.')
# Вычисляем "полезный" объём.
sizes = {'B': 1, 'K': 1024, 'M': 1048576, 'G': 1073741824}
size_needed = 0
size_not_needed = 0
for pkg in installed:
size = float(installed_dict[pkg]['Установленный размер'].split()[0].replace(',', '.')) * sizes[installed_dict[pkg]['Установленный размер'].split()[1][0]]
if pkg in needed:
size_needed += size
else:
size_not_needed += size
print(f'Размер необходимых пакетов {int(size_needed / 1048576)} MB')
print(f'Размер пакетов для удаления {int(size_not_needed / 1048576)} MB')
print('Завершено.')
В итоге, получен универсальный способ сборки громоздких opensource комплексов с кучей зависимостей под Windows. По производительности, конечно, не ах. Всё-таки виртуализация (а-то и без VT-x может запуститься). Но при ограничениях, описанных в начале статьи, лучшего добиться можно, но неимоверно сложно. Напомню, что это должно было выглядеть, как программа, собранная исключительно под Windows.
В большей части случаев можно обойтись родными для системы средствами разработки и сторонними инструментами. Но представьте себе, что для работы Вашего комплекса нужно собрать вместе более десятка не маленьких opensource проектов с перекрёстными зависимостями. Причём часть из них написана под python (и тут никаких проблем нет - виртуальная среда и всё ок), а часть собирается в бинарные исполняшки, от которых зависят другие модули. И тут может быть как минимум три решения:
- собрать всё с помощью mingw-тулчейна;
- воспользоваться msys2 или cygwin;
- собрать всё быстро и удобно на виртуальной машине с Linux.
- Для mingw-тулчейна вам потребуется руками собирать неимоверное количество библиотек-зависимостей.
- Среды msys2 или cygwin хороши тем, что в их репозиториях уже есть почти всё, что может пригодиться (а в вашем случае может быть и вообще всё). Но вот беда: заказчик хочет, чтобы система была монолитной и не требовала установки дополнительного ПО, а обе среды в базовой реализации не совсем портабельны. Что-то может перестать работать после переноса на новое место и в новую систему. Есть их портабелизации на portableapps.com, но тут тоже могут ожидать подводные камни: в обоих случаях при исследовании были пакеты, которые ставились как-то не так. Например binutils в portable msys2 не устанавливал исполняемые файлы. О какой сборке чего бы то ни было в таком случае может идти речь?
- Виртуальная машине с Linux с точки зрения сборки комплекса безусловно является оптимальным решением (если конечно не требуется CUDA). Но тут возникает уже человеческий фактор. При словах "Linux" и "виртуальная машина" у довольно большого количества людей возникает примерно одинаковая реакция: "Не, не, не! Люди не умеют этим пользоваться. Учить долго и/или дорого. Делай так, чтобы было только на Винде".
Я не буду здесь писать о чём-то новом. На просторах Хабра всё, что будет описано ниже уже не раз встречалось. Но вот применительно к конкретной задаче маскировки работы виртуальной машины под работу обычной консольной программы Windows, текст будет интересен.
Я буду описывать весь процесс на примере Manjaro. Во первых, я её очень Люблю. Во вторых это Arch-дистрибутив с установленным из коробки pamac и AUR. Конечно при использовании чистого Arch Linux итоговый образ получился бы меньше, но не на много.
Идея заключается в том, что создаваемый комплекс должен вести некоторую обработку файлов и выдавать файловый же результат. То есть он должен вести себя, как консольная программа. Для Windows-пользователей также будет не лишним добавить диалог открытия файлов (а, если нужно, и ввода параметров), чтобы им не пришлось параметры в командную строку вбивать.
На хосте должны быть установлены пакеты из группы qemu-full. Образ установщика гостевой системы находится на https://manjaro.org/download/. В принципе можно брать любой. Потом всё равно нужно будет удалять лишние пакеты. Но вот беда: некоторые пакеты (например tesseract) ставят себе в зависимость пакеты окружения рабочего стола. Так что лучше сразу поставить что-то полегче (xfce, например), чтобы потом не жалко было его оставлять.
Создаём диск виртуальной машины:
qemu-img create -f qcow2 image.qcow2 32G
И запускаем её установку:
qemu-system-x86_64 \
-enable-kvm \
-cpu host \
-hda image.qcow2 \
-drive file=manjaro-xfce-21.0-210318-linux510.iso,media=cdrom \
-boot d \
-smp 4 \
-m 4G \
-display gtk \
-vga std \
-device virtio-net,netdev=vmnic -netdev user,id=vmnic
Советую собирать на единственном разделе. Либо вообще без свопа, либо со своп-файлом. Это нужно для того, чтобы образ в итоге занял меньше места и не имел тенденции к чрезмерному росту.
Из дополнительных пакетов нам понадобится samba для получения доступа к папке Windows-хоста.
Также далее будем считать, что пользователя гостевой системы зовут user, а его пароль pass.
После установки команда запуска поменяется на:
qemu-system-x86_64 \
-enable-kvm \
-cpu host \
-hda image.qcow2 \
-smp 4 \
-m 4G \
-display gtk \
-vga std \
-device virtio-net,netdev=vmnic -netdev user,id=vmnic
Мы изъяли установочный диск:
-drive file=manjaro-xfce-21.0-210318-linux510.iso,media=cdrom \
-boot d \
Теперь можно устанавливать и настраивать свой программный комплекс в приятной Linux-среде. Раз уж мы работаем в Manjaro, откроем сразу GUI pamac'а и в настройках разрешим использование AUR-репозитория, также изменив сборочную директорию на ~/AUR. Теперь собирать стало ещё проще. Найти что-то, чего нет в AUR - это не так-то просто.
Пришло время маскироваться под Windows-приложение. Для этого нам потребуется portable python. Забираем любой понравившийся с https://github.com/oskaritimperi/portablepython/releases. Я буду писать на примере python3. Естественно нужен QEMU с https://qemu.weilnetz.de/w64/.
Создаём папки SystemName/workdir/qemu. В workdir распаковываем содержимое папки python'а из архива и перемещаем образ image.qcow2. A в qemu копируем содержимое установленного QEMU (установить его можно и wine'ом).
Для теста системы на поддержку виртуализации нам потребуется cpuinfo в portable python'е на Windows, а для передачи параметров (да и мало ли ещё чего) через подключаемый iso-образ pycdlib:
# Опять-таки можно и через wine запустить.
python.exe -m pip install py-cpuinfo pycdlib
В папке SystemName создаём run.bat с простеньким содержимым:
cd workdir
python.exe run.py
cd ..
А в workdir создаём скрипт run.py, который и будет заниматься получением параметров запуска, запуском виртуальной машины и выводом лога работы программы на консоль:
import cpuinfo
import os
import sys
import subprocess
import time
import getpass
from io import BytesIO
import pycdlib
from tkinter import *
from tkinter import filedialog
user = getpass.getuser()
qemu_bin = 'qemu/qemu-system-x86_64.exe'
password = getpass.win_getpass(f'{user}, введите свой пароль: ')
# Проверяем на включенность виртуализации и предупреждаем пользователя, если что.
if len(set(cpuinfo.get_cpu_info()['flags']).intersection({'vmx', 'svm'})) > 0:
cpu = '-cpu host'
else:
cpu = '-cpu qemu64'
print('Если включить в BIOS поддержку Intel VT-x, программа будет работать быстрее.')
# Открываем диалог выбора папки и просим пользователя показать, где лежат данные.
# При необходимости сюда и окно с параметрами можно записать.
# Но спрашивать нужно именно папку, чтобы записать туда конфигурацию для программы
# на виртуальной машине.
base_root = Tk()
base_dialog = filedialog.Directory(base_root)
base_root.withdraw()
base = base_dialog.show().strip().replace('\\', '/') # Приводим слеши в порядок.
if base[-1] != '/':
base += '/'
# Пишем диск с конфигурацией (здесь только пользователь и пароль к разделяемой папке).
iso = pycdlib.PyCdlib()
iso.new()
conf = bytes(f'{user}\n{password}\n', 'utf-8')
iso.add_fp(BytesIO(conf), len(conf), '/CONFIG.;1')
iso.write_fp(BytesIO(conf))
iso.write('config.iso')
iso.close()
# Команда запуска виртуальной машины.
# Обратите внимание на параметр -display none.
# Это позволяет не показывать экран виртуальной машины.
run_qemu = f'{qemu_bin} {cpu} -hda ./image.qcow2 -smp {os.cpu_count()} \
-m 4G -display none -vga std -device virtio-net,netdev=vmnic \
-netdev user,id=vmnic -drive file=config.iso,media=cdrom'.replace('/', '\\')
open(f'{base}system.log', 'w') # Очищаем лог.
# Команда запуска черезчур сложна для subprocess.Popen, поэтому нужен скрипт.
open(f'./qemu.bat', 'w').write(run_qemu)
# На Windows-хосте нужно расшарить папку и только потом запускать виртуальную машину.
base_win = base.replace('/', '\\')[: -1]
os.system(f'net share vmshare={base_win} /GRANT:"{user},FULL"')
qemu = subprocess.Popen(['qemu.bat'])
# Начинаем читать лог.
print('Загрузка...')
lines_printed = 0
while qemu.poll() is None: # Пока виртуальная машина запущена.
log = open(f'{base}system.log', 'rb').read()
if b'\n' in log:
if log[-1] != b'\n':
log = log[: log.rfind(b'\n')]
log = log.split(b'\n')
if len(log[-1]) == 0:
log = log[: -1]
log = log[lines_printed:]
if len(log) > 0:
lines_printed += len(log)
for line in log:
try: # На случай битого вывода.
print(line.decode('utf-8'))
except:
pass
else:
time.sleep(1)
# Отключаем папку.
os.system('net share /delete vmshare')
print('Завершено')
Ключевыми особенностями нового скрипта запуска QEMU (в переменной run_qemu) являются два параметра:
# Не показывать пользователю экран виртуальной машины.
-display none
# Подключить сгенерированный CD с параметрами.
-drive file=config.iso,media=cdrom
Стоит отметить, что на подключаемый CD можно складывать всё, что угодно. Например, так удобно обновлять ПО внутри системы прямо на лету.
Теперь нужно, чтобы гостевая система запускалась, выполняла задачу и выключалась. Для этого нам понадобится ещё один файл на гостевой системе, который нужно прописать в автозапуск любым удобным для вас способом.
#!/bin/bash
# Пример скрипта запуска комплекса run.sh
# Весь полезный вывод перенаправляется в /mnt/system.log.
# Файл конфигурации все могут прочитать в config на подключаемом CD.
# Пусть в этой папке лежат все исполняемые файлы комплекса.
cd /home/user/SystemName
# Чтение параметров.
echo "pass" | sudo -S umount /dev/sr0
mkdir /home/user/SystemName/config
echo "pass" | sudo -S mount /dev/sr0 /home/user/SystemName/config
user=`sed -n "1p" < /home/user/SystemName/config`
pass=`sed -n "2p" < /home/user/SystemName/config`
echo "pass" | sudo -S umount /dev/sr0
# Монтирование рабочей директории.
echo "pass" | sudo -S mount -t cifs -o username=$user,password=$pass,workgroup=workgroup,iocharset=utf8,uid=user //10.0.2.2/vmshare /mnt
./it_works_all_the_time.py >> /mnt/system.log &
all_time_worker=$!
# Здесь все данные, с которыми комплекс должен работать.
base="/mnt/"
# То, что нельзя распараллелить.
./1_line.py >> /mnt/system.log
./2_line.py >> /mnt/system.log
./3_line.py >> /mnt/system.log
# То, что может быть выполнено параллельно.
./4_parallel.py >> /mnt/system.log &
pid_41=$!
-S ./4_parallel.py >> /mnt/system.log &
pid_42=$!
./4_parallel.py >> /mnt/system.log &
pid_43=$!
# Ожидание распараллеленных процессов.
wait $pid_41
wait $pid_42
wait $pid_34
echo "pass" | sudo -S ./5_end.py >> /mnt/system.log
# Завершение.
kill $all_time_worker
echo "pass" | sudo -S umount /mnt
echo "pass" | sudo -S poweroff
Всё! Виртуальная машина запускается, делает, что ей положено и выключается, передавая все строки вывода в файл, который читается скриптом на хосте.
После сборки всего необходимого мой образ весил 22 ГБ - неприлично много, но там точно около 13 ГБ "полезной" нагрузки. Нужно уменьшать. Опять открываем pamac на гостевой системе и в меню переходим в "Режим приложений". Удаляем всё ненужное. Теперь возвращаемся в обычный режим, в закладку "Установлены" и группу "Неиспользуемые". Итеративно удаляем всё, пока список не опустеет. И выключаем систему.
Теперь нужно по возможности уменьшить размер образа, удалив лишние файлы, дефрагментировав и обрезав его:
### В гостевой системе.
# Удаляем кэш пакетов. Устанавливать нам больше нечего.
pacman -Sc # Дважды соглашаемся.
rm -f /var/cache/pacman/pkg/*
rm -rf ~/AUR
### На хосте.
# Подключаем модуль ядра.
modprobe nbd max_part=8
# Монтируем файловую систему.
qemu-nbd --connect=/dev/nbd0 image.qcow2
mkdir /mnt/qcow2
mount /dev/nbd0p1 /mnt/qcow2
# Дефрагментируем. Всё свободное место скапливается
# в конце диска.
e4defrag /dev/nbd0p1 /mnt/qcow2
# Заполняем свободное место нулями.
# Будьте внимательны. В процессе диск раздуется до
# предельного размера.
dd if=/dev/zero of=/mnt/qcow2/tempfile
rm -f /mnt/qcow2/tempfile
# Отключаем файловую систему.
umount /mnt/qcow2
qemu-nbd --disconnect /dev/nbd0
rmmod nbd
# Пересобираем диск, чтобы он занимал минимум места.
mv image.qcow2 image.qcow2.old
qemu-img convert -O qcow2 image.qcow2.old image.qcow2
rm -f image.qcow2.old
Вот теперь 13 ГБ. Точно, как и ожидалось.
Пара слов о том, как понять, какой объём системы является "полезным":
import pacman
import locale
from ipywidgets import IntProgress
from IPython.display import display
from datetime import datetime
locale.setlocale(locale.LC_TIME, 'ru_RU.UTF-8')
print('Получение списка установленных пакетов.')
installed = pacman.get_installed()
installed = {pkg['id'] for pkg in installed}
print('Получение информации о пакетах.')
progress = IntProgress(min=0, max=len(installed))
display(progress)
specially_installed = []
all_installed = []
for pkg in installed:
info = pacman.get_info(pkg)
if info['Причина установки'] != 'Установлен как зависимость другого пакета':
specially_installed.append(info)
all_installed.append(info)
progress.value = progress.value + 1
installed_dict = {pkg['Название']: pkg for pkg in all_installed}
print('Получение пакетов, зависящих от пакета.')
progress = IntProgress(min=0, max=len(installed))
display(progress)
dependensies = dict()
for pkg in installed:
dependensies[pkg] = set()
try:
for depend in pacman.depends_for(pkg):
if depend != pkg and depend in installed:
dependensies[pkg].add(depend)
except:
dependensies.pop(pkg)
progress.value = progress.value + 1
# Ищем нужные пакеты и их зависимости.
needed_words = {
'В', 'этом', 'списке', 'должны', 'быть', 'солва', ',', 'являющиеся',
'базовыми', 'формами', 'имён', 'пакетов', ',', 'необходимых', 'Вашему',
'комплексу', '.',
'То', 'есть', ',', 'если', 'пакет', 'называется', 'python-pip', ',',
'то', 'в', 'списке', 'должен', 'быть', 'просто', 'pip', '.'}
needed = set()
for name in needed_words:
for pkg in installed:
if name in pkg:
needed.add(pkg)
new_needed = set()
for pkg in list(needed):
if pkg in dependensies:
for dep in dependensies:
if dep not in needed:
needed.add(dep)
new_needed.add(dep)
print(f'Добавлено {len(new_needed)} пакетов. Всего {len(needed)} пакетов.')
# Вычисляем "полезный" объём.
sizes = {'B': 1, 'K': 1024, 'M': 1048576, 'G': 1073741824}
size_needed = 0
size_not_needed = 0
for pkg in installed:
size = float(installed_dict[pkg]['Установленный размер'].split()[0].replace(',', '.')) * sizes[installed_dict[pkg]['Установленный размер'].split()[1][0]]
if pkg in needed:
size_needed += size
else:
size_not_needed += size
print(f'Размер необходимых пакетов {int(size_needed / 1048576)} MB')
print(f'Размер пакетов для удаления {int(size_not_needed / 1048576)} MB')
print('Завершено.')
В итоге, получен универсальный способ сборки громоздких opensource комплексов с кучей зависимостей под Windows. По производительности, конечно, не ах. Всё-таки виртуализация (а-то и без VT-x может запуститься). Но при ограничениях, описанных в начале статьи, лучшего добиться можно, но неимоверно сложно. Напомню, что это должно было выглядеть, как программа, собранная исключительно под Windows.
Как незаметно запускать виртуальный Linux на QEMU
В некоторых задачах Linux просто необходим. И самым ярким тому примером на сегодня является наличие системы WSL. Однако не везде ею можно пользоваться. Некоторые предприятия принципиально застревают...
habr.com