tm2027 · борд

Дека деке

Дверь, которую мы открыли телефону, годится не только телефону. За ней стоит дека - и как только она входит, «phones» перестаёт быть верным словом: устройств становится два рода, а у связи - две стороны. Дека впервые не только дверь, но и стучащийся. Здесь десять независимых решений: чем питается глаз тонкого клиента, как адресуется чужой пан, где его видно, как две деки знакомятся без камеры, что гостю можно и где лежат ключи. Каждая карточка рисует настоящую сцену деки в её же чернилах, с этим одним решением внутри. Внизу пульт: он собирает все четыре сцены сразу из того, что выбрано, и говорит, если выбранное не сходится.

Половина уже написана mobile_tls::connect_pinned - клиентская сторона с пиннингом - уже есть, и examples/mobile_probe.rs уже ходит этой дорогой. Тонкому клиенту не нужен ни новый TLS, ни новый разбор строки паринга.
Телефон и дека хотят разного телефону нужен узкий вид - lines/runs под его экран. Деке нужен терминал: своя сетка, свой resize, своя история. Одна дверь может нести оба режима, но они не один режим.
Четвёртый вариант enum не бесплатен HerdrServer::{Local,Wsl,Ssh} разбирается в трёх с половиной сотнях мест, и каждый путь «запусти herdr по транспорту» должен будет отказать явно. Адресация - отдельное решение, не деталь.
У клиента нет камеры весь паринг v2 стоит на QR. Дека не сфотографирует чужой экран - и значит момент, когда отпечаток впервые узнаётся, приходится придумать заново. Это тот самый единственный момент, когда есть что подделать.
1 · чем питается глаз клиента

Сегодня шлюз отдаёт телефону кадр: строки и цветные отрезки, десять раз в секунду, в сетке хозяина. Вопрос в том, годится ли это деке - у которой есть свой тайл, свой шрифт, своя ширина и свой скролл.

1A

Зеркало телефона

Дека - это большой телефон: тот же watch, тот же кадр lines/runs. На шлюзе не меняется ни строки. Плата честная и крупная: сетка чужая, resize невозможен, скролла назад нет, а 120 колонок хозяина в тайл на 96 придётся либо резать, либо мельчить.

цена: малая - второй потребитель существующего кадра; но клиент навсегда остаётся зрителем чужого окна
1B

Своя труба

watch{mode:"raw"}: шлюз проксирует строки бриджа herdr как есть - base64-ANSI, generation и seq сквозные - и принимает resize обратно. Клиент получает ровно то же, что дал бы локальный бридж: своя проекция, парковка, скролл, кот. Глаз с хозяином при этом общий - мой resize он увидит.

цена: средняя - режим raw на шлюзе, BridgeProcess расщепляется на «процесс» и «реле»
1C

Свой бридж на той стороне

Хозяин по просьбе клиента поднимает отдельный herdr terminal session control - клиент не делит глаз ни с кем. Геометрии независимы, resize никого не трогает, две деки смотрят один пан каждая по-своему. Плата: процесс и два потока на хозяине за каждого гостя за каждый пан.

цена: большая - жизненный цикл чужих бриджей, их лимиты и уборка на стороне хозяина
2 · как адресован чужой пан

Пан деки адресуется парой «сервер + id». У чужого пана сервер тоже чужой - и его надо как-то назвать, не сломав те три с половиной сотни мест, где HerdrServer означает «машина, на которой можно запустить herdr».

2A

Четвёртый сервер

HerdrServer::Deck{id} рядом с Local, Wsl, Ssh. Чужая дека - один узел флота, и всё, что умеет флот, умеет её. Плата двойная: каждый match, который спавнит процесс, обязан явно отказать - и вся внутренняя разница чужой деки (её LOCAL, её WSL) сминается в один список.

цена: средняя по строкам, большая по риску - ~350 мест сопоставления, из них десятки спавнят процессы
2B

Второе измерение

Адрес пана вырастает на одно поле: (DeckRef, HerdrServer, pane), где DeckRef::Here - сегодняшнее поведение. HerdrServer не меняется вовсе, чужие серверы сохраняют свои настоящие роды, а компилятор сам проводит по всем местам, где ключ собирается.

цена: средняя - ключи by_pane, parked, observing, server_key; правка аддитивная
2C

Рядом с флотом, не внутри

Чужая дека не вливается никуда: у неё свой лист, своё дерево, свои тайлы, свой Ctrl+Tab. Адресация не трогается ни на строку - зато и переиспользуется мало: поиск, Mission Control, WATCH и палитра про чужие паны не узнают ничего.

цена: малая в ядре, большая в UI - вторая экосистема рядом с первой
3 · где чужое видно

Рельс мы недавно чистили, и каждая новая строка в нём - это долг. Mission Control, наоборот, уже умеет показывать паны любого сервера тайлами.

3A

