PocketDeploy: веб-приложение и свой Bluesky PDS на Android — что получилось и где пока пределы

Мне хотелось дать старому Android работу сервера: открыть приложение, создать проект и получить сайт с API и данными на самом устройстве. Обновление не должно стирать данные, а ИИ-агент — получать доступ ко всему телефону только потому, что ему поручили поправить главную страницу. За отправную точку я взял подход Coolify; PocketDeploy — самостоятельный исследовательский проект, с Coolify мы не связаны.
В первой версии рабочего сценария появилась доска задач с JavaScript API. Затем — постоянный HTTPS-адрес, разрешения для MCP-клиентов и отдельный Node.js runtime через Termux. В 0.7.0-alpha.2 дошли до более интересной проверки: официальный Bluesky PDS хранит данные в Android, а его лабораторный аккаунт виден публичным Relay и AppView.
Сайт проекта тоже получает HTML из облачного Android. Это именно эмулятор Android 11 x86_64 с заданными 2048 МиБ RAM, запущенный на VPS; физический старый телефон в этих результатах не участвовал. Облако здесь нужно для воспроизводимой лаборатории. Практический смысл проекта предстоит подтвердить на реальном устройстве, где появятся Doze, нагрев, батарея и смена сети.
Я задаю направление и требования; код, автоматические прогоны и подготовку материалов выполняет команда ИИ-агентов. После разбора результатов мы решили сузить ближайшую цель: воспроизводимый личный сайт/PDS с HTTPS, понятной диагностикой, восстановлением и резервной копией. Сейчас важнее довести этот путь, чем добавлять протоколы. Ниже — устройство системы, реальные экраны и сбои, которые помогли перестать путать постоянный домен с доступностью сервиса.
Срез исследовательской альфы. APK 0.7.0-alpha.2, versionCode 10, официальный
@atproto/pds0.5.49. Последняя проверка перед обновлением статьи: 26 октября 2026, федерация — 02:20:23 UTC; сайт и PDS health — 02:13:39 UTC. После обслуживания 26 октября публичные запросы и федерация прошли. На экране сохранялся диалог старого ANR System UI; он закрылся после действия владельца, при этом сайт и PDS продолжали отвечать. Подробности ниже. Матрица Android 11/15 относится к прежней версии 0.6; минимум RAM для телефонов, полный OAuth, почтовое восстановление и 24/7 пока не приняты.
Что здесь можно попробовать
В PocketDeploy несколько контуров. Они не дают одинаковых гарантий и не открываются наружу одним общим переключателем.
Сценарий | Исполнение и доступ | Проверенная граница |
|---|---|---|
Доска задач, заметки, небольшой JSON API | Встроенный Java/Rhino runtime; устройство или ПК по USB | Страницы, авторизованный API и постоянные данные на одном порту |
Публичный сайт или справочник | Готовый HTML на Android; отдельный HTTPS-шлюз | В профиле лендинга разрешены GET/HEAD, backend и управление закрыты |
Node.js-проект из Git | Отдельный runtime в Termux | Node/Git-сценарии проверены на host; сквозной приватный GitLab clone на Android ещё не принят |
Свой Bluesky PDS | Официальный Node.js-сервер в Termux; отдельный домен и прокси с POST/WebSocket | Один лабораторный аккаунт проверен через публичные Relay и AppView |
Обновление проекта агентом | MCP-клиент на доверенном ПК, приватный Android-контроллер | Только действия и проекты, которые разрешил владелец в нативном окне |
Публичная ссылка лендинга не открывает CRUD API доски автоматически. А доступ к PDS не даёт доступа к MCP, Termux-контроллеру или ADB. Для сайта и PDS используются разные профили шлюза.
Где действительно исполняется приложение
Встроенный runtime состоит из Java UI, службы хостинга, HTTP-сервера, хранилища проектов и Rhino. У проекта свои порт, релизы и данные. Браузер получает HTML, CSS и JavaScript с Android; запрос к /api/* вызывает обработчик там же. Компьютер не держит вторую копию бэкенда.
Локальный сервер работает без root, Termux и ПК. Доверенный ПК нужен для stdio-моста MCP и может держать публичный HTTPS-шлюз. В текущей лаборатории роль публичного шлюза выполняет VPS: локальный Mac в выдаче сайта не участвует. В любом варианте внешний адрес зависит от работоспособности своего шлюза, даже если Android продолжает обслуживать локальные запросы.

Схема встроенного runtime из материалов 0.6. Она не включает добавленный позже PDS: тот работает отдельным процессом Termux, а текущий публичный шлюз находится на VPS.
Первый прикладной пример — доска задач из пяти файлов: index.html, app.js, styles.css, backend.js и pocketdeploy.json. В браузере можно создавать, переименовывать, перемещать и удалять задачи. Файлы интерфейса входят в релиз, записи задач лежат отдельно.

Сеанс 25 октября 2026 года: браузер подключён через USB/adb-проброс к API Android 11 с 0.6.0-alpha.3 и показывает четыре сохранённые задачи. Для этих кадров выполнялись подключение и чтение, без нового полного CRUD-прогона. Названия и статусы карточек — демонстрационные данные: «Проверить старый Android» в колонке «Готово» не означает приёмку физического телефона.
React или Vue для встроенного режима сначала нужно собрать на ПК и загрузить готовые файлы. В нём нет npm-сборки, Docker, Node.js SSR, SQL или WebSocket. Для Node.js существует отдельный контур Termux: его реальную установку и запуск на Android проверили на PDS. Это не заменяет сквозную проверку Android → Termux → приватный GitLab, которая ещё впереди.
Первый запуск: задача, которая остаётся после перезапуска
Для знакомства достаточно встроенного режима. APK 0.7.0-alpha.2 и инструкция лежат в публичном репозитории; сборка подписана ключом разработки. В манифесте указан minSdk 26, то есть Android 8. Это порог установки APK, а не подтверждение всех функций на этой ОС. Прежний встроенный runtime проверяли на эмуляторах Android 11 и 15; новый PDS — на Android 11.
Создайте проект из шаблона «Доска задач · SPA + API + база». Выберите свободный порт, например
8081: стартовый пример занимает8080.Оставьте «Только устройство / ПК по USB» и завершите создание. Затем отдельно нажмите «Запустить сервер» → «Открыть сайт»: создание проекта само сервер не запускает.
Откройте «API-токен» проекта и вручную введите его в доску. Он остаётся в памяти вкладки; после её перезагрузки потребуется повторный ввод.
Создайте задачу, остановите и снова запустите сервер, затем откройте доску и введите токен. Запись должна сохраниться: её данные находятся вне файлов релиза.
Чтобы открыть этот же проект на ПК, после подключения своего устройства по USB и подтверждения USB-отладки выполните:
adb -s DEVICE_SERIAL forward tcp:18081 tcp:8081
# В браузере ПК: http://127.0.0.1:18081/
DEVICE_SERIAL заменяется идентификатором вашего устройства из adb devices. Для этого примера облачный аккаунт и включённый MCP не нужны.

Нативный обзор работающего стенда Android 11: три запущенных проекта. Архивный снимок 0.6.0-alpha.2, показывающий состояние того сеанса. Это не новый экран PDS и не доказательство трёх работающих проектов в текущем прогоне.
Маленький API и данные вне релиза
Контракт бэкенда — синхронная функция handle(request, db). Она получает запрос и хранилище, возвращает статус и JSON. Контекст JavaScript создаётся заново для каждого вызова, поэтому глобальная переменная не заменяет базу. Например, счётчик выглядит так:
function handle(request, db) {
if (request.path !== '/api/counter') {
return {status: 404, body: {error: 'Not found'}};
}
if (request.method === 'POST') {
var value = (db.get('counter') || 0) + 1;
db.set('counter', value);
return {body: {value: value}};
}
if (request.method === 'GET') {
return {body: {value: db.get('counter') || 0}};
}
return {status: 405, body: {error: 'Method not allowed'}};
}
При прямом обращении к серверу проекта, в том числе через USB-проброс, API требует Bearer-токен проекта. Его нельзя зашивать в публичный frontend; это секрет владельца, а не полноценная система пользовательских аккаунтов. В готовой доске токен вводится вручную и хранится в памяти вкладки до её перезагрузки.
База содержит JSON-значения в контейнере Java Properties v1: data/records.db, до 256 ключей, 64 КиБ на значение и 2 МиБ на файл. Каждый авторизованный вызов backend читает небольшой файл целиком; запросы статики и отклонённая авторизация базу не загружают. Запись переписывает его через временный файл, fsync и атомарное переименование. Это осознанно простая модель с небольшими пределами.
Запросы одного проекта сериализованы. Изменения сначала накапливаются в памяти и фиксируются после корректного ответа 2xx или 3xx. Исключение либо 4xx/5xx отменяют записи этого вызова. Невалидный PATCH не должен сохранять половину задачи.
У TaskBoard это можно проследить до ключей: задача t7 лежит в task_t7, последовательность идентификаторов — в task_next. При ограничении в 200 задач получается 201 ключ из доступных 256. Удалённые идентификаторы не переиспользуются. Обновление заголовка и статуса проходит в одной транзакции, поэтому конкурентные изменения разных полей не должны затереть друг друга.

Тот же сеанс 25 октября, маршрут /task/t7. Открыта существующая запись с заголовком и статусом из API. Кнопка сохранения на этом кадре не нажималась: снимок подтверждает чтение карточки, а проверки записи приведены отдельно в протоколе.
Что происходит с одним HTTP-запросом
У каждого сайта четыре HTTP-worker и очередь на 16 заданий. Таймер на десять секунд начинается сразу после accept, ещё до очереди, и закрывает сокет. Переполненная очередь приводит к закрытию нового соединения. После ответа соединение также закрывается: keep-alive здесь нет. Сериализация API по файлу базы — отдельный механизм: четыре worker не означают четыре параллельные транзакции одного проекта.
Таймер ограничивает жизнь соединения, а не гарантирует немедленную остановку произвольного вычисления Java/Rhino или отмену уже выполненной записи. У Rhino свой счётчик инструкций. Это разные границы, которые нельзя складывать в обещание полной изоляции.
Часть запроса или приложения | Предел и следствие для клиента |
|---|---|
JSON-тело API | 64 КиБ; для POST/PUT/PATCH нужны |
Исходник | 128 КиБ UTF-8 |
JSON-ответ | 256 КиБ; потоковый ответ и Promise не поддерживаются |
Передача HTTP |
|
Поэтому клиент сначала формирует законченное JSON-тело. Заголовок авторизации не передаётся самому handle; runtime проверяет его раньше. CORS для другого origin не включён — frontend и API предполагаются в одном проекте. Ошибка обработчика возвращается без stack trace и путей файлов; в журнале остаётся номер строки, без значений базы и токена.
Данные находятся вне webroot и каталогов релизов. Обновление обработчика не переносит базу в новый bundle; откат кода не откатывает содержимое базы. Совместимость старого обработчика с новой схемой остаётся задачей приложения. Автоматического backup/restore встроенных проектов пока нет. Проверка отдельной офлайн-копии PDS, о которой ниже, относится к другому хранилищу. Подробности собраны в контракте бэкенда.
Почему одного переключения версии недостаточно
Деплой создаёт и проверяет новый релиз, затем переключает указатель. Текущий и предыдущий сохраняются для отката. Квоты проверяются по всему получившемуся релизу, включая старые ресурсы при merge: маленькое дополнение не должно обходить ограничение общего размера.
Оказалось важно выбрать правильное начало отсчёта для удаления. Старый релиз можно создать утром, а перестать использовать вечером. Поэтому двадцатисекундная отсрочка начинается после его выхода из пары «текущий/предыдущий», а не после создания. Очистка происходит при следующих операциях, а не обязательно ровно по таймеру.
Это оставляет запас для уже принятых HTTP-запросов с абсолютным десятисекундным сроком от принятия соединения, включая очередь. Такой срок отличается от тайм-аута между чтениями: клиент не может бесконечно продлевать соединение редкими байтами. Но старому HTML он не гарантирует доступ к прежним ресурсам в последующих запросах браузера. Для frontend по-прежнему полезны имена файлов с хешами содержимого.
Rhino исполняется в процессе PocketDeploy. Доступ к Java-классам закрыт, действуют ограничения инструкций, стека и размеров, однако отдельной изоляции процесса и жёсткого бюджета памяти нет. Практический сценарий — собственный доверенный код владельца.
PDS в Termux: сервер аккаунта, а не вся социальная сеть
Следующей проверкой стал официальный @atproto/pds 0.5.49. PocketDeploy не реализует AT Protocol заново: приложение готовит профиль, устанавливает закреплённые npm-зависимости и управляет процессом upstream-сервера в Termux. На облачном Android проверены официальный Termux 0.118.3, Node.js 24.22.1 и нативная сборка SQLite. Первая сборка зависимостей заняла около семи минут; свежему bootstrap понадобился pkg update && pkg upgrade из-за библиотек C++/OpenSSL.
PDS хранит аккаунты, подписанные репозитории записей, SQLite и медиа на Android. Общая лента, поиск, модерация и сбор данных Relay/AppView остаются отдельными компонентами сети. Поэтому собственный PDS не означает, что весь Bluesky переехал в телефон. Роли компонентов описаны в официальном руководстве по self-hosting.

Реальный экран alpha.2 на облачном Android 11. У существующего PDS проверили открытие и отмену диалога обновления; отмена сохранила текущий релиз. Состояние процесса — running/ready. Это локальная готовность, которую надо отличать от внешнего доступа и федерации.
В нативном интерфейсе владелец задаёт домен и порт, устанавливает сервер, запускает, останавливает или перезапускает его, читает журнал и создаёт одноразовое приглашение. Запись домена в карточке не настраивает DNS и сертификат. Для публичного доступа нужен отдельный HTTPS/WebSocket-прокси; read-only профиль лендинга для PDS не подходит.
Каталоги данных и ключей находятся вне npm-релиза. Ключи создаются один раз: повреждённый или неполный набор не заменяется молча новым. Обновление APK само по себе не меняет уже установленный npm-релиз. Для этого есть отдельное действие «Обновить сервер PDS»; сборка зависимостей может занять минуты. Обычный restart не исправит старое дерево зависимостей.
Автоматические rollback и удаление PDS отключены: у новой версии может измениться схема SQLite, а потеря ключей — нарушить доступ к аккаунтам. Для лаборатории отдельно проверили офлайн-восстановление в другой каталог: совпали ключи, прошли четыре SQLite quick_check, восстановленный PDS ответил health и вернул тот же CID профиля. Внешние службы копии были выключены. Это одна проверка данных и запуска, не автоматическое резервирование и не аварийное восстановление всей VPS-инфраструктуры.
Как мы отличили «процесс запущен» от федерации
Ответ /_health проверяет живой PDS. Для федерации мы последовательно проверили другую цепочку:
Handle
android-lab.pds.pocketdeploy.vibecoding.by, DID и адрес PDS согласованы.Публичный WebSocket
/xrpc/com.atproto.sync.subscribeReposотдаёт коммит с тем же CID профиля.Relay
bsky.networkсчитает репозиторий активным; его ревизия совпадает с локальной.Публичный AppView возвращает тот же профиль.

Публичный AppView показывает лабораторный профиль, данные которого обслуживает Android. Это внешний клиент Bluesky, не встроенный клиент PocketDeploy. Для публичной проверки не создавались посты ленты, подписки или сообщения.
После обслуживания, продолжавшегося примерно девять минут, Relay временно показывал PDS offline. Повторный requestCrawl для собственного hostname был принят, итоговая проверка вернула active. Этот случай полезнее формального health: остановка локального процесса и восстановление видимости в сети — разные события.
Read-only скрипт проверки федерации сохраняет отдельный результат для каждого уровня и не создаёт аккаунтов или записей. Его текущая приёмочная логика рассчитывает получить коммит лабораторного профиля с cursor=0; это ещё не универсальный монитор долгоживущего PDS. Полный протокол alpha.2 содержит версии, команды и границы испытаний.
Где проходит граница безопасности
PDS слушает loopback. Регистрация разрешена по приглашениям, импорт аккаунтов отключён; включены SSRF-защита и ограничения запросов upstream. Приглашение передаётся в Android в AES-GCM envelope с привязкой к маршруту и nonce, ответ проверяется HMAC; окно с кодом защищено FLAG_SECURE. При этом все Termux-проекты работают под одним Android UID. Отдельные каталоги не превращают их в песочницы: рядом с PDS нельзя считать безопасным произвольный чужой npm-код.
В alpha.2 установка идёт по закреплённому lockfile. После npm ci применяется проверяемый backport для Zod v3: проверяются точные исходные и исправленные байты, неизвестный исходник останавливает установку до активации. Перед запуском повторяется проверка только на чтение. Такой подход позволяет явно отказаться от неожиданного кода, но заставляет сопровождать патч при смене upstream.
Оставшаяся проблема не скрыта: сохранённый npm audit содержит 26 high, 0 moderate — распространение одного advisory Zod по зависимостям, а не 26 независимо найденных дефектов приложения. Принятый backport рано отказывает на превышающих размер ограниченных коллекциях; общего бюджета ресурсов в опубликованной alpha.2 нет. Уменьшение audit с 39 до 26 записей не означает, что сервер получил гарантию безопасности. Область исправлений и зависимости вынесены отдельно.
Вход, refresh, AppView-прокси и logout после обновления прошли. Полный OAuth с PAR, согласием пользователя, PKCE, DPoP, refresh и revoke ещё не принят. Ответ OAuth metadata со статусом 200 не заменяет этот сценарий. SMTP-доставка и почтовое восстановление тоже пока не проверены; настройку личного SMTP или Resend API планируем оставлять владельцу. Переносить основной аккаунт в эту альфу я пока не предлагаю.
Агент запрашивает, владелец разрешает
MCP работает через Node.js stdio-мост на ПК и приватный Android-контроллер 127.0.0.1:18124, выключенный по умолчанию. Это отдельный управляющий контур: контроллер остаётся приватным, публичный профиль его не публикует.
Агент запрашивает действия и проекты; владелец сравнивает код и подтверждает разрешение в нативном окне Android. Само подключение MCP-клиента доступа не даёт. Имя агента — отображаемая метка, а не подтверждённая личность. Каждая операция проверяет проект, действие, срок и отзыв разрешения. Отзыв блокирует следующие операции, но не отменяет уже выполненный деплой.

Архивный экран 0.6.0-alpha.2: контроллер включён, ожидающих заявок — ноль. Это экран управления доступом, а не изображение выдачи нового разрешения. Для съёмки текущей статьи новые права агентам не выдавались.
Например, для поручения «прочитай выбранный проект и обнови главную страницу» достаточно запросить projects.read и projects.deploy, а в Android выбрать один проект. Изменение backend.js требует отдельного projects.backend; запуск и остановка — projects.runtime. Разрешение на правку HTML не превращается в право менять серверный код.
Есть менее очевидный случай: replace-набор без backend.js означает удаление обработчика и тоже требует projects.backend. В merge-режиме отсутствие файла сохраняет старый backend. При rollback сравниваются байты текущего и предыдущего обработчиков. Проверка права и активация релиза находятся под одной блокировкой store, чтобы параллельная операция из интерфейса не подменила релиз между проверкой и применением.
У MCP собственные бюджеты: до 64 файлов, 512 КиБ на файл, 1 МиБ декодированных данных, но 1,75 МиБ на сериализованный JSON-запрос. Экранирование кавычек и обратных слешей увеличивает размер сообщения. Ответ чтения также ограничен; клиенту нужно использовать structuredContent и учитывать sourceReadLimited=true: значение null может означать пропуск исходника из-за бюджета, а не отсутствие файла.
В протоколе используются ECDH и HMAC с nonce, временем и хешем тела; мост проверяет подпись ответа до разбора JSON. Сохранённые разрешения на Android шифруются через Keystore. Эти механизмы задают конкретные границы доверия, но не доказывают безопасность любого клиентского окружения. Произвольного shell и доступа ко всем файлам устройства агент не получает. Устройство MCP описано отдельно.
Публичная страница должна существовать до JavaScript
В 0.6.0-alpha.3 появился шаблон «Публичный SEO-сайт · HTML / SSG». Он создаёт страницы с основным текстом прямо в HTML, canonical, Open Graph, robots.txt и sitemap.xml. Постоянный рендерер на Android для этого не нужен: публичные маршруты можно заранее собрать на ПК или в CI, а затем загрузить как обычные файлы.
Клиентский JavaScript может добавлять интерактивность. Но пустая SPA с метатегами и единственным корневым div не проходит наш публичный профиль. Для него используется spaFallback:false: неизвестная страница должна отдавать 404, а не главную с успешным статусом.
Практический порядок такой: выбрать постоянный адрес, собрать публичные страницы в HTML на ПК или в CI, согласовать canonical и sitemap, затем проверить build локально:
node scripts/pocket-seo.cjs dist --site-url https://your-site.pages.dev --json
При успешном аудите содержимое build-папки загружается в проект. После деплоя отдельно проверяются живые HTTP-статусы, MIME и доступность ресурсов. Код выхода 0 у локальной команды сам по себе ничего не публикует. Для обновления публичного текста нужен новый build и деплой.
Команде нужен Node 20+. Она проверяет исходный текст страниц, согласованность метаданных, canonical, маршруты sitemap и crawler-файлы. Она не запускает JavaScript, не вычисляет внешний CSS и не обращается к поисковикам. Даже минимальный объём основного текста — договорённость нашего профиля, а не требование Google к ранжированию.
Консервативный XML-профиль понимает структуру urlset → url → loc; расширения и дополнительные поля требуют другого валидатора. Это полезное ограничение инструмента, которое лучше назвать прямо, чем выдавать несколько проверок за полноценный браузер и XML-schema validator.
Закрытая доска задач получает noindex, nofollow и не входит в публичный sitemap. При этом robots и метатеги не защищают данные: авторизация API остаётся отдельной обязанностью. Документация SEO разделяет локальный аудит и проверку живого сервера.
Почему /done работает в браузере, но не с любым curl
У приватной TaskBoard включён spaFallback:true. Оболочка приложения возвращается только для отсутствующего пути без точки в последнем сегменте и с явным Accept: text/html, где допустимый q больше нуля. Пути /api, /assets, /static, /pair и /rpc исключены. Поэтому переход на /done может получить оболочку, а отсутствующий app.js должен получить 404.
Для собственного запущенного проекта с USB-пробросом это проверяется так:
# Браузерная навигация: ожидается HTML оболочки TaskBoard.
curl -i -H 'Accept: text/html' http://127.0.0.1:18081/done
# Стандартный Accept: */* не включает fallback.
curl -i http://127.0.0.1:18081/done
# Пропавший ресурс не подменяется главной страницей.
curl -i -H 'Accept: text/html' http://127.0.0.1:18081/missing.js
Это команды для повторения, а не новый прогон на физическом устройстве. У публичного SEO-проекта остаётся spaFallback:false: неизвестные страницы возвращают 404 независимо от желания клиента получить HTML.

