Dev & EngARTIGO

fosforo na App Store: um terminal com shell próprio

fosforo na App Store: um terminal com shell próprio
Imagem: Cesar Gimenes
Artigo · Dev & Eng
Leitura
6 min
Código
2

O fosforo está na App Store. Grátis, para iPad e iPhone; no Mac é um cask do Homebrew. É um emulador de terminal em C e Swift↳Swift13 conteúdosSwift 6: Segurança na Corrida de Dados — AtoresDev (Back & Front) · jan 2025Swift no Server-Side: o crescimento do Swift além do iOSDev (Back & Front) · mai 2026IA e Machine Learning com SwiftDev (Back & Front) · out 2025Ver tudo em Dev (Back & Front) →, desenhado com Metal, e no iOS ele carrega o próprio shell, o próprio cliente SSH e o próprio cliente Mosh. Abra o app num iPad sem rede e você tem um prompt, um filesystem, pipes e uns sessenta comandos. Essa é a parte de que eu mais gosto.

fosforo num iPad, com o shell rocchetto e o gato Gohan de banner, e num iPhone com um monitor de sistema aberto por SSH

O repositório foi criado em 29 de setembro. O primeiro build subiu para o App Store Connect em 8 de outubro, e a 1.0.1 foi aprovada em 20 de outubro.

Um shell que roda no iOS

O iOS não tem fork nem exec. Não existe /bin/sh para abrir e não existe como abrir processo nenhum. Então o fosforo carrega o shell dentro do processo: o rocchetto, um shell POSIX escrito em C, sem libc por baixo do núcleo. O mesmo código roda como binário de desktop, numa aba de browser em wasm, numa placa de microcontrolador com tela de 20×8 e dentro do fosforo como biblioteca estática. Pipes, redirects, $(...), funções, if/for/while, histórico, Tab completion, editor de linha.

sh
mobile@iPad:~$ ls /bin | cut -c1 | sort | uniq -c | sort -rn | head -3
     10 c
      6 t
      5 s

Esse pipeline roda no iPad, offline, sem servidor nenhum por trás. Cada estágio é um programa no /bin.

Os comandos são Filo

ls, grep, sed, sort, find, wc, tr, xargs e o resto dos 64 comandos do /bin não são C. São programas em Filo, compilados para bytecode da VM do Filo e embarcados no app. O que está no /bin é o programa compilado; o fonte de cada um fica em commands/bin no repositório do rocchetto, grep.filo incluso.

Filo é a minha linguagem de script. Nasceu para dar poder de script ao usuário sem entregar a máquina, que é exatamente a restrição que uma loja de apps impõe. E se mostrou um bom encaixe para o userland de um shell: um comando é um programa curto, o conjunto inteiro compila em segundos, e um bug no sort é um fix de um arquivo só. expr, sed e date foram os que deram trabalho de verdade; o resto saiu rápido depois que o padrão ficou pronto.

A configuração do terminal também é Filo. O ~/.config/fosforo/init.filo define fonte, grid, cores, bell. Temas são arquivos. E existem hooks de eventos, então o terminal reage sem eu precisar inventar um checkbox para cada caso:

lisp
(set on-blur (fn (arg) (theme "phosphor")))
(set on-focus (fn (arg) (theme "default")))

Perdeu o foco, a janela fica verde fósforo. Voltou, volta. Quatro linhas, nenhuma tela de settings.

No Mac nada disso é necessário e nada disso está lá. O app do Mac é emulador e só: o seu login shell num pty, o ssh do sistema, o git do sistema. Terminal que traz o próprio shell para uma máquina que já tem um está resolvendo um problema que a máquina não tem.

SSH e Mosh dentro do app

O ssh do iPad é o cliente do próprio app, escrito em Swift sobre o CryptoKit. Key exchange curve25519, e o híbrido mlkem768x25519-sha256 quando o servidor oferece; AES-GCM; strict kex; host keys ed25519, ECDSA e RSA; known_hosts com trust on first use; ~/.ssh/config com a semântica de sempre, todo Host que casa aplica e o primeiro valor vence; ProxyJump; -L, -R e -D; agent forwarding em que a assinatura acontece no aparelho e a chave nunca sai dele. Um segundo ssh para um host já conectado compartilha a conexão e abre na hora.

