Из любой сети
./scripts/1-node.sh serve --domain node.example.org # or: --tailscalecasty web работает на вашей машине и доступен телефону в той же сети. 1-node.sh serve
запускает ту же страницу на хосте ноды, рядом с нодой, за прокси, который завершает HTTPS, так что
телефон достаёт до неё и из мобильной сети. Это тот же код casty, собранный на
хосте в контейнере из этого клона, и он читает и пишет через ноду по адресу
http://snapchain:3381 в собственной Docker-сети хоста.
Что он делает
Заголовок раздела «Что он делает»- Читает каталог конфига casty здесь (
~/.config/castyили--config):config.jsonс вашим fid иkeys.enc.jsonс ключом приложения, и спрашивает пароль хранилища ключей. С--read-onlyни хранилище ключей, ни пароль не покидают эту машину, а страница читает, но не может писать. - Отправляет исходники клиента (без
node_modules, тестов и сборки) в~/snapchain/casty/srcна хосте вместе с Dockerfile:node:22-alpine, зависимости из lock-файла, та же сборка, что и у2-client.sh --from-source. Образ собирает хост; в первый раз это занимает минуту-две. - Пишет
~/snapchain/docker-compose.casty.ymlрядом с compose-файлом ноды и указывает его вCOMPOSE_FILEв~/snapchain/.env, так что каждая командаdocker composeтам его включает. Сервисcastyзапускаетcasty serve --public-host <name>, перезапускается вместе с хостом, хранит свой токен и кэши в~/snapchain/casty/config(bind-mount) и читает пароль из Docker-секрета, файла~/snapchain/casty/passphrase, режим 0600. - Ставит перед ним прокси:
--domain <name>: сервисcaddyпубликует 80 и 443 и сам получает сертификат от Let’s Encrypt. Имя должно указывать на хост, а 80 и 443 должны быть доступны из интернета:./scripts/0-provision.sh --httpsоткрывает их на файрволе Hetzner или AWS (повторный запуск сохраняет сервер); на собственном хосте откройте их сами.--tailscale:castyпубликуется только на loopback хоста, аtailscale serve --bg 3390отдаёт его какhttps://<host>.<tailnet>.ts.netвашему tailnet с сертификатом, который выдаёт Tailscale. Tailscale должен быть установлен на хосте и в нём должен быть выполнен вход, а телефон должен быть в tailnet. Ни один порт не открывается в интернет.
- Ждёт, пока сервис запишет свой токен для связывания, затем печатает
https://<name>/#token=.... QR-код этой ссылки находится в логах сервиса:./scripts/1-node.sh logs casty.
Запускайте serve снова после изменения casty: он заново отправляет исходники, пересобирает и перезапускает.
./scripts/1-node.sh down останавливает ноду и casty вместе; на хосте
docker compose stop casty (и caddy, с --domain) останавливает только страницу. Чтобы связать заново
с нуля, удалите на хосте ~/snapchain/casty/config/web-token и выполните
docker compose restart casty.
Что casty serve добавляет к casty web
Заголовок раздела «Что casty serve добавляет к casty web»casty serve — это casty web поверх описанного ниже индекса, с push-уведомлениями, ограничением запросов и,
с --public-host, именем прокси:
- печатаемые им ссылки содержат
https://<public host>, и это имя принимается какHostзапроса, помимо IP-адреса илиlocalhost; - каждый ответ несёт
Strict-Transport-Security, так что браузер, который однажды видел страницу, никогда не запрашивает её по обычному HTTP; /api/*ограничен 120 запросами в минуту на адрес клиента, считая до проверки токена, с429иRetry-Afterсверх этого. За прокси адрес клиента — это последний вX-Forwarded-For, тот, который добавил прокси; адрес, который клиент вписал туда сам, не учитывается;- без
--public-host, на ноутбуке,casty serve— этоcasty webс индексом и ограничением: те же ссылки, без HSTS, а ограничение считается по адресу сокета, поскольку без прокси вX-Forwarded-Forто, что написал клиент. При сборке из исходниковcasty serveнужен Node 22.13 или новее для его SQLite; однофайловый бинарник несёт свой собственный; - пароль хранилища ключей берётся из
CASTY_PASSPHRASE_FILE, Docker-секрета, так что сервис запускается без терминала.
Всё остальное — это страница, описанная в В браузере и на телефоне: то же связывание по токену, та же нумерация, общая с терминалом, те же записи через ноду.
Лента из индекса
Заголовок раздела «Лента из индекса»casty serve хранит ленту в index.sqlite в своём каталоге конфига
(~/snapchain/casty/config/index.sqlite на хосте), с помощью встроенного SQLite в Node. При первом
запуске он читает ваши подписки, последние 100 кастов ваших и каждого из них, касты, которые
вас упоминают, ответы на ваши последние 25 кастов, а также ваши лайки и рекасты. С этого момента он
следует за журналом событий ноды (/v1/events, каждый шард, каждые 1,5 с): каст того, на кого вы подписаны,
упоминание вас или ответ вам попадает в индекс; удалённый или вычищенный каст уходит из него; подписка,
которую вы делаете, приносит последние касты нового аккаунта, а отписка убирает аккаунт; ваши
лайки и рекасты остаются актуальными. Лента, упоминания и состояние лайков на странице тогда берутся из
индекса за несколько миллисекунд вместо сотен запросов к ноде, а запись, которую вы делаете
на странице, сразу читает события, так что она появляется в следующем списке. Когда первый проход
завершён, читаются ещё и имена и приложения ваших подписок, так что первая лента после запуска ничего не спрашивает
у ноды. Пока первый проход не завершён, страница читает ноду, как это делает casty web.
Профили и треды всегда читают ноду.
Пока вы читаете, страница каждые полминуты и каждый раз, когда снова появляется на экране, спрашивает индекс, не пришли ли касты новее верхнего. Если да, плашка под панелью говорит «3 new casts»; список никогда не сдвигается у вас под руками. Нажатие показывает их.
Нода хранит события примерно за три дня; сервис, который был выключен дольше двух, при запуске снова делает
первый проход и продолжает оттуда. Удалите index.sqlite на хосте и
перезапустите casty, чтобы начать с нуля.
Упоминание приходит на телефон
Заголовок раздела «Упоминание приходит на телефон»В разделе о вас «notifications» включает Web Push для этого браузера: страница регистрирует свой service
worker (sw.js), подписывается ключом, который casty serve создал при первом запуске
(vapid.json в его каталоге конфига, режим 0600), и передаёт подписку сервису,
который хранит её в cache/push.json. Когда события приносят каст от кого-то другого, который упоминает
вас или отвечает на один из ваших кастов, сервис отправляет каждому подписанному браузеру одно сообщение: «new mention
from @name» или «new reply from @name», и ничего больше, не текст каста. Оно зашифровано для
собственного ключа браузера (RFC 8291) и подписано ключом сервиса (RFC 8292), так что push-сервис
посередине, Apple, Google или Mozilla, передаёт шифротекст, который не может прочитать. Нажатие открывает
mentions. Подписка, о которой push-сервис сообщает, что её больше нет, удаляется.
На iPhone Web Push работает только для страницы, добавленной на домашний экран (iOS 16.4 и новее); счётчик рядом с mentions работает везде, как и раньше. Выключение уведомлений удаляет подписку и из браузера, и из сервиса.
Что это оставляет на хосте
Заголовок раздела «Что это оставляет на хосте»С serve на хосте, помимо ноды, хранится:
| Что | Где | Что с этим можно сделать |
|---|---|---|
| Ключ приложения, зашифрованный | ~/snapchain/casty/config/keys.enc.json, режим 0600 |
Ничего без пароля |
| Пароль хранилища ключей | ~/snapchain/casty/passphrase, режим 0600, читается контейнером при запуске |
Расшифровать ключ: вместе с ним писать от вашего имени |
| Токен страницы | ~/snapchain/casty/config/web-token и в каждом связанном браузере |
Читать и писать от вашего имени, откуда угодно, пока работает сервис |
| Сертификат и его ключ | Том caddy_data у Caddy или у Tailscale |
Отдавать страницу под этим именем |
| Индекс вашей ленты | ~/snapchain/casty/config/index.sqlite |
Ничего: в нём публичные касты, ваши подписки и реакции, которые есть и у ноды |
| Пара ключей для push | ~/snapchain/casty/config/vapid.json, режим 0600 |
Отправлять push подписанным браузерам; новая пара аннулирует каждую подписку |
| Push-подписки | ~/snapchain/casty/config/cache/push.json |
Отправить push этим браузерам, как эндпоинты push-сервиса, с публичными ключами браузеров |
Тот, у кого есть root на хосте, получает ключ приложения, расшифрованный, в памяти сервиса. Это
цена страницы, доступной откуда угодно, и вот почему риск остаётся ограниченным: ключ подписывает только то, что разрешают его
scopes (по умолчанию касты, реакции, подписки), и истекает без использования после своего TTL;
casty key revoke <key> с seed-фразой с любой машины отзывает его сразу; а
--read-only оставляет ключ дома для страницы, которая только читает. Seed-фраза никогда никуда
не уходит. Безопасность перечисляет каждый секрет.
Usage: casty serve [options]
the page over an index of the feed, with push; on the node's host behind HTTPS,which 1-node.sh serve installs
Options: --public-host <domain> the domain the page is reached at through a proxy that ends HTTPS; it goes into the pairing link --listen <host:port> where to serve, for the proxy (default: "0.0.0.0:3390") --new-token pair again; links printed before stop working --read-only do not unlock the app key; the page reads but cannot write --no-allow-fname-server look @names up with your node only; by default names it does not know are looked up on fnames.farcaster.xyz (public, no key) -h, --help display help for command