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
/usre o binário dosshd) é substituído por inteiro a cada atualização — qualquer coisa escrita ali viapacmandesaparece. 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_keysali é 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/homee persiste automaticamente, sem whitelist — por isso o instalador oficial do Tailscale usa/opt/tailscale/em vez do caminhopacmanmais 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_keysdentro de/home, que é permanente; desativar autenticação por senha via um drop-in desshd_config.dem 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 tailscaleinstala 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áriostailscale/tailscaledem/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 symlinkswants/que o atualizador descarta mais agressivamente. Diferente do SSH, a instalação do Tailscale não exige estritamente entradas ematomic-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 comtailscale up --qr(adicione--login-serverpara Headscale), depois fixetailscale set --auto-updatepara 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:
| Caminho | Conteúdo | Por que persiste |
|---|---|---|
/opt/tailscale/ | Binários tailscale e tailscaled | /opt é bind mount de /home/.steamos/offload/opt — vive em /home |
/etc/systemd/system/tailscale.service | Unit file do systemd | Overlayfs 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.conf | Override apontando o binário em /opt | Idem |
/etc/default/tailscaled | Variáveis PORT e FLAGS | Idem |
/etc/profile.d/tailscale.sh | Adiciona /opt/tailscale/ ao PATH | Idem |
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
tailscaledrodando ou não. O Tailscale SSH é baseado em identidade e roteado pela tailnet independentemente de NAT ou filtragem de porta, mas precisa detailscaledativo e do nó autenticado. Se a autenticação do Tailscale expirar outailscaledtravar, 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 tailscaledetailscale statusdepois de cada atualização do SteamOS. Sesshdaparecer inativo ou não habilitado, a whitelist doatomic-update.conf.dnão aplicou — verifique se/etc/atomic-update.conf.d/sshd-persistent.confexiste e lista tanto o symlink de enable quanto o drop-in de config. Setailscaledestiver inativo, verifiquejournalctl -u tailscaled -b; o binário em/opt/tailscale/tailscaleddeve continuar presente, já que/optpersiste através de atualizações normais. O único caso contra o qual nada disso protege é um reset de fábrica: ele apaga/homepor completo, levando juntoauthorized_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
- tailscale-dev/deck-tailscale — instalador oficial do Tailscale para Steam Deck; instala em
/opt/tailscale/para sobreviver a atualizações do rootfs, com overrides de serviço apontando para esse caminho - Tailscale no Steam Deck (KB) — documentação do Tailscale explicando por que
pacman -S tailscaleé descrito como bomba-relógio e indicando o instalador específico para o deck - Tailscale SSH — cobre a flag
--sshe como o modelo baseado em identidade do Tailscale SSH difere do OpenSSH por chave - sshd_config(5) — referência para as diretivas
PasswordAuthenticationePubkeyAuthenticatione para a diretivaIncludenos diretórios de drop-in config - Headscale — servidor de controle self-hosted do Tailscale;
--login-servernotailscale upaponta para sua instância em vez da infraestrutura do próprio Tailscale