Только Mission Control

Первый заход отдаёт чужие паны канвасу: там уже есть тайл любого сервера и путь Observe. Рельс не растёт ни на строку, «тише рельс» не нарушено, а посмотреть и напечатать - можно сразу. Плата: чужого пана нет в дереве, и вернуться к нему можно только через канвас.

цена: малая - источник тайлов и один режим watch
3B

Полноправно в рельсе

Чужие серверы встают строками флота между своими: та же вложенность, те же плашки, тот же Ctrl+Tab, тот же поиск. Тонкий клиент перестаёт быть отдельным режимом - это просто ещё часть флота. Плата: рельс растёт, а всякий отказ чужой деки теперь виден там же, где отказы своих.

цена: большая - весь рельс, поиск, watch и палитра учатся чужому адресу
3C

Свой ярус DECKS

Чужие деки живут под собственным заголовком в подвале рельса - как WATCH и SUBS живут над флотом. Видно, что это не твоя машина, и одним взглядом понятно, где кончается своё. Плата: третий ярус в рельсе, который мы обещали не растить.

цена: средняя - новый ярус и его правила свёртки
4 · как две деки знакомятся

У телефона есть камера, и весь паринг v2 стоит на ней. У деки её нет: знакомство надо переизобрести.

4A

Строка в буфере

Та же строка, что под QR - КОПИРОВАТЬ СТРОКУ уже есть - переезжает в клиента через буфер, Alt+V. Адрес, код и отпечаток приходят вместе и целиком: подделывать нечего, потому что клиент ничего не набирает вслепую. Плата: строку надо чем-то донести до другой машины.

цена: малая - поле вставки и разбор PairPayload, который уже написан
4B

Имя и код руками

Клиент выбирает деку из списка tailnet (tailscale status уже опрашивается ради имени) и набирает шесть цифр с её экрана. Ничего носить не надо - но отпечаток клиент впервые узнаёт только из рукопожатия, а значит первый ответ придётся кому-то доверить.

цена: средняя - список пиров, экран ввода, и обязательная пара к оси 5
4C

Хозяин зовёт сам

Обратный ход: хозяин выбирает пир в своём tailnet и жмёт ПРИГЛАСИТЬ; на той деке всплывает приглашение, и согласие даёт уже её человек. Оба конца подтверждены живыми руками. Плата: клиент должен слушать входящие приглашения - то есть у него тоже открывается дверь.

цена: большая - у обеих сторон появляется и сервер, и клиент
5 · чем доказан хозяин

Отпечаток сертификата - вся защита этой двери. Вопрос в том, что происходит в первую секунду, когда сравнивать ещё не с чем.

5A

Первый сертификат молча

TOFU как у ssh, но без вопроса: что ответило в первый раз, то и записано. Честно при 4A - там отпечаток уже приехал в строке и сверять нечего. При наборе руками это дыра ровно в том месте, ради которого всё остальное построено.

цена: никакая - и ровно столько же доказательств
5B

Четыре слова

Короткая строка проверки: из отпечатка и кода выводятся четыре слова, и обе деки показывают их одновременно. Человек сверяет глазами - это две секунды, а подделать нужно обе стороны сразу. Так поступает всякий протокол, у которого нет удостоверяющего центра.

цена: малая - словарь и вывод слов из SHA-256 + код
5C

Весь отпечаток

Показать SHA-256 целиком на обеих деках, как host_key.rs показывает ключ незнакомого SSH-хоста. Никакой новой криптографии, тот же язык, что у деки уже есть. Плата: 64 знака человек сверяет плохо - и на третий раз перестаёт сверять вовсе.

цена: малая - переиспользованный диалог первой встречи
6 · что деке можно

Телефон смотрит и печатает. Дека - полноценный клиент, и у неё есть кнопки, которых у телефона не было никогда.

6A

Смотреть

Гость видит паны и их вывод - и всё. Ни клавиши. Годится для второго монитора и для «посмотреть, как идёт сборка», и абсолютно безопасно. Плата: половина смысла тонкого клиента остаётся за дверью.

цена: малая - гейт на входе type и key
6B

Смотреть и печатать

Ровно то, что умеет телефон: ввод идёт в чужой пан той же дверью, что ввод Mission Control. Тонкий клиент становится рабочим местом. Создавать и закрывать паны - нельзя, и это разумная граница: разрушение остаётся за хозяином.

цена: малая - caps в записи устройства
6C

И распоряжаться

Гость может создать пан, закрыть пан, поднять worktree - то есть быть настоящей второй декой той же машины. Отдельная галочка в карточке впуска, не подарок по умолчанию: право уничтожать чужую работу выдаётся руками и только тому, кого опознали.

цена: средняя - верхи протокола (create/close/worktree) и проверка прав на каждом
7 · форма листа

Впервые у листа две стороны: кого я впустил и куда стучусь сам. Это разные вопросы, и они по-разному отвечаются.

