Интернет когда‑то выглядел иначе. До бесконечных лент, рекомендательных алгоритмов и личных брендов существовали 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)


  1. JBFW
    18.10.2026 13:25

    Напомнило: были когда-то такие "ньюсы", NNTP - чуть ли не прямое наследие фидошных эхоконференций.

    Только текст, локальные клиенты, которые сначала выкачивали сообщения, а потом синхронизировали ответы с сервером - то есть не требовали онлайна.
    Можно сравнить ну вот хотя бы со статьями и комментариями на Хабре - кто-то пишет статью, кто-то комментирует, кто-то потом отвечает.
    И главное - не тормозило!

    (впрочем, с нынешними заморочками с блокировками и их обходом тема "синхронизации при подключении к интернету" снова становится актуальной)


    1. frog
      18.10.2026 13:25

      Так ньюсы появились раньше фидошных конференций и существовали параллельно с ними (Usenet). Проблема там была в спаме - не было возможности от него избавиться, из-за чего содержимое было очень ограниченно полезным.


      1. checkpoint
        18.10.2026 13:25

        Решений по защите Usenet от спама предостаточно, но все он появились после того как Usenet окончательно скончался. А вот mail-lists вполне дожили до наших дней, я с удовольствием читаю с десяток из них (freebsd-, linux-). Иногда даже пишу туда. С не меньшим удовольствием перешел бы на Хабр в формате mail-list. ;)

        PS: Существует проект Big-8 по реанимации Usenet и его зачистки. В ряде групп там даже теплится какая-то жизнь.


  1. 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 ничего не делает в редакторе.


    1. pavel_poroshin Автор
      18.10.2026 13:25

      Учту в обновлениях. Пока я просто сделал это поностальгировать, но буду допиливать.

      Reply post от Reply thread отличаются тем, что первый это ответ на пост, в конце заголовка пишется номер поста на какой был ответ, а Reply thread отвечает создателю треда, по крайней мере так задумывалось


  1. checkpoint
    18.10.2026 13:25

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


    1. vadimk91
      18.10.2026 13:25

      из-за опечатки кто-то не поймет, что речь про Golded :)


      1. checkpoint
        18.10.2026 13:25

        Да, про него родимого. :)


  1. domix32
    18.10.2026 13:25

    sudo install -m 0755 "bord_v0.2.0-dev.a9eb8d1_linux_amd64/bord" /usr/local/bin/bord

    А там точно привилегии нужны? Почему бы не запаковать это всё в AppImage тогда вместо того чтобы делать вот эту всю грязь руками?


    1. JBFW
      18.10.2026 13:25

      Никто не мешает поставить бинарник в HOME/bin/

      chmod 700 board

      Всё

      Зачем плодить лишние сущности, запаковывать во что-то, ещё докер на базе Убунты или snap креатить...


      1. domix32
        18.10.2026 13:25

        Ну, фиг знает, кажется скачать один бинарник, выставить ему chmod +x и запустить проще чем скачать архив, распаковать, запустить скрипт установки и потом ручками же подтереть архив и распакованные файлы. AppImage крафтится довольно просто небольшим скриптом и его опций установки там заметно больше - и версию глянуть и путь установки уточнить. Плюс все зависимости при себе можно таскать, а не ждать пока необходимый apt/brew install что-нибудь отломает.