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