Кадр того же сеанса 25 октября: переход по ссылке «Готово» открыл /done и отфильтровал две задачи из загруженных четырёх. Это проверка клиентской навигации; отдельный прямой HTTP-вход проверяется с заголовком Accept, как в примере выше.
Постоянный домен: имя отдельно, маршрут отдельно
Текущий адрес сайта — pocketdeploy.vibecoding.by, PDS использует отдельный pds.pocketdeploy.vibecoding.by. Путь лаборатории теперь такой:
Браузер / клиент Bluesky
│ HTTPS, постоянное имя
▼
VPS: Traefik → отдельный закрытый прокси нужного сервиса
│ принадлежащий стенду ADB-проброс
▼
Облачный Android 11
├─ встроенный HTTP-сервер PocketDeploy → HTML сайта
└─ Termux → официальный PDS → данные и ключи
Mac и Quick Tunnel в этом пути больше не участвуют. HTML берётся из Android; наличие VPS-шлюза не означает вторую копию приложения на сервере. TLS заканчивается на edge, внутренний участок до Android использует HTTP. Управляющие порты Termux/MCP и ADB наружу не публикуются.
У PDS отдельный профиль прокси: ему нужны POST и WebSocket, тогда как публичный лендинг остаётся read-only. Nginx использует фиксированный upstream, нормализует forwarding headers, закрывает административные пути и Basic-авторизацию. Для handle-поддоменов разрешены только GET/HEAD идентичности, удаляется Authorization. Запрос к чужому маршруту на таком поддомене не должен стать обходом основного профиля PDS.
Handle аккаунта и домен PDS — разные имена. По спецификации, handle подтверждается _atproto TXT либо /.well-known/atproto-did, а DID должен ссылаться на него обратно. Wildcard DNS не выдаёт wildcard TLS-сертификат. В примере шлюза reconciler проверяет локальные handle, ограничивает добавление имён и атомарно обновляет конфигурацию; неполный ответ сохраняет прежний рабочий вариант. Получение сертификата и реальная внешняя идентичность проверяются отдельно. Конфигурация и её границы опубликованы вместе с кодом.
Что осталось от варианта с домашним ПК
В 0.6 мы проверяли другой маршрут: Android → USB/adb → доверенный ПК → Quick Tunnel → постоянный Pages-адрес. Managed helper следил за своим маршрутом и менял origin существующего Pages-проекта после смены туннеля. Постоянное имя сохранялось, но вся цепочка зависела от работающего ПК. Этот режим и его инструкция остаются в проекте; исторические результаты нельзя автоматически приписывать новой VPS-схеме.