7A

Один список

Все устройства в одном списке, а род - бейджем на строке: ТЕЛЕФОН, ДЕКА. Одна разметка, одна сортировка, минимум нового. Плата: «мой ключ у них» и «их ключ у меня» - вещи противоположные, а стоят в одной колонке.

цена: малая - бейдж рода на существующей строке
7B

Две половины

Лист делится надвое: У МЕНЯ - кого я впустил, Я ТАМ - куда стучусь сам. Глаголы у половин разные (ОТКЛЮЧИТЬ против ОТКЛЮЧИТЬСЯ), и это видно сразу. Одно место отвечает на оба вопроса про связи деки.

цена: малая - вторая группа с собственными глаголами
7C

Исходящие - в рельсе

Лист остаётся про входящих, а «куда стучусь» живёт там же, где живут серверы - строками рельса. Логично: чужая дека для меня и есть сервер. Плата: чтобы забыть ключ, надо идти в рельс, а чтобы отозвать чужой - в лист.

цена: средняя - управление ключами расходится по двум местам
8 · где живут ключи

mobile.json знает один род записи: «кого я впустил». Появляется второй - «мой ключ в чужой деке», а это уже секрет, который стоит хранить иначе.

8A

mobile.json как есть

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

цена: никакая - и накопленный долг в имени
8B

devices.json

Один файл нового имени с двумя разделами - впущенные и исходящие. mobile.json читается один раз и переносится; старый файл остаётся на диске нетронутым, пока новый не записан. Имя наконец соответствует содержимому.

цена: малая - миграция при чтении и тест на неё
8C

Два файла

Разное - врозь: devices.json - список тех, кого я впускаю (терять не страшно, отзывается кнопкой), keyring.json - мои ключи в чужих деках (это мои секреты в чужих домах, права 0600, в бэкап иначе). Плата: два файла и два набора правил вместо одного.

цена: средняя - две схемы, две миграции, два места записи
9 · обрыв и возврат

Телефон переживает обрыв как «открыл приложение заново». Дека стоит на столе часами, и wifi-провал посреди рабочего дня не должен стоить экрана.

9A

Заново с нуля

Как сейчас: разорвалось - переподключаемся, шлём watch, получаем полный кадр. Просто и работает. Плата: всё, что пробежало за 40 секунд обрыва, потеряно, и вернувшийся экран - это не тот экран, от которого отошли.

цена: никакая - существующее поведение
9B

Докат по seq

Клиент возвращается с resume{generation, seq}, и шлюз доливает то, что пропущено, из кольца хозяина. Экран продолжается там, где оборвался. Требует сырого потока: у мобильного кадра номера последовательности нет вовсе.

цена: средняя - кольцо байтов на пан у хозяина и его предел
9C

И тёплый бридж

Сверх доката хозяин держит бридж ушедшего клиента припаркованным - той же парковкой, что деке служит для панов вне стекла - и вернувшийся получает не только пропущенное, но и тот же самый бридж, без нового рукопожатия. Плата: чужие парковки живут на хозяине и стоят ему потоков.

цена: средняя - PARKED_BRIDGE_TTL для чужих и учёт их числа
10 · чужое чужого

У STUDIO есть свои SSH-хосты, и она видит их панельные списки. Вопрос: приходят ли они ко мне - и что случится, когда две деки будут смотреть друг на друга.

10A

Только своё

Дека отдаёт гостю только те серверы, что стоят на ней самой - её LOCAL, её WSL. Всё, что она видит через третьи машины, остаётся за её дверью. Петля невозможна по устройству, а не по счётчику прыжков.

цена: никакая - одно правило в ответе panes
10B

Один прыжок

Приходит и то, что STUDIO видит по SSH - помеченным «через STUDIO», чтобы никто не спутал транзит со своим. Одна машина в цепочке, дальше стоп. Требует deck_id в приветствии, иначе своя дека вернётся ко мне под чужим именем.

цена: средняя - предел прыжков, метка пути, deck_id
10C

Весь граф

Каждая дека отдаёт всё, что видит, включая увиденное через других. Цепочки любой длины, и весь tailnet - один флот. Соблазнительно и опасно: A видит B, B видит A, и без предела прыжков список панов растёт, пока не кончится память.

цена: большая - обнаружение петель, дедупликация по deck_id, пределы на всём пути
пульт

Пульт собирает все четыре сцены сразу: поток задаёт, чем питается глаз и что бывает после обрыва; рельс - как адресован чужой пан, где он виден и приходит ли транзит; лист - что гостю можно, какой у листа вид и где лежат ключи; знакомство - как две деки встречаются и чем доказан хозяин. Выбирай в секциях выше или прямо тут - это одно и то же решение, показанное дважды.

выбрано 0 из 10
кликни по карточке - или по тумблеру на пульте