tmux autostart (boot + Display)
tmux Orchestrator (boot + Display) – Installation auf neuem Server
Diese Anleitung richtet pro Benutzer einen systemd-Userdienst ein, der:
- beim Boot sofort
tm0startet (ohne DISPLAY), - anschließend in einer Schleife auf die Anzeige wartet (Xauth-Cookie), dann DISPLAY/XAUTH/XDG_RUNTIME_DIR in den tmux-Server injiziert,
- und danach die in
~/.config/tmux/autostart.confdefiniertendisplay|…-Sessions/Fenster vorbereitet/ausführt.
Der Orchestrator startet Kommandos in der bestehenden zsh, sodass Ctrl+C nur das Programm beendet. Ein cd … im Command wirkt persistent.
0) Voraussetzungen
sudo apt update
sudo apt install -y tmux zsh xauth x11-xserver-utils
# (optional) setxkbmap ist in x11-xserver-utils enthalten
Falls der Benutzer nicht jj heißt, ersetze ihn in den folgenden Befehlen entsprechend. Die Inhalte der Dateien bleiben gleich.
1) Benutzer jj einrichten
1.1 Linger aktivieren (User-Manager auch ohne Login)
sudo loginctl enable-linger jj
1.2 Verzeichnisse anlegen
sudo -u jj mkdir -p /home/jj/.config/systemd/user /home/jj/.config/tmux
1.3 Service-Datei (User-Unit)
/home/jj/.config/systemd/user/tmux-autostart.service
[Unit]
Description=Start/refresh tmux for jj (boot + wait for display, fixed socket)
[Service]
Type=simple
# Delay, bis /run/user/<uid> steht
ExecStartPre=/bin/sleep 10
# Sockets mit sicheren Rechten vorbereiten
ExecStartPre=/usr/bin/install -d -m 0700 -o %u -g %u %t
ExecStartPre=/usr/bin/install -d -m 0700 -o %u -g %u %t/tmux-%u
# alten Default-Socket ggf. entfernen (sauberer Start)
ExecStartPre=/usr/bin/env bash -c 'test -S "%t/tmux-%u/default" && rm -f "%t/tmux-%u/default" || true'
Environment=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
Environment=TMUX_TMPDIR=%t
Environment=TMUX=
UMask=0077
KillMode=process
ExecStart=%h/.config/tmux/orchestrator.sh
[Install]
WantedBy=default.target
1.4 Orchestrator-Skript (fixer Socket, persistentes cd, :0 nutzen)
/home/jj/.config/tmux/orchestrator.sh
#!/usr/bin/env bash
# ~/.config/tmux/orchestrator.sh (jj) – fixed socket /run/user/<uid>/tmux-<uid>/default
set -euo pipefail
unset TMUX
CONF="$HOME/.config/tmux/autostart.conf"
TMUX_BIN="$(command -v tmux)"
ZSH_BIN="$(command -v zsh || echo /usr/bin/zsh)"
UIDNUM="$(id -u)"
RUNDIR="${XDG_RUNTIME_DIR:-/run/user/${UIDNUM}}"
SOCKDIR="$RUNDIR/tmux-${UIDNUM}"
SOCK="$SOCKDIR/default"
umask 077
install -d -m 0700 -o "$UIDNUM" -g "$UIDNUM" "$RUNDIR" >/dev/null 2>&1 || true
install -d -m 0700 -o "$UIDNUM" -g "$UIDNUM" "$SOCKDIR" >/dev/null 2>&1 || true
export TMUX_TMPDIR="$RUNDIR"
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$PATH"
TMUXS=(-S "$SOCK")
log() { printf '[tmux-auto] %s\n' "$*" >&2; }
command -v "$TMUX_BIN" >/dev/null 2>&1 || { log "missing tmux"; exit 127; }
ensure_session() {
local s="$1"
if ! "$TMUX_BIN" "${TMUXS[@]}" has-session -t "$s" 2>/dev/null; then
"$TMUX_BIN" "${TMUXS[@]}" new-session -d -s "$s" "$ZSH_BIN -l"
sleep 0.1
"$TMUX_BIN" "${TMUXS[@]}" send-keys -t "$s" "cd $HOME" C-m
log "created session $s"
fi
}
ensure_window() {
local s="$1" w="$2"
if ! "$TMUX_BIN" "${TMUXS[@]}" list-windows -t "$s" -F '#W' 2>/dev/null | grep -Fxq -- "$w"; then
"$TMUX_BIN" "${TMUXS[@]}" new-window -t "$s" -n "$w" "$ZSH_BIN -l"
sleep 0.05
"$TMUX_BIN" "${TMUXS[@]}" send-keys -t "$s:$w" "cd $HOME" C-m
log "created window $s:$w"
fi
}
run_in_window_shell() {
local tgt="$1" cmd="$2"
if printf '%s' "$cmd" | grep -Eq '^[[:space:]]*cd[[:space:]]+'; then
"$TMUX_BIN" "${TMUXS[@]}" send-keys -t "$tgt" "$cmd" C-m
else
"$TMUX_BIN" "${TMUXS[@]}" send-keys -t "$tgt" "bash -lc '$cmd'" C-m
fi
log "started $tgt -> $cmd"
}
harden_tmux_server() {
"$TMUX_BIN" "${TMUXS[@]}" set -g remain-on-exit on >/dev/null
"$TMUX_BIN" "${TMUXS[@]}" set -g exit-empty off >/dev/null
"$TMUX_BIN" "${TMUXS[@]}" set -g detach-on-destroy off >/dev/null
}
run_phase_boot() {
ensure_session tm0
harden_tmux_server
[[ -f "$CONF" ]] || return 0
awk -F '|' '/^[[:space:]]*#/ || /^[[:space:]]*$/ {next} $1=="boot"{print}' "$CONF" |
while IFS='|' read -r _p s w cmd; do
s="${s:-tm0}"; ensure_session "$s"
if [ -z "${cmd:-}" ]; then [ -n "${w:-}" ] && ensure_window "$s" "$w"; continue; fi
if [ "$s" = "tm0" ]; then
[ -n "${w:-}" ] && "$TMUX_BIN" "${TMUXS[@]}" rename-window -t tm0:0 "$w" 2>/dev/null || true
run_in_window_shell "tm0:0" "$cmd"
else
if [ -n "${w:-}" ]; then
ensure_window "$s" "$w"; run_in_window_shell "$s:$w" "$cmd"
else
run_in_window_shell "$s:0" "$cmd" # :0 wiederverwenden
fi
fi
done
}
wait_for_display_and_inject() {
local xauth disp
log "waiting for display (xauth cookie in $RUNDIR)…"
while true; do
xauth="$(ls -t "$RUNDIR"/xauth_* 2>/dev/null | head -n1 || true)"
if [ -n "${xauth:-}" ] && [ -s "$xauth" ]; then
disp="$(xauth -f "$xauth" list 2>/dev/null | awk '{ if (match($0,/unix:([0-9]+)/)) {n=substr($0,RSTART+5,RLENGTH-5); print ":" n; exit} if (match($0,/:([0-9]+)/)) {n=substr($0,RSTART+1,RLENGTH-1); print ":" n; exit} }')"
if [ -n "${disp:-}" ]; then
"$TMUX_BIN" "${TMUXS[@]}" set-environment -g XAUTHORITY "$xauth"
"$TMUX_BIN" "${TMUXS[@]}" set-environment -g DISPLAY "$disp"
"$TMUX_BIN" "${TMUXS[@]}" set-environment -g XDG_RUNTIME_DIR "$RUNDIR"
export XAUTHORITY="$xauth" DISPLAY="$disp" XDG_RUNTIME_DIR="$RUNDIR"
log "injected DISPLAY=$disp, XAUTHORITY=$xauth"
return 0
fi
fi
sleep 2
done
}
set_xkb_for_xwayland() {
command -v setxkbmap >/dev/null 2>&1 || return 0
local cur_layout cur_variant
cur_layout="$(setxkbmap -query 2>/dev/null | awk '/^layout:/ {print $2}')"
cur_variant="$(setxkbmap -query 2>/dev/null | awk '/^variant:/ {print $2}')"
if [ "$cur_layout" != "de" ] || [ "${cur_variant:-}" != "nodeadkeys" ]; then
setxkbmap -layout de -variant nodeadkeys 2>/dev/null || true
log "setxkbmap de nodeadkeys for Xwayland clients"
fi
}
run_phase_display() {
[[ -f "$CONF" ]] || return 0
awk -F '|' '/^[[:space:]]*#/ || /^[[:space:]]*$/ {next} $1=="display"{print}' "$CONF" |
while IFS='|' read -r _p s w cmd; do
s="${s:-tm0}"; ensure_session "$s"
if [ -z "${w:-}" ]; then w=""; fi
if [ -z "${cmd:-}" ]; then
if [ -n "$w" ]; then ensure_window "$s" "$w"; fi
continue
fi
if [ -n "$w" ]; then
ensure_window "$s" "$w"; run_in_window_shell "$s:$w" "$cmd"
else
run_in_window_shell "$s:0" "$cmd"
fi
done
}
log "start (boot phase)"; run_phase_boot
log "wait/display phase"; wait_for_display_and_inject
set_xkb_for_xwayland
run_phase_display
log "done"; exit 0
1.5 Beispiel-Config
/home/jj/.config/tmux/autostart.conf
# phase|session|window|command
# BOOT: sofort in tm0:0 ausführen
boot|tm0|AI|cd /home/jj/Test_Lab/Camera/; source /home/jj/Test_Lab/Camera/.venv/bin/activate && python3 /home/jj/Test_Lab/Camera/Version_0.8__capture.py
# DISPLAY: Sessions vorbereiten/Ordner setzen
display|tm12||cd /home/jj/OpenVPN/Schneider.land
display|tm5||
1.6 Rechte & Start
sudo -u jj chmod 0755 /home/jj/.config/tmux/orchestrator.sh
sudo -u jj systemctl --user daemon-reload
sudo -u jj systemctl --user enable --now tmux-autostart.service
1.7 Test
# direkt nach Aktivierung
sudo -u jj tmux -S /run/user/1000/tmux-1000/default ls
# nach Login (GUI): display-Phase sollte gelaufen sein
sudo -u jj tmux ls
2) Optional: root identisch als User-Unit
2.1 Linger & Verzeichnisse
sudo loginctl enable-linger root
sudo mkdir -p /root/.config/systemd/user /root/.config/tmux
2.2 Service-Datei
/root/.config/systemd/user/tmux-autostart.service
[Unit]
Description=Start/refresh tmux for root (boot + wait for display, fixed socket)
[Service]
Type=simple
ExecStartPre=/bin/sleep 10
ExecStartPre=/usr/bin/install -d -m 0700 -o root -g root /run/user/0
ExecStartPre=/usr/bin/install -d -m 0700 -o root -g root /run/user/0/tmux-0
ExecStartPre=/usr/bin/env bash -c 'test -S "/run/user/0/tmux-0/default" && rm -f "/run/user/0/tmux-0/default" || true'
Environment=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
Environment=TMUX_TMPDIR=%t
Environment=TMUX=
UMask=0077
KillMode=process
ExecStart=%h/.config/tmux/orchestrator.sh
[Install]
WantedBy=default.target
2.3 Orchestrator-Skript (root, fester Socket + Boot-:0-Fix)
/root/.config/tmux/orchestrator.sh
#!/usr/bin/env bash
# /root/.config/tmux/orchestrator.sh – fixed socket /run/user/0/tmux-0/default
set -euo pipefail
unset TMUX
CONF="/root/.config/tmux/autostart.conf"
TMUX_BIN="$(command -v tmux)"
ZSH_BIN="$(command -v zsh || echo /usr/bin/zsh)"
RUNDIR="/run/user/0"
SOCKDIR="$RUNDIR/tmux-0"
SOCK="$SOCKDIR/default"
umask 077
install -d -m 0700 -o root -g root "$RUNDIR" >/dev/null 2>&1 || true
install -d -m 0700 -o root -g root "$SOCKDIR" >/dev/null 2>&1 || true
export TMUX_TMPDIR="$RUNDIR"
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$PATH"
TMUXS=(-S "$SOCK")
log() { printf '[tmux-root] %s\n' "$*" >&2; }
command -v "$TMUX_BIN" >/dev/null 2>&1 || { log "missing tmux"; exit 127; }
ensure_session() {
local s="$1"
if ! "$TMUX_BIN" "${TMUXS[@]}" has-session -t "$s" 2>/dev/null; then
"$TMUX_BIN" "${TMUXS[@]}" new-session -d -s "$s" "$ZSH_BIN -l"
sleep 0.1
"$TMUX_BIN" "${TMUXS[@]}" send-keys -t "$s" "cd /root" C-m
log "created session $s"
fi
}
ensure_window() {
local s="$1" w="$2"
if ! "$TMUX_BIN" "${TMUXS[@]}" list-windows -t "$s" -F '#W' 2>/dev/null | grep -Fxq -- "$w"; then
"$TMUX_BIN" "${TMUXS[@]}" new-window -t "$s" -n "$w" "$ZSH_BIN -l"
sleep 0.05
"$TMUX_BIN" "${TMUXS[@]}" send-keys -t "$s:$w" "cd /root" C-m
log "created window $s:$w"
fi
}
run_in_window_shell() {
local tgt="$1" cmd="$2"
if printf '%s' "$cmd" | grep -Eq '^[[:space:]]*cd[[:space:]]+'; then
"$TMUX_BIN" "${TMUXS[@]}" send-keys -t "$tgt" "$cmd" C-m
else
"$TMUX_BIN" "${TMUXS[@]}" send-keys -t "$tgt" "bash -lc '$cmd'" C-m
fi
log "started $tgt -> $cmd"
}
harden_tmux() {
"$TMUX_BIN" "${TMUXS[@]}" set -g remain-on-exit on >/dev/null
"$TMUX_BIN" "${TMUXS[@]}" set -g exit-empty off >/dev/null
"$TMUX_BIN" "${TMUXS[@]}" set -g detach-on-destroy off >/dev/null
}
run_phase_boot() {
ensure_session tm0
harden_tmux
[[ -f "$CONF" ]] || return 0
awk -F '|' '/^[[:space:]]*#/ || /^[[:space:]]*$/ {next} $1=="boot"{print}' "$CONF" |
while IFS='|' read -r _p s w cmd; do
s="${s:-tm0}"; ensure_session "$s"
if [ -z "${cmd:-}" ]; then [ -n "${w:-}" ] && ensure_window "$s" "$w"; continue; fi
if [ "$s" = "tm0" ]; then
[ -n "${w:-}" ] && "$TMUX_BIN" "${TMUXS[@]}" rename-window -t tm0:0 "$w" 2>/dev/null || true
run_in_window_shell "tm0:0" "$cmd"
else
if [ -n "${w:-}" ]; then
ensure_window "$s" "$w"; run_in_window_shell "$s:$w" "$cmd"
else
run_in_window_shell "$s:0" "$cmd" # :0 wiederverwenden (kein "auto")
fi
fi
done
}
find_latest_xauth() { ls -t /run/user/*/xauth_* 2>/dev/null | head -n1 || true; }
wait_for_display_and_inject() {
local xauth disp cookie_dir
log "waiting for display (any /run/user/*/xauth_*)…"
while true; do
xauth="$(find_latest_xauth)"
if [ -n "${xauth:-}" ] && [ -s "$xauth" ]; then
disp="$(xauth -f "$xauth" list 2>/dev/null | awk '{ if (match($0,/unix:([0-9]+)/)) {n=substr($0,RSTART+5,RLENGTH-5); print ":" n; exit} if (match($0,/:([0-9]+)/)) {n=substr($0,RSTART+1,RLENGTH-1); print ":" n; exit} }')"
if [ -n "${disp:-}" ]; then
cookie_dir="$(dirname "$xauth")"
"$TMUX_BIN" "${TMUXS[@]}" set-environment -g XAUTHORITY "$xauth"
"$TMUX_BIN" "${TMUXS[@]}" set-environment -g DISPLAY "$disp"
"$TMUX_BIN" "${TMUXS[@]}" set-environment -g XDG_RUNTIME_DIR "$cookie_dir"
export XAUTHORITY="$xauth" DISPLAY="$disp" XDG_RUNTIME_DIR="$cookie_dir"
log "injected DISPLAY=$disp, XAUTHORITY=$xauth, XDG_RUNTIME_DIR=$cookie_dir"
return 0
fi
fi
sleep 2
done
}
set_xkb_for_xwayland() {
command -v setxkbmap >/dev/null 2>&1 || return 0
local cur_layout cur_variant
cur_layout="$(setxkbmap -query 2>/dev/null | awk '/^layout:/ {print $2}')"
cur_variant="$(setxkbmap -query 2>/dev/null | awk '/^variant:/ {print $2}')"
if [ "$cur_layout" != "de" ] || [ "${cur_variant:-}" != "nodeadkeys" ]; then
setxkbmap -layout de -variant nodeadkeys 2>/dev/null || true
log "setxkbmap de nodeadkeys"
fi
}
run_phase_display() {
[[ -f "$CONF" ]] || return 0
awk -F '|' '/^[[:space:]]*#/ || /^[[:space:]]*$/ {next} $1=="display"{print}' "$CONF" |
while IFS='|' read -r _p s w cmd; do
s="${s:-tm0}"; ensure_session "$s"
if [ -z "${w:-}" ]; then w=""; fi
if [ -z "${cmd:-}" ]; then
if [ -n "$w" ]; then ensure_window "$s" "$w"; fi
continue
fi
if [ -n "$w" ]; then
ensure_window "$s" "$w"; run_in_window_shell "$s:$w" "$cmd"
else
run_in_window_shell "$s:0" "$cmd"
fi
done
}
log "start (boot phase)"; run_phase_boot
log "wait/display phase"; wait_for_display_and_inject
set_xkb_for_xwayland
run_phase_display
log "done"; exit 0
2.4 Beispiel-Config (root)
/root/.config/tmux/autostart.conf
# phase|session|window|command
boot|tm11||cd /etc/JJ_SystemMetric/; source /etc/JJ_SystemMetric/.venv/bin/activate && python3 /etc/JJ_SystemMetric/Version_0.19__Start_SystemMetric.py
boot|tm12||cd /home/jj/OpenVPN/Schneider.land
boot|tm13||cd /home/jj/Cronjob/ntfy/; python3 /home/jj/Cronjob/ntfy/Version_0.13__ntfy.py
2.5 Rechte & Start
sudo chmod 0755 /root/.config/tmux/orchestrator.sh
sudo systemctl --user daemon-reload
sudo systemctl --user enable --now tmux-autostart.service
# prüfen:
sudo tmux -S /run/user/0/tmux-0/default ls
3) Nutzung & Beispiele
- Nur Verzeichnis setzen:
display|tm12||cd /pfad(Fenster:0bleibt, kein zweites Fenster) - Fenster benennen:
display|tm8|vpn|cd /home/jj/OpenVPN/Schneider.land - Direkt Programm starten:
display|tm11|metric|cd /etc/JJ_SystemMetric/; source .venv/bin/activate && python3 run.py
4) Troubleshooting
- jj: kein Socket:
sudo -u jj tmux -S /run/user/1000/tmux-1000/default ls– sofern leer:journalctl --user -u tmux-autostart.service -n100 -o cat - root: unsafe permissions: wird durch die Unit via
install -d -m 0700behoben. Manuell:chmod 0700 /run/user/0 /run/user/0/tmux-0 - Reset aller Sessions (jj):
sudo -u jj tmux -S /run/user/1000/tmux-1000/default kill-server || true - Reset aller Sessions (root):
sudo tmux -S /run/user/0/tmux-0/default kill-server || true
5) Sicherheitshinweis (root → User-Display)
Root nutzt das Xauth-Cookie der eingeloggten Nutzersitzung, um X/Wayland-Apps an diese Session anzubinden. Das ist üblich, aber beachte die Sicherheitsimplikationen (root erhält Display-Zugriff). Kein xhost + nötig.
6) Deinstallation
# jj
sudo -u jj systemctl --user disable --now tmux-autostart.service
sudo rm -f /home/jj/.config/systemd/user/tmux-autostart.service /home/jj/.config/tmux/orchestrator.sh
# root
sudo systemctl --user disable --now tmux-autostart.service
sudo rm -f /root/.config/systemd/user/tmux-autostart.service /root/.config/tmux/orchestrator.sh