O Mosh é a mesma história uma camada abaixo: SSP sobre UDP com OCB3, o wire format feito à mão, testado contra um servidor de verdade com um terço dos pacotes descartados de propósito. O local echo aparece acima de 30 ms de RTT e sublinha acima de 80 ms, e a única regra que importa: depois do Return nada é previsto até o servidor confirmar um palpite, que é o que mantém a senha fora da tela. Testei isso com read -s contra um servidor real antes de confiar.

A rede de revisão da Apple é só IPv6, com NAT64. O cliente SSH resolve AF_INET e AF_INET6 em paralelo e fica com quem responder primeiro; o Mosh depois usa o endereço por onde a sessão SSH chegou. Descobri isso nas diretrizes antes de o revisor descobrir no app, que é a ordem barata.

Chaves no Secure Enclave

As chaves ficam em ~/.ssh dentro do app, modo 0600, sob a proteção de arquivo mais estrita que o iOS tem, e fora do backup do aparelho, para não viajarem para o aparelho seguinte. Esse é o padrão e a chave é um arquivo comum: ssh-keygen -t ed25519 no iPad funciona como num computador, e o ssh-copy-id também está lá.

key protect NOME vai além. A chave é selada pelo Secure Enclave deste aparelho, e cada uso pede Face ID, Touch ID ou o código. Copiou o arquivo para outro lugar, virou ruído. O app nunca vê dado biométrico; quem pergunta é o sistema.

Nada é movido, apagado ou cifrado sem um comando e uma confirmação. Não gosto de software que faz coisa sozinho, e não escrevi nenhum.

Nada coletado

Sem analytics, sem crash report, sem conta, sem anúncio, sem assinatura. O rótulo de privacidade na loja diz “Dados não coletados” e é isso mesmo: o app só conecta nas máquinas que você nomear, ou que um programa que você rodar pedir.

O terminal em si

O mesmo núcleo desenha as duas plataformas. O vt/ é um parser VT em C11 escrito do zero, sem I/O e sem estado global, passado por fuzz, uns 600 MB/s de ASCII puro neste Mac. O plano inicial era portar o parser de um terminal antigo meu; tinha lacunas demais, então as lições ficaram e o código não.

Box drawing, blocos, sombras, braille e os separadores do Powerline são desenhados por geometria, não tirados da fonte, então arte ANSI não tem emenda entre células. Eu me importo com isso mais do que é razoável. A fonte padrão é a IBM 3270. Programas de tela cheia renderizam como devem.

No Mac: várias sessões por janela, ⌘F pelo scrollback, um copy mode com hjkl, ⌘↑/⌘↓ entre prompts quando o shell emite OSC 133, notificação quando um comando longo termina numa sessão que não está na frente. O app do Mac não está na Mac App Store: o sandbox não cabe num terminal que roda o seu login shell. Ele é assinado, notarizado e distribuído como cask.

A revisão

Aprovado na primeira revisão. As notas ao revisor explicavam o que o shell é: roda scripts que o usuário escreve e não baixa funcionalidade, que é o que a diretriz 2.5.2 pede. Um login num servidor de demonstração deixou os comandos de SSH e Mosh serem exercitados. Uma coisa que aprendi no caminho: ícone de iOS não pode ter canal alfa, e o script que desenha o ícone agora tira o alfa no iOS e mantém no macOS.

Onde pegar

Trabalha com tecnologia desde a década de 90. Já atuou na área de educação e participou de projetos de mobilidade de grande volume para laboratórios farmacêuticos. Criou games tanto para PC, como para iOS. Hoje está direcionando seus esforços em plataformas de Sistemas Embarcados, IoT, microservices e cloud computing. É um entusiasta de tecnologias como Golang e Docker.

Ver perfil →