Интернет когда‑то выглядел иначе. До бесконечных лент, рекомендательных алгоритмов и личных брендов существовали BBS, локальные сети, IRC, форумы и имиджборды. Пользователь приходил туда не ради собственного профиля, а ради темы, разговора или случайной встречи с незнакомым человеком.
У этих систем было много ограничений. Соединение могло быть медленным, интерфейс — текстовым, а навигация — целиком клавиатурной. Но именно ограничения часто делали общение более прямым. На экране не было десятка уведомлений, автовоспроизводящегося видео и алгоритма, который решал, что показать следующим.
Я сделал попытку вернуть это ощущение, не копируя технические проблемы
прошлого, написав публичную анонимная борду, основной клиент которой работает прямо
в современном терминале.
Проект открытый, каждый может развернуть его на своем сервере и погрузиться в ностальгию. Так же он развернут на моем VPS — все подробности в статье, ссылка на исходный код прикреплена.

Midnight Commander стал языком интерфейса
Внешне Bord напоминает Midnight Commander. Слева находится список досок, справа — каталог тредов или посты открытого обсуждения.
Двухпанельный интерфейс хорошо подходит для иерархии «доска → тред → пост». Пользователь одновременно видит контекст и содержимое, а основные действия всегда остаются на экране.
TUI поддерживает:
управление клавиатурой и мышью;
полноэкранную адаптивную компоновку;
узкие и широкие терминалы;
темы
classic,amber,greenиmono;поиск по каталогу и открытому треду;
просмотр длинного поста с прокруткой;
JPEG‑ и PNG‑вложения;
inline‑графику в совместимых терминалах;
открытие изображения системным приложением как fallback.
Скачать клиент можно на bord.pw. После распаковки он подключается одной командой:
bord --server https://bord.pw
Есть сборки для Linux, macOS и современного Windows Terminal на архитектурах AMD64 и ARM64.
На чём написан Bord
Проект реализован как Go‑монорепозиторий и собирается в два основных бинарника:
bord— терминальный клиент;bord-server— публичный API, хранилище и операторские команды.
Go выбран не только ради производительности. Он позволяет собирать автономные бинарники для разных операционных систем, удобно работать с конкурентными соединениями и обходиться без большого runtime‑окружения на сервере
Клиент построен на экосистеме Charm:
Bubble Tea управляет состоянием TUI по архитектуре Model‑Update‑View;
Bubbles предоставляет готовые интерактивные компоненты, включая редактор;
Lip Gloss отвечает за цвета, размеры и оформление;
x/ansi помогает безопасно работать с шириной Unicode‑текста и переносами.
Backend использует стандартный HTTP‑стек Go, сгенерированные адаптеры OpenAPI и SQLite через pure‑Go драйвер modernc.org/sqlite. Для сборки не нужна системная библиотека SQLite или C‑компилятор.
Как устроен TUI
Bubble Tea основан на однонаправленном потоке данных. В упрощённом виде цикл выглядит так:
событие клавиатуры, мыши или сети
↓
Update
↓
новое состояние модели
↓
View
↓
новый кадр терминала
Модель хранит текущий экран, активную панель, выбранную доску, тред, пост, позиции прокрутки, состояние загрузки и черновик редактора. Сетевые запросы выполняются как команды Bubble Tea и возвращают типизированные сообщения обратно в цикл обновления.
Такой подход отделяет ввод от отображения. Один и тот же переход состояния можно вызвать клавишей, кликом по нижней панели или мышью. Поэтому мышь не имеет отдельной «параллельной» логики: она использует те же операции модели, что и клавиатура.
Текст перед выводом проходит санитарную обработку. Управляющие последовательности из пользовательской публикации не должны превращаться в команды терминалу, менять заголовок окна или обращаться к буферу обмена. Дополнительно рендер учитывает широкие символы, emoji и комбинируемые Unicode‑ последовательности.
HTTP хранит состояние, SSE сообщает об изменениях
Bord разделяет получение данных и уведомления:
обычный HTTP API является источником истины;
Server‑Sent Events сообщает клиенту, что состояние изменилось;
после события клиент повторно читает нужный каталог или тред через HTTP.
Это проще и надёжнее, чем передавать полную модель данных внутри непрерывного потока. Если SSE‑соединение оборвалось, клиент переподключается и использует Last-Event-ID. Если история событий уже недоступна, сервер сообщает о reset, после чего клиент заново синхронизирует состояние.
На reverse proxy SSE вынесен в отдельную цепочку без буферизующего сжатия. Иначе небольшие события могли бы задерживаться до накопления достаточного объёма данных, и живое обновление перестало бы быть живым.
SQLite тоже осознанный выбор
Для централизованного MVP используется SQLite в режиме WAL. У такого решения есть практические преимущества:
один файл базы данных;
транзакции и внешние ключи;
простое резервное копирование;
отсутствие отдельного сервера СУБД;
предсказуемое обслуживание небольшого публичного проекта.
Изображения не складываются внутрь базы. SQLite хранит метаданные, состояния и ссылки, а нормализованные blobs находятся в файловом хранилище. Запись файла проходит через временный файл и атомарное перемещение, чтобы незавершённая загрузка не стала видимым вложением.
Репозиторий содержит миграции, проверку версии схемы, команды status/migrate, согласованное резервное копирование базы и blobs, а также проверку восстановления по SHA-256.
Анонимность
У Bord нет пользовательских аккаунтов, но это не означает, что сервер вообще не обрабатывает технические данные. Публичному сервису всё равно нужны защита от спама, rate limits, блокировки и расследование злоупотреблений.
Поэтому адрес клиента по умолчанию преобразуется в HMAC‑SHA-256 индекс с серверным ключом. Такой индекс позволяет обнаруживать повторные злоупотребления, не сохраняя IP открытым текстом. Опциональное хранение исходного IP выключено; если оператор сознательно включает его, значение шифруется AES-256-GCM отдельным ключом и подпадает под ограниченный срок хранения.
Trusted proxy boundary настроена явно. Backend принимает forwarded‑адрес только от известного reverse proxy, поэтому произвольный клиент не может просто подставить чужой X-Forwarded-For.
Сам клиент создаёт локальный случайный session secret. Для каждой публикации он также хранит delete capability — секрет, позволяющий позднее удалить собственный пост без аккаунта. Сервер получает стойкий verifier, а исходный delete secret остаётся в локальном конфигурационном файле пользователя с ограниченными правами.
Такая модель не делает человека невидимым для сети или закона. Она решает более конкретную задачу: сервису не требуется собирать профили, email и пароли только ради публикации текста.
Безопасная работа с изображениями
Загрузка файла — одна из самых опасных частей публичного сервиса. Bord принимает только JPEG и PNG, ограничивает размер тела запроса и параметры изображения, декодирует файл и создаёт нормализованную серверную копию.
Клиент никогда не строит shell‑команду из имени вложения. Для внешнего просмотра используется системный opener с отдельными аргументами. HTTP‑ответы изображений получают безопасные заголовки и предсказуемый Content‑Type.
Контракт вместо JSON
Публичное API описано в api/openapi.yaml. Из контракта генерируется Go‑клиент, а CI проверяет, что generated‑код не расходится с исходной спецификацией.
Запросы на запись используют строгий JSON: неизвестные поля отклоняются. Для создания тредов и постов предусмотрен Idempotency-Key. Если сеть оборвалась после отправки, клиент может безопасно повторить запрос с тем же ключом, не создавая дубликат.
Отдельный management API предназначен только для операторов и не публикуется через внешний Caddy. Действия модераторов записываются в audit log.
Как проект развёрнут
Публичный экземпляр работает за Caddy с автоматическим TLS‑сертификатом Let's Encrypt. Внешний reverse proxy принимает https://bord.pw, добавляет проверенные forwarded‑заголовки и передаёт запрос во внутреннюю Docker‑сеть.
Backend и внутренний edge не публикуют собственные порты в интернет. Для сайта включены HTTPS redirect, HSTS, Content Security Policy, запрет framing, nosniff и строгая referrer policy.
Сам landing полностью статический: HTML, CSS и небольшой JavaScript без frontend‑фреймворка и внешнего CDN. Он показывает возможности проекта, выбирает архив для ОС и архитектуры и формирует команды установки.
Для альтернативного развёртывания в репозитории также есть systemd unit, Caddyfile, инструкции по установке, backup, restore, ротации ключей, retention и диагностике.
Выпуск
Проект собирается для шести клиентских платформ:
linux/amd64 linux/arm64
darwin/amd64 darwin/arm64
windows/amd64 windows/arm64
Release pipeline создаёт детерминированные .tar.gz и .zip архивы. Каждый архив содержит бинарник, инструкцию, MIT License и third‑party notices. Рядом публикуется SHA256SUMS.
Возвращение в прошлое
Bord не пытается доказать, что старый интернет во всём был лучше. В нём было меньше удобства, меньше доступности и гораздо меньше встроенной безопасности.
Но у него была ценная идея: интерфейс может отойти в сторону и оставить человеку текст, тему и собеседников. Терминал хорошо напоминает об этом. Он ограничивает визуальный шум, поощряет клавиатурную навигацию и не пытается превратить каждое действие в метрику вовлечённости.
Под ретро‑интерфейсом не обязана находиться ретро‑архитектура. Там могут быть типизированные контракты, безопасная обработка медиа, шифрование, audit, идемпотентность, автоматические тесты и воспроизводимые релизы.
В этом и состоит идея Bord: вернуться не к старым ограничениям, а к старому ощущению прямого, необязательного и немного случайного разговора — используя инструменты, которых тогда ещё не существовало.
Попробовать: https://bord.pw
Исходный код: https://github.com/pavel‑up‑lab/bord
Комментарии (11)

