Установка Tails 7.8 на eMMC / SSD

Tails 7.8 на внутренней eMMC — финальная инструкция (single source of truth)

Конфигурация: ASUS E410M, Celeron N4020, 4 GB RAM. Windows 11 на SSD NVMe (UEFI), Tails ставится на внутреннюю eMMC 60 GB. Документ собран только из реально сработавших команд и выводов консоли. Override-блоки, тупиковые попытки (if False-патч интерфейса, --force, ручная разметка как обход, диагностика call_format_sync) и неуспешные шаги удалены — оставлена только итоговая рабочая последовательность.


0. КРИТИЧНО: защита SSD и идентификация дисков

Перед любой записью обязательно убедиться, какой диск есть что. На этой машине роли распределены так (из реальных lsblk):

  • nvme0n1 (238.5G) — SSD с Windows 11 и EFI. НИКОГДА не трогать. Содержит nvme0n1p1 (100M, vfat — это EFI System Partition Windows), p2 (16M, MSR), p3 (100G, ntfs — система Windows), p4 (750M, ntfs — recovery).
  • mmcblk0 (58.3G) — внутренняя eMMC, целевой диск для Tails.
  • sda / sdb — загрузочная USB-флешка с Tails (live-носитель сессии).
  • mmcblk0boot0 / mmcblk0boot1 (по 4M, RO) — служебные boot-области eMMC, в установщике отсеиваются автоматически, их трогать не нужно.

Проверочные команды (read-only):

lsblk
sudo fdisk -l /dev/mmcblk0

Эталонный вывод lsblk рабочей сессии (USB как sda+sdb, цель — mmcblk0):

NAME         MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
loop0          7:0    0   1.7G  1 loop /usr/lib/live/mount/rootfs/filesystem.squashfs
sda            8:0    1  28.9G  0 disk
└─sda1         8:1    1  28.9G  0 part /usr/lib/live/mount/medium
sdb            8:16   1  14.6G  0 disk
└─sdb1         8:17   1  14.6G  0 part /media/amnesia/14F32
mmcblk0      179:0    0  58.3G  0 disk
nvme0n1      259:0    0 238.5G  0 disk
├─nvme0n1p1  259:1    0   100M  0 part
├─nvme0n1p2  259:2    0    16M  0 part
├─nvme0n1p3  259:3    0   100G  0 part
└─nvme0n1p4  259:4    0   750M  0 part
mmcblk0boot0 179:256  0     4M  1 disk
mmcblk0boot1 179:512  0     4M  1 disk

Правило безопасности: во всех командах записи фигурирует только /dev/mmcblk0. Если в команде разметки/записи когда-либо появляется nvme0n1 или sda/sdb — стоп, это ошибка, Windows и live-носитель под угрозой. Установщик Tails сам отсеивает nvme* (warning Skipping device ... connected to "" interface) — это штатный шум, не сбой.

fdisk -l /dev/mmcblk0 подтверждает целевой диск (GPT, ~58.31 GiB):

Disk /dev/mmcblk0: 58.31 GiB, 62612570112 bytes, 122290176 sectors
Disklabel type: gpt
Disk identifier: XXX-XXX

Windows на SSD при всей процедуре не затрагивается ни одной командой: ни разметка, ни запись загрузчика, ни правка GRUB не обращаются к nvme0n1. Tails ставит собственный загрузчик в ESP на mmcblk0p1, отдельная UEFI-запись. Windows Boot Manager на SSD остаётся как был.


1. Патч установщика: разрешить установку на несъёмную eMMC

Tails-installer по умолчанию принимает только съёмные (removable) устройства. eMMC несъёмная (HintSystem: true, HintAuto: false), поэтому проверки removable нужно нейтрализовать. Бэкапы делаются автоматически. Запускать целиком:

# === PATCH TAILS INSTALLER FOR eMMC ===

# 1. Backups
sudo cp /usr/lib/python3/dist-packages/tails_installer/creator.py \
        /usr/lib/python3/dist-packages/tails_installer/creator.py.bak
