install.sh
340 Zeilen · 12.7 KB · Shell
| 1 | |
| 2 | # =========================================================================== |
| 3 | # TV-Wall - Installation auf dem Raspberry Pi (Raspberry Pi OS / Raspbian) |
| 4 | # |
| 5 | # Aufruf: sudo ./install.sh [Optionen] |
| 6 | # |
| 7 | # Optionen: |
| 8 | # --no-ap WLAN-Access-Point nicht einrichten |
| 9 | # --ap-only nur den Access Point einrichten |
| 10 | # --config PFAD andere Konfigurationsdatei verwenden |
| 11 | # --yes keine Rueckfragen stellen |
| 12 | # =========================================================================== |
| 13 | set -euo pipefail |
| 14 | |
| 15 | SRC_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" |
| 16 | CONFIG_DST="/etc/tvwall/tvwall.conf" |
| 17 | BIN_DST="/usr/local/bin/tvwall" |
| 18 | UNIT_DST="/etc/systemd/system/tvwall.service" |
| 19 | DOC_DST="/opt/tvwall" |
| 20 | |
| 21 | WITH_AP=1 |
| 22 | AP_ONLY=0 |
| 23 | ASSUME_YES=0 |
| 24 | CONFIG_SRC="" |
| 25 | |
| 26 | info() { printf '\033[1;36m==>\033[0m %s\n' "$*"; } |
| 27 | ok() { printf '\033[1;32m ✓\033[0m %s\n' "$*"; } |
| 28 | warn() { printf '\033[1;33m !\033[0m %s\n' "$*"; } |
| 29 | die() { printf '\033[1;31mFehler:\033[0m %s\n' "$*" >&2; exit 1; } |
| 30 | |
| 31 | while [[ $# -gt 0 ]]; do |
| 32 | case "$1" in |
| 33 | --no-ap) WITH_AP=0; shift ;; |
| 34 | --ap-only) AP_ONLY=1; shift ;; |
| 35 | --yes|-y) ASSUME_YES=1; shift ;; |
| 36 | --config) CONFIG_SRC="${2:-}"; shift 2 ;; |
| 37 | -h|--help) sed -n '2,15p' "$0"; exit 0 ;; |
| 38 | *) die "unbekannte Option: $1" ;; |
| 39 | esac |
| 40 | done |
| 41 | |
| 42 | [[ $EUID -eq 0 ]] || die "bitte mit sudo starten: sudo $0" |
| 43 | |
| 44 | # --------------------------------------------------------------------------- |
| 45 | # Konfiguration einlesen |
| 46 | # --------------------------------------------------------------------------- |
| 47 | if [[ -z "$CONFIG_SRC" ]]; then |
| 48 | if [[ -f "$CONFIG_DST" ]]; then |
| 49 | CONFIG_SRC="$CONFIG_DST" # bestehende Installation: nicht ueberschreiben |
| 50 | else |
| 51 | CONFIG_SRC="$SRC_DIR/config/tvwall.conf" |
| 52 | fi |
| 53 | fi |
| 54 | [[ -f "$CONFIG_SRC" ]] || die "Konfigurationsdatei nicht gefunden: $CONFIG_SRC" |
| 55 | |
| 56 | set -a |
| 57 | # shellcheck disable=SC1090 |
| 58 | . "$CONFIG_SRC" |
| 59 | set +a |
| 60 | |
| 61 | AP_SSID="${AP_SSID:-TVWall-01}" |
| 62 | RUN_USER="${RUN_USER:-tvwall}" |
| 63 | STATE_DIR="${STATE_DIR:-/var/lib/tvwall}" |
| 64 | DISPLAY_MODE="${DISPLAY_MODE:-auto}" |
| 65 | HOSTNAME_NEW="${HOSTNAME:-tvwall-01}" |
| 66 | WEB_PORT="${WEB_PORT:-80}" |
| 67 | |
| 68 | # --- Bildausgabe bestimmen -------------------------------------------------- |
| 69 | # Der Desktop-Benutzer ist der einzige, der an die Sitzung herankommt. |
| 70 | detect_desktop_user() { |
| 71 | local d u |
| 72 | for d in /run/user/*; do |
| 73 | [[ -d "$d" ]] || continue |
| 74 | compgen -G "$d/wayland-*" >/dev/null 2>&1 || continue |
| 75 | u="$(getent passwd "$(basename "$d")" | cut -d: -f1)" |
| 76 | [[ -n "$u" ]] && { echo "$u"; return; } |
| 77 | done |
| 78 | u="$(sed -n 's/^autologin-user=//p' /etc/lightdm/lightdm.conf 2>/dev/null | head -1)" |
| 79 | [[ -n "$u" ]] && { echo "$u"; return; } |
| 80 | u="$(sed -n 's/.*--autologin \([^ ]*\).*/\1/p' \ |
| 81 | /etc/systemd/system/getty@tty1.service.d/autologin.conf 2>/dev/null | head -1)" |
| 82 | [[ -n "$u" && "$u" != "root" ]] && { echo "$u"; return; } |
| 83 | getent passwd 1000 | cut -d: -f1 |
| 84 | } |
| 85 | |
| 86 | GRAPHICAL=0 |
| 87 | [[ "$(systemctl get-default 2>/dev/null)" == "graphical.target" ]] && GRAPHICAL=1 |
| 88 | pgrep -x labwc >/dev/null 2>&1 && GRAPHICAL=1 |
| 89 | pgrep -x wayfire >/dev/null 2>&1 && GRAPHICAL=1 |
| 90 | pgrep -x Xorg >/dev/null 2>&1 && GRAPHICAL=1 |
| 91 | |
| 92 | DISPLAY_MODE_CHANGED=0 |
| 93 | # Eine bestehende Konfiguration wird nicht ueberschrieben - deshalb kann hier |
| 94 | # noch ein "drm" aus einer frueheren Installation stehen. Auf einem System mit |
| 95 | # Desktop fuehrt das zwangslaeufig zum schwarzen Bildschirm. |
| 96 | if [[ "$DISPLAY_MODE" == "drm" && $GRAPHICAL -eq 1 ]]; then |
| 97 | echo |
| 98 | warn "In der Konfiguration steht DISPLAY_MODE=\"drm\", auf diesem Pi laeuft aber" |
| 99 | warn "ein Desktop. mpv kann den Bildschirm so nicht bekommen - er bliebe schwarz." |
| 100 | if [[ $ASSUME_YES -eq 1 ]]; then |
| 101 | DISPLAY_MODE="auto"; DISPLAY_MODE_CHANGED=1 |
| 102 | else |
| 103 | read -r -p " Auf DISPLAY_MODE=\"auto\" umstellen? [J/n] " answer |
| 104 | if [[ ! "$answer" =~ ^[nN]$ ]]; then |
| 105 | DISPLAY_MODE="auto"; DISPLAY_MODE_CHANGED=1 |
| 106 | fi |
| 107 | fi |
| 108 | echo |
| 109 | fi |
| 110 | |
| 111 | EFFECTIVE_MODE="$DISPLAY_MODE" |
| 112 | if [[ "$EFFECTIVE_MODE" == "auto" ]]; then |
| 113 | if [[ $GRAPHICAL -eq 1 ]]; then |
| 114 | EFFECTIVE_MODE="desktop" |
| 115 | else |
| 116 | EFFECTIVE_MODE="drm" |
| 117 | fi |
| 118 | fi |
| 119 | |
| 120 | RUN_USER_CHANGED=0 |
| 121 | if [[ "$EFFECTIVE_MODE" == "desktop" && "$RUN_USER" == "tvwall" ]]; then |
| 122 | DESKTOP_USER="$(detect_desktop_user || true)" |
| 123 | if [[ -n "$DESKTOP_USER" ]]; then |
| 124 | RUN_USER="$DESKTOP_USER" |
| 125 | RUN_USER_CHANGED=1 |
| 126 | fi |
| 127 | fi |
| 128 | |
| 129 | info "Konfiguration: $CONFIG_SRC" |
| 130 | echo " WLAN-Name (SSID) : $AP_SSID" |
| 131 | echo " Hostname : $HOSTNAME_NEW" |
| 132 | if [[ "$DISPLAY_MODE" == "auto" ]]; then |
| 133 | echo " Bildausgabe : auto -> $EFFECTIVE_MODE" |
| 134 | else |
| 135 | echo " Bildausgabe : $EFFECTIVE_MODE" |
| 136 | fi |
| 137 | if [[ $RUN_USER_CHANGED -eq 1 ]]; then |
| 138 | echo " Benutzer : $RUN_USER (Desktop-Benutzer, automatisch erkannt)" |
| 139 | else |
| 140 | echo " Benutzer : $RUN_USER" |
| 141 | fi |
| 142 | echo " Medienordner : $STATE_DIR/media" |
| 143 | echo |
| 144 | |
| 145 | if [[ $AP_ONLY -eq 1 ]]; then |
| 146 | exec "$SRC_DIR/scripts/setup-ap.sh" "$CONFIG_SRC" |
| 147 | fi |
| 148 | |
| 149 | # --------------------------------------------------------------------------- |
| 150 | # Pakete |
| 151 | # --------------------------------------------------------------------------- |
| 152 | if command -v apt-get >/dev/null; then |
| 153 | MISSING=() |
| 154 | command -v mpv >/dev/null || MISSING+=(mpv) |
| 155 | command -v ffmpeg >/dev/null || MISSING+=(ffmpeg) |
| 156 | if [[ ${#MISSING[@]} -gt 0 ]]; then |
| 157 | info "Installiere Pakete: ${MISSING[*]}" |
| 158 | DEBIAN_FRONTEND=noninteractive apt-get update -qq |
| 159 | DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends "${MISSING[@]}" |
| 160 | fi |
| 161 | ok "mpv und ffmpeg vorhanden" |
| 162 | else |
| 163 | warn "kein apt-get gefunden - mpv und ffmpeg bitte selbst installieren" |
| 164 | fi |
| 165 | |
| 166 | # --------------------------------------------------------------------------- |
| 167 | # Programm installieren |
| 168 | # --------------------------------------------------------------------------- |
| 169 | info "Installiere Programm nach $BIN_DST" |
| 170 | ARCH="$(uname -m)" |
| 171 | case "$ARCH" in |
| 172 | aarch64|arm64) PREBUILT="$SRC_DIR/build/tvwall-linux-arm64" ;; |
| 173 | armv7l|armv6l) PREBUILT="$SRC_DIR/build/tvwall-linux-arm" ;; |
| 174 | x86_64) PREBUILT="$SRC_DIR/build/tvwall-linux-amd64" ;; |
| 175 | *) PREBUILT="" ;; |
| 176 | esac |
| 177 | |
| 178 | if [[ -n "$PREBUILT" && -f "$PREBUILT" ]]; then |
| 179 | install -m 0755 "$PREBUILT" "$BIN_DST" |
| 180 | ok "fertiges Programm verwendet ($ARCH)" |
| 181 | elif command -v go >/dev/null && [[ -f "$SRC_DIR/go.mod" ]]; then |
| 182 | info "Baue aus dem Quelltext (das dauert einen Moment)" |
| 183 | ( cd "$SRC_DIR" && go build -trimpath -ldflags "-s -w" -o /tmp/tvwall.build ./cmd/tvwall ) |
| 184 | install -m 0755 /tmp/tvwall.build "$BIN_DST" && rm -f /tmp/tvwall.build |
| 185 | ok "aus Quelltext gebaut" |
| 186 | elif [[ -x "$BIN_DST" ]]; then |
| 187 | # Erneuter Aufruf aus /opt/tvwall, nur um die Konfiguration zu uebernehmen. |
| 188 | ok "vorhandenes Programm beibehalten ($("$BIN_DST" -version 2>/dev/null || echo unbekannt))" |
| 189 | else |
| 190 | die "kein passendes Programm gefunden ($ARCH) und Go ist nicht installiert. |
| 191 | Auf dem Entwicklungsrechner 'make dist' ausfuehren und das Paket hierher kopieren." |
| 192 | fi |
| 193 | |
| 194 | # --------------------------------------------------------------------------- |
| 195 | # Benutzer und Verzeichnisse |
| 196 | # --------------------------------------------------------------------------- |
| 197 | if ! id -u "$RUN_USER" >/dev/null 2>&1; then |
| 198 | info "Lege Benutzer $RUN_USER an" |
| 199 | useradd --system --create-home --home-dir "/home/$RUN_USER" --shell /usr/sbin/nologin "$RUN_USER" |
| 200 | fi |
| 201 | for grp in video render audio input tty; do |
| 202 | getent group "$grp" >/dev/null && usermod -aG "$grp" "$RUN_USER" || true |
| 203 | done |
| 204 | ok "Benutzer $RUN_USER eingerichtet" |
| 205 | |
| 206 | install -d -m 0775 -o "$RUN_USER" -g "$RUN_USER" "$STATE_DIR" "$STATE_DIR/media" "$STATE_DIR/thumbs" |
| 207 | chown -R "$RUN_USER":"$RUN_USER" "$STATE_DIR" 2>/dev/null || true |
| 208 | ok "Medienordner: $STATE_DIR/media" |
| 209 | |
| 210 | install -d -m 0755 /etc/tvwall |
| 211 | if [[ -f "$CONFIG_DST" && "$CONFIG_SRC" != "$CONFIG_DST" ]]; then |
| 212 | install -m 0644 "$CONFIG_SRC" "$CONFIG_DST.neu" |
| 213 | warn "$CONFIG_DST existiert bereits - neue Fassung liegt als $CONFIG_DST.neu daneben" |
| 214 | elif [[ ! -f "$CONFIG_DST" ]]; then |
| 215 | install -m 0644 "$CONFIG_SRC" "$CONFIG_DST" |
| 216 | ok "Konfiguration nach $CONFIG_DST kopiert" |
| 217 | fi |
| 218 | |
| 219 | if [[ $RUN_USER_CHANGED -eq 1 && -f "$CONFIG_DST" ]]; then |
| 220 | sed -i "s|^RUN_USER=.*|RUN_USER=\"$RUN_USER\"|" "$CONFIG_DST" |
| 221 | ok "RUN_USER in $CONFIG_DST auf '$RUN_USER' gesetzt (Desktop-Benutzer)" |
| 222 | fi |
| 223 | if [[ $DISPLAY_MODE_CHANGED -eq 1 && -f "$CONFIG_DST" ]]; then |
| 224 | sed -i "s|^DISPLAY_MODE=.*|DISPLAY_MODE=\"auto\"|" "$CONFIG_DST" |
| 225 | ok "DISPLAY_MODE in $CONFIG_DST auf 'auto' gesetzt" |
| 226 | fi |
| 227 | |
| 228 | install -d -m 0755 "$DOC_DST" |
| 229 | [[ -f "$SRC_DIR/README.md" ]] && install -m 0644 "$SRC_DIR/README.md" "$DOC_DST/README.md" |
| 230 | if [[ -d "$SRC_DIR/scripts" ]]; then |
| 231 | install -d -m 0755 "$DOC_DST/scripts" |
| 232 | install -m 0755 "$SRC_DIR"/scripts/* "$DOC_DST/scripts/" |
| 233 | fi |
| 234 | # Installer und Vorlagen mitnehmen, damit spaetere Aenderungen ohne das |
| 235 | # Quellpaket auskommen: sudo /opt/tvwall/install.sh --no-ap |
| 236 | if [[ "$SRC_DIR" != "$DOC_DST" ]]; then |
| 237 | install -m 0755 "$SRC_DIR/install.sh" "$DOC_DST/install.sh" |
| 238 | install -d -m 0755 "$DOC_DST/systemd" "$DOC_DST/config" |
| 239 | install -m 0644 "$SRC_DIR"/systemd/*.in "$DOC_DST/systemd/" |
| 240 | install -m 0644 "$SRC_DIR"/config/tvwall.conf "$DOC_DST/config/tvwall.conf" |
| 241 | fi |
| 242 | |
| 243 | # --------------------------------------------------------------------------- |
| 244 | # systemd-Dienst |
| 245 | # --------------------------------------------------------------------------- |
| 246 | info "Richte systemd-Dienst ein" |
| 247 | if [[ "$EFFECTIVE_MODE" == "desktop" ]]; then |
| 248 | TEMPLATE="$SRC_DIR/systemd/tvwall-desktop.service.in" |
| 249 | RUN_UID="$(id -u "$RUN_USER")" |
| 250 | if [[ "$RUN_USER" == "tvwall" ]]; then |
| 251 | warn "Bildausgabe im Desktop, aber der Dienst laeuft als 'tvwall'." |
| 252 | warn "Nur der Benutzer, dem die Desktop-Sitzung gehoert, kommt an den Bildschirm." |
| 253 | warn "Bitte RUN_USER in $CONFIG_DST auf den Desktop-Benutzer setzen (meist 'pi')." |
| 254 | fi |
| 255 | else |
| 256 | TEMPLATE="$SRC_DIR/systemd/tvwall-drm.service.in" |
| 257 | RUN_UID="" |
| 258 | fi |
| 259 | [[ -f "$TEMPLATE" ]] || die "Vorlage fehlt: $TEMPLATE" |
| 260 | |
| 261 | sed -e "s|@RUN_USER@|$RUN_USER|g" \ |
| 262 | -e "s|@RUN_UID@|$RUN_UID|g" \ |
| 263 | -e "s|@CONFIG@|$CONFIG_DST|g" \ |
| 264 | "$TEMPLATE" > "$UNIT_DST" |
| 265 | chmod 0644 "$UNIT_DST" |
| 266 | |
| 267 | if [[ "$EFFECTIVE_MODE" != "desktop" ]]; then |
| 268 | # Auf der Konsole soll kein Login-Prompt um den Bildschirm streiten. |
| 269 | systemctl disable --now getty@tty1.service >/dev/null 2>&1 || true |
| 270 | else |
| 271 | # Im Desktop-Betrieb wird tty1 gebraucht - eine frueher im Konsolenmodus |
| 272 | # abgeschaltete Konsole hier wieder einschalten. |
| 273 | if ! systemctl is-enabled --quiet getty@tty1.service 2>/dev/null; then |
| 274 | systemctl enable --now getty@tty1.service >/dev/null 2>&1 || true |
| 275 | ok "Konsole auf tty1 wieder aktiviert" |
| 276 | fi |
| 277 | fi |
| 278 | |
| 279 | systemctl daemon-reload |
| 280 | systemctl enable tvwall.service >/dev/null |
| 281 | ok "Dienst aktiviert (startet ab jetzt bei jedem Einschalten mit)" |
| 282 | |
| 283 | # --------------------------------------------------------------------------- |
| 284 | # Hostname |
| 285 | # --------------------------------------------------------------------------- |
| 286 | if [[ -n "$HOSTNAME_NEW" && "$(hostname)" != "$HOSTNAME_NEW" ]]; then |
| 287 | info "Setze Hostname auf $HOSTNAME_NEW" |
| 288 | hostnamectl set-hostname "$HOSTNAME_NEW" || true |
| 289 | sed -i "s/^127\.0\.1\.1.*/127.0.1.1\t$HOSTNAME_NEW/" /etc/hosts 2>/dev/null || true |
| 290 | grep -q "^127.0.1.1" /etc/hosts || printf '127.0.1.1\t%s\n' "$HOSTNAME_NEW" >> /etc/hosts |
| 291 | fi |
| 292 | command -v avahi-daemon >/dev/null && systemctl enable --now avahi-daemon >/dev/null 2>&1 || true |
| 293 | |
| 294 | # --------------------------------------------------------------------------- |
| 295 | # Access Point |
| 296 | # --------------------------------------------------------------------------- |
| 297 | if [[ $WITH_AP -eq 1 ]]; then |
| 298 | if [[ $ASSUME_YES -eq 0 ]]; then |
| 299 | echo |
| 300 | warn "Der Access Point belegt das WLAN des Pi." |
| 301 | warn "Bist du gerade per WLAN verbunden, bricht diese Verbindung ab." |
| 302 | read -r -p " Access Point '$AP_SSID' jetzt einrichten? [j/N] " answer |
| 303 | [[ "$answer" =~ ^[jJyY]$ ]] || WITH_AP=0 |
| 304 | fi |
| 305 | fi |
| 306 | if [[ $WITH_AP -eq 1 ]]; then |
| 307 | "$SRC_DIR/scripts/setup-ap.sh" "$CONFIG_DST" || warn "Access Point konnte nicht eingerichtet werden - siehe Meldungen oben" |
| 308 | else |
| 309 | info "Access Point uebersprungen (spaeter: sudo $DOC_DST/scripts/setup-ap.sh)" |
| 310 | fi |
| 311 | |
| 312 | # --------------------------------------------------------------------------- |
| 313 | # Start |
| 314 | # --------------------------------------------------------------------------- |
| 315 | info "Starte Dienst" |
| 316 | systemctl restart tvwall.service |
| 317 | sleep 2 |
| 318 | if systemctl is-active --quiet tvwall.service; then |
| 319 | ok "tvwall laeuft" |
| 320 | else |
| 321 | warn "Dienst laeuft nicht - Protokoll ansehen mit: journalctl -u tvwall -n 50" |
| 322 | fi |
| 323 | |
| 324 | PORT_SUFFIX="" |
| 325 | [[ "$WEB_PORT" != "80" ]] && PORT_SUFFIX=":$WEB_PORT" |
| 326 | cat <<EOF |
| 327 | |
| 328 | =========================================================================== |
| 329 | Fertig. |
| 330 | |
| 331 | WLAN verbinden mit : $AP_SSID |
| 332 | Webinterface : http://${AP_IP:-192.168.4.1}$PORT_SUFFIX |
| 333 | http://$HOSTNAME_NEW.local$PORT_SUFFIX |
| 334 | Medienordner : $STATE_DIR/media |
| 335 | |
| 336 | Protokoll ansehen : journalctl -u tvwall -f |
| 337 | Dienst neu starten : sudo systemctl restart tvwall |
| 338 | Konfiguration : sudo nano $CONFIG_DST |
| 339 | =========================================================================== |
| 340 | EOF |