Реальный архивный экран мастера 0.6. Здесь выбран временный HTTPS через ПК; это не автоматическая настройка нынешнего VPS. Нажатие «Далее» само не создаёт DNS, туннель или сертификат.

Снимок после восстановления прежнего стенда 25 октября 2026 года. Источник страниц — Android 11; текущий адрес и схема шлюза приведены выше.
Постоянный IP телефону для такой публикации не обязателен. DHCP reservation закрепляет адрес внутри домашней сети; DNS связывает имя с публичной инфраструктурой, но не прокладывает путь через CGNAT. Нужен работающий маршрут — исходящий туннель или собственный шлюз. Поддержка постоянных доменов у проекта есть; обещания автоматически купить домен, настроить чужой роутер и обеспечить 24/7 нет.
История 0.6: два зависания и один непройденный тест
Этот эксперимент относится к прежнему локальному стенду с Pages и Quick Tunnel, а не к текущему PDS-шлюзу. Постоянное имя оказалось проще обеспечить, чем постоянную доступность всей цепочки. В штатной проверке остановка выбранного сайта в Android дала 503 без сохранённого HTML; запуск вернул 200 и прежний хеш страницы. Остановка собственного tunnel-процесса тоже прошла: supervisor получил новый origin, обновил Pages, адрес остался прежним.
Затем Android framework перестал предоставлять package service. После долгой перезагрузки стенд восстановили с запуском трёх проектов через нативный интерфейс. Четыре записи TaskBoard сохранились побайтно. Причина зависания не установлена; этот результат не подтверждает автозапуск после reboot.
Во втором эксперименте удалили только собственный adb-проброс tcp:18110. Наблюдались 503, завершение дочернего шлюза и создание нового origin. Но Android снова перестал отвечать. Полное восстановление не прошло десятиминутный критерий приёмки. Работу supervisor нельзя засчитать как успешное восстановление сайта.
Позже стенд вернули холодным запуском AVD с сохранением пользовательских данных и управлением от лица владельца. Три проекта запустили через ограниченный автоматический UI-драйвер. В 14:13:23 UTC 25 октября все девять публичных проверок снова прошли на том же hostname; Activity была в фоне, четыре записи задач остались прежними. Это восстановление с вмешательством владельца и повторным развёртыванием, которое не исправляет задним числом провал десятиминутного теста.
В том прогоне supervisor работал отдельным процессом на доверенном ПК; его автозапуск как системной службы не принимался. Зависимостями были Android, ПК, adb, туннель и провайдер. Затем стенд перенесли на VPS, но сам перенос не засчитывает старый провал как исправленный. Поэтому 503 здесь — существенная часть поведения системы, а не повод спрятать отказ за старой страницей. Ответ самого edge-провайдера при недоступности маршрута также нельзя автоматически приписывать Android.
Обслуживание 26 октября: HTTP 200 ещё не означает здоровую систему
Новая попытка подготовить стенд к испытаниям на 2 ГБ дала полезный результат, хотя нагрузочная часть до старта не дошла. Мы сохранили конфигурацию запуска, APK и свежую согласованную копию данных PDS, затем холодно перезапустили тот же Android с прежними userdata. Второй эмулятор не создавали, память гостя не увеличивали, соседние проекты не останавливали.
Копию PDS проверили отдельно: архив читался, четыре SQLite quick_check вернули ok, ключи совпали с исходными. В этот раз восстановленный PDS из копии не запускался — полноценный пробный запуск относится к предыдущему протоколу. Снимок приватных данных нативного приложения получить не удалось: релизный APK не debuggable. Диагностика отказа не выдавалась за резервную копию; приложение сохранило данные в существующем userdata. Это существенная разница между «есть копия PDS» и «можем восстановить весь Android-сервер».
Холодная загрузка заняла больше трёх минут. Сначала появилось boot_complete=1, но затем system_server перезапустился, а системные приложения записывали DeadSystemException. Стабильной работы package/activity services пришлось дождаться отдельно. После этого восстановили Termux-агент, прежний сайт и PDS без замены его релиза. Десятиминутную цель обслуживания выдержать не удалось. Причину перезапуска system_server этот прогон не установил.
После восстановления провели короткую попытку ограничить влияние эмулятора на соседние проекты: runtime-параметры CPUWeight/IOWeight 10 и CPUQuota 50%. На нулевой и десятой секунде оба сервиса отвечали, на двадцатой запрос к сайту завершился HTTP-ошибкой, а PDS продолжал отвечать 200. Точный статус ошибки сайта в этой пробе не сохранён. Через 26,4 секунды исходные параметры автоматически вернулись; второй вариант квоты не запускали. Совпадения по времени достаточно, чтобы отклонить этот режим стенда, но недостаточно, чтобы объявить CPUQuota единственной причиной отказа.
После отката семь пар низкочастотных запросов за 60 секунд дали 14/14 ответов 200, полный проверяющий скрипт подтвердил федерацию. Relay успел отметить сервер offline во время обслуживания; один requestCrawl для нашего hostname восстановил active, после чего проверка прошла. Аккаунты, посты и ключи в этом процессе не менялись.
Затем, в 02:11 UTC, на экране был зафиксирован диалог ANR у System UI. В 02:13:39 UTC сайт и PDS по-прежнему вернули 200. В журнале нашлись два события KeyguardService за 01:52 и 01:56 UTC, то есть во время обслуживания; новых событий в последующей проверке не было. В 02:19 UTC одно нажатие «Wait» закрыло диалог, затем удалось открыть экран сервера. Глубинная причина этих ANR не установлена: один скриншот не доказывает дефект именно PocketDeploy или связь с уже отменённой квотой. Зато он показывает, зачем модели здоровья нужны отдельные уровни:
Уровень | Что удалось увидеть | Чего это не доказывает |
|---|---|---|
Android UI и системные службы | Сохранившийся диалог ANR; закрылся после «Wait» | Причину сбоя и исправность всех приложений |
Встроенный сайт | Публичный HTTP 200 после ANR | Возможность владельца нормально управлять UI |
PDS | Health 200 и running/ready в протоколе восстановления | Прохождение OAuth, почтового восстановления и операций с данными |
Федерация | Relay, AppView и WSS прошли в отдельном прогоне после восстановления | Непрерывную доступность и повторную полную проверку после каждого следующего события |
Поэтому следующая приёмка должна начинаться с повторяемого холодного старта и восстановления, а не с очередного успешного скриншота health. В UI нужен понятный ответ, какая часть цепочки работает и где требуется вмешательство. Один зелёный индикатор для всех четырёх уровней скрывал бы именно тот отказ, который мы только что увидели.
Сколько памяти нужно: пока профиль стенда, а не минимум для телефона
Возник естественный вопрос: требуется ли устройству хотя бы 4 ГБ RAM. По имеющимся измерениям такой порог установить нельзя. Рабочий сайт и PDS уже запускались в гостевом Android с заданными 2048 МиБ, но это функциональный прогон без нагрузочной квалификации.
Один снимок простоя 26 октября 2026 года в 01:19 UTC дал следующие значения:
Объект | Наблюдение | Что из него нельзя заключить |
|---|---|---|
PocketDeploy, процесс UI и встроенного сервера | 37,0 МиБ PSS | Пик при деплое или под нагрузкой |
Приложение Termux | 90,6 МиБ PSS | Память всех его дочерних процессов |
Node-процесс PDS | 121,0 МиБ PSS | Ёмкость WebSocket или стоимость большой базы |
Ещё два служебных Node-процесса | 61,3 и 47,7 МиБ RSS | Точный суммарный PSS всего стека |
Гостевой Android | Около 1981 МиБ MemTotal, 972 МиБ MemAvailable | Поведение физического ARM-телефона |
PSS распределяет стоимость разделяемых страниц между процессами, RSS считает их в каждом процессе. Складывать эти столбцы в «точную стоимость приложения» было бы ошибкой. Около 3 ГиБ, которые тогда занимал QEMU на VPS, включают виртуализацию и гостевую память: это тоже не минимальное ОЗУ телефона.
Измерения подсказали направление оптимизации. Каждый Termux-проект имеет отдельный Node-супервизор, который владеет группой дочерних процессов. Просто удалить его ради красивого числа памяти нельзя: при смерти агента или потере IPC не должны остаться осиротевшие процессы. Поэтому оптимизацию запуска и транспорта журналов проверяем вместе с остановкой, SIGKILL и дочерними процессами, игнорирующими TERM. В опубликованной alpha.2 это ещё не новая принятая оптимизация.
Нагрузку тоже придётся разделять. Встроенный HTTP-сервер не реализует WebSocket, а PDS имеет другой runtime и протокол. Число idle-сокетов, активные сообщения, fan-out, медленный получатель и переподключения требуют разных тестов. В PDS уже есть outbox с ограничением 500 событий на подписчика и отключением медленного клиента; это не заменяет измерение байтов, памяти и задержек.
Стенд делит ресурсы VPS с другими проектами. После перезапуска и отката квоты запас cgroup эмулятора держался лишь в диапазоне 110–116 МиБ, а минимальная доступная память хоста за минуту составила 930 МиБ. Для старта нагрузочного теста мы заранее требовали не менее 256 МиБ запаса cgroup и не менее 1 ГиБ доступной памяти хоста в течение всей минуты. Оба условия не выполнены: нагрузку не запускали. Граница безопасного старта важнее попытки получить эффектное число соединений.
Перед ступенями нагрузки нужны достаточный резерв, ограниченные время/частота/объём трафика и прекращение прогона при ухудшении health. Предельное число клиентов на перегруженном общем хосте было бы плохим основанием для требований к телефону. Этот результат указывает на недостаточный резерв лаборатории, а не доказывает, что телефону нужно 4 ГБ. Пока я публикую конфигурацию и результаты отдельных проверок, а не обещание «любой Android с 2 ГБ выдержит N пользователей».
Какие результаты относятся к текущей alpha.2
Проверка | Результат и область |
|---|---|
PDS на host | 35/35, без пропусков: профиль, секреты, установка, официальный runtime, backport и handle reconciliation |
Termux на host | 35/35, без пропусков: Node/Git-сценарии и supervisor |
Общая host-регрессия |
|
Настоящий nginx | 7/7: маршруты, Host/forwarding headers, удаление Authorization, запреты и rate limit |
Security-набор на Android | 6 pass, 1 skip; fixture для восстановления оригинального Zod не было на Android, на host проверка прошла |
Официальный runtime на Android | 1/1: account, record, blob, WebSocket-коммит, restart с сохранением данных |
Сессия на обновлённом PDS | Вход, refresh, AppView-прокси и logout |
Публичная федерация | Один лабораторный аккаунт: идентичность, WSS, Relay и AppView |
Резервная копия | Одно отдельное офлайн-восстановление PDS с прежними ключами и CID профиля |
Числа не суммируются в универсальную оценку качества. Например, 80 запросов к локальной fixture nginx при concurrency 20 дали 43 ответа 200 и 37 ответов 429 — это проверка rate limit, а не ёмкость публичного сервера. Успех host-runtime тоже не заменяет Android: на host и на Android сохранены отдельные протоколы.
Исторические результаты встроенного режима 0.6.0-alpha.3 сохранены отдельно: 402 JVM-проверки и 33 adversarial-сценария, 149 Node-проверок по компонентам; на каждой из Android 11 и 15 — 21 smoke и 43 проверки сетевого мастера. Они относятся к тем сборкам. Полные старые MCP/UI-наборы не переписывались задним числом как новая приёмка PDS. Протокол 0.6 и протокол 0.7 доступны рядом с исходниками.
APK подписан ключом разработки и устанавливается вручную. Проверка одного эмулятора, отдельного аккаунта и нескольких отказов не равна независимому аудиту безопасности, поддержке всех OEM-прошивок или доказанной работе 24/7.
Что меняем в плане: сначала один воспроизводимый путь
Ближайшая цель теперь сформулирована конкретно: владелец повторяет инструкцию, получает личный сайт/PDS с HTTPS, понимает состояние каждого компонента, сохраняет данные и восстанавливает их после отказа. Установить APK и один раз получить 200 для этого недостаточно. Перед расширением набора функций нужны:
Повторяемый quick start и холодный запуск: фиксировать шаги, время, ожидаемое состояние UI, сайта, PDS и внешней видимости; явно отмечать ручное вмешательство.
Проверенная резервная копия и восстановление в отдельное окружение, включая данные и ключи. Экспорт должен оставаться понятным владельцу, а успешное создание архива — отличаться от успешного восстановления.
Закрытие известных проблем безопасности, полный OAuth и реальная проверка почты, которую владелец настраивает через свой SMTP или Resend API.
Ограниченные прогоны на стенде с достаточным резервом, затем физические ARM64-устройства: выключенный экран, Doze, смена Wi‑Fi/LTE, диск, температура, перезагрузка и обновление. Замеры 2/3/4 ГБ — проверяемая матрица, а не заранее выбранный маркетинговый минимум.
Более длинные сеансы работы после коротких тестов, с журналом отказов и восстановления. Доступность в процентах появится только вместе с длительностью наблюдения и способом измерения.
Пока этот путь не принят, расширение Nostr и TON Storage с выплатами заморожены. TON добавил бы к сетевой доступности ещё диск, целостность, передачу данных и сроки proofs; количество WebSocket-соединений не измеряет ёмкость такого провайдера. Заработок на хранении в проекте не показан и не обещан. К широкой рекламной кампании, новым видео и локализации вернёмся после воспроизводимой приёмки базового сценария. Технические отчёты о ходе работы при этом остаются полезными, в том числе когда результат отрицательный.
Исходники, APK и инструкции — в GitLab. Отдельная лицензия основного кода пока не выбрана. Для небольших изменений есть инструкция участника. Рабочие отчёты собираем в Telegram и в Discord для тестеров (это приглашение действует 30 дней с 26 октября). Автоматическое зеркало Telegram в Discord пока не подключено.
Самая полезная помощь сейчас — не общий отзыв «всё работает», а воспроизводимый тест: модель телефона, RAM, Android и OEM-прошивка, версия APK, шаги, ожидаемый и фактический результат, настройки батареи и поведение с погашенным экраном. Скриншоты и обезличенные логи помогут; токены, ключи и коды приглашений в отчёт не включайте.
Пока PocketDeploy доказал более узкую, но интересную вещь: Android может не только показывать интерфейс, а исполнять API, хранить данные и обслуживать PDS, видимый публичной сети. Следующий этап должен показать, при каких ресурсах и ограничениях такой сервер остаётся полезным вне лаборатории.
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.