SSD Nodes Learn 🎉 VPS $5.50/aydan başlayan
Rehberler Matt ConnorYazan Matt Connor

SSH bağlantısı kopunca komutun çalışmaya devam etmesi

SSH bağlantısı kesildiğinde SIGHUP sinyali nedeniyle işlemleriniz sonlanır. Süreçleri arka planda tutmak için nohup, disown, tmux veya systemd-run yöntemlerini kullanın.

SSH bağlantısı koptuğunda komutunuz neden sonlanır

Bir komutun SSH bağlantısı kesildikten sonra çalışmaya devam etmesi için, komutun "hangup" sinyalinin ulaşamayacağı bir yere taşınması gerekir. Aşağıdaki her yöntem bunu sağlamak için farklı bir mekanizma kullanır; bu nedenle işe mekanizmayı anlamakla başlayın.

Oturumunuz, sunucuda oturumunuz için sshd tarafından oluşturulan bir pty (sözde terminal) üzerinde çalışır. Bu, kabuğunuzun ve o kabuktan başlattığınız her komutun kontrol terminalidir. Bu yolun geri kalanını merak ediyorsanız, SSH oturum açtığınızda neyi yapılandırır bölümü bunu açıklar. TCP bağlantısı koptuğunda, sshd kendi tarafını kapatır ve pty yok edilir. Çekirdek bunu terminalin kapanması olarak değerlendirir ve terminalin ön plan süreç grubuna ve oturum lideri olan kabuğunuza SIGHUP sinyali gönderir. SIGHUP sinyali için varsayılan işlem süreci sonlandırmaktır. Komutunuz ön plan süreç grubunda olduğu için komutunuz sonlanır.

Arka plan işleri de güvende değildir. & ile başlatılan bir iş kendi süreç grubunda yer alır, bu nedenle çekirdek ona doğrudan sinyal göndermez. Ancak Bash bunu yapar. Etkileşimli bir bash, SIGHUP sinyalini aldığında, çıkış yapmadan önce tablosundaki her işe SIGHUP sinyalini yeniden gönderir. Sizin açınızdan sonuç aynıdır: iş kaybolur ve günlük dosyası satırın ortasında durur.

Burada insanları karıştıran bir asimetri vardır. exit tuşlarına basmak arka plan işlerinizi sonlandırmaz; çünkü bash bunu yalnızca huponexit seçeneği ayarlandığında yapar ve bu seçenek varsayılan olarak kapalıdır. Ancak kopan bir bağlantı onları sonlandırır. Terminali nazikçe kapattığınızda hayatta kalan iş, Wi-Fi koptuğunda yine de ölebilir.

Bundan iki sonuç çıkar ve konunun tamamı bunlardır. SIGHUP sinyalini görmezden gelen veya hiçbir kontrol terminali olmayan bir süreç sonlandırılmaz. Standart çıktısı hala yok edilmiş pty'ye yönlendirilmiş bir süreç ise yazacak yer bulamaz: yazma işlemi EIO (girdi/çıktı hatası) ile başarısız olur ve çoğu program bu noktada çıkar. Her iki sorunu da çözmeniz gerekir. Birçok tarif yalnızca ilkini çözer; bu yüzden insanlar "nohup çalışmadı" şeklinde geri bildirimde bulunur.

Bağlantınız günde birkaç kez kopuyorsa, bunu da düzeltin. ~/.ssh/config içindeki ServerAliveInterval 60 ayarı, boşta kalan bir oturumun yol üzerindeki bir NAT (ağ adresi çevirisi) zaman aşımı nedeniyle düşürülmesini engeller. Hiç açılmayan bir oturum, farklı nedenleri olan başka bir hatadır ve bu noktada connection refused ile connection timed out arasındaki fark önem kazanır.

SSH bağlantısı kesildikten sonra bir komutun çalışmaya devam etmesi için hangi yöntem kullanılır?

İşin ciddiyetine göre sıralanmış dört yanıt:

  • nohup veya setsid: Şimdi başlattığınız ve daha sonra log dosyasını okuyacağınız tek seferlik bir işlem. Çıktıyı kendiniz yönlendirirsiniz.
  • disown: Zaten başlattığınız ve korumayı unuttuğunuz bir iş. Süreci kurtarır ancak çıktıları size geri veremez.
  • tmux veya screen: Birkaç gün boyunca izlemeniz, kesintiye uğratmanız ve geri dönmeniz gereken işler.
  • systemd-run veya gerçek bir unit dosyası: Altı saatlik bir rsync veya gece boyu süren veritabanı içe aktarma işlemi gibi, oturumunuzdan daha uzun süre yaşaması gereken her şey.

