This one started as an itch. I wanted to build something for Linux — not for a cluster, not for work, just for Linux — so I went looking for the smallest piece of the system I did not understand all the way down. I ended up writing an init.
Este aqui começou como uma coceira. Eu queria construir alguma coisa pro Linux — não pra um cluster, não pro trabalho, só pro Linux — então fui procurar a menor peça do sistema que eu não entendia até o fundo. Acabei escrevendo um init.
It is called oxinit: a service manager and PID 1, written in Rust,
dual-licensed MIT or Apache-2.0, and honestly labelled pre-alpha. First commit on 8 August
2026, v0.1.0 tagged on the 11th. Four days — which is not a boast about speed. An
init is a small program with an enormous blast radius, and most of those four days went into
deciding what it would refuse to do.
Ele se chama oxinit: um gerenciador de serviços e PID 1, escrito
em Rust, com licença dupla MIT ou Apache-2.0, e honestamente marcado como pre-alpha. Primeiro
commit em 8 de agosto de 2026, v0.1.0 marcado no dia 11. Quatro dias — o que não é
vaidade de velocidade. Um init é um programa pequeno com um raio de dano enorme, e a maior
parte desses quatro dias foi decidir o que ele ia se recusar a fazer.
The process that cannot fail
O processo que não pode falhar
An init system is the first program the kernel starts. It is process number 1, it is the parent of every other process on the machine, and it has one property nothing else on the system has: if it exits, the kernel panics. There is no supervisor above it. Nothing restarts it.
Um init é o primeiro programa que o kernel inicia. É o processo número 1, é o pai de todos os outros processos da máquina, e tem uma propriedade que nada mais no sistema tem: se ele sai, o kernel entra em panic. Não existe supervisor acima dele. Nada o reinicia.
I spend my working hours on infrastructure, so that shape is familiar in an uncomfortable way. Every runbook I have ever written assumes something above the failing thing is still alive to notice it failed. Here, nothing is. That single fact is the entire design brief.
Eu passo minhas horas de trabalho em infraestrutura, então esse formato é familiar de um jeito desconfortável. Todo runbook que eu já escrevi assume que alguma coisa acima do que falhou continua viva pra perceber que falhou. Aqui, não tem nada. Esse fato sozinho é o briefing inteiro do projeto.
Every other process on a Linux box has a parent that outlives its
mistakes. PID 1 does not.
Todo outro processo numa máquina Linux tem um pai que sobrevive aos
erros dele. O PID 1 não tem.
No panic, and the compiler enforces it
Sem panic, e quem garante é o compilador
The first rule is that PID 1 does not panic. Saying it is easy; it needed actual
machinery. The binary is built with panic = "unwind" and never abort,
because an abort in PID 1 is a kernel panic. Every handler in the event loop runs
inside catch_unwind. The oxinit crate denies unwrap,
expect, panic and slice indexing outright — the compiler refuses the
shortcut rather than trusting me not to take it at 2am.
A primeira regra é que o PID 1 não dá panic. Dizer é fácil; precisou de
maquinário de verdade. O binário é compilado com panic = "unwind" e nunca
abort, porque um abort no PID 1 é um kernel panic. Todo handler do event
loop roda dentro de catch_unwind. O crate oxinit proíbe
unwrap, expect, panic e indexação de slice — o
compilador recusa o atalho em vez de confiar que eu não vou tomá-lo às 2 da manhã.
And when the loop genuinely cannot continue, it still does not exit. It spawns
/bin/sh on the console, because a machine with a shell on it is recoverable and a
machine whose PID 1 is gone is not.
E quando o loop realmente não consegue continuar, ele ainda assim não sai. Ele
lança /bin/sh no console, porque uma máquina com um shell nela é recuperável e uma
máquina cujo PID 1 sumiu não é.
The unsafe lives in one file
O unsafe mora em um arquivo só
Syscalls go through rustix, and whatever unsafe remains is
quarantined in a single module with a // SAFETY: comment per block stating the
invariant. That is not a convention I promised in a document and then policed by hand: the
crate denies unsafe_code, exactly one module relaxes it, and every crate that
should contain none carries forbid. It is a property of the build, so it cannot
quietly stop being true.
As syscalls passam por rustix, e o unsafe que sobra
está isolado num único módulo, com um comentário // SAFETY: por bloco declarando a
invariante. Isso não é uma convenção que eu prometi num documento e depois fiscalizei na mão: o
crate proíbe unsafe_code, exatamente um módulo relaxa isso, e todo crate que não
deveria ter nenhum carrega forbid. É uma propriedade do build, então não tem como
deixar de ser verdade em silêncio.
It paid off somewhere I could measure. oxinit compiled and ran on aarch64 unchanged,
and cargo xtask test-boot --arch all boots both architectures and runs the same
twenty-six checks on each.
Isso rendeu num lugar onde deu pra medir. O oxinit compilou e rodou em aarch64
sem alterar nada, e cargo xtask test-boot --arch all boota as duas arquiteturas e
roda as mesmas vinte e seis verificações em cada uma.
One loop, one thread, no async runtime
Um loop, uma thread, sem runtime async
There is no async runtime in PID 1. One epoll loop on one thread
multiplexes everything: the signalfd, the timerfd, the notify socket,
the control socket, every socket unit's listening descriptor, and every service cgroup's
cgroup.events.
Não existe runtime async no PID 1. Um loop de epoll numa thread só
multiplexa tudo: o signalfd, o timerfd, o socket de notify, o socket
de controle, o descritor de escuta de cada socket unit, e o cgroup.events do
cgroup de cada serviço.
The CLI and the log writer are separate programs that talk to it over a unix socket,
for the reason you would expect: a bug in the log writer kills the log writer, and a bug in
PID 1 kills the machine. PID 1 never reads a byte of service output — it creates the pipe,
hands the write end to the child and the read end to oxlogd, so a service writing
a megabyte a second cannot make PID 1 do any work at all.
O CLI e o escritor de log são programas separados que falam com ele por um socket
unix, pelo motivo que você imagina: um bug no escritor de log mata o escritor de log, e um bug
no PID 1 mata a máquina. O PID 1 nunca lê um byte da saída de um serviço — ele cria o pipe,
entrega a ponta de escrita pro filho e a ponta de leitura pro oxlogd, então um
serviço escrevendo um megabyte por segundo não consegue fazer o PID 1 trabalhar.
"Needs it running" and "starts after it" are different sentences
"Precisa dele rodando" e "inicia depois dele" são frases diferentes
Units are TOML. No shell, no scripting, no runtime interpolation beyond a small documented set of specifiers.
As units são TOML. Sem shell, sem scripting, sem interpolação em runtime além de um conjunto pequeno e documentado de especificadores.
# /etc/oxinit/units/sshd.toml [unit] description = "OpenSSH daemon" after = ["network-online"] requires = ["network-online"] [service] type = "notify" exec = "/usr/sbin/sshd -D" restart = "on-failure" restart-sec = "5s" user = "sshd"
Both keys are declared, and neither implies the other. "A needs B running" and "A
must start after B" are different statements, and a format that conflates them makes both
impossible to say precisely. after means the unit does not start until everything
it names has finished activating — and for a notify service that means until
READY=1 actually arrives, not until the process exists. Boot is a queue drained by
the event loop, not a loop issuing starts in order.
As duas chaves são declaradas, e nenhuma implica a outra. "A precisa de B
rodando" e "A tem que iniciar depois de B" são afirmações diferentes, e um formato que confunde
as duas torna as duas impossíveis de dizer com precisão. after significa que a
unit não inicia até tudo que ela nomeia terminar de ativar — e pra um serviço
notify isso quer dizer até o READY=1 realmente chegar, não até o
processo existir. O boot é uma fila drenada pelo event loop, não um laço disparando starts em
ordem.
The non-goals are load-bearing
Os não-objetivos sustentam o projeto
oxinit does not resolve DNS, does not implement NTP, does not manage network configuration or logins, does not run containers — it runs inside one, as PID 1 — and does not boot the machine. It is what a distribution's initramfs hands over to, not a bootloader.
O oxinit não resolve DNS, não implementa NTP, não gerencia configuração de rede nem logins, não roda containers — ele roda dentro de um, como PID 1 — e não boota a máquina. Ele é pra quem o initramfs de uma distribuição entrega o controle, não um bootloader.
It also will not read systemd unit files. Supporting a subset of
.service means adopting a specification defined by another project's
implementation rather than by a document, and the subset you implement is never the subset your
distribution actually uses. It does implement sd_notify and socket
activation, which is a different thing entirely: those are small, stable, documented wire
protocols that unmodified daemons already speak. A runtime protocol is not a configuration
language.
Ele também não vai ler arquivos de unit do systemd. Suportar um subconjunto de
.service significa adotar uma especificação definida pela implementação de outro
projeto em vez de por um documento, e o subconjunto que você implementa nunca é o subconjunto
que a sua distribuição realmente usa. Ele implementa sd_notify e ativação
por socket, o que é outra coisa completamente: são protocolos de comunicação pequenos, estáveis
e documentados que daemons sem modificação já falam. Um protocolo de runtime não é uma linguagem
de configuração.
Why Rust, in this specific place
Por que Rust, neste lugar específico
Every init in production use is written in C: systemd, OpenRC, runit, s6, dinit. The Rust attempts either stalled — rustysd is unmaintained — or explicitly declined to be PID 1, as initd did.
Todo init em uso em produção é escrito em C: systemd, OpenRC, runit, s6, dinit. As tentativas em Rust ou pararam — o rustysd está sem manutenção — ou recusaram explicitamente ser PID 1, como o initd fez.
PID 1 owns the cgroup hierarchy, holds the file descriptors for socket activation, and decides the order in which the machine shuts down. That is a very large blast radius for a language in which a bounds error is a memory error. The properties Rust enforces at compile time are worth more in PID 1 than anywhere else on the system — which is exactly the place nobody had put them.
O PID 1 é dono da hierarquia de cgroups, guarda os descritores de arquivo da ativação por socket, e decide a ordem em que a máquina desliga. Isso é um raio de dano muito grande pra uma linguagem em que um acesso fora dos limites é um erro de memória. As propriedades que o Rust garante em tempo de compilação valem mais no PID 1 do que em qualquer outro lugar do sistema — que é exatamente o lugar onde ninguém as tinha colocado.
Where it is now
Onde está agora
Milestones M0 through M16 are done and the roadmap is closed. It boots under QEMU on
x86_64 and aarch64, runs a real Alpine userspace, and works as a container's PID 1: fourteen
crates, about 12,500 lines of Rust, 148 #[test] functions. Three of those crates —
the unit parser, the dependency graph and the restart policy — have no OS dependency at all and
test on any host, with no VM and no kernel. That is deliberate, because that is where the logic
errors actually live.
Os milestones M0 até M16 estão fechados e o roadmap está encerrado. Ele boota sob
QEMU em x86_64 e aarch64, roda um userspace Alpine de verdade, e funciona como PID 1 de
container: catorze crates, cerca de 12.500 linhas de Rust, 148 funções
#[test]. Três desses crates — o parser de unit, o grafo de dependências e a
política de restart — não têm dependência de sistema operacional nenhuma e testam em qualquer
host, sem VM e sem kernel. Isso é de propósito, porque é ali que os erros de lógica realmente
moram.
It is pre-alpha and I mean it literally: nothing is stable, and the unit format may
change between any two releases. If you would rather see it than take my word for it, the demo
is a 4.6 MB FROM scratch image and needs nothing but Docker:
É pre-alpha e eu digo isso literalmente: nada é estável, e o formato das units
pode mudar entre dois releases quaisquer. Se você prefere ver a coisa em vez de acreditar em
mim, a demo é uma imagem FROM scratch de 4,6 MB e não precisa de nada além de
Docker:
docker run --rm --name oxinit-demo -p 8080:8080 ghcr.io/youhide/oxinit:demo
Then docker stop it. That is an ordered shutdown, not a ten-second
grace period ending in a SIGKILL: every unit is stopped in the reverse of the
order it was started, each one is waited for until it is actually gone, and the process exits
0. The exit code 137 you are used to seeing is the other outcome. The
source is at github.com/youhide/oxinit.
Depois dê docker stop nele. Isso é um desligamento ordenado, não um
período de graça de dez segundos terminando num SIGKILL: cada unit é parada na
ordem inversa da que foi iniciada, cada uma é esperada até realmente ter sumido, e o processo
sai com 0. O código de saída 137 que você está acostumado a ver é o
outro desfecho. O código está em
github.com/youhide/oxinit.
I set out to build something for Linux and ended up in the one process on the machine that has nothing above it. I would not put it under anything I cared about yet. But it boots, it supervises, it shuts down in the right order — and every line of the reason it can be trusted to do that is in the repository rather than in this post.
Eu queria fazer alguma coisa pro Linux e acabei no único processo da máquina que não tem nada acima dele. Eu ainda não colocaria ele embaixo de nada que me importasse. Mas ele boota, supervisiona, desliga na ordem certa — e cada linha do motivo pelo qual dá pra confiar nisso está no repositório, não neste post.