← Todos os posts
30 de jun. de 2026

SSH e Tailscale Persistentes no Steam Deck / SteamOS Após Atualizações

TL;DR: O SteamOS usa um rootfs imutável A/B e um /etc em overlayfs cujo upper layer é descartado seletivamente nas atualizações (SteamOS 3.6+), fazendo serviços habilitados de forma ingênua e configs jogadas em /etc desaparecerem sem aviso após o próximo OTA. O SSH sobrevive porque seu binário fica na imagem imutável — o symlink de enable e a config de chave precisam de proteção explícita via /etc/atomic-update.conf.d/. O Tailscale exige o instalador oficial deck-tailscale em vez de pacman, que coloca os binários em /opt/tailscale/ — um caminho respaldado por /home/.steamos/offload/opt via bind mount, persistente por design, sem necessidade de whitelist. Passos: definir senha do deck, habilitar sshd, adicionar chave pública em ~/.ssh/authorized_keys, criar /etc/ssh/sshd_config.d/30-keyonly.conf para desativar autenticação por senha, registrar o symlink de enable e a config em /etc/atomic-update.conf.d/sshd-persistent.conf; depois clonar e rodar tailscale.sh, autenticar com tailscale up --qr --operator=deck --ssh (adicionar --login-server para Headscale), e fixar tailscale set --auto-update.

O modelo de atualização do SteamOS foi projetado para jogos, não para administração de servidores. As mesmas propriedades que tornam as atualizações seguras e atômicas — rootfs imutável, /etc em overlayfs, troca de partição A/B — criam uma armadilha para quem segue instruções padrão de Linux sem entender o que realmente persiste. Fazer SSH e Tailscale sobreviverem a atualizações significa trabalhar com o modelo de persistência, não contra ele.

Você acessa o Steam Deck por SSH. Funciona. Uma semana depois, atualiza o SteamOS e tenta conectar de novo: nada. Nenhum serviço, nenhum nó do Tailscale na tailnet, apenas silêncio. Você não quebrou nada — a atualização fez exatamente o que foi projetada para fazer, que é restaurar o sistema para um estado conhecido. Você só não sabia o que “estado conhecido” significava para /etc.

Este post é para operadores de homelab que tratam o Steam Deck como um nó — acesso remoto, presença constante na tailnet. Pressupõe familiaridade com systemd e autenticação SSH por chave. Tudo aqui foi feito no SteamOS 3.6+ (baseado em Arch Linux); o mecanismo de whitelist do overlayfs é específico dessa versão em diante.

Veja também Headscale e Tailscale no OPNSense pro lado router/subnet da mesma tailnet — este post cobre um cliente, aquele cobre o gateway.

Índice

Os três níveis de persistência

Resposta direta: O SteamOS tem três níveis de armazenamento com garantias de persistência diferentes, e confundir esses níveis é o motivo de configurações sumirem após atualizações. O rootfs (partições A/B, guarda /usr e o binário do sshd) é substituído por inteiro a cada atualização — qualquer coisa escrita ali via pacman desaparece. O /etc é um overlayfs cujo upper layer sobreviveu a todas as atualizações até o SteamOS 3.6 adicionar um descarte seletivo, então symlinks de enable do systemd e drop-ins de config ali agora precisam de whitelist explícita para sobreviver. O /home é uma partição separada intocada pela troca A/B — authorized_keys ali é seguro permanentemente, salvo reset de fábrica. O /opt é bind mount de /home/.steamos/offload/opt, então qualquer coisa instalada ali fisicamente vive em /home e persiste automaticamente, sem whitelist — por isso o instalador oficial do Tailscale usa /opt/tailscale/ em vez do caminho pacman mais convencional.

Antes de tocar em qualquer configuração, o modelo mental importa. Errar aqui significa repetir a configuração após cada atualização.

O rootfs imutável é um par de partições A/B. As atualizações trocam a partição ativa por uma imagem recém-construída. Tudo aqui — binários do sistema, a maior parte de /usr, o próprio binário do sshd — é substituído integralmente. O pacman escreve aqui. É por isso que pacman -S tailscale é o que a própria documentação do Tailscale chama de bomba-relógio: funciona até a próxima atualização trocar a partição, depois desaparece sem aviso.

/etc: o overlayfs que esquece seletivamente

/etc é um overlayfs sobreposto ao /etc do rootfs:

overlay on /etc type overlay (rw, lowerdir=/sysroot/etc, upperdir=/sysroot/var/lib/overlays/etc/upper, ...)