Unutulmaması gereken kural şudur: Eğer işi unutmak bir sorun yaratacaksa, o iş tmux'a değil systemd'ye aittir. Bir tmux penceresi, bir insanın hatırlaması gereken bir şeydir. Bir unit dosyasının ise bir adı, durumu, log kaydı ve bir sonraki kişinin kendisine söylenmesine gerek kalmadan bulabileceği bir yeniden başlatma politikası vardır.

nohup ve setsid: başlatın ve uzaklaşın

nohup ./import.sh > ~/import.log 2>&1 &
echo $! > ~/import.pid

nohup, SIGHUP sinyalinin işlenmesini yok sayacak şekilde ayarlar ve ardından komutunuzu çalıştırır; böylece çekirdeğin gönderdiği "hangup" sinyali hiçbir etki yaratmaz. Yönlendirme işlemini sizin yapmanız gerekir. Standart çıktıyı terminale yönlendirilmiş halde bırakırsanız, nohup bunu sizin yerinize mevcut dizindeki nohup.out dosyasına yönlendirir, bu dosya yazılamazsa $HOME/nohup.out dosyasına yazar ve şu çıktıyı verir:

nohup: ignoring input and appending output to 'nohup.out'

Bu dosyayı takip etmek zor olabileceğinden, isimlendirmeyi kendiniz yapın. $!, arka plana atılan son işin PID (süreç tanımlayıcı) değerini tutar; bunu kaydetmek, sisteme tekrar giriş yaptığınızda işin durumunu kontrol edebilmenizi sağlar.

setsid, aynı soruna farklı bir açıdan yaklaşır. Komutu, denetleyici terminali olmayan yeni bir oturumda çalıştırır; böylece süreci sonlandırabilecek hiçbir terminal mevcut olmaz.

setsid --fork ./import.sh > ~/import.log 2>&1

--fork kullanın. Bu bayrak olmadan setsid, süreç zaten bir süreç grubu lideri değilse (bir kabuk betiği içinde olduğu gibi) setsid() çağrısını yerinde yapar ve betiğiniz orada takılı kalır. --fork ile betik içinde ve komut satırında aynı davranış elde edilir.

Elde ettiğiniz sonucu kontrol edin:

ps -o pid,ppid,sid,tty,stat,cmd -p "$(cat ~/import.pid)"

? çıktısındaki TTY sütununun boş olması, sürecin denetleyici bir terminale sahip olmadığını ve hiçbir şeyin onu sonlandıramayacağını gösterir. nohup altında TTY sütunu, siz bağlı kaldığınız sürece pts/0 gibi bir değer gösterir ve pty yok edildiğinde ? değerine dönüşür. Her iki sonuç da sürecin sağlıklı olduğunu ve işin hayatta kaldığını gösterir.

disown: halihazırda başlattığınız bir işi kurtarma

Ön planda iki saat sürecek bir iş başlattınız ve ardından bu sorunu hatırladınız. İşlemi sonlandırıp yeniden başlatmayın.

# press Ctrl-Z to suspend the job first
bg
jobs -l
disown -h %1

Ctrl-Z işi askıya alır, bg arka planda devam ettirir ve jobs -l iş numarasını PID'sinin yanında görüntüler. disown -h %1, bash'in o işe SIGHUP göndermemesi için işi işaretler. Yalın disown %1 ise işi bash'in tablosundan tamamen düşürür; bu işlem hangup sinyali açısından aynı etkiyi yaratsa da, jobs artık işi listelemez.

disown komutunun yapamadığı şey çıktıyı taşımaktır. Süreç, standart çıktı olarak pty'yi tutmaya devam eder ve pty kapandığında bir sonraki yazma işlemi EIO hatası döndürür. Bu nedenle disown, bir dosyaya yazan derleme işlemi gibi sessiz işleri güvenilir bir şekilde kurtarırken, çok fazla çıktı üreten işleri genellikle kaybeder. İş ya yazdıracak yeri olmadan hayatta kalır ya da bir sonraki çıktı satırında sonlanır.

