Scripts, scenes, bridges, bots and more
This commit is contained in:
581
scripts/deploy-rig.sh
Executable file
581
scripts/deploy-rig.sh
Executable file
@@ -0,0 +1,581 @@
|
||||
#!/usr/bin/env bash
|
||||
# scripts/deploy-rig.sh — bring up the ophi118 OBS rig from scratch.
|
||||
# Idempotent: re-running is safe, every phase checks before acting.
|
||||
#
|
||||
# Usage:
|
||||
# bash scripts/deploy-rig.sh # interactive, full install
|
||||
# bash scripts/deploy-rig.sh --yes # non-interactive, accept all prompts
|
||||
# bash scripts/deploy-rig.sh --check # validate state, write nothing
|
||||
# bash scripts/deploy-rig.sh --rig-name X # override hostname-derived rig name
|
||||
#
|
||||
# Run on a fresh Midgolem (or re-run on Ignia to verify). After:
|
||||
# git clone <repo> ~/.config/obs-studio
|
||||
# cd ~/.config/obs-studio
|
||||
# bash scripts/deploy-rig.sh
|
||||
#
|
||||
# What it covers:
|
||||
# 1. Arch system packages (pacman + AUR for sansation-font)
|
||||
# 2. Python user packages (python-mpd2, websockets)
|
||||
# 3. Flatpak OBS Studio + filesystems=host override
|
||||
# 4. Git submodules + asset directories under $HOME
|
||||
# 5. PipeWire mpd_stream null sink + loopback
|
||||
# 6. MPD config (zeroconf_name = this rig) + system units
|
||||
# 7. Custom systemd --user units (bridge, bot)
|
||||
# 8. OBS first-launch to seed obs-websocket plugin config
|
||||
# 9. Secrets stub + manual next-steps report
|
||||
set -euo pipefail
|
||||
|
||||
# ── globals ────────────────────────────────────────────────────────────────
|
||||
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
OBS_DIR="$(cd "$DIR/.." && pwd)"
|
||||
RIG_NAME="${RIG_NAME:-$(hostname)}"
|
||||
CHECK_ONLY=0
|
||||
ASSUME_YES=0
|
||||
|
||||
# ── arg parsing ────────────────────────────────────────────────────────────
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--check) CHECK_ONLY=1; shift ;;
|
||||
--yes|-y) ASSUME_YES=1; shift ;;
|
||||
--rig-name) RIG_NAME="${2:?--rig-name needs an argument}"; shift 2 ;;
|
||||
-h|--help) sed -n 's/^# \?//p' "$0" | head -28; exit 0 ;;
|
||||
*) echo "unknown flag: $1" >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
RIG_NAME_TC="${RIG_NAME^}" # title-case: ignia → Ignia
|
||||
|
||||
# ── output helpers ─────────────────────────────────────────────────────────
|
||||
PHASE=0
|
||||
TOTAL=9
|
||||
step() { PHASE=$((PHASE+1)); printf '\n\033[1;36m[%d/%d] %s\033[0m\n' "$PHASE" "$TOTAL" "$*"; }
|
||||
ok() { printf ' \033[32m✓\033[0m %s\n' "$*"; }
|
||||
skip() { printf ' \033[90m·\033[0m %s\n' "$*"; }
|
||||
warn() { printf ' \033[33m!\033[0m %s\n' "$*" >&2; }
|
||||
err() { printf ' \033[31m✗\033[0m %s\n' "$*" >&2; }
|
||||
die() { err "$*"; exit 1; }
|
||||
|
||||
confirm() {
|
||||
(( ASSUME_YES )) && return 0
|
||||
local q="$1"
|
||||
read -rp " ? $q [y/N]: " a
|
||||
[[ "${a,,}" =~ ^(y|yes)$ ]]
|
||||
}
|
||||
|
||||
would() {
|
||||
# In --check mode, prefix the message and skip the action.
|
||||
(( CHECK_ONLY )) && { skip "would: $*"; return 1; }
|
||||
return 0
|
||||
}
|
||||
|
||||
# ── pre-flight ─────────────────────────────────────────────────────────────
|
||||
[[ "$OBS_DIR" == "$HOME/.config/obs-studio" ]] \
|
||||
|| die "Run this from \$HOME/.config/obs-studio (current OBS_DIR=$OBS_DIR)"
|
||||
|
||||
cat <<BANNER
|
||||
╔═══════════════════════════════════════════════════════════════╗
|
||||
║ ophi118 rig deployment ║
|
||||
║ rig name : $RIG_NAME_TC
|
||||
║ config : $OBS_DIR
|
||||
║ mode : $( ((CHECK_ONLY)) && echo '--check (no writes)' || echo 'install' )
|
||||
║ confirms : $( ((ASSUME_YES)) && echo 'auto-yes' || echo 'interactive' )
|
||||
╚═══════════════════════════════════════════════════════════════╝
|
||||
BANNER
|
||||
|
||||
# ────────────────────────────────────────────────────────────────────────────
|
||||
# Phase 1 — system packages
|
||||
# ────────────────────────────────────────────────────────────────────────────
|
||||
step "System packages (pacman)"
|
||||
|
||||
PACMAN_PKGS=(
|
||||
flatpak
|
||||
python python-pip
|
||||
pipewire pipewire-pulse pipewire-alsa wireplumber
|
||||
ffmpeg
|
||||
mpd mpc
|
||||
avahi nss-mdns
|
||||
fontconfig
|
||||
)
|
||||
|
||||
if command -v pacman >/dev/null; then
|
||||
MISSING=()
|
||||
for p in "${PACMAN_PKGS[@]}"; do
|
||||
pacman -Qq "$p" >/dev/null 2>&1 || MISSING+=("$p")
|
||||
done
|
||||
if (( ${#MISSING[@]} == 0 )); then
|
||||
ok "all base packages installed"
|
||||
elif would "sudo pacman -S --needed ${MISSING[*]}"; then
|
||||
sudo pacman -S --needed $( ((ASSUME_YES)) && echo --noconfirm ) "${MISSING[@]}"
|
||||
ok "installed ${#MISSING[@]} package(s)"
|
||||
fi
|
||||
|
||||
# NVIDIA branch
|
||||
if lspci 2>/dev/null | grep -qi 'vga.*nvidia'; then
|
||||
NV_MISSING=()
|
||||
for p in nvidia nvidia-utils; do
|
||||
pacman -Qq "$p" >/dev/null 2>&1 || NV_MISSING+=("$p")
|
||||
done
|
||||
if (( ${#NV_MISSING[@]} == 0 )); then
|
||||
ok "NVIDIA driver present"
|
||||
elif would "sudo pacman -S --needed ${NV_MISSING[*]}"; then
|
||||
sudo pacman -S --needed $( ((ASSUME_YES)) && echo --noconfirm ) "${NV_MISSING[@]}"
|
||||
ok "installed NVIDIA driver"
|
||||
fi
|
||||
else
|
||||
skip "no NVIDIA GPU detected (skipping nvidia/nvidia-utils)"
|
||||
fi
|
||||
|
||||
# AUR: sansation-font (used by the `info` text source on Game scene).
|
||||
if pacman -Qq sansation-font >/dev/null 2>&1 \
|
||||
|| fc-list 2>/dev/null | grep -qi 'sansation'; then
|
||||
ok "Sansation font present"
|
||||
else
|
||||
AUR_HELPER=""
|
||||
for h in paru yay; do command -v "$h" >/dev/null && { AUR_HELPER="$h"; break; }; done
|
||||
if [[ -z "$AUR_HELPER" ]]; then
|
||||
warn "Sansation font missing and no AUR helper (paru/yay) found"
|
||||
warn " → install manually from AUR or accept the system-default fallback"
|
||||
elif would "$AUR_HELPER -S sansation-font"; then
|
||||
"$AUR_HELPER" -S $( ((ASSUME_YES)) && echo --noconfirm ) sansation-font || \
|
||||
warn "AUR install failed — install sansation-font manually if you want the Game scene 'info' text to render exactly"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
warn "pacman not found — this script targets Arch. Skipping system-package phase."
|
||||
warn " Make sure these are installed by other means: ${PACMAN_PKGS[*]}"
|
||||
fi
|
||||
|
||||
# ────────────────────────────────────────────────────────────────────────────
|
||||
# Phase 2 — Python user packages
|
||||
# ────────────────────────────────────────────────────────────────────────────
|
||||
step "Python user packages"
|
||||
|
||||
PIP_PKGS=(python-mpd2 websockets)
|
||||
PY_MISSING=()
|
||||
for p in "${PIP_PKGS[@]}"; do
|
||||
python -c "import importlib, sys; importlib.import_module('${p//-/_}'.replace('python_mpd2','mpd'))" 2>/dev/null \
|
||||
|| PY_MISSING+=("$p")
|
||||
done
|
||||
if (( ${#PY_MISSING[@]} == 0 )); then
|
||||
ok "python-mpd2 and websockets importable"
|
||||
elif would "pip install --user ${PY_MISSING[*]}"; then
|
||||
python -m pip install --user --upgrade "${PY_MISSING[@]}"
|
||||
ok "installed ${PY_MISSING[*]}"
|
||||
fi
|
||||
|
||||
# ────────────────────────────────────────────────────────────────────────────
|
||||
# Phase 3 — OBS Flatpak
|
||||
# ────────────────────────────────────────────────────────────────────────────
|
||||
step "OBS Studio (Flatpak)"
|
||||
|
||||
if ! command -v flatpak >/dev/null; then
|
||||
die "flatpak not installed — phase 1 should have done this"
|
||||
fi
|
||||
|
||||
if ! flatpak remotes --user 2>/dev/null | grep -q '^flathub' \
|
||||
&& ! flatpak remotes 2>/dev/null | grep -q '^flathub'; then
|
||||
if would "flatpak remote-add flathub"; then
|
||||
flatpak remote-add --if-not-exists --user flathub \
|
||||
https://dl.flathub.org/repo/flathub.flatpakrepo
|
||||
ok "added Flathub remote"
|
||||
fi
|
||||
else
|
||||
ok "Flathub remote present"
|
||||
fi
|
||||
|
||||
if flatpak info com.obsproject.Studio >/dev/null 2>&1; then
|
||||
ok "OBS Studio flatpak installed"
|
||||
else
|
||||
if would "flatpak install flathub com.obsproject.Studio"; then
|
||||
flatpak install $( ((ASSUME_YES)) && echo -y ) flathub com.obsproject.Studio
|
||||
ok "installed OBS Studio flatpak"
|
||||
fi
|
||||
fi
|
||||
|
||||
if flatpak info --show-permissions com.obsproject.Studio 2>/dev/null \
|
||||
| grep -q 'filesystems=.*host'; then
|
||||
ok "filesystems=host override present (config dir reads from ~/.config/)"
|
||||
else
|
||||
if would "flatpak override --user com.obsproject.Studio --filesystems=host"; then
|
||||
flatpak override --user com.obsproject.Studio --filesystems=host
|
||||
ok "added filesystems=host override"
|
||||
else
|
||||
warn "without filesystems=host, OBS will read from ~/.var/app/... not from this dir!"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ────────────────────────────────────────────────────────────────────────────
|
||||
# Phase 4 — submodules + asset dirs
|
||||
# ────────────────────────────────────────────────────────────────────────────
|
||||
step "Submodules + asset directories"
|
||||
|
||||
if [[ -f "$OBS_DIR/.gitmodules" ]]; then
|
||||
if would "git submodule update --init --recursive"; then
|
||||
( cd "$OBS_DIR" && git submodule update --init --recursive )
|
||||
ok "submodules initialized"
|
||||
fi
|
||||
fi
|
||||
|
||||
ASSET_DIRS=(
|
||||
"$HOME/Videos"
|
||||
"$HOME/HDD/Music"
|
||||
"$HOME/HDD/Images/Gifs"
|
||||
"$HOME/cloud.jakubzych.com/_img/logos"
|
||||
)
|
||||
for d in "${ASSET_DIRS[@]}"; do
|
||||
if [[ -d "$d" ]]; then
|
||||
ok "exists: $d"
|
||||
elif would "mkdir -p $d"; then
|
||||
mkdir -p "$d"
|
||||
ok "created: $d"
|
||||
fi
|
||||
done
|
||||
|
||||
ASSET_FILES=(
|
||||
"$HOME/HDD/Images/Gifs/stamd.png:Game-scene 'Starting Soon' image"
|
||||
"$HOME/cloud.jakubzych.com/_img/logos/dgw.png:Doomguard watermark"
|
||||
)
|
||||
MISSING_ASSETS=0
|
||||
for entry in "${ASSET_FILES[@]}"; do
|
||||
path="${entry%%:*}"; what="${entry##*:}"
|
||||
if [[ -f "$path" ]]; then
|
||||
ok "asset present: $path"
|
||||
else
|
||||
warn "missing asset: $path ($what)"
|
||||
MISSING_ASSETS=1
|
||||
fi
|
||||
done
|
||||
(( MISSING_ASSETS )) && warn " → bring these over manually from Ignia; OBS will surface 'missing source' errors otherwise"
|
||||
|
||||
# ────────────────────────────────────────────────────────────────────────────
|
||||
# Phase 5 — PipeWire mpd_stream sink
|
||||
# ────────────────────────────────────────────────────────────────────────────
|
||||
step "PipeWire null sink (mpd_stream)"
|
||||
|
||||
PW_CONF_DIR="$HOME/.config/pipewire/pipewire-pulse.conf.d"
|
||||
PW_CONF="$PW_CONF_DIR/mpd-stream.conf"
|
||||
|
||||
if [[ -f "$PW_CONF" ]]; then
|
||||
ok "$PW_CONF already present"
|
||||
else
|
||||
if would "write $PW_CONF"; then
|
||||
mkdir -p "$PW_CONF_DIR"
|
||||
cat > "$PW_CONF" <<'PWCONF'
|
||||
# Persistent virtual sink for streaming MPD into OBS without mixing in
|
||||
# desktop/browser/Discord audio.
|
||||
#
|
||||
# MPD ──► null sink "mpd_stream"
|
||||
# │
|
||||
# ├──► loopback ──► default sink (so you still hear it on speakers)
|
||||
# │
|
||||
# └──► monitor ──► OBS "Audio Output Capture (PulseAudio) →
|
||||
# Monitor of MPD-Stream"
|
||||
#
|
||||
# Wired on the MPD side via target "mpd_stream" in ~/.config/mpd/mpd.conf.
|
||||
# Loaded automatically when pipewire-pulse starts.
|
||||
|
||||
pulse.cmd = [
|
||||
{
|
||||
cmd = "load-module"
|
||||
args = "module-null-sink sink_name=mpd_stream sink_properties=device.description=MPD-Stream"
|
||||
}
|
||||
{
|
||||
cmd = "load-module"
|
||||
args = "module-loopback source=mpd_stream.monitor latency_msec=50"
|
||||
}
|
||||
]
|
||||
PWCONF
|
||||
ok "wrote $PW_CONF"
|
||||
if would "systemctl --user restart pipewire-pulse"; then
|
||||
systemctl --user restart pipewire-pulse.service 2>/dev/null || \
|
||||
warn "could not restart pipewire-pulse (will load on next login anyway)"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Verify the sink is actually loaded (only meaningful in install mode).
|
||||
if (( ! CHECK_ONLY )); then
|
||||
sleep 1
|
||||
if pactl list short sinks 2>/dev/null | grep -q '^[0-9]*[[:space:]]\+mpd_stream\b'; then
|
||||
ok "mpd_stream sink loaded"
|
||||
else
|
||||
warn "mpd_stream sink NOT visible to pactl — restart pipewire-pulse or relog and re-check"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ────────────────────────────────────────────────────────────────────────────
|
||||
# Phase 6 — MPD
|
||||
# ────────────────────────────────────────────────────────────────────────────
|
||||
step "MPD config + service"
|
||||
|
||||
MPD_DIR="$HOME/.config/mpd"
|
||||
MPD_CONF="$MPD_DIR/mpd.conf"
|
||||
|
||||
write_mpd_conf() {
|
||||
mkdir -p "$MPD_DIR/playlists"
|
||||
cat > "$MPD_CONF" <<MPDCONF
|
||||
# ~/.config/mpd/mpd.conf — generated by scripts/deploy-rig.sh.
|
||||
# Re-run the deploy script to regenerate, or hand-edit for local tweaks.
|
||||
|
||||
music_directory "~/HDD/Music"
|
||||
playlist_directory "~/.config/mpd/playlists"
|
||||
db_file "~/.config/mpd/database"
|
||||
state_file "~/.config/mpd/state"
|
||||
sticker_file "~/.config/mpd/sticker.sql"
|
||||
|
||||
log_file "syslog"
|
||||
auto_update "yes"
|
||||
restore_paused "yes"
|
||||
filesystem_charset "UTF-8"
|
||||
|
||||
zeroconf_enabled "yes"
|
||||
zeroconf_name "MPD on $RIG_NAME_TC"
|
||||
|
||||
audio_output {
|
||||
type "pipewire"
|
||||
target "mpd_stream"
|
||||
name "MPD"
|
||||
}
|
||||
MPDCONF
|
||||
}
|
||||
|
||||
if [[ -f "$MPD_CONF" ]]; then
|
||||
if grep -q "MPD on $RIG_NAME_TC" "$MPD_CONF"; then
|
||||
ok "$MPD_CONF already configured for $RIG_NAME_TC"
|
||||
else
|
||||
warn "$MPD_CONF exists but zeroconf_name doesn't match $RIG_NAME_TC"
|
||||
if confirm "overwrite $MPD_CONF?" && would "rewrite $MPD_CONF"; then
|
||||
cp "$MPD_CONF" "$MPD_CONF.before-deploy.$$"
|
||||
write_mpd_conf
|
||||
ok "rewrote $MPD_CONF (backup: $MPD_CONF.before-deploy.$$)"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
if would "write $MPD_CONF"; then
|
||||
write_mpd_conf
|
||||
ok "wrote $MPD_CONF"
|
||||
fi
|
||||
fi
|
||||
|
||||
if systemctl --user is-enabled mpd.socket >/dev/null 2>&1; then
|
||||
ok "mpd.socket enabled"
|
||||
else
|
||||
if would "systemctl --user enable --now mpd.socket mpd.service"; then
|
||||
systemctl --user enable --now mpd.socket mpd.service
|
||||
ok "enabled mpd.socket + mpd.service"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -d "$HOME/HDD/Music" ]] && [[ -n "$(find "$HOME/HDD/Music" -maxdepth 3 -type f -print -quit 2>/dev/null)" ]]; then
|
||||
if would "mpc update"; then
|
||||
mpc update >/dev/null 2>&1 || warn "mpc update failed (mpd not yet running?)"
|
||||
ok "MPD library scan kicked off"
|
||||
fi
|
||||
else
|
||||
warn "~/HDD/Music is empty — MPD will have no tracks until you populate it"
|
||||
fi
|
||||
|
||||
# Avahi for zeroconf advertisement (system service).
|
||||
if systemctl is-enabled avahi-daemon.service >/dev/null 2>&1; then
|
||||
ok "avahi-daemon enabled (system)"
|
||||
else
|
||||
warn "avahi-daemon not enabled — MPD zeroconf won't advertise. To fix:"
|
||||
warn " sudo systemctl enable --now avahi-daemon.service"
|
||||
fi
|
||||
|
||||
# ────────────────────────────────────────────────────────────────────────────
|
||||
# Phase 7 — custom systemd --user units
|
||||
# ────────────────────────────────────────────────────────────────────────────
|
||||
step "Systemd --user units (bridge + bot)"
|
||||
|
||||
UNIT_DIR="$HOME/.config/systemd/user"
|
||||
mkdir -p "$UNIT_DIR"
|
||||
|
||||
write_unit() {
|
||||
local name body path
|
||||
name="$1"
|
||||
body="$2"
|
||||
path="$UNIT_DIR/$name"
|
||||
if [[ -f "$path" ]] && diff -q <(printf '%s' "$body") "$path" >/dev/null 2>&1; then
|
||||
ok "$name already up to date"
|
||||
elif would "write $path"; then
|
||||
printf '%s' "$body" > "$path"
|
||||
ok "wrote $path"
|
||||
fi
|
||||
}
|
||||
|
||||
write_unit obs-mpd-bridge.service "[Unit]
|
||||
Description=MPD → OBS WebSocket state bridge
|
||||
Documentation=file:%h/.config/obs-studio/bridges/mpd-state.py
|
||||
After=mpd.service mpd.socket pipewire.service
|
||||
Wants=mpd.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/usr/bin/python3 %h/.config/obs-studio/bridges/mpd-state.py
|
||||
Restart=on-failure
|
||||
RestartSec=2
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
"
|
||||
|
||||
write_unit obs-twitch-bot.service "[Unit]
|
||||
Description=Twitch chat → MPD control bot
|
||||
Documentation=file:%h/.config/obs-studio/twitch-bot/bot.py
|
||||
After=mpd.service mpd.socket network-online.target
|
||||
Wants=mpd.service network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/usr/bin/python3 %h/.config/obs-studio/twitch-bot/bot.py
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
"
|
||||
|
||||
if would "systemctl --user daemon-reload"; then
|
||||
systemctl --user daemon-reload
|
||||
fi
|
||||
|
||||
# bridge can start now (no secrets needed)
|
||||
if systemctl --user is-enabled obs-mpd-bridge.service >/dev/null 2>&1; then
|
||||
ok "obs-mpd-bridge.service enabled"
|
||||
else
|
||||
if would "systemctl --user enable --now obs-mpd-bridge.service"; then
|
||||
systemctl --user enable --now obs-mpd-bridge.service || \
|
||||
warn "obs-mpd-bridge failed to start — needs vendor/obs-config.js (see phase 8)"
|
||||
fi
|
||||
fi
|
||||
|
||||
# bot must NOT auto-start until .env is populated; just enable it
|
||||
if systemctl --user is-enabled obs-twitch-bot.service >/dev/null 2>&1; then
|
||||
ok "obs-twitch-bot.service enabled (start manually after .env is filled)"
|
||||
else
|
||||
if would "systemctl --user enable obs-twitch-bot.service"; then
|
||||
systemctl --user enable obs-twitch-bot.service
|
||||
ok "enabled obs-twitch-bot.service (NOT started — needs .env)"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ────────────────────────────────────────────────────────────────────────────
|
||||
# Phase 8 — OBS first-launch + WebSocket bootstrap
|
||||
# ────────────────────────────────────────────────────────────────────────────
|
||||
step "OBS first-launch + WebSocket bootstrap"
|
||||
|
||||
WS_CFG="$OBS_DIR/plugin_config/obs-websocket/config.json"
|
||||
|
||||
if [[ -f "$WS_CFG" ]]; then
|
||||
ok "obs-websocket config exists ($WS_CFG)"
|
||||
else
|
||||
if would "launch OBS once to seed plugin_config"; then
|
||||
echo " → launching OBS once to seed plugin configs."
|
||||
echo " Close the OBS window when it appears (or wait — script auto-stops it)."
|
||||
flatpak run com.obsproject.Studio >/dev/null 2>&1 &
|
||||
OBS_PID=$!
|
||||
for _ in $(seq 1 60); do
|
||||
[[ -f "$WS_CFG" ]] && break
|
||||
sleep 1
|
||||
done
|
||||
kill "$OBS_PID" 2>/dev/null || true
|
||||
wait "$OBS_PID" 2>/dev/null || true
|
||||
if [[ -f "$WS_CFG" ]]; then
|
||||
ok "obs-websocket config seeded"
|
||||
else
|
||||
warn "config not seeded after 60s — start OBS manually once and re-run"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -f "$WS_CFG" ]]; then
|
||||
if python3 -c "import json, sys; sys.exit(0 if json.load(open('$WS_CFG')).get('server_enabled') else 1)" 2>/dev/null; then
|
||||
ok "obs-websocket server_enabled = true"
|
||||
else
|
||||
if would "set server_enabled=true in $WS_CFG"; then
|
||||
python3 - "$WS_CFG" <<'PY'
|
||||
import json, sys
|
||||
p = sys.argv[1]
|
||||
d = json.load(open(p))
|
||||
d['server_enabled'] = True
|
||||
json.dump(d, open(p, 'w'), indent=4)
|
||||
PY
|
||||
ok "set server_enabled=true (takes effect on next OBS launch)"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -f "$OBS_DIR/vendor/obs-config.js" ]]; then
|
||||
ok "vendor/obs-config.js present"
|
||||
else
|
||||
if would "bash scripts/setup.sh"; then
|
||||
bash "$DIR/setup.sh" || warn "setup.sh failed — re-run after OBS WS port is reachable"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# ────────────────────────────────────────────────────────────────────────────
|
||||
# Phase 9 — secrets stub + final report
|
||||
# ────────────────────────────────────────────────────────────────────────────
|
||||
step "Secrets stub + final report"
|
||||
|
||||
stub_env() {
|
||||
local target="$1"
|
||||
if [[ -f "$target" ]]; then
|
||||
ok "$target exists (leaving alone)"
|
||||
return
|
||||
fi
|
||||
if [[ -f "$OBS_DIR/twitch-bot/.env.example" ]] && would "stub $target from .env.example"; then
|
||||
cp "$OBS_DIR/twitch-bot/.env.example" "$target"
|
||||
chmod 600 "$target"
|
||||
ok "stubbed $target (chmod 600)"
|
||||
fi
|
||||
}
|
||||
|
||||
stub_env "$OBS_DIR/twitch-bot/.env"
|
||||
stub_env "$OBS_DIR/twitch-bot/.env.ophi118"
|
||||
|
||||
# Refresh telemetry so the rig name is correct on this machine.
|
||||
if (( ! CHECK_ONLY )); then
|
||||
if would "scripts/telemetry.sh --collect --no-review (rig snapshot)"; then
|
||||
bash "$DIR/telemetry.sh" --collect --no-review || \
|
||||
warn "telemetry.sh --collect failed — re-run interactively to review"
|
||||
ok "telemetry refreshed (rig=$RIG_NAME_TC)"
|
||||
fi
|
||||
fi
|
||||
|
||||
cat <<DONE
|
||||
|
||||
╔═══════════════════════════════════════════════════════════════╗
|
||||
║ Deployment complete — manual steps remaining: ║
|
||||
╚═══════════════════════════════════════════════════════════════╝
|
||||
|
||||
1. Twitch bot token (chat:read chat:edit)
|
||||
https://twitchtokengenerator.com
|
||||
→ fill twitch-bot/.env
|
||||
|
||||
2. Broadcaster token (channel:manage:broadcast)
|
||||
https://twitchtokengenerator.com
|
||||
→ fill twitch-bot/.env.ophi118
|
||||
|
||||
3. Twitch stream key
|
||||
Twitch Dashboard → Settings → Stream → Copy stream key
|
||||
→ paste into OBS Settings → Stream
|
||||
(saved to basic/profiles/ophi118/service.json)
|
||||
|
||||
4. Once .env is populated:
|
||||
systemctl --user restart obs-twitch-bot.service
|
||||
|
||||
5. Review telemetry interactively:
|
||||
bash scripts/telemetry.sh --collect
|
||||
|
||||
6. Launch OBS:
|
||||
flatpak run com.obsproject.Studio
|
||||
|
||||
Sanity checks:
|
||||
pactl list short sinks | grep mpd_stream
|
||||
systemctl --user status mpd obs-mpd-bridge obs-twitch-bot
|
||||
mpc status
|
||||
ss -tlnp | grep 4455
|
||||
|
||||
DONE
|
||||
Reference in New Issue
Block a user