hiddenman
18.10.2026 13:25@pavel_poroshinИнтересное приложение. Скучаю по временам Fido/Usenet, по клиентам уровня Golded/Gnus. Запустил приложение, потыкал. Первые же комментарии/предложения:
показывать в списке Досок количество тем внутри (и еще, например, количество новых сообщений в темах, на которые подписан [если есть такое понятие] или где отвечал)
тут не очень походит UX из mc при перемещении между досками: например, я сначала открыл одну доску и увидел список тем (хотя хотелось бы его вообще видеть при перемещении между досками вверх-вниз, а не каждый раз "входить" в нее для их просмотра), а потом перешел по Tab или мышкой на другую доску, она выделена, но не выбрана (два раза не кликнул по ней); в итоге получается, что слева у меня выделена доска general, а справа у меня до сих пор открыты темы из доски bred и это совсем не очевидно и нигде не написано;
если уж использовать интерфейс mc, то можно сделать как минимум поддержку тем mc (у меня, например, solarized-dark) и навигацию курсором, когда нажимаешь стрелку вправо, то проваливаешься глубже, влево - выходишь на уровень выше (в mc это называется Lynx-like motion); я, например, долго пытался перейти в список досок из списка тем, пока не залез в справку (для mc нажать tab логично, а тут почему-то такое желание не возникло)
в чем отличие Reply post от Reply thread, когда я читаю сообщение? ответ на post будет отдельным сообщением вне треда?
при составлении ответа нет вообще никаких базовых функций редакторов обычных или того же mcedit (раз мы на mc ориентируемся): не могу текст выделить; перемещаться быстро между словами могу только C-a, C-e; даже нажатие Tab ничего не делает в редакторе.