sudo cp /usr/lib/python3/dist-packages/tails_installer/gui.py \
        /usr/lib/python3/dist-packages/tails_installer/gui.py.bak
sudo cp /usr/lib/python3/dist-packages/tails_installer/utils.py \
        /usr/lib/python3/dist-packages/tails_installer/utils.py.bak

# 2. creator.py — skip the removable check
sudo python3 -c "
path = '/usr/lib/python3/dist-packages/tails_installer/creator.py'
with open(path, 'r') as f:
    content = f.read()
old = '        if not data[\"removable\"]:'
new = '        if False:  # patched: allow eMMC'
if old in content:
    content = content.replace(old, new, 1)
    with open(path, 'w') as f:
        f.write(content)
    print('creator.py: OK')
else:
    print('creator.py: WARNING — line not found, check manually')
"

# 3. gui.py — skip the removable check in the GUI device list
sudo python3 -c "
path = '/usr/lib/python3/dist-packages/tails_installer/gui.py'
with open(path, 'r') as f:
    content = f.read()
old = '                if not info[\"removable\"]:'
new = '                if False:  # patched: allow eMMC'
if old in content:
    content = content.replace(old, new, 1)
    with open(path, 'w') as f:
        f.write(content)
    print('gui.py: OK')
else:
    print('gui.py: WARNING — line not found, check manually')
"

# 4. utils.py — guard against None block (avoids a crash during enumeration)
sudo python3 -c "
path = '/usr/lib/python3/dist-packages/tails_installer/utils.py'
with open(path, 'r') as f:
    content = f.read()
old = '    drive = udisksclient.get_drive_for_block(block)'
new = '    if block is None: return None  # patched: None guard\n    drive = udisksclient.get_drive_for_block(block)'
if old in content:
    if 'patched: None guard' not in content:
        content = content.replace(old, new, 1)
        with open(path, 'w') as f:
            f.write(content)
        print('utils.py: OK')
    else:
        print('utils.py: already patched')
else:
    print('utils.py: WARNING — line not found, check manually')
"

# 5. Verify all three patches
echo ''
echo '=== VERIFY ==='
grep -n "if False\|patched" \
  /usr/lib/python3/dist-packages/tails_installer/creator.py \
  /usr/lib/python3/dist-packages/tails_installer/gui.py \
  /usr/lib/python3/dist-packages/tails_installer/utils.py

Фактический вывод проверки (то, что должно получиться):

/usr/lib/python3/dist-packages/tails_installer/creator.py:240:        if False:  # patched: allow eMMC
/usr/lib/python3/dist-packages/tails_installer/gui.py:589:                if False:  # patched: allow eMMC
/usr/lib/python3/dist-packages/tails_installer/utils.py:115:    if block is None: return None  # patched: Ventoy fix

После этого mmcblk0 (58.3G) проходит фильтры установщика и появляется в списке целей. Warning-строки про mmcblk0boot0/boot1 (too small) и nvme0n1* (skipping interface) — нормальный шум; сам mmcblk0 в списке отброшенных не значится, то есть прошёл.


2. Патч partition_device: заменить зависающий udisks-формат на sgdisk