Todas as modificações em /etc vão fisicamente para /var/lib/overlays/etc/upper, não para a partição raiz. Isso teoricamente permite que sobrevivam à troca A/B — e sobreviviam, até o SteamOS 3.6 introduzir um mecanismo de descarte seletivo. O atualizador agora decide quais arquivos modificados em /etc mantém e quais descarta após uma atualização, evitando que configs antigas conflitem com a nova imagem. O symlink de enable do sshd vive aqui. Drop-ins de sshd_config.d vivem aqui. Sem registro explícito na whitelist, podem sumir após a próxima atualização maior.

/home: a partição que sempre sobrevive

/home sempre persiste. É uma partição separada, intocada pela troca A/B. ~/.ssh/authorized_keys vive aqui. Configure uma vez e sobrevive a todas as atualizações — reset de fábrica à parte.

/opt: bind mount projetado para persistir

/opt é um bind mount de /home/.steamos/offload/opt. Esse é o insight central por trás do instalador deck-tailscale. Instalar binários em /opt/tailscale/ significa que eles ficam fisicamente em /home/.steamos/offload/opt/tailscale/, montado automaticamente em /opt no boot. Persistente por design, sem necessidade de whitelist. A equipe do SteamOS construiu esse mecanismo de offload especificamente para software de terceiros que precisa sobreviver a atualizações.

SSH: autenticação por chave, seguro contra atualizações

Resposta direta: O SSH sobrevive a atualizações em cinco passos: definir a senha do usuário deck (fica em /etc/shadow, parte do overlay persistido); systemctl enable --now sshd (o binário está na imagem imutável e sempre presente, mas o symlink de enable vive no overlay descartável); colocar a chave pública em ~/.ssh/authorized_keys dentro de /home, que é permanente; desativar autenticação por senha via um drop-in de sshd_config.d em vez de editar a config principal, já que o SteamOS já inclui essa pasta; e — o passo que realmente importa — registrar tanto o symlink de enable quanto o drop-in de config em /etc/atomic-update.conf.d/, que o atualizador lê antes de descartar o upper layer do overlay e preserva explicitamente. Pular esse último passo faz uma atualização do SteamOS 3.6+ deletar o symlink, a config, ou ambos, silenciosamente, e o acesso SSH some até refazer a configuração na mão.

Definir a senha do deck

O usuário deck não tem senha por padrão. Sem ela, o sudo não funciona interativamente. Defina a senha no terminal do modo desktop:

passwd

A senha vai para /etc/shadow, parte do upper layer do overlayfs. Não fica no rootfs, então sobrevive à troca A/B. Uma atualização do SteamOS não a redefine — um reset de fábrica, sim.

Habilitar o daemon SSH

sudo systemctl enable --now sshd

O binário do sshd (/usr/lib/systemd/system/sshd.service) faz parte da imagem imutável e sobrevive a todas as atualizações. O que o systemctl enable cria é um symlink:

/etc/systemd/system/multi-user.target.wants/sshd.service → /usr/lib/systemd/system/sshd.service

Esse symlink vive no upper layer do overlayfs de /etc — exatamente o que o atualizador do SteamOS 3.6+ pode descartar.

Verifique se o daemon está rodando:

systemctl status sshd

Saída esperada:

● sshd.service - OpenSSH Daemon
     Loaded: loaded (/usr/lib/systemd/system/sshd.service; enabled; preset: disabled)
     Active: active (running) since ...

Se aparecer enabled mas inactive, verifique journalctl -u sshd -b antes de continuar.

Adicionar sua chave pública

No Steam Deck:

mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "ssh-ed25519 AAAA... você@maquina" >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys

Ou, da sua máquina cliente, enquanto a autenticação por senha ainda está ativa:

ssh-copy-id deck@<ip-do-deck>

Esse arquivo fica em /home/deck/.ssh/authorized_keys — a partição /home persistente. Uma vez aqui, nunca precisa ser recriado entre atualizações.

Confirme que a autenticação por chave funciona antes de desativar senhas:

ssh -o PasswordAuthentication=no deck@<ip-do-deck>

Se abrir um shell, pode continuar. Se pedir senha, a autenticação por chave não está funcionando — não avance para o próximo passo até isso estar resolvido.

Desativar autenticação por senha