Dosya tanımlayıcıları için bir kurtarma aracı mevcuttur. reptyr, çalışan bir süreci mevcut terminalinize taşır: sudo apt install -y reptyr ile kurun ve ardından bir tmux penceresi içinden reptyr <pid> komutunu çalıştırın. Bu araç ptrace üzerinden çalışır. Ubuntu, yalnızca kendi alt süreçlerinizi izlemenize izin veren kernel.yama.ptrace_scope = 1 ile gelir; bu nedenle devraldığınız bir süreç için sudo reptyr <pid> gerekir. Bunu acil durum aracı olarak değerlendirin. Rutin işlerinizde kullanmayın.

tmux: izlemeniz ve geri dönmeniz gereken işler

tmux (terminal çoğullayıcı), sorunu farklı bir noktada çözer. Sürecinizi pty'den korumak yerine, ona SSH oturumunuza ait olmayan bir pty sağlar. tmux sunucusu, bu oturumun dışında çalışır ve içindeki her şeyin terminallerine sahip olur. SSH bağlantınız, yalnızca ona bağlı bir görüntüleyicidir. Bağlantıyı kesseniz dahi sunucu bunu fark etmez.

sudo apt update && sudo apt install -y tmux
tmux new -s import

İşi bu pencerede başlatın, ardından ayırmak için Ctrl-b tuşuna ve hemen ardından d tuşuna basın. Daha sonra tekrar giriş yapın ve kaldığınız yerden devam edin:

tmux ls
tmux attach -t import

tmux ls komutu, import: 1 windows ile başlayan bir satır yazdırmalıdır. Eğer no server running on /tmp/tmux-1000/default çıktısını veriyorsa, bağlanılacak bir oturum yoktur; bunun nedeni ya oturumun hiç oluşturulmamış olması ya da bir şeyin sunucuyu sonlandırmış olmasıdır.

screen, farklı bir tuş kombinasyonuyla aynı işi yapar. screen -S import bir oturum oluşturur, Ctrl-a ve ardından d ise oturumdan ayırır. screen -ls mevcut olanları listeler, screen -r import ise birini geri getirir. Her iki araç da bu iş için uygundur. Ayırma tuşu, insanların genellikle unuttuğu kısımdır.

Çoğullayıcı, sinyal kesintilerinden etkilenmemesi gereken etkileşimli çalışmalar için de doğru yerdir. Claude Code'u bir VPS üzerinde tmux içinde çalıştırmak bu yüzden standart kurulumdur ve bir sunucu oturumunu telefondan yönetmek işlemini, birkaç dakikada bir yeniden bağlanan mobil ağlarda kullanılabilir kılan şey budur.

systemd-run: işi PID 1'e devretme

Sizden hiçbir şekilde bağımlı olmaması gereken bir iş için, işi init sistemine devredin.

sudo systemd-run --unit=bigsync --collect /usr/bin/rsync -aH --stats /srv/data/ /mnt/backup/

Bu komut, bigsync.service adında geçici bir servis birimi oluşturur. Bu birim kendi cgroup yapısına sahip olur, kontrol terminali bulunmaz ve oturumunuzla hiçbir ilişkisi kalmaz. Komut hemen döner ve Running as unit: bigsync.service çıktısını verir. İşlemi şu komutlardan biriyle izleyebilirsiniz:

systemctl status bigsync
journalctl -u bigsync -f

--collect, systemd'ye birim tamamlandığında (başarısız olsa dahi) onu kaldırmasını söyler. Bu bayrak kullanılmazsa, başarısız olan geçici birim yüklü kalır ve adı meşgul edilir; bu durumda bir sonraki çalıştırma, birimin zaten var olduğuna dair bir hata ile başarısız olur. Çıktı, her satırda zaman damgası olacak şekilde journal içine yazılır. Journal kayıtlarının yeniden başlatma sonrasında da kalıcı olması için /var/log/journal dizininin var olması gerekir; bu nedenle kalıcılık istiyorsanız sudo mkdir -p /var/log/journal komutunu çalıştırın ve systemd-journald servisini yeniden başlatın.

Normal bir kullanıcı olarak, sudo bayrağı olmadan systemd-run komutunu çağırmak, polkit üzerinden yetkilendirme ister ve ==== AUTHENTICATING FOR org.freedesktop.systemd1.manage-units === çıktısını verir. Sistem birimleri için sudo bayrağını kullanın.

