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