Raising the marker makes the older app refuse a 2.2.0 package through its own update path, but a root-manager flash never consults it. That leaves one reachable combination in which a completed stop is displayed as a failure, so the operations guide names it and the remedy. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
204 lines
16 KiB
Markdown
204 lines
16 KiB
Markdown
# Эксплуатация Zapret2: поддержка, разрешения и восстановление
|
||
|
||
Этот документ дополняет основной README точными эксплуатационными границами APK и
|
||
root-модуля. Он описывает контракт исходников. Подписанный релиз и конкретное
|
||
устройство считаются проверенными только после прохождения release/device-матрицы.
|
||
|
||
## Поддерживаемая конфигурация
|
||
|
||
| Компонент | Контракт |
|
||
| --- | --- |
|
||
| Android | API 24 (Android 7.0) или новее |
|
||
| Архитектура | ARM64 или ARM; x86/x86_64 не поставляются |
|
||
| Root-менеджер | Magisk 20.4+, KernelSU или APatch |
|
||
| Установка ZIP | Только boot mode: интерфейс менеджера либо `magisk --install-module`, `ksud module install` или `apd module install` из Zapret2 Control |
|
||
| Firewall | IPv4 `iptables` с `NFQUEUE`, `--queue-bypass`, `multiport` и `mark`; `connbytes` необязателен и при отсутствии включает исходящий `KEEPALIVE`-режим оригинального zapret2 |
|
||
| IPv6 | Требует того же набора в `ip6tables`; при его отсутствии IPv6 явно не перехватывается |
|
||
| Интерфейс | Jetpack Compose, Material 3 Expressive, светлая/тёмная тема и dynamic color на Android 12+ |
|
||
|
||
KernelSU 3.x требует metamodule только для содержимого `system/`: CLI-wrapper’ов и
|
||
hosts-оверлея. Основной сервис запускается модульным `service.sh` и не требует
|
||
монтирования `/system`. Другие root-менеджеры не входят в подтверждённую матрицу:
|
||
приложение не угадывает неизвестный installer backend.
|
||
|
||
Прошивка ZIP через TWRP/custom recovery не поддерживается. Installer получает
|
||
корректные `MODPATH`, архитектуру и manager environment только в boot mode. Поддерживаемый
|
||
root-менеджер сначала распаковывает кандидата в `/data/adb/modules_update/zapret2`,
|
||
а активный модуль остаётся неизменным до перезагрузки.
|
||
|
||
## Зачем APK нужны разрешения
|
||
|
||
- `INTERNET` — только для запроса метаданных релиза и скачивания выбранных APK/ZIP.
|
||
- `ACCESS_NETWORK_STATE` — для отображения типа текущего подключения.
|
||
- `REQUEST_INSTALL_PACKAGES` — для передачи уже проверенного APK системному
|
||
установщику. Приложение не может незаметно завершить установку за пользователя.
|
||
- Root выдаётся отдельно через активный менеджер/libsu. Он нужен для чтения защищённого
|
||
состояния, изменения конфигурации, запуска/остановки сервиса и staging обновления
|
||
через manager backend.
|
||
|
||
Приватный `FileProvider` не экспортируется и выдаёт временный read-доступ только к
|
||
проверенному APK внутри `cache/updates/`. Доступ ко всему cache или внешнему хранилищу
|
||
не предоставляется.
|
||
|
||
## Что происходит при обновлении
|
||
|
||
Каждое обновление создаёт новую чистую генерацию из release ZIP в
|
||
`/data/adb/modules_update/zapret2`. Старое дерево не копируется и не объединяется с
|
||
новым: runtime-настройки, пользовательские пресеты, hostlist-файлы и Lua-изменения
|
||
начинаются с содержимого релиза. Активный модуль и запущенная служба не меняются до
|
||
перезагрузки. Downgrade через APK не поддерживается.
|
||
|
||
## Нормальное восстановление
|
||
|
||
У пакетного обновления нет собственного lock/transaction/backup/recovery слоя.
|
||
Незавершённый staging можно повторить через root-менеджер; активная генерация остаётся
|
||
неизменной до перезагрузки. Runtime lifecycle и Full rollback имеют отдельные
|
||
контракты и не участвуют в установке новой версии.
|
||
|
||
Безопасный порядок действий:
|
||
|
||
1. Откройте Control и дождитесь завершения автоматической проверки состояния.
|
||
2. Если доступно, повторите операцию или выполните обычную остановку/перезапуск.
|
||
3. Для подтверждённо повреждённой установки используйте Full rollback в Control.
|
||
4. Сохраните вывод Logs/Diagnostics; экспорт ограничен последними 64 КиБ и проходит
|
||
редактирование приватных идентификаторов.
|
||
5. Если приложение не может доказать безопасное состояние, перезагрузитесь и повторите
|
||
проверку до любых ручных удалений.
|
||
|
||
Fail-closed сообщение не означает, что нужно стирать state-файлы. Оно означает, что
|
||
автоматическая операция отказалась продолжать без достаточного доказательства.
|
||
|
||
## Удаление и чистая переустановка
|
||
|
||
Для обычного удаления используйте интерфейс своего root-менеджера и затем
|
||
перезагрузите устройство.
|
||
Обычный uninstall удаляет runtime-состояние, но сохраняет защищённый tombstone как
|
||
доказательство завершённого удаления.
|
||
|
||
Для полностью чистой переустановки есть отдельная необратимая операция:
|
||
|
||
1. В APK откройте Control → «Восстановление» → «Удалить данные модуля полностью» и
|
||
подтвердите удаление. APK останется установленным, а его внутреннее состояние будет
|
||
сброшено вместе с модулем.
|
||
2. Если интерфейс APK недоступен, нажмите Action у модуля в root-менеджере два раза
|
||
в течение 120 секунд. Первое нажатие создаёт одноразовое подтверждение, второе
|
||
выполняет удаление.
|
||
3. Перезагрузите устройство до повторной установки модуля.
|
||
|
||
Обе кнопки вызывают один канонический lifecycle-сценарий. Он останавливает сервис,
|
||
проверяет отсутствие принадлежащих Zapret2 процессов и firewall-правил, затем удаляет
|
||
активный/ожидающий модуль, настройки, списки, журналы, state, install/update/recovery
|
||
каталоги и устаревшие файлы. Сам установленный APK не удаляется. Операция отказывается
|
||
продолжать при активной или неоднозначной транзакции и не обходит recovery-доказательства.
|
||
|
||
Uninstall сначала публикует защищённый tombstone, затем выполняет bounded stop и
|
||
проверяет отсутствие принадлежащих Zapret2 процессов/firewall-правил. Неавторизованные
|
||
или неоднозначные файлы намеренно сохраняются для диагностики вместо слепого удаления.
|
||
|
||
## Диагностика
|
||
|
||
Базовые root-команды:
|
||
|
||
```sh
|
||
su -c zapret2-status
|
||
su -c zapret2-stop
|
||
su -c zapret2-start
|
||
su -c zapret2-restart
|
||
```
|
||
|
||
Проверьте в таком порядке:
|
||
|
||
1. Root-менеджер действительно выдал APK root и модуль не отключён/не ожидает удаления.
|
||
2. Control не показывает active update/recovery или partial package state.
|
||
3. В статусе доказаны текущий owner, PID/starttime, generation и ruleset.
|
||
4. Ядро поддерживает обязательные NFQUEUE/firewall-возможности.
|
||
5. AdGuard, NetGuard, AFWall+ и другие владельцы firewall временно отключены для
|
||
изолированной проверки.
|
||
6. Для стратегии выбран совместимый preset/category и существующие hostlist-файлы.
|
||
|
||
Read-only device preflight и формат сохраняемых доказательств описаны в
|
||
[`tests/device/README.md`](../tests/device/README.md). Изменяющие стадии допускаются
|
||
только на одноразовом устройстве с явными подтверждениями, указанными в harness.
|
||
|
||
### Модуль не запускается и не останавливается
|
||
|
||
Если и `zapret2-start`, и `zapret2-stop` отвечают `RECOVERY_BLOCKED`
|
||
(в приложении — ошибка с доменом `LIFECYCLE` и стадией `START_RECOVERY`
|
||
или `STOP_RECOVERY`), значит в `/data/adb/zapret2-state` осталось состояние
|
||
прерванной операции. Снять его вправе только загрузочный проход
|
||
восстановления — **перезагрузите устройство**, после чего автостарт
|
||
отработает штатно. Это же указано в тексте самой ошибки.
|
||
|
||
Текст ошибки называет и само лекарство: перезагрузка помогает, когда состояние
|
||
классифицировано как `unsafe`. Если речь о незавершённом полном откате
|
||
(`rollback-partial`), сначала доведите откат до конца — перезагрузка сама по
|
||
себе такие улики не снимает, они durable по замыслу.
|
||
|
||
Если и это не помогло, каталог состояния можно удалить целиком. **Сначала
|
||
проверьте, нет ли в нём резервной копии вашего hosts-оверлея** — во время
|
||
незавершённого полного отката это может быть единственная копия:
|
||
|
||
```sh
|
||
su -c 'ls -l /data/adb/zapret2-state/hosts.rollback.backup'
|
||
# если файл есть — сохраните его, например:
|
||
su -c 'cp /data/adb/zapret2-state/hosts.rollback.backup /sdcard/zapret2-hosts.backup'
|
||
su -c 'rm -rf /data/adb/zapret2-state'
|
||
```
|
||
|
||
Пресеты, hostlist-файлы и `runtime.ini` лежат в каталоге модуля и при этом не
|
||
страдают.
|
||
|
||
Отказ `STATE_UNAVAILABLE` («insecure or unavailable zapret2 state directory»)
|
||
лечится тем же способом: каталог должен принадлежать root и иметь режим 0700.
|
||
|
||
### Старт отказывает с «uninstall tombstone blocks start/restart»
|
||
|
||
Так бывает после ручного запуска `uninstall.sh` (без удаления модуля через
|
||
root-менеджер): скрипт намеренно оставляет метку сериализации, а снимает её
|
||
только полное удаление модуля менеджером. Если модуль остался установленным и
|
||
вы хотите им пользоваться, метку нужно убрать вручную:
|
||
|
||
```sh
|
||
su -c 'rm -f /data/adb/zapret2-state/uninstall.tombstone'
|
||
```
|
||
|
||
Штатное удаление через root-менеджер этой ситуации не создаёт: там удаляется
|
||
весь приватный каталог целиком.
|
||
|
||
### Сообщение «IPv6 не удалось перепроверить»
|
||
|
||
На части устройств ядро собрано без `ip6table_mangle`, поэтому `ip6tables`
|
||
установлен, но на запросы не отвечает. Модуль отличает такую ситуацию от занятой
|
||
блокировки `xtables`: он повторяет опрос несколько раз и только после этого
|
||
считает семейство недоступным на этом устройстве.
|
||
|
||
Доказать чистоту такого семейства невозможно, а отказывать навсегда нельзя —
|
||
следующая загрузка отказала бы точно так же и заблокировала бы stop, uninstall и
|
||
полное удаление. Поэтому семейство пропускается, а неопределённость доводится до
|
||
вас вместо того, чтобы быть тихо списанной:
|
||
|
||
- остановка и удаление завершаются успешно, но не заявляют «проверено чисто»;
|
||
- полный откат сообщает `Z2_RB_STATUS=partial` при `Z2_RB_FIREWALL_CLEAN=0`;
|
||
- полное удаление отдаёт квитанцию `partial` с той же оговоркой;
|
||
- приложение показывает оговорку рядом с результатом, а не как ошибку.
|
||
|
||
Это не сбой и не требует вмешательства. Правила несут `--queue-bypass`, поэтому
|
||
трафик не блокируется даже если что-то осталось, а перезагрузка снимает все
|
||
правила netfilter независимо от того, удалось ли их прочитать. Если сообщение
|
||
появляется на каждой операции — это свойство ядра устройства, а не состояния
|
||
модуля.
|
||
|
||
## Известные границы текущего дерева
|
||
|
||
- Material 3 Expressive закреплён на экспериментальном API; это не stable Material 3.
|
||
- Source-аудит не заменяет compile/lint/test, screenshot/TalkBack и rooted-device gates.
|
||
- x86/x86_64, неизвестные installer backend’ы и устройства без обязательного
|
||
NFQUEUE-контракта не заявлены как поддержанные.
|
||
- До публикации следует сверить SHA-256 и подпись именно финальных APK/ZIP, а не только
|
||
имена файлов или страницу релиза.
|
||
- Обновляйте APK и модуль вместе. В 2.2.0 остановка вправе не подтверждать набор
|
||
правил, когда одно семейство прочитать не удалось; приложение 2.1.x такую форму
|
||
выразить не умеет и покажет успешную остановку как отказ. Маркер контракта поднят
|
||
до 8, поэтому обновление модуля **из приложения** старая версия отклонит сама, —
|
||
но прошивка ZIP через рут-менеджер эту проверку обходит, и там пара «старый APK +
|
||
новый модуль» достижима.
|