Как неудавшийся клон GodotOS стал Unix на Godot

У каждой домашней сети начала 2000-х была своя география. До соседнего компьютера — двадцать метров витой пары, до сервера провайдера — DSL-модем и неизвестность за ним. Но мерили мы это пространство не метрами, а мегабитами: 10 через хаб, 100 через свитч, 256 килобит до остального мира. И внутри этих мегабит помещался целый маленький интернет — чьи-то домашние страницы, IRC-канал, FTP с демками, почтовый сервер, который вечно лежал.
AgoraOS — моя попытка снова построить такое пространство. Это Unix-подобная операционная система, написанная на GDScript внутри Godot: с ядром, процессами, пайпами, сетевым стеком и маленьким интернетом 2003 года вокруг, который пока статичный, но об этом позже. Хотя задумывалась она совсем не так. Начиналось всё с желания аккуратно переписать чужой проект — а закончилось тем, что я перестал рисовать операционную систему и начал её строить.

Смена курса
У меня давняя любовь к глубоким симуляциям — к играм и системам, где вещи работают не потому, что так задано скриптами, а потому, что так устроена архитектура.
Поэтому, когда я сел за реализацию, сразу стало понятно: обычная имитация мне не нужна. Окно терминала, которое печатает заготовленные ответы на заготовленные команды, — это та же красивая картинка. Я хотел, чтобы процессы работали в связке. Чтобы ls | grep был настоящим пайпом между двумя процессами, ping — настоящими ICMP-пакетами, а выдернутый кабель — настоящей проблемой для всего, что через него шло.
Всё есть файл!
Так родилась идея сделать ОС по всем принципам Unix, только на Godot. И оказалось, что изобретать почти ничего не нужно. Unix уже полвека назад ответил на большинство вопросов, которые возникают при проектировании такой системы. Каждый принцип становился кирпичом, на который ложился следующий.
Всё — файл. Аккаунты лежат в /etc/passwd и /etc/shadow, поэтому useradd просто дописывает строку, login её читает, а игрок может отредактировать файл руками — и это тоже сработает. Процессы видны в /proc, устройства — в /dev. Даже программы в /bin — файлы: их можно скопировать, переименовать или снять с них x-бит. А значит, пакетному менеджеру не нужно ничего, кроме копирования файлов.
Процессы и пайпы. Каждая программа — корутина GDScript, без потоков. Блокирующее чтение, sleep или ожидание дочернего процесса просто приостанавливают её через await. Вот yes целиком:
extends AgoraProgram
func summary() -> String:
return "print a string forever"
func main(p: AgoraProcess) -> int:
var text := " ".join(p.argv.slice(1)) if p.argv.size() > 1 else "y"
while p.outln(text):
await p.tick()
return 0
Когда у пайпа не остаётся читателей, outln возвращает false, писатель получает SIGPIPE, и yes | head останавливается так же, как в настоящей системе. p.tick() отдаёт кадр движку не чаще раза в 8 мс, поэтому while true; do :; done не вешает игру.
Здесь был единственный по-настоящему коварный момент. Godot возобновляет ожидающие корутины синхронно, прямо внутри emit(). Если запустить стадии пайплайна слева направо, первая начнёт писать раньше, чем вторая встанет в ожидание. Решение — создать все процессы пайплайна заранее, а запускать их с конца:
for i in range(stages.size() - 1, -1, -1):
if stages[i].proc:
procs.append(stages[i].proc)
(stages[i].proc as AgoraProcess).start()
var foreground: bool = interactive and p.tty != null and not io.get("bg", false)pid 1. Ядро загружается, монтирует /proc, /dev, /sys и запускает /sbin/init. Каким он будет — systemd с юнитами или sysvinit с /etc/inittab — решает установленная система, а не ядро. Если init умирает — kernel panic.
Конфигурация в файлах, а не в памяти. Этот принцип сэкономил больше всего работы. При сохранении мира пишутся только диски, CMOS и часы. Процессы, маршруты, правила файрвола не сохраняются вообще: загруженная машина просто загружается заново и настраивается из своих /etc, как настоящая. Не нужно сериализовать живое ядро — нужно честно реализовать загрузку.
В итоге слои повторяют настоящий компьютер. Ядро — чистый GDScript без нод и интерфейса, оно работает и в headless-режиме. Рабочий стол — лишь один из фронтендов, как X11 или Wayland.
Когда кирпичи начинают работать вместе
Самое ценное в таком подходе проявляеться не сразу. Ни один компонент не знает о других больше, чем знал бы его настоящий прототип. А в итоге они начинают взаимодействовать без отдельных скрптов.
Лучше всего это видно на сети. Её первая версия была ровно тем, от чего я уходил: класс AgoraNetwork с плоской таблицей «адрес → машина». Одна плоская сеть, без маршрутизации и без пакетов. По сути, это была маленькая GodotOS внутри моего проекта: сеть, нарисованная поверх, а не устроенная изнутри.
Дальше сеть собрали снизу вверх. Ethernet и ARP, ip и ifconfig, ICMP, ifupdown, мост, DHCP, форвардинг, netfilter, NAT, TCP, DNS. Соединения находят друг друга только по адресам и портам. Никаких ссылок на объекты в пакетах, никаких обходных каналов.
Симуляция вместо сценария
После этого начались вещи, которых нет ни в одном отдельном объекте.
Выдёргиваешь кабель — и открытая ssh-сессия не обрывается мгновенно, а подвисает. TCP перепосылает неподтверждённые данные с удваивающимся таймаутом и только потом сдаётся с ETIMEDOUT. Никто не писал код «если кабель выдернут, сломать ssh». Его сломала физика.
Та же история с кабелем, который игрок обжал сам в 3D-режиме. Коробка с кабелями, обжимка, тестер, битовые ошибки в кабеле, счётчики ошибок в ядре и повторная передача в TCP. Связались они сами, потому что каждый слой делает только то, что делает его настоящий прототип.
Ещё несколько таких цепочек:
Тонкий коаксиал. Забыл терминатор на конце шины — и сеть ложится целиком, потому что каждый кадр сталкивается с собственным эхо. Ровно так это и было в 10BASE2.
Домашняя страничка. Кладёшь
index.htmlв~/public_html— apache с UserDir отдаёт её, краулер поисковика Ferret находит её по HTTP, и через какое-то время она появляется в поиске.traceroute через NAT. NAT на роутере откатывает трансляцию не только у ответов, но и у ICMP-ошибок, включая заголовок, процитированный внутри. Поэтому
tracerouteиз-за домашнего роутера показывает честный маршрут.Забытый пароль BIOS. Открываешь корпус, переставляешь перемычку Clear CMOS или вынимаешь батарейку вместе со шнуром питания — и настройки вместе с паролем пропадают. Способ из 2003 года работает, потому что пароль лежит в CMOS, а CMOS питается от батарейки.
Честность иногда возвращала и настоящие баги. Когда TCP стал передавать данные сегментами, оказалось, что nc, через который прогоняли 25 000 строк, отправляет 25 000 сегментов — по одному на каждую запись. Решение в итоге оказалось тем же, что и в реальных стеках: алгоритм Нейгла, а ssh и sshd выставляют TCP_NODELAY, как это делает OpenSSH.
Маленький интернет
Когда кирпичей стало достаточно, из них сложился целый интернет. Демо-мир — октябрьский вечер 2003 года. На столе ПК с монитором и домашний роутер, за ним провайдер LanLink, который выдаёт адрес по DHCP и держит DNS, а дальше — серверы с сайтами той эпохи.

