# Linux - Tmux

# 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 `tm0` startet (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.conf` definierten `display|…`-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 &amp; 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 &amp; 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 &amp; 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 &amp; Beispiele

- **Nur Verzeichnis setzen:** `display|tm12||cd /pfad` (Fenster `:0` bleibt, 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 0700` behoben. 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

```