Cebimdeki Claude Code: Uzaktan Erişimi Elle Kurmak
iPhone'dan MacBook'taki Claude Code'a Tailscale, sertleştirilmiş OpenSSH, mosh ve tmux ile — adım adım, elle ve tuzaklarıyla birlikte.
tau, kendim için yaptığım kişisel bir ajan platformu: MacBook'ta çalışan bir hub, onunla da Telegram'dan, terminalden ve sesle konuşuyorum. Katman katman kuruyorum; en alttaki, sıfırıncı katmanın ajanla bir ilgisi bile yok. Tek işi, telefondan Mac'teki Claude Code'u — Anthropic'in terminalde çalışan kodlama ajanını — kullanabilmek. Masadan kalktığımda iş de durmasın diye.
Repoda bunu kuran script'ler hazırdı; tmux ve mosh gibi sudo istemeyen kısım da zaten kuruluydu. Şifre, hesap ve telefon isteyen kalanını yine de script'i çalıştırmadan, elle ve adım adım yaptım: ne yaptığını anlamadığım bir kapıyı sonra savunamam. En çok da yol boyunca çıkan tuzaklardan öğrendim.
Zincir
iPhone'da Termius → Tailscale → Mac'te OpenSSH → mosh → tmux → Claude Code. Her halkanın tek bir işi var.
Yol: Tailscale
Tailscale, cihazları nerede olurlarsa olsunlar aynı yerel ağdaymış gibi
birbirine bağlayan bir VPN; bu özel ağa tailnet deniyor. Router'da port
açmak gerekmiyor. Mac'e brew install --cask tailscale-app ile kurdum, Gizlilik
ve Güvenlik'te sistem uzantısına izin verip giriş yaptım. iPhone'a da kurup
aynı hesapla girdim.
İlk karışıklık hemen geldi: Tailscale'i açtığımı sanıp Termius'u açmışım. Termius, iPhone'da kullandığım SSH istemcisi; SSH de başka bir makinede güvenli bir terminal açmanın standart yolu. İkisini kafamda ayıran benzetme şu oldu: Tailscale yol, Termius araba. Tailscale telefonla Mac arasına bir yol döşüyor ama o yolda kendiliğinden hiçbir şey gitmiyor; Mac'e giden şey Termius.
Yolun gerçekten var olduğunu Mac'ten görmek için:
tailscale ping <telefon>
Cevap via <yerel-ip>:41641 diye geldi: iki cihaz birbirini aynı Wi-Fi'da
bulmuş, trafik doğrudan, eşten eşe gidiyor. via DERP(...) görseydim trafik
Tailscale'in aktarma sunucuları olan DERP üzerinden dolaşıyor olacaktı. Sonra
5G'de de denedim, orada da çalıştı.
Anahtar ve panodaki komut
SSH'ta şifre yerine anahtar çifti kullanıyorum: özel anahtar telefonda kalıyor,
açık (public) anahtar Mac'e gidiyor ve Mac yalnızca o özel anahtarı taşıyanı
içeri alıyor. Termius'ta Keychain → Generate Key ile bir ED25519 anahtarı
ürettim ve public key'i Mac'teki ~/.ssh/authorized_keys dosyasına ekledim:
mkdir -p ~/.ssh && chmod 700 ~/.ssh
echo "$(pbpaste)" >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
Kontrol ettim:
ssh-keygen -l -f ~/.ssh/authorized_keys
# ... is not a public key file.
cat ile baktığımda dosyada anahtar değil, komutun kendisi yazıyordu.
echo 'ssh-ed25519 AAAA… iphone-termius' > ~/.ssh/authorized_keys
Tek >, bozuk satırı da silsin diye. Sondaki iphone-termius bir yorum: SSH
onu umursamıyor, ama telefon kaybolursa hangi satırı sileceğimi bir bakışta
gösteriyor. ssh-keygen -l bu sefer anahtarın parmak izini (fingerprint,
SHA256:…) yazdı.
Kapıyı açmadan önce kilitle
macOS'ta SSH sunucusu, yani OpenSSH'ın sshd servisi, Sistem Ayarları'nda
Uzaktan Giriş (Remote Login) olarak geçiyor. Açmadan önce sertleştirdim; bir
dakikalığına bile varsayılan ayarlarla açık kalmasın istedim.
/etc/ssh/sshd_config içinde Include /etc/ssh/sshd_config.d/* satırı var: o
klasördeki dosyalar alfabetik sırayla okunuyor ve çoğu ayarda ilk görülen
değer kazanıyor. macOS kendi ayarlarını 100-macos.conf dosyasına koyuyor.
Benimki bu yüzden 010-tau.conf: önce okunuyor, sözü önce o söylüyor.
Dosyayı repodaki şablondan sed ile ürettim, install ile yerine koydum:
sed -e 's|__USER__|<kullanıcı>|g' \
-e 's|__TAILNET_V4__|100.64.0.0/10|g' \
-e 's|__TAILNET_V6__|fd7a:115c:a1e0::/48|g' \
infra/remote/sshd/tau.conf > 010-tau.conf
sudo install -m 644 -o root -g wheel 010-tau.conf /etc/ssh/sshd_config.d/010-tau.conf
install, çünkü sshd grubun ya da herkesin yazabildiği bir config dosyasını
kabul etmiyor; install sahibi ve izinleri tek adımda doğru koyuyor. İşin özü
şu satırlar:
PasswordAuthentication no
KbdInteractiveAuthentication no
AuthenticationMethods publickey
PermitRootLogin no
AllowUsers <kullanıcı>@100.64.0.0/10 <kullanıcı>@fd7a:115c:a1e0::/48 <kullanıcı>@127.0.0.1 <kullanıcı>@::1
ClientAliveInterval 30
ClientAliveCountMax 4
AllowAgentForwarding no
AllowTcpForwarding local
- Şifre iki yerden kapanıyor.
PasswordAuthentication notek başına yetmiyor: macOSUsePAM yesile geliyor ve PAM, yani sistemin kimlik doğrulama katmanı, keyboard-interactive yolundan şifreyi yine sorabiliyor.KbdInteractiveAuthentication nobu yüzden şart;AuthenticationMethods publickeyda kapıyı tek yönteme kilitliyor. - Kaynak kısıtı
AllowUsers'ta. macOS'ta sshd'yi launchd (macOS'un servis yöneticisi) her arayüzde başlatıyor veListenAddressetkisiz kalıyor. Kısıt bu yüzden burada: yalnızca benim kullanıcım, yalnızca tailnet'ten ve makinenin kendisinden.100.64.0.0/10, Tailscale'in adres verdiği CGNAT aralığı (internette yönlendirilmeyen, paylaşımlı bir blok);fd7a:115c:a1e0::/48de IPv6 karşılığı. Kafedeki biri port 22'ye ulaşsa bile içeri alınmıyor. - Ölü bağlantılar temizleniyor.
ClientAliveInterval 30×ClientAliveCountMax 4: 30 saniyede bir yoklama, dört cevapsızda kapanış. Uyuyan telefonun bıraktığı yarım bağlantılar ~2 dakikada gidiyor. - Yönlendirme dar. Agent forwarding kapalı; TCP'de yalnızca yerel (
-L) yönlendirme var, Mac'teki bir dev server'a telefondan ulaşmaya yetiyor.
"no hostkeys available"
Kurduktan sonra, Uzaktan Giriş'i açmadan önce test ettim:
sudo sshd -t
# sshd: no hostkeys available -- exiting.
Buradaki anahtar benim anahtarım değil. SSH'ta iki taraf da kimliğini kanıtlıyor. User key benim Mac'e "ben buyum" dememi sağlıyor; Termius'ta ürettiğim buydu. Host key ise sunucunun kimliği: bağlandığım şeyin gerçekten benim Mac'im olduğunu bana kanıtlıyor. Bu Mac'te Uzaktan Giriş hiç açılmamıştı; macOS host key'leri normalde ilk bağlantıda ürettiği için ortada hiç yoktu.
sudo ssh-keygen -A
-A yalnızca eksik host key'leri üretiyor, var olanlara dokunmuyor. Ardından
sshd -t sessizce geçti. Repodaki kurulum script'inde de aynı boşluk vardı;
onu da düzelttim.
Host key'in bir faydası daha var: ilk bağlantıda Termius onun parmak izini gösterip güvenip güvenmediğini soruyor. Körlemesine "evet" demek yerine Mac'teki değerle karşılaştırmak lazım; ikisi aynıysa bağlandığın makine gerçekten o Mac:
ssh-keygen -l -f /etc/ssh/ssh_host_ed25519_key.pub
Uzaktan Giriş'i açmak
sudo systemsetup -setremotelogin on
# ... requires Full Disk Access privileges.
Terminale Tam Disk Erişimi (Full Disk Access) vermek, o terminalde çalışan her şeye bütün diski açmak demek; tek bir ayar için fazla. Uzaktan Giriş'i Sistem Ayarları → Genel → Paylaşma'dan elle açtım.
Açık kalmış iki kapı daha
Asıl sürpriz o Paylaşma ekranındaydı. Uzaktan Giriş'in yanında iki şey daha açıktı:
- Remote Management: Apple Remote Desktop ve ekran paylaşımı; 5900 ve 3283 portları.
- Remote Application Scripting: başka makinelerden gelen Apple Events, yani uygulamaları uzaktan komutla yönetme; 3031 portu.
netstat -an -p tcp | grep LISTEN
Bu portlar * adresinde dinliyordu, yani her arayüzde, kafenin Wi-Fi'ı dahil.
Üstüne macOS giriş şifresini kabul ediyorlar, az önceki sertleştirme onları hiç
kapsamıyor (o yalnızca sshd'nin ayarı) ve güvenlik duvarı kapalıydı, çünkü mosh
UDP ile çalışıyor. Yani aynı kafe Wi-Fi'ındaki herkes bu servislere şifre
deneyebilirdi. İkisini de kapattım; uzaktan erişim kapılarından geriye yalnızca
22 kaldı.
Sağlık kontrolü
doctor.sh, repodaki sağlık kontrolü; hiçbir şeyi değiştirmeden bütün zinciri
yokluyor. En sevdiğim kontrolü şu: kendi makinesine şifreyle girmeyi deniyor ve
sunucu yalnızca Permission denied (publickey) diyorsa şifrenin gerçekten
kapalı olduğuna inanıyor. Config'e "no" yazmakla sunucunun gerçekten reddetmesi
aynı şey değil. Hepsi yeşil yandı.
Araba: Termius ve mosh
Termius'ta yeni bir host:
- Hostname: Mac'in tam MagicDNS adı,
<mac>.<tailnet>.ts.net. MagicDNS, Tailscale'in her cihaza verdiği isim; IP ezberlemek gerekmiyor. - Username: Mac'teki kullanıcı adım.
- Key: Termius'ta ürettiğim anahtar; şifre yok.
- Mosh: açık.
mosh (mobile shell), SSH'ı yalnızca kimlik doğrulamak ve Mac'te mosh-server'ı
başlatmak için kullanıyor; sonrasında oturum UDP üzerinden akıyor. Tek bir TCP
bağlantısına bağlı olmadığı için Wi-Fi'dan 5G'ye geçmek ya da telefonun uyuması
onu öldürmüyor; telefon uyanınca ekran kaldığı yerden devam ediyor.
tmux: oturum Mac'te yaşar
mosh bağlantıyı dayanıklı yapıyor, ama bağlantıyı tamamen kapatırsam terminal de, içinde çalışan Claude Code da gidiyor. tmux, Mac'te bağlantıdan bağımsız yaşayan bir terminal oturumu: ben kopsam da içindekiler çalışmaya devam ediyor, geri bağlanınca aynı ekrana dönüyorum.
Repodaki tau-attach özünde tek satır — tau adlı tmux oturumuna bağlan, yoksa
oluştur:
exec tmux new-session -A -s tau
Önce telefondan elle yazdım, ekranın altında tmux'un durum çubuğu belirdi. Sonra
Termius'ta host'un Startup Snippet alanına tau-attach yazdım; artık her
bağlantı doğrudan çalışan oturuma düşüyor.
Test
5G üzerinden bağlandım, claude yazdım. Yeniden giriş istemedi. (SSH
oturumunda macOS'un şifre kasası keychain kilitli görünür de Claude Code giriş
isterse diye README'de claude setup-token ile kalıcı bir token yolu yazılı;
gerek kalmadı.)
Asıl test: telefonu kilitleyip iki dakikadan fazla beklemek, yani sshd'nin ölü bağlantıları temizlediği süreden uzun. Döndüğümde aynı ekran. Sonra bağlantıyı tamamen kapatıp yeniden bağlandım: yine aynı, hâlâ çalışan Claude oturumu.
Tek eksik: uyku
Kapak kapanınca Mac uyuyor ve uyuyan Mac'e hiçbir şey ulaşamıyor. Şimdilik bir
launchd ajanı caffeinate -s çalıştırıyor: caffeinate macOS'un uykuyu
engelleyen komutu, -s ile de yalnızca şarjdayken geçerli; pildeyken ve kapak
kapalıyken Mac yine uyuyor. Bu işi ileride tau'nun menü çubuğunda yaşayacak
tray uygulaması TauBar devralacak.
Güvenlik modeli
- Ağ: router'da açık port yok; Mac'e giden yol tailnet'ten geçiyor.
- Kim, nereden: yalnızca benim kullanıcım, yalnızca tailnet ve localhost'tan
(
AllowUsers). - Nasıl: yalnızca anahtar; şifre, keyboard-interactive (PAM) ve root girişi kapalı.
- Ne kadar: agent forwarding kapalı, TCP yönlendirmede yalnızca
-L. - Başka kapı yok: güvenlik duvarı mosh için kapalı olduğundan sshd dışında dinleyen her servis açıkta demek; Remote Management ve Remote Application Scripting bu yüzden kapalı.
- Telefon kaybolursa:
authorized_keys'teniphone-termiussatırını sil, cihazı Tailscale yönetim panelinden çıkar.
Nasıl doğrularsın
tailscale ping <telefon>cevap veriyor:via <ip>:<port>doğrudan,via DERP(...)aktarmalı bağlantı demek.ssh-keygen -l -f ~/.ssh/authorized_keysher satır için bir parmak izi yazıyor, "is not a public key file" demiyor.sudo sshd -thiçbir şey yazmadan dönüyor.ssh -o BatchMode=yes -o PubkeyAuthentication=no <kullanıcı>@127.0.0.1 trueyalnızcaPermission denied (publickey)diyor.netstat -an -p tcp | grep LISTENçıktısında 5900, 3283 ve 3031 yok.- Termius'un ilk bağlantıda gösterdiği host key parmak izi,
ssh-keygen -l -f /etc/ssh/ssh_host_ed25519_key.pubçıktısıyla aynı. - Telefondan bağlan,
claudebaşlat, telefonu iki dakikadan fazla kilitle: aynı ekran. Bağlantıyı tamamen kapatıp yeniden aç: yine aynı oturum.
Sıfırıncı katman bu kadar. Artık Mac başında değilken de Claude Code'la çalışabiliyorum; sırada ajanın kendisi var.
Kendin kurmak istersen: Telefondan Claude Code: Sıfırdan Kurulum Rehberi.