Каждый сервер — такая же машина, как ПК игрока, с тем же ядром, тем же init и тем же apache. Сайты — это обычные HTML-файлы в /var/www и ~/public_html, написанные в духе времени. Есть Linux user group, гиковские новости, e-zine, сайт демосцены с анонимным FTP. Есть поисковик Ferret, чей краулер действительно обходит все эти сайты, мессенджер Ping! в духе ICQ, почта провайдера и IRC, где в #lanlink дежурит бот техподдержки.

Каждая машина при этом загружается по-настоящему: BIOS с POST и Setup, GRUB 0.97 с menu.lst, ядро, init, login:. Первый раз ПК пустой, в приводе лежит установочный диск, и систему нужно поставить текстовым инсталлятором. А в 3D-режиме всё это стоит на столе: индикаторы мигают от трафика, на мониторе — настоящий экран, корпус открывается, а кабели можно переткнуть или обжать заново.

Когда интернет оживёт
Пока в этом интернете живёт один человек, остальные его жители — боты и статичные страницы. Но фундамент для большего уже лежит. В мире есть свитч абонентов провайдера, и к нему можно подключать дома нескольких игроков — у каждого свой роутер, свой компьютер и свой почтовый ящик на сервере LanLink.
Когда я доделаю мультиплеер, этот интернет сможет ожить.
Метры и мегабиты
Я начинал с того, что хотел переписать чужую красивую картинку. А закончил тем, что перестал рисовать и начал строить — по чертежу, которому полвека. Unix не пришлось улучшать: достаточно было класть его кирпичи по одному и следить, чтобы каждый вёл себя как настоящий. Остальное они сделали сами.
Сейчас внутри AgoraOS можно собрать полноценный интернет. Пока он маленький и тихий. Но в нём уже есть то, ради чего всё затевалось: пространство, размер которого считается в метрах кабеля, а ощущается в мегабитах в секунду. Хаб на 10, свитч на 100, коаксиал, который ложится от одного забытого терминатора. Если мультиплеер получится, может быть, кто-то снова почувствует тот самый — маленький локальный интернет, в котором ты знаешь каждый сервер по имени.
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.