İşi kendi kullanıcı yöneticiniz altında da çalıştırabilirsiniz:

systemd-run --user --unit=bigsync --collect /usr/bin/rsync -aH /srv/data/ /mnt/backup/

Burada bir tuzak vardır. Kullanıcı bazlı yöneticiniz olan user@1000.service, normalde son oturumunuz kapandığında durur ve tüm kullanıcı birimlerini de beraberinde kapatır. Kalıcılığı (lingering) bir kez etkinleştirin:

loginctl enable-linger "$USER"
loginctl show-user "$USER" --property=Linger

İkinci komut Linger=yes çıktısını vermelidir. Kalıcılık etkinleştirildiğinde, kullanıcı yöneticiniz sistem açılışında başlar ve oturum açıp açmadığınıza bakılmaksızın çalışmaya devam eder. Bu ayar olmadan, systemd-run --user komutu size nohup komutuna kıyasla bir avantaj sağlamaz.

systemd-run --scope farklı bir konudur. Komutu ön planda, terminalinize bağlı olarak çalıştırır; bu nedenle burada bir faydası olmaz.

Birden fazla kez çalıştıracağınız her iş için, her seferinde geçici bir birim yazmak yerine birimi kalıcı olarak kaydedin.

Tekrar çalıştıracağınız bir iş için kalıcı bir birim
[Unit]
Description=Nightly data sync
Wants=network-online.target
After=network-online.target

[Service]
Type=oneshot
User=deploy
WorkingDirectory=/srv/data
ExecStart=/usr/local/bin/nightly-sync.sh

Bunu /etc/systemd/system/nightly-sync.service olarak kaydedin, sudo systemctl daemon-reload komutunu çalıştırın, ardından sudo systemctl start nightly-sync ile başlatın ve journalctl -u nightly-sync ile durumunu kontrol edin. İşin isteğe bağlı değil de bir zaman çizelgesine göre çalışması gerekiyorsa, buna uygun bir .timer dosyası ekleyin.

Bir systemd servis birimi ve zamanlayıcısı yazma konusu, dosya biçimini ve zamanlama sözdizimini tüm detaylarıyla ele alır.

Çıktının nereye gittiği ve neden kaybolduğu

Yönlendirmelerin sırası önemlidir. > file 2>&1, standart çıktıyı dosyaya yönlendirir ve ardından standart hatayı aynı yere işaret eder. 2>&1 > file ise bunu tersinden yapar: standart hata terminale gitmeye devam eder ve terminal, kaybolmak üzere olan şeydir. Bash, her iki akış için de &> file kullanımını destekler.

İkinci sürpriz ise tamponlamadır (buffering). Standart çıktı bir terminal olduğunda, C kütüphanesi her satırı anında yansıtır (flush). Standart çıktı bir dosya olduğunda ise birkaç kilobaytlık bir blok tamponuna geçer; bu nedenle tail -f ~/import.log dakikalarca hiçbir şey göstermez ve iş askıda kalmış gibi görünür. Satır bazlı tamponlamayı zorunlu kılmak için stdbuf -oL ./import.sh > ~/import.log 2>&1 kullanın veya programın kendi anahtarını, örneğin python3 -u veya grep --line-buffered, tercih edin.

Şu kalıptan kaçının:

nohup ./import.sh 2>&1 | tee ~/import.log &

nohup yalnızca import.sh komutunu korur, başka hiçbir şeyi değil. tee aynı işlem hattındaki (pipeline) ayrı bir süreçtir ve bağlantı kesildiğinde sonlanmaya devam eder. Bu durumda import.sh, okuyucusu olmayan bir boruya (pipe) yazmaya çalışır, bu yüzden SIGPIPE sinyalini alır ve durur. Tüm işlem hattını setsid bash -c '...' içine alın veya doğrudan dosyaya yazıp yeniden bağlandığınızda üzerinde tail -f çalıştırın.

Özellikle rsync için bir detay daha bulunmaktadır. --info=progress2, terminalde doğru görünen ancak günlük dosyasında veya journal içinde tek bir devasa satıra dönüşen bir satır başı (carriage return) akışı yazar. Gözetimsiz bir çalışma için bu seçeneği kaldırın ve yerine --stats kullanın.

