ET: Legacy TV-Bot & Twitch Streaming Headless Server Guide

From fresh Debian/Ubuntu server to a fully automated ET: Legacy spectator client streaming to Twitch — with audio, Xvfb and ultra-low graphics for smooth FPS.

Environment Debian/Ubuntu · headless Role ET: Legacy client (TV-bot) + Twitch User non-root tvbot Includes Xvfb · PulseAudio · ffmpeg · auto follow

Quick Summary

  • Create user tvbot (never run this as root).
  • Install: xvfb, ffmpeg, pulseaudio, Mesa OpenGL libs, xdotool.
  • Unpack ET: Legacy client to /home/tvbot/etlegacy.
  • Run ET client in a virtual X display (Xvfb :1) as a spectator.
  • Auto-run follownext for a TV-style camera.
  • Stream Xvfb to Twitch via ffmpeg, with audio from PulseAudio Null sink.
  • Optional cron job for scheduled TV-bot restarts (e.g. 05:51 and every 2 hours).

📋 Requirements

  • Debian/Ubuntu server (root SSH access).
  • No GPU/monitor required (pure headless setup).
  • ET: Legacy Linux 64-bit client archive.
  • A Twitch account + stream key.

Step 0 Goal / Overview

What you are building

The setup runs an ET: Legacy client as a spectator TV-bot on a headless server, inside a virtual X display, and streams that display (with in-game sound) to Twitch using ffmpeg.

Client ET:Legacy spectator Display Xvfb :1 Audio PulseAudio Null sink (ETTV) Stream ffmpeg → Twitch RTMP

Step 1 Create the dedicated user

Never run the TV-bot as root

All processes (ET client, PulseAudio, ffmpeg) should run under a normal user, not root. Here we use tvbot.

Run as root Create user
adduser tvbot
# set a password, press Enter through the remaining questions

Step 2 Install system packages

Xvfb, ffmpeg, audio & OpenGL

Install the required tools and libraries on your Debian/Ubuntu server:

Run as root Packages
apt update
apt install -y \
  xvfb \
  ffmpeg \
  pulseaudio \
  libgl1 libglx-mesa0 libgl1-mesa-dri mesa-utils \
  xdotool

Note: xdotool is optional (for debugging or manual key injection), but does not hurt to have installed.

Step 3 Install ET: Legacy client

Client only, not dedicated server
  1. On your local PC, download the Linux 64-bit ET: Legacy client archive (.tar.gz) from the official ET:Legacy download page.
  2. Copy the file to your server into /home/tvbot:
    Run on your PC Upload
    scp ETLegacy-*-x86_64.tar.gz tvbot@your-server-ip:/home/tvbot/
  3. Log in as the tvbot user and unpack the archive:
    Run as tvbot Unpack ET:L
    su - tvbot
    mkdir -p ~/etlegacy
    tar -xf ETLegacy-*-x86_64.tar.gz -C ~/etlegacy --strip-components=1
    
    ls ~/etlegacy
    # etl.x86_64, etlded.x86_64, etmain, legacy, ...

Step 4 TV-bot configs (performance + auto follow)

Ultra-low graphics + cyclical follownext

Create ET: Legacy configs under ~/.etlegacy/etmain (for user tvbot).

4.1 Ultra-low performance config

~/.etlegacy/etmain/tv_fps.cfg Graphics
// tv_fps.cfg – Ultra-low graphics for TV-bot

seta com_maxfps "60"

seta r_mode "-1"
seta r_customwidth "800"
seta r_customheight "600"
seta r_fullscreen "0"

seta r_colorbits "16"
seta r_depthbits "16"
seta r_texturebits "16"

seta r_ext_multisample "0"
seta r_ext_texture_filter_anisotropic "0"
seta r_textureMode "GL_LINEAR_MIPMAP_NEAREST"
seta r_picmip "3"
seta r_ext_compressed_textures "1"

seta r_dynamiclight "0"
seta r_dynamicTextures "0"
seta r_flares "0"
seta r_wolffog "0"
seta r_lodscale "30"
seta cg_shadows "0"
seta cg_coronas "0"

seta cg_wolfparticles "0"
seta cg_impactparticles "0"
seta cg_trailparticles "0"
seta cg_smokeparticles "0"
seta cg_atmosphericEffects "0"

seta cg_gibs "0"
seta cg_showblood "0"
seta cg_bloodTime "0"
seta cg_bloodFlash "0"
seta cg_bloodDamageBlend "0"

seta cg_markTime "2000"
seta cg_markDistance "128"
seta cg_railTrailTime "0"
seta cg_brassTime "0"

seta cg_tracers "0"
seta cg_muzzleFlash "0"