При выборе mmcblk0 штатный установщик зацикливается на разметке — строка INFO: Partitioning device /dev/mmcblk0 повторяется каждые ~48 секунд (таймаут D-Bus × retry). Причина: udisks-вызов call_format_sync("gpt") зависает на этой eMMC (известный баг freedesktop #76178), а декоратор @retry уводит метод в бесконечный цикл, каждый раз заново стирая диск.

Проверено, что железо исправно и пишется: cat /sys/block/mmcblk0/ro0; sgdisk создаёт GPT за миллисекунды (exit 0); dd пишет в начало устройства 34.6 MB/s (exit 0). Значит достаточно заменить udisks-формат на sgdisk. Метод partition_device заменяется целиком, @retry снимается (чтобы ошибки всплывали сразу, а не уходили в петлю).

Запускать целиком (ставит отдельный бэкап .bak2):

# === Replace partition_device(): sgdisk instead of hanging udisks Format ===

sudo cp /usr/lib/python3/dist-packages/tails_installer/creator.py \
        /usr/lib/python3/dist-packages/tails_installer/creator.py.bak2

sudo python3 << 'PYEOF'
path = '/usr/lib/python3/dist-packages/tails_installer/creator.py'
src = open(path).read()

start_anchor = '    @retry\n    def partition_device(self)'
end_anchor   = '    @retry\n    def is_partition_GPT'

s = src.find(start_anchor)
e = src.find(end_anchor)
if s == -1 or e == -1 or e <= s:
    print('ABORT: anchors not found (s=%s e=%s) — file structure differs' % (s, e))
    raise SystemExit(1)

new_method = '''    def partition_device(self):
        # patched: udisks Format("gpt") hangs on this eMMC (fdo#76178).
        # Create GPT + ESP partition with sgdisk (proven), then use udisks
        # only to fetch the partition object and set system flags.
        # @retry intentionally removed so failures surface immediately.
        if not self.opts.partition:
            return
        import subprocess
        dev = self.drive["device"]
        self.log.info("Partitioning %s via sgdisk (patched)" % dev)
        size_bytes = self.system_partition_size(
            self.drive["parent_size"] if self.drive["parent_size"] else self.drive["size"]
        )
        size_mib = max(1, int(size_bytes) // (1024 * 1024))
        subprocess.run(["sgdisk", "--zap-all", dev], check=False)
        subprocess.run(
            ["sgdisk", "--clear",
             "--new=1:2048:+%dM" % size_mib,
             "--typecode=1:EF00",
             "--change-name=1:%s" % self.label,
             dev],
            check=True,
        )
        subprocess.run(["partprobe", dev], check=False)
        time.sleep(2)
        self.rescan_block_device(self._get_object(prop="block").props.block)
        # wait for udev/udisks to register the new partition object
        system_partition = None
        for _i in range(15):
            system_partition = self.get_system_partition()
            if system_partition is not None:
                break
            time.sleep(1)
            self.rescan_block_device(self._get_object(prop="block").props.block)
        if system_partition is None:
            raise Exception("patched partition_device: udisks never saw the new partition")
        self._set_partition_flags(system_partition, SYSTEM_PARTITION_FLAGS)
        self.rescan_block_device(self._get_object(prop="block").props.block)
        if hasattr(system_partition, "get_object_path"):
            return system_partition.get_object_path()
        return system_partition

'''

src = src[:s] + new_method + src[e:]
open(path, 'w').write(src)
print('OK: partition_device replaced')
PYEOF

echo ''
echo '=== verify the new method compiles ==='
sudo python3 -c "import ast; ast.parse(open('/usr/lib/python3/dist-packages/tails_installer/creator.py').read()); print('syntax OK')"

echo ''
echo '=== prep a clean target ==='
sudo wipefs -a /dev/mmcblk0; sudo sgdisk --zap-all /dev/mmcblk0
echo 'Now run:  tails-installer   — select mmcblk0 (58.3G), Install.'

Прежние три патча из шага 1 остаются — без них устройство не выбралось бы. Этот блок меняет только partition_device.

Откат при необходимости:

sudo cp /usr/lib/python3/dist-packages/tails_installer/creator.py.bak2 \
        /usr/lib/python3/dist-packages/tails_installer/creator.py

3. Установка

Запустить установщик с записью лога и выбрать mmcblk0:

tails-installer 2>&1 | tee /tmp/ti.log

В окне выбрать mmcblk0 (58.3G) и нажать Install. На этот раз разметка проходит через sgdisk и установка идёт до конца. Фактический ход рабочего лога (ключевые INFO-строки):

[creator.py:865 (partition_device)] INFO: Partitioning /dev/mmcblk0 via sgdisk (patched)
...
[creator.py:1159 (format_device)] INFO: Formatting /dev/mmcblk0p1 as FAT32
[creator.py:967 (verify_filesystem)] INFO: Verifying filesystem...
[creator.py:383 (extract_iso)] INFO: Extracting live image to the target device...
[creator.py:370 (wrapper)] INFO: Wrote to device at 15 MB/s
[creator.py:996 (install_bootloader)] INFO: Installing bootloader...
[creator.py:1271 (flush_buffers)] INFO: Synchronizing data on disk...
[creator.py:1255 (reset_mbr_and_write_random_seed)] INFO: Resetting Master Boot Record of /dev/mmcblk0
[creator.py:1260 (reset_mbr_and_write_random_seed)] INFO: Writing random seed to LBA 34 of /dev/mmcblk0
[creator.py:1271 (flush_buffers)] INFO: Synchronizing data on disk...

Внимание на безвредный warning от sgdisk в процессе: unable to inform the kernel of the change ... You should reboot now. Это значит, что таблица записана на диск, но ядро держало устройство и не перечитало её на лету — снимается перезагрузкой. На сами данные не влияет: дальше форматирование, распаковка образа и установка загрузчика прошли успешно.


4. Проверка установки (read-only, до перезагрузки)

# === verify on-disk GPT is consistent and partition is the Tails system part ===
sudo sgdisk -v /dev/mmcblk0
sudo sgdisk -p /dev/mmcblk0
lsblk -o NAME,SIZE,TYPE,FSTYPE,LABEL /dev/mmcblk0
sudo blkid /dev/mmcblk0p1

Фактический рабочий результат:

No problems found. 105512893 free sectors (50.3 GiB) available in 2
segments, the largest of which is 105510879 (50.3 GiB) in size.
...
Number  Start (sector)    End (sector)  Size       Code  Name
   1            2048        16779263   8.0 GiB     EF00  Tails
...
NAME         SIZE TYPE FSTYPE LABEL
mmcblk0     58.3G disk
└─mmcblk0p1    8G part vfat   TAILS
/dev/mmcblk0p1: LABEL_FATBOOT="TAILS" LABEL="TAILS" UUID="1E56-1081" BLOCK_SIZE="512" TYPE="vfat" PARTLABEL="Tails" PARTUUID="XXX-XXX"

Критерии успеха: sgdisk -v → «No problems found»; раздел 1 = 8.0 GiB, код EF00, имя Tails, FAT32, метка TAILS; после раздела остаётся ~50.3 GiB единым свободным сегментом под Persistent Storage.


5. Первая загрузка с eMMC

Закрыть установщик, извлечь USB-флешку, перезагрузиться (ребут обязателен — синхронизирует таблицу разделов с ядром после warning из шага 3).

При старте многократно нажимать Esc → ASUS E410M показывает boot-меню (или вход в BIOS). В меню Tails выбрать пункт «Tails — External Hard Disk» (это третий пункт, GRUB-id livenonremovable). Именно он грузится с внутренней eMMC. Без нажатия Esc машина уходит в обычный Windows 11 — Tails «не виден просто так», что является штатным и удобным поведением (маскировка, правдоподобное отрицание).

Почему именно этот пункт: из трёх пунктов GRUB только livenonremovable не содержит параметра live-media=removable. Пункты live и livefailsafe содержат его и на несъёмной eMMC падают с ошибкой Unable to find a medium containing a live file system.

Проверка, что загрузились именно с eMMC (после входа в систему):

findmnt /usr/lib/live/mount/medium     # expect /dev/mmcblk0p1
cat /proc/cmdline | tr ' ' '\n' | grep -E 'live-media|boot=live'   # must NOT contain live-media=removable

findmnt должен показать /dev/mmcblk0p1; в /proc/cmdline не должно быть live-media=removable.


6. Welcome Screen: админ-пароль и Persistent Storage

На экране приветствия (до входа в рабочий стол):

  • Administration Password — задавать только если в сессии понадобится sudo (например, для правки GRUB в шаге 7). Пароль временный, сбрасывается при перезагрузке, задаётся заново при каждом старте. Без него sudo в Tails не работает вовсе.
  • Это отдельная вещь от парольной фразы Persistent Storage. Парольная фраза persistent постоянная (шифрует том, нужна при каждой разблокировке). Админ-пароль сессионный. Делать их разными.

7. Persistent Storage (LUKS) на eMMC

Запустить Applications → Tails → Persistent Storage. На этой установке приложение запустилось без проблем и том разблокировался — отдельного патча persistence-setup не потребовалось (опасение про removable-проверку в tps не подтвердилось). Приложение создаёт LUKS-зашифрованный том в свободных ~50 GiB после системного раздела. Задать парольную фразу, включить нужные функции, перезагрузиться, на Welcome Screen разблокировать том той же фразой.

Рабочий набор включённых функций на этой системе:

  • Personal Data
  • Welcome Screen
  • Tor Connection
  • Additional Software
  • GnuPG
  • Kleopatra
  • KeePassXC
  • Dotfiles

(Printers и Thunderbird оставлены выключенными — включаются при необходимости.)


8. Trace-free / только Tor

Весь трафик Tails по умолчанию идёт через Tor — это поведение самой системы Tails, не требует настройки. Включённая в шаге 7 функция Tor Connection лишь сохраняет настройки подключения к Tor между сессиями (мосты и т.п.), но не меняет принцип: приложения, пытающиеся идти в обход Tor, блокируются на уровне firewall Tails. Никаких дополнительных действий для «трафик только через Tor» не нужно — не отключать и не обходить штатный механизм.


9. GRUB-автозагрузка: сделать рабочий пункт дефолтным

Чтобы не выбирать пункт 3 вручную каждый раз, в grub.cfg на eMMC добавляется set default="livenonremovable". Установлено по факту: строки set default в конфиге изначально нет, есть только set timeout=4, поэтому GRUB по умолчанию берёт нулевой (нерабочий на eMMC) пункт live. Оба файла grub.cfg идентичны (по 2502 байта); реально читается /EFI/debian/grub.cfg, второй в подкаталоге grub/ — дубликат, правятся оба.

Правка только добавляет строку, пункты меню не трогаются, timeout=4 сохраняется. Worst case при ошибке — GRUB просто покажет меню, и пункт 3 выбирается руками, как раньше; заблокировать загрузку этим невозможно. Раздел монтируется read-only намеренно, поэтому на время правки ремонтируется в rw и обязательно возвращается в ro. Блок идемпотентный.

# === FINAL EDIT: make the working "External Hard Disk" entry the GRUB default ===
# Only ADDS a default line. All three menu entries stay intact, timeout stays 4s.

MED=/usr/lib/live/mount/medium
F1="$MED/EFI/debian/grub.cfg"
F2="$MED/EFI/debian/grub/grub.cfg"

echo '--- hardware writable? must print 0 ---'
cat /sys/block/mmcblk0/ro

echo '--- remount the medium read-write ---'
sudo mount -o remount,rw "$MED"
findmnt -no OPTIONS "$MED"                    # must contain rw

echo '--- backups ---'
sudo cp "$F1" "$F1.bak"
sudo cp "$F2" "$F2.bak"

echo '--- insert default ONLY if absent (safe to re-run) ---'
for f in "$F1" "$F2"; do
  if grep -q '^set default=' "$f"; then
    echo "already set: $f"
  else
    sudo sed -i '/^set timeout=/a set default="livenonremovable"' "$f"
    echo "patched: $f"
  fi
done

echo '--- VERIFY 1: default + timeout present in BOTH files ---'
grep -nE '^set default=|^set timeout=' "$F1" "$F2"

echo '--- VERIFY 2: all three menu entries still intact ---'
grep -nE "menuentry .*--id '(live|livefailsafe|livenonremovable)'" "$F1"

echo '--- remount medium back to read-only ---'
sudo sync
sudo mount -o remount,ro "$MED"
findmnt -no OPTIONS "$MED"                     # back to ro

Фактический успешный вывод проверок:

--- VERIFY 1: default + timeout present in BOTH files ---
/usr/lib/live/mount/medium/EFI/debian/grub.cfg:32:set timeout=4
/usr/lib/live/mount/medium/EFI/debian/grub.cfg:33:set default="livenonremovable"
/usr/lib/live/mount/medium/EFI/debian/grub/grub.cfg:32:set timeout=4
/usr/lib/live/mount/medium/EFI/debian/grub/grub.cfg:33:set default="livenonremovable"
--- VERIFY 2: all three menu entries still intact ---
37:menuentry 'Tails 7.8' --id 'live' {
46:menuentry 'Tails 7.8 (Troubleshooting Mode)' --id 'livefailsafe' {
55:menuentry 'Tails 7.8 (External Hard Disk)' --id 'livenonremovable' {

После этого при перезагрузке без флешки мелькает меню 4 секунды и система сама уходит в livenonremovable. Esc по-прежнему позволяет перехватить меню. Без Esc на холодном старте грузится Windows 11 (приоритет загрузки в UEFI у Windows Boot Manager выше — это сохраняет нормальную загрузку Windows и не требует изменений).

Откат правки GRUB:

MED=/usr/lib/live/mount/medium
sudo mount -o remount,rw "$MED"
sudo cp "$MED/EFI/debian/grub.cfg.bak" "$MED/EFI/debian/grub.cfg"
sudo cp "$MED/EFI/debian/grub/grub.cfg.bak" "$MED/EFI/debian/grub/grub.cfg"
sudo sync && sudo mount -o remount,ro "$MED"

10. Итоговая модель загрузки и разделение ОС

  • Холодный старт без действий → Windows Boot Manager с SSD nvme0n1 → Windows 11. SSD и его EFI не изменены ни одной командой за всю процедуру.
  • Старт с зажатым Esc → UEFI boot-меню → выбор записи Tails (eMMC mmcblk0) → GRUB → авто-вход в livenonremovable через 4 секунды.
  • Две ОС физически на разных носителях (Windows на NVMe, Tails на eMMC), их загрузчики независимы. Повредить Windows установкой Tails невозможно, пока в командах записи фигурирует исключительно /dev/mmcblk0.

11. Ключевые факты для справки

  • Целевое устройство: /dev/mmcblk0 (58.3 GiB eMMC).
  • SSD с Windows (НЕ ТРОГАТЬ): /dev/nvme0n1 (238.5 GiB).
  • Системный раздел Tails: /dev/mmcblk0p1, FAT32, LABEL=TAILS, UUID=1E56-1081, PARTUUID=XXX-XXX, тип EF00, 8 GiB.
  • Свободно под persistent: ~50.3 GiB единым сегментом.
  • Рабочий GRUB-пункт: --id 'livenonremovable' («External Hard Disk»).
  • Нерабочие на eMMC: 'live' и 'livefailsafe' (содержат live-media=removable).
  • Реально читаемый конфиг: /EFI/debian/grub.cfg (дубликат в grub/ правится тоже).
  • Версия: Tails 7.8.

12. Предупреждения на будущее

  1. Обновление Tails — это перезапись системной части на mmcblk0p1 (squashfs, ядро, initrd, оба grub.cfg), а не образ с нуля. Persistent-том НЕ затрагивается, данные сохраняются. После апдейта строка set default слетит → прогнать блок из шага 9 повторно (идемпотентный).
  2. Автообновление на eMMC может не сработать — оно тоже завязано на removable-логику и разметку, которые обходились патчами установщика. Если откажет — ручной путь: загрузочная флешка с новым Tails → переустановка системного раздела тем же пропатченным методом (шаги 1–5), persistent-том при этом пересоздавать не нужно.
  3. Раздел монтируется read-only намеренно. Для любых правок временно ремонтировать в rw и обязательно возвращать в ro.
  4. Патчи установщика (creator.py, gui.py, utils.py, partition_device) живут только в RAM-сессии установщика и для уже готовой системы значения не имеют — помнить о них нужно лишь при переустановке/обновлении.
BitoNator
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: