← terug naar artikelen

De helft van mijn tijd programmeer ik op mijn telefoon, met Termius en tmux

TLDR

Sinds kort programmeer ik de helft van mijn tijd op mijn telefoon, in exact dezelfde tmux-sessie die op mijn Mac draait. Je leest hoe je die opzet, en waarom je de tmux-server bij je GUI-login moet laten starten en niet vanuit je SSH-login.

Sinds kort programmeer ik (als ik thuis werk) 50% van mijn tijd op mijn mobiel met Termius. Vanaf de bank, mijn tuin, mijn bed, you name it open ik de SSH-client op mijn telefoon en kijk naar exact hetzelfde scherm dat ik op mijn Mac achterliet. Claude Code draait door, en ik tik een vervolgprompt alsof ik nooit ben opgestaan.

Ik gebruik hiervoor één gedeelde tmux-sessie, die werkt doordat ik een paar instellingen op de juiste plek heb gezet.

Eén sessie verdeeld over meerdere schermen

tmux houdt je shell in leven los van wie er kijkt. Sluit je het venster, dan blijft de sessie lopen; open je ‘m ergens anders, dan zie je dezelfde panes weer terug. De sessie draait op mijn laptop, die is de host. Ik noem ‘m termius.

Lokaal stap ik erin met een alias die ik heb gedefinieerd in mijn .zshrc. De -A attacht aan een bestaande sessie, of maakt ‘m aan:

alias termius='tmux new -A -s termius'

Vanaf mijn telefoon wil ik niet in een verse shell landen maar meteen in die sessie. Dat regelt dit blok in mijn .zshrc:

# Auto-attach aan de tmux-sessie "termius", alleen bij een login vanaf mijn telefoon,
# niet als we al in tmux zitten, en alleen interactief.
if [[ -n $TERMIUS && -z $TMUX && $- == *i* ]]; then
  tmux has-session -t termius 2>/dev/null || {
    launchctl kickstart "gui/$(id -u)/tmux-termius"
    sleep 0.5
  }
  exec tmux attach -t termius
fi

De exec vervangt de login-shell door tmux, zodat een detach me uitlogt in plaats van me in een kale shell achter te laten. De $TERMIUS-check en de launchctl kickstart leg ik hieronder uit, daar zit het echte werk.

De Mac setup

Je hebt vier dingen nodig: tmux, een SSH-ingang, de sessie die op het juiste moment start, en een manier om te weten dát een login van je telefoon komt.

brew install tmux
sudo systemsetup -setremotelogin on   # of: Systeeminstellingen -> Delen -> Externe login

De sessie laat ik bij mijn GUI-login starten met een LaunchAgent in ~/Library/LaunchAgents/tmux-termius.plist, zodat er altijd een termius-sessie is:

<key>ProgramArguments</key>
<array>
  <string>/opt/homebrew/bin/tmux</string>
  <string>new-session</string>
  <string>-d</string>
  <string>-s</string>
  <string>termius</string>
</array>
<key>EnvironmentVariables</key>
<dict>
  <key>SHELL</key>
  <string>/bin/zsh</string>
</dict>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/tmux-termius.plist

KeepAlive herstart ‘m als de server sterft doordat ik mijn laatste venster sluit. De SHELL-key zorgt ervoor dat zsh wordt gebruikt in plaats van de standaard sh shell.

Die $TERMIUS-var zet de app zelf niet; ik heb ‘m gekoppeld aan de SSH-key van mijn telefoon in ~/.ssh/authorized_keys:

environment="TERMIUS=1" ssh-ed25519 AAAA...jouwkey... telefoon

En sshd moet die var doorlaten, scherp afgebakend in een drop-in /etc/ssh/sshd_config.d/100-termius.conf:

PermitUserEnvironment TERMIUS

Alleen een login met die key krijgt TERMIUS=1, dus de auto-attach opent de tmux-sessie alleen bij een login vanaf mijn telefoon, en nooit per ongeluk lokaal.

Op de telefoon voeg je in Termius een host toe met het lokale IP van je Mac, genereer je een SSH-key en zet je de publieke helft in die authorized_keys. Ik verbind bewust alleen binnen mijn eigen netwerk, om te voorkomen dat ik altijd en overal aan het prompten ben.

Zorgen dat je de juiste rechten hebt

Het grootste gedoe zat in waar de tmux-server leeft. Eerst liet ik de sessie aanmaken vanuit de SSH-login. Werkte prima, tot Claude Code me steeds om /login vroeg en mijn git-push faalde met een permission denied.

Start je de server vanuit een SSH-login, dan zit die in een security-sessie met een gelockte login-keychain, en alles wat je daarin start erft dat. Claude Code komt niet bij zijn credentials, en er is geen GUI-ssh-agent, dus git-over-SSH heeft geen sleutel.

Vandaar dat LaunchAgent opzet, en vandaar de launchctl kickstart in het zshrc-blok. Die zorgt ervoor dat de server altijd draait als ik via de app binnen kom.

Twee dingen die je daarna nog wilt fixen

Mijn Mac-scherm kromp zodra mijn telefoon meekeek, want tmux schaalt naar de kleinste client. Eén regel in ~/.tmux.conf:

set -g window-size latest

En als laatste: een SSH-verbinding op een telefoon valt zomaar weg (scherm op slot, van wifi naar 4G). tmux vangt dat op, je detacht en de sessie draait door. Om de drops te beperken laat ik sshd de verbinding actief houden:

ClientAliveInterval 60

Na een wijziging in de sshd-config moet je ‘m herladen, anders draai je nog op de oude:

sudo launchctl kickstart -k system/com.openssh.sshd

Een productiviteitsboost

Ik zat de laatste tijd te vaak onnodig naar mijn scherm te staren terwijl agents aan het werk waren. Nu kan ik ondertussen andere dingen doen. En een klein scherm beperkt je minder dan je denkt, dat merkte ik toen ik ziek was. Ik heb vanuit mijn bed een volledige Mac-app voor mijn PR’s ontwikkeld, van eerste idee tot iets dat ik nu echt gebruik, met alleen mijn telefoon in mijn handen.

Hands-off werkt het pas echt in combinatie met claude-auto-mode; op een telefoon wil je niet om de tien seconden een permissie-prompt wegtikken. Voor een bedrijf dat op agents leunt is dat het punt: de bottleneck is niet meer waar de developer zit, maar hoeveel taken je tegelijk kunt laten lopen en losjes kunt bewaken.

Ik pak nog steeds het liefst mijn laptop voor het echte werk. Maar dat een taak doorloopt terwijl ik andere dingen aan het doen ben en ik ‘m vanaf mijn telefoon bijstuur, scheelt me elke dag een paar context-switches.

Eén keer per maand een mailtje met wat ik schreef. Geen spam, geen gedoe.