Shell ortamında çalışan bir işin systemd veya cron altında başarısız olma nedeni

Etkileşimli kabuğunuz /etc/profile, ~/.profile ve ~/.bashrc dosyalarını okur; bu sayede PATH değişkenlerinize, sürüm yöneticisi shims yapılandırmanıza ve dışa aktarılan değişkenlerinize erişebilir. Bir systemd birimi bunların hiçbirini okumaz. Cron da bunları okumaz: Debian ve Ubuntu üzerinde cron, işleri SHELL=/bin/sh ve PATH=/usr/bin:/bin ile çalıştırır.

systemd altındaki belirti, (code=exited, status=203/EXEC) hatası veren systemctl status raporudur; bu, yolun yanlış olması veya dosyanın çalıştırılabilir olarak işaretlenmemiş olması nedeniyle systemd'nin dosyayı hiçbir şekilde yürütemediği anlamına gelir. Cron altında ise bu durum genellikle yerel posta yoluyla iletilen veya posta sistemi kurulu değilse hiçbir yere iletilmeyen command not found hatasıdır.

Tahmin yürüterek zaman kaybetmeden önce ortamın ne olduğunu kanıtlayın:

sudo systemd-run --collect --wait --unit=envtest /usr/bin/env
journalctl -u envtest --no-pager

Bu komut, işinizin çalışacağı tam ortamı yazdırır. Ardından aradaki farkı giderin. Kendi dosyalarınız için mutlak yollar (absolute paths) kullanın; çünkü systemd, yalın bir rsync ifadesini kabuğunuzun PATH değişkenine göre değil, sabit bir sistem yol listesine göre çözümler. İhtiyacınız olan değişkenleri komut satırında -p Environment="KEY=value" ile veya bir birim dosyasında EnvironmentFile=/etc/default/myjob ile geçirin. Bir iş gerçekten oturum açma ortamınıza ihtiyaç duyuyorsa, onu /bin/bash -lc 'my-command' olarak çalıştırın ve işin artık dotfile dosyalarınıza bağımlı olduğunu kabul edin.

Ayrılmış bir işi hala ne sonlandırır

  • Yeniden başlatma. tmux ile ilgili hiçbir şey yeniden başlatma sonrasında kalıcı olmaz; çünkü sunucu sıradan bir süreçtir ve oturumlar onun bellek içi durumudur. Çekirdek güncellemeleri yeniden başlatma gerektirir, bu nedenle kolayca yeniden başlatamayacağınız bir işi systemctl enable biriminde çalıştırmalısınız.
  • Bellek yetersizliği (OOM) sonlandırıcısı. dmesg -T | grep -i 'killed process', seçtiği sürecin adı da dahil olmak üzere bu durumu gösterir. Küçük bir VPS üzerinde yapılan büyük bir içe aktarma işlemi, sık karşılaşılan bir hedeftir.
  • logind temizliği. Eğer /etc/systemd/logind.conf üzerinde KillUserProcesses=yes ayarlanmışsa, son oturumunuz kapandığında geride kalan süreçleriniz sonlandırılır; buna tmux sunucusu da dahildir. Mevcut ayarı loginctl show --property=KillUserProcesses ile kontrol edin ve kullanıcınızı loginctl enable-linger "$USER" ile muaf tutun.
  • Dolu disk. İş, siz ayrıldığınız için değil, yönlendirdiğiniz günlük dosyası dosya sistemini doldurduğu için durur. Sinyali suçlamadan önce df -h komutunu çalıştırın.

SSH üzerinden bağlantıyı korumadan bir iş başlatma

ssh vps 'sudo systemd-run --unit=import --collect /usr/local/bin/import.sh'

systemd-run, birim başlatılır başlatılmaz geri döner; bu nedenle ssh komutu da sonlanır ve işin, onu başlatan oturumla herhangi bir bağlantısı kalmaz. Bu, temiz olan yöntemdir.

nohup sürümü daha fazla dikkat gerektirir:

ssh vps 'nohup ./import.sh > ~/import.log 2>&1 < /dev/null &'