Crie um drop-in de configuração. O SteamOS já vem com Include /etc/ssh/sshd_config.d/*.conf no sshd_config principal, então qualquer .conf nessa pasta é lido automaticamente sem precisar editar o arquivo principal.

sudo tee /etc/ssh/sshd_config.d/30-keyonly.conf << 'EOF'
PasswordAuthentication no
PubkeyAuthentication yes
EOF

Recarregue:

sudo systemctl reload sshd

Verifique se as diretivas estão em vigor — sshd -T imprime a config mesclada em runtime:

sudo sshd -T | grep -E 'passwordauthentication|pubkeyauthentication'

Saída esperada:

passwordauthentication no
pubkeyauthentication yes

Se passwordauthentication ainda mostrar yes, verifique se Include /etc/ssh/sshd_config.d/*.conf aparece em /etc/ssh/sshd_config e se aparece antes de qualquer diretiva PasswordAuthentication conflitante.

Registrar os dois arquivos na whitelist de atualização atômica

Esse é o passo que faz o SSH sobreviver a atualizações.

sudo mkdir -p /etc/atomic-update.conf.d
sudo tee /etc/atomic-update.conf.d/sshd-persistent.conf << 'EOF'
/etc/systemd/system/multi-user.target.wants/sshd.service
/etc/ssh/sshd_config.d/30-keyonly.conf
EOF

O atualizador lê atomic-update.conf.d antes de descartar o upper layer do overlayfs e preserva explicitamente os caminhos listados no novo estado do sistema. Sem isso, uma atualização do SteamOS 3.6+ pode deletar o symlink de enable (fazendo o sshd não subir no boot), deletar a config de chave (restaurando autenticação por senha), ou ambos — silenciosamente.

O próprio diretório atomic-update.conf.d vive no overlayfs de /etc, mas o atualizador o processa antes de descartar o overlay, então suas entradas são aplicadas à atualização que está chegando. Os caminhos listados são preservados; todo o resto no upper layer fica a critério do atualizador.

Tailscale: instalar via script oficial do deck

Resposta direta: pacman -S tailscale instala em /usr/bin/, parte do rootfs imutável — funciona até a próxima atualização do SteamOS trocar a partição, e os binários somem silenciosamente, sem erro. A correção é o instalador oficial deck-tailscale, que coloca os binários tailscale/tailscaled em /opt/tailscale/ (bind mount de /home, então sobrevive) e escreve seus unit files do systemd diretamente em /etc/systemd/system/ em vez do subdiretório de symlinks wants/ que o atualizador descarta mais agressivamente. Diferente do SSH, a instalação do Tailscale não exige estritamente entradas em atomic-update.conf.d — mas adicioná-las de qualquer forma, usando o mesmo padrão de whitelist, não custa nada e remove qualquer dúvida. Depois de rodar o script: autentique com tailscale up --qr (adicione --login-server para Headscale), depois fixe tailscale set --auto-update para os binários se atualizarem sozinhos, independentemente do ciclo de atualização do SteamOS.

Por que o pacman falha aqui?

pacman -S tailscale instala os binários em /usr/bin/ — o rootfs imutável. O nó entra na tailnet, o serviço sobe, tudo parece funcionar. Depois o SteamOS atualiza, troca a partição do rootfs, e os binários somem. A definição do serviço pode ainda existir no overlayfs (apontando para um caminho que não existe mais), o nó desaparece da tailnet, e não há nenhum erro — só ausência.

Isso não é um caso de borda. Acontece depois de cada atualização do SteamOS.

Clonar e rodar o instalador do deck

git clone https://github.com/tailscale-dev/deck-tailscale.git ~/deck-tailscale
cd ~/deck-tailscale
bash tailscale.sh

O script escreve em vários locais, cada um escolhido para sobreviver a atualizações:

CaminhoConteúdoPor que persiste
/opt/tailscale/Binários tailscale e tailscaled/opt é bind mount de /home/.steamos/offload/opt — vive em /home
/etc/systemd/system/tailscale.serviceUnit file do systemdOverlayfs de /etc — unit files em system/ (não em wants/) sobrevivem ao descarte do atualizador de forma mais consistente
/etc/systemd/system/tailscaled.service.d/override.confOverride apontando o binário em /optIdem
/etc/default/tailscaledVariáveis PORT e FLAGSIdem
/etc/profile.d/tailscale.shAdiciona /opt/tailscale/ ao PATHIdem

A diferença chave em relação ao SSH: o script tailscale.sh não requer entradas em atomic-update.conf.d. Os arquivos do serviço do Tailscale ficam diretamente em /etc/systemd/system/ (não no subdiretório de symlinks wants/), e o atualizador trata esses com mais conservadorismo. Se quiser ter certeza absoluta — adicione-os à whitelist usando o mesmo padrão da config do SSH. O princípio é idêntico.

Após o script terminar, verifique:

systemctl status tailscaled

Esperado: active (running).

Autenticar

Para o servidor de controle do próprio Tailscale:

tailscale up --qr --operator=deck --ssh

Para uma instância self-hosted do Headscale:

tailscale up --qr --operator=deck --ssh --login-server=https://seu.headscale.host

--qr gera um QR code no terminal — escaneie com o app do Tailscale no celular para autenticar sem precisar copiar URLs manualmente no modo desktop. --operator=deck permite que o usuário deck rode tailscale status, tailscale ping e similares sem sudo; sem isso, só root controla o daemon. --ssh habilita o Tailscale SSH, uma camada de acesso baseada em identidade que o daemon do Tailscale gerencia diretamente, paralela e independente do OpenSSH configurado acima.

Confirme que o nó está na tailnet:

tailscale status

Esperado: Steam Deck listado com endereço 100.64.0.0/10 e estado active.

Habilitar auto-update

sudo tailscale set --auto-update

Os binários ficam em /opt/tailscale/ — persistente, fora do ciclo de atualização do rootfs. O mecanismo de auto-update do Tailscale os substitui no lugar, independentemente das atualizações do SteamOS. Sem isso, você precisaria re-rodar tailscale.sh a cada nova versão do Tailscale. O único motivo para re-rodar o script é se uma atualização do SteamOS descartar as entradas do overlayfs de /etc — não é o caminho normal de atualização, mas possível em cenários de reset agressivo.

Dois caminhos de acesso independentes — o que acontece se um falhar?

Resposta direta: Depois dessa configuração, OpenSSH e Tailscale SSH são dois caminhos independentes para o Deck que não compartilham modo de falha. O OpenSSH na porta 22 só precisa de autenticação por chave e do caminho de rede aberto — funciona esteja tailscaled rodando ou não. O Tailscale SSH é baseado em identidade e roteado pela tailnet independentemente de NAT ou filtragem de porta, mas precisa de tailscaled ativo e do nó autenticado. Se a autenticação do Tailscale expirar ou tailscaled travar, o OpenSSH não é afetado. Se algo bloquear a porta 22 inbound, o Tailscale SSH passa assim mesmo. Para um Headscale self-hosted especificamente, o Tailscale SSH também falha se a própria instância do Headscale estiver fora do ar — o único cenário em que o OpenSSH por um caminho de rede separado é a única forma de entrar.

Após essa configuração há dois caminhos de SSH que não dependem um do outro:

OpenSSH na porta 22 — autenticação por chave, disponível na rede local e pelo IP do Tailscale. Funciona independentemente de tailscaled estar rodando.

Tailscale SSH — baseado em identidade, roteado pela tailnet independentemente de filtragem de porta ou NAT. Requer tailscaled ativo e o nó autenticado.

São complementares. Se a autenticação do Tailscale expirar ou tailscaled travar, o OpenSSH continua disponível. Se houver algo bloqueando a porta 22 inbound, o Tailscale SSH passa assim mesmo. Para o caso do Headscale, o Tailscale SSH também falha se a instância do Headscale estiver inacessível — então o OpenSSH por outro caminho permanece como fallback.

Depois de uma atualização, o que verificar?

Resposta direta: Rode systemctl is-active sshd, systemctl is-active tailscaled e tailscale status depois de cada atualização do SteamOS. Se sshd aparecer inativo ou não habilitado, a whitelist do atomic-update.conf.d não aplicou — verifique se /etc/atomic-update.conf.d/sshd-persistent.conf existe e lista tanto o symlink de enable quanto o drop-in de config. Se tailscaled estiver inativo, verifique journalctl -u tailscaled -b; o binário em /opt/tailscale/tailscaled deve continuar presente, já que /opt persiste através de atualizações normais. O único caso contra o qual nada disso protege é um reset de fábrica: ele apaga /home por completo, levando junto authorized_keys, o estado do Tailscale e os binários em /opt/tailscale/, além de limpar o upper layer do overlay de /etc. Um reset de fábrica não é uma atualização — é destruição intencional do estado do usuário, e tudo neste post precisa ser refeito do zero depois.

Depois de uma atualização do SteamOS, execute:

systemctl is-active sshd
systemctl is-active tailscaled
tailscale status

Se sshd estiver inativo ou não habilitado após a atualização, a whitelist do atomic-update.conf.d não aplicou. Verifique se /etc/atomic-update.conf.d/sshd-persistent.conf existe e contém os dois caminhos. Se tailscaled estiver inativo, verifique journalctl -u tailscaled -b — o binário em /opt/tailscale/tailscaled ainda deve estar presente, pois /opt persiste através das atualizações.

Uma exceção absoluta: um reset de fábrica apaga /home. ~/.ssh/authorized_keys desaparece junto com os arquivos de estado do Tailscale e os binários em /opt/tailscale/ (que fisicamente ficam em /home/.steamos/offload/opt). Um reset de fábrica não é uma atualização — é destruição intencional do estado do usuário. Tudo precisa ser refeito. O upper layer do overlayfs de /etc também é limpo. A senha some. Começa do zero.

Referências


Breno Zanato Detomini
Breno Zanato Detomini

Engenheiro de sistemas embarcados e redes, baseado no Brasil.

← Todos os posts