seta cg_bobyaw "0"
seta cg_bobroll "0"
seta cg_bobpitch "0"
seta cg_bobup "0"
seta cg_runroll "0"
seta cg_runpitch "0"

seta cg_drawGun "0"
seta cg_drawBanners "0"
seta cg_drawHUDStats "0"
seta cg_drawFireteamOverlay "0"
seta cg_drawBuddies "0"
seta cg_drawSnapshot "0"
seta cg_drawPing "0"
seta cg_lagometer "0"
seta cg_drawReinforcementTime "0"
seta cg_drawSpectatorNames "0"

seta cg_countryflags "0"
seta cg_pingColors "0"
seta cg_hitSounds "0"
seta cg_goatSound "0"
seta cg_announcer "0"

seta cg_drawTime "0"
seta cg_drawTimeSeconds "0"
seta cg_drawStatus "0"
seta cg_drawRoundTimer "1"
seta cg_draw2D "1"

seta cg_fov "90"

4.2 Auto follownext loop

~/.etlegacy/etmain/tv_autofollow.cfg Camera loop
// tv_autofollow.cfg – follownext every 60 seconds
// Assumes com_maxfps 60 → 60fps → 60s * 60 = 3600 frames

seta com_maxfps "60"

seta tv_cycle_start "vstr tv_cycle"
seta tv_cycle "echo ^2[TV]^7 follownext; follownext; wait 3600; vstr tv_cycle"

// Optional manual key:
bind h "follownext"

4.3 Autoexec to load TV configs

~/.etlegacy/etmain/autoexec.cfg Autoload
exec tv_fps.cfg
exec tv_autofollow.cfg
vstr tv_cycle_start

Step 5 TV-bot script (Xvfb + ET:L client)

Runs the client headless & survives SSH logout

Create a script to start Xvfb and the ET: Legacy client as TV-bot. Both Xvfb and ETL are detached using nohup + setsid, so they keep running after you close PuTTY.

/home/tvbot/etlegacy/etl.sh Executable
#!/bin/bash
# etl.sh – ET: Legacy TV-Bot im Hintergrund (User: tvbot)

DISPLAY_NUM=":1"
SCREEN_RES="800x600x24"

# Xvfb für diesen User starten, falls noch nicht läuft (voll detached mit nohup + setsid)
if ! pgrep -u "$USER" -x Xvfb >/dev/null 2>&1; then
    nohup setsid Xvfb "$DISPLAY_NUM" -screen 0 "$SCREEN_RES" \
        > "$HOME/xvfb.log" 2>&1 &
    sleep 2
fi

export DISPLAY="$DISPLAY_NUM"
export PULSE_SINK="ETTV"   # Sound in den Null-Sink schicken

# in das Verzeichnis des Scripts wechseln
cd "$(dirname "$(readlink -f "$0")")"

LOGFILE="$PWD/etltv.log"
PIDFILE="$PWD/etltv.pid"

# Läuft schon ein TV-Bot?
if [ -f "$PIDFILE" ] && kill -0 "$(cat "$PIDFILE")" 2>/dev/null; then
    echo "ETc|TV läuft bereits mit PID $(cat "$PIDFILE")."
    exit 0
fi

# ET:Legacy-Client als TV-Bot starten (nohup + setsid, damit auch SSH-Logout überlebt)
nohup setsid ./etl.x86_64 \
  +connect 84.200.135.3:27960 \
  +set name "ETc|TV" \
  +set r_fullscreen 0 \
  +set r_mode -1 \
  +set r_customwidth 800 \
  +set r_customheight 600 \
  +set com_maxfps 60 \
  +set cg_draw2D 1 \
  +set s_mute 0 \
  +set s_volume 1 \
  +exec bot.cfg \
  >> "$LOGFILE" 2>&1 &

echo $! > "$PIDFILE"
echo "ETc|TV gestartet, PID: $(cat "$PIDFILE")"
echo "Logfile: $LOGFILE"
Make it executable (as tvbot)
chmod 700 /home/tvbot/etlegacy/etl.sh

Step 6 PulseAudio & Null sink for audio

Virtual sound device for ffmpeg

Run PulseAudio and create a Null sink named ETTV under the tvbot user.

Run as tvbot PulseAudio
pulseaudio --start --exit-idle-time=-1

pactl load-module module-null-sink \
  sink_name=ETTV sink_properties=device.description="ETTV"

pactl set-default-sink ETTV

pactl list short sinks
pactl list short sources | grep ETTV
# you should see ETTV and ETTV.monitor

If you control this from a root shell, always use: sudo -u tvbot -H <command> so PulseAudio and pactl talk to the same user session.

Step 7 Twitch streaming script (ffmpeg)