pavel_poroshin Автор
18.10.2026 13:25Учту в обновлениях. Пока я просто сделал это поностальгировать, но буду допиливать.
Reply post от Reply thread отличаются тем, что первый это ответ на пост, в конце заголовка пишется номер поста на какой был ответ, а Reply thread отвечает создателю треда, по крайней мере так задумывалось

checkpoint
18.10.2026 13:25Нафига нам твой Board, "Голого Дела" давай, и чтобы с Хабром! ;)

domix32
18.10.2026 13:25sudo install -m 0755 "bord_v0.2.0-dev.a9eb8d1_linux_amd64/bord" /usr/local/bin/bordА там точно привилегии нужны? Почему бы не запаковать это всё в AppImage тогда вместо того чтобы делать вот эту всю грязь руками?

JBFW
18.10.2026 13:25Никто не мешает поставить бинарник в HOME/bin/
chmod 700 board
Всё
Зачем плодить лишние сущности, запаковывать во что-то, ещё докер на базе Убунты или snap креатить...

domix32
18.10.2026 13:25Ну, фиг знает, кажется скачать один бинарник, выставить ему chmod +x и запустить проще чем скачать архив, распаковать, запустить скрипт установки и потом ручками же подтереть архив и распакованные файлы. AppImage крафтится довольно просто небольшим скриптом и его опций установки там заметно больше - и версию глянуть и путь установки уточнить. Плюс все зависимости при себе можно таскать, а не ждать пока необходимый apt/brew install что-нибудь отломает.
JBFW
Напомнило: были когда-то такие "ньюсы", NNTP - чуть ли не прямое наследие фидошных эхоконференций.
Только текст, локальные клиенты, которые сначала выкачивали сообщения, а потом синхронизировали ответы с сервером - то есть не требовали онлайна.
Можно сравнить ну вот хотя бы со статьями и комментариями на Хабре - кто-то пишет статью, кто-то комментирует, кто-то потом отвечает.
И главное - не тормозило!
(впрочем, с нынешними заморочками с блокировками и их обходом тема "синхронизации при подключении к интернету" снова становится актуальной)
frog
Так ньюсы появились раньше фидошных конференций и существовали параллельно с ними (Usenet). Проблема там была в спаме - не было возможности от него избавиться, из-за чего содержимое было очень ограниченно полезным.
checkpoint
Решений по защите Usenet от спама предостаточно, но все он появились после того как Usenet окончательно скончался. А вот mail-lists вполне дожили до наших дней, я с удовольствием читаю с десяток из них (freebsd-, linux-). Иногда даже пишу туда. С не меньшим удовольствием перешел бы на Хабр в формате mail-list. ;)
PS: Существует проект Big-8 по реанимации Usenet и его зачистки. В ряде групп там даже теплится какая-то жизнь.