Yönlendirmeler yapılmadığında, bu komut asılı kalmış gibi görünür. sshd, uzak komutun standart çıktısı veya standart hatası herhangi bir süreç tarafından kullanılmaya devam edildiği sürece kanalı açık tutar; arka plana atılan bir iş de her ikisini devralır. Yalnızca nohup kullanmak sorunu çözmez, çünkü nohup yalnızca çıktı bir terminal olduğunda yönlendirme yapar; burada ise çıktı, istemcinize geri dönen bir boru hattıdır (pipe). < /dev/null eklemek, girdi tarafını da kapatır. ssh -n ise aynı işlemi istemci tarafında gerçekleştirir.

FAQ

SSH bağlantısı koptuğunda komutum neden duruyor?

Oturumunuzun kullandığı pty (sözde uçbirim) yok edilir ve çekirdek, o uçbirimdeki ön plan süreç grubuna SIGHUP sinyali gönderir. SIGHUP sinyali için varsayılan eylem süreci sonlandırmaktır. Arka plan işleri de sonlanır; çünkü bash, çıkış yapmadan önce tablosundaki her işe SIGHUP sinyali gönderir. SIGHUP sinyalini görmezden gelen, nohup ile başlatılan veya systemd birimi gibi oturumunuzu hiç paylaşmayan bir komut bu durumdan etkilenmez.

Altı saat sürecek bir rsync işlemi için tmux mı yoksa systemd-run mı daha iyidir?

systemd-run. Bir tmux oturumu, başlattığınız bir sunucu sürecine bağlıdır; bu nedenle yeniden başlatma ile sona erer ve tmux ls komutunu çalıştırmayı bilmeyen biri için görünmezdir. sudo systemd-run --unit=bigsync --collect /usr/bin/rsync -aH --stats /srv/data/ /mnt/backup/ çalıştırmak, durum için systemctl status bigsync ve çıktı için journalctl -u bigsync sağlar; her ikisi de bir sonraki yönetici tarafından herhangi bir bildirim olmaksızın bulunabilir. Ekranı izlemeniz ve içine veri girmeniz gereken işler için tmux kullanın.

Yönlendirmeyi unuttuğum bir işin çıktısını nasıl görebilirim?

Genellikle göremezsiniz, çünkü o çıktı artık var olmayan bir uçbirime gitmiştir. Süreç hala çalışıyorken sudo ls -l /proc/<pid>/fd ile açık dosyalarını inceleyebilir veya sudo strace -p <pid> ile sistem çağrılarını izleyebilirsiniz, ancak daha önce yazılmış metin kaybolmuştur. reptyr <pid> süreci yeni bir uçbirime taşıyabilir; ancak Ubuntu'nun kernel.yama.ptrace_scope = 1 özelliği, kendi alt süreciniz olmayan bir süreç için sudo yetkisi gerektirir. Tüm bunlardan kaçınmanın yolu, başlangıçta bir dosyaya yönlendirme yapmak ve o dosyayı tail -f komutuyla izlemektir.

Ayrılmış (detached) bir tmux oturumu yeniden başlatmadan sonra hayatta kalır mı?

Hayır. tmux sunucusu sıradan bir süreçtir ve oturumlar onun bellek içi durumudur; bu nedenle yeniden başlatma her ikisini de sonlandırır. Ayrıca /etc/systemd/logind.conf, KillUserProcesses=yes değerini ayarladığında ve son oturumunuzdan çıkış yaptığınızda da ölür; loginctl enable-linger "$USER" bunu engeller. Yeniden başlatma sonrasında kendiliğinden geri gelmesi gereken işler için bir systemd birimi yazın ve systemctl enable ile etkinleştirin.

Betiğim kabukta çalışıyor ancak systemd birimi olarak neden başarısız oluyor?

Bir birim /etc/profile veya ~/.bashrc dosyalarını okumaz; bu nedenle ne PATH eklemelerinize ne de dışa aktarılan değişkenlerinize sahiptir. (code=exited, status=203/EXEC) hatasını gösteren systemctl status, systemd'nin dosyayı hiç çalıştıramadığı anlamına gelir; bu yüzden mutlak bir yol (absolute path) kullanın ve çalıştırılabilir bitini kontrol edin. sudo systemd-run --collect --wait --unit=envtest /usr/bin/env komutunu çalıştırın, çıktısını journalctl -u envtest ile okuyun; işinizin aldığı tam ortamı göreceksiniz. Eksik olan her şeyi Environment= veya EnvironmentFile= ile sağlayın.

#ssh#tmux#nohup#systemd#long-running-jobs