Xvfb video + PulseAudio audio

Create a script that grabs Xvfb :1, mixes in audio from ETTV.monitor and sends it to Twitch. ffmpeg is also detached with nohup + setsid.

/home/tvbot/etlegacy/tw.sh Executable
#!/bin/bash
# tw.sh – Streamt Xvfb :1 nach Twitch MIT Audio (User: tvbot)

DISPLAY_NUM=":1"
RESOLUTION="800x600"
FRAMERATE=30

RTMP_URL="rtmp://live.twitch.tv/app/your_key"

LOGFILE="/home/tvbot/twitch_stream.log"
PIDFILE="/home/tvbot/twitch_stream.pid"

export DISPLAY="$DISPLAY_NUM"

# ffmpeg vorhanden?
if ! command -v ffmpeg >/dev/null 2>&1; then
    echo "Fehler: ffmpeg wurde nicht gefunden. Bitte mit 'sudo apt install ffmpeg' installieren." >&2
    exit 1
fi

# PulseAudio-Quelle für ETTV-Null-Sink ermitteln
AUDIO_SOURCE=$(pactl list short sources | awk '/ETTV\.monitor/ {print $2; exit}')

if [ -z "$AUDIO_SOURCE" ]; then
    echo "Fehler: Konnte PulseAudio-Quelle 'ETTV.monitor' nicht finden. Läuft PulseAudio + Null-Sink für tvbot?" >&2
    exit 1
fi

# Läuft schon ein Stream?
if [ -f "$PIDFILE" ] && kill -0 "$(cat "$PIDFILE")" 2>/dev/null; then
    echo "Twitch-Stream läuft bereits mit PID $(cat "$PIDFILE")."
    exit 0
fi

# ffmpeg im Hintergrund starten – Video + Audio (nohup + setsid, logout-sicher)
nohup setsid ffmpeg \
  -f x11grab -video_size "$RESOLUTION" -framerate "$FRAMERATE" -i "${DISPLAY_NUM}.0" \
  -f pulse -i "$AUDIO_SOURCE" \
  -c:v libx264 -preset veryfast -tune zerolatency \
  -crf 21 -maxrate 2500k -bufsize 5000k \
  -pix_fmt yuv420p \
  -g 60 \
  -c:a aac -b:a 128k -ac 2 \
  -f flv "$RTMP_URL" \
  >> "$LOGFILE" 2>&1 &

echo $! > "$PIDFILE"
echo "Twitch-Stream gestartet, PID: $(cat "$PIDFILE")"
echo "Logfile: $LOGFILE"
Make it executable (as tvbot)
chmod 700 /home/tvbot/etlegacy/tw.sh

Replace RTMP_URL with your own Twitch stream key from the Creator Dashboard if it changes.

Step 8 Start / stop cheat sheet

Everyday commands

All of the following should be run as tvbot (or via sudo -u tvbot -H from root).

Start TV-bot client

cd ~/etlegacy
./etl.sh

Start Twitch stream

cd ~/etlegacy
./tw.sh

Stop TV-bot

kill "$(cat ~/etlegacy/etltv.pid)"

Stop Twitch stream

kill "$(cat ~/twitch_stream.pid)"

Step 9 Optional: Auto restart via cron

Scheduled TV-bot restarts

Example: restart the TV-bot at 05:51 and then every 2 hours (07:51, 09:51, …, 23:51).

Restart script

/home/tvbot/etlegacy/restart_etl.sh Executable
#!/bin/bash
# restart_etl.sh – TV-Bot (etl) neu starten
# cron-Beispiel:
# 51 5-23/2 * * * /home/tvbot/etlegacy/restart_etl.sh >> /home/tvbot/etlegacy/cron_restart.log 2>&1

cd /home/tvbot/etlegacy

PIDFILE="etltv.pid"

# alten TV-Bot beenden, falls er läuft
if [ -f "$PIDFILE" ]; then
    PID=$(cat "$PIDFILE")
    if kill -0 "$PID" 2>/dev/null; then
        echo "$(date '+%F %T') – stoppe ETc|TV (PID $PID)" >> etltv_restart.log
        kill "$PID"
        sleep 5
    fi
fi

# ggf. alte PID-Datei entfernen
rm -f "$PIDFILE"

# neu starten
echo "$(date '+%F %T') – starte ETc|TV neu" >> etltv_restart.log
./etl.sh
Make it executable (as tvbot)
chmod 700 /home/tvbot/etlegacy/restart_etl.sh

Cron entry (user tvbot)

crontab -e
51 5-23/2 * * * /home/tvbot/etlegacy/restart_etl.sh >> /home/tvbot/etlegacy/cron_restart.log 2>&1