#!/usr/bin/env bash # Bootstrap a complete NodeCG + nodecg-venue-gfx install on a venue machine. # # Creates a standalone NodeCG installation (the nodecg npm package extracted # as the install root — the layout NodeCG itself calls "standalone"; running # it as a node_modules dependency is still flagged experimental upstream), # clones this bundle into bundles/, builds it, writes the config files, and # generates an importable OBS scene collection. # # Usage: # ./install.sh [--dir ~/venue-gfx] [--port 9090] [--obs-password SECRET] # [--repo ] [--local ] [--yes] # # --local uses an existing checkout (copied, not cloned) instead of --repo; # when run from inside a checkout, --local defaults to that checkout. # --yes accepts all defaults without prompting. set -euo pipefail BUNDLE=nodecg-venue-gfx DEFAULT_REPO="https://git.dvdrw.dev/kckljajicevo/nodecg-venue-gfx.git" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" dir="" port="" obs_password="" repo="" local_src="" assume_yes=0 while [[ $# -gt 0 ]]; do case "$1" in --dir) dir="$2"; shift 2 ;; --port) port="$2"; shift 2 ;; --obs-password) obs_password="$2"; shift 2 ;; --repo) repo="$2"; shift 2 ;; --local) local_src="$2"; shift 2 ;; --yes|-y) assume_yes=1; shift ;; --help|-h) sed -n '2,17p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;; *) echo "unknown option: $1" >&2; exit 1 ;; esac done # ── prerequisites ───────────────────────────────────────────────────────── command -v node >/dev/null || { echo "node not found — install Node.js 20+" >&2; exit 1; } command -v npm >/dev/null || { echo "npm not found" >&2; exit 1; } node_major=$(node -p 'process.versions.node.split(".")[0]') if (( node_major < 20 )); then echo "Node.js $(node -v) is too old — need 20+" >&2 exit 1 fi # When run from inside a checkout of the bundle, default to copying it. if [[ -z $local_src && -z $repo && -f "$SCRIPT_DIR/../package.json" ]]; then if node -p 'require(process.argv[1]).name' "$SCRIPT_DIR/../package.json" 2>/dev/null | grep -qx "$BUNDLE"; then local_src="$(cd "$SCRIPT_DIR/.." && pwd)" fi fi ask() { # ask local prompt="$1" var="$2" def="$3" answer if [[ -n ${!var} ]]; then return; fi if (( assume_yes )); then printf -v "$var" '%s' "$def"; return; fi read -r -p "$prompt [$def]: " answer printf -v "$var" '%s' "${answer:-$def}" } ask "Install directory" dir "$HOME/venue-gfx" ask "NodeCG port" port "9090" ask "OBS websocket password (empty = auth disabled)" obs_password "" if [[ -z $local_src ]]; then ask "Bundle git URL" repo "$DEFAULT_REPO" fi dir="${dir/#\~/$HOME}" echo echo "Installing into $dir (NodeCG on port $port)" # ── NodeCG itself (standalone layout: install root = the nodecg package) ── mkdir -p "$dir" cd "$dir" if [[ -f package.json ]] && node -p 'require("./package.json").name' | grep -qx nodecg; then echo "NodeCG already present — skipping" else echo "Downloading NodeCG…" tarball=$(npm pack nodecg@^2 --silent | tail -n1) tar xzf "$tarball" --strip-components=1 rm -f "$tarball" echo "Installing NodeCG dependencies…" npm install --omit=dev --no-audit --no-fund fi mkdir -p bundles cfg obs # ── the bundle ──────────────────────────────────────────────────────────── if [[ ! -d bundles/$BUNDLE ]]; then if [[ -n $local_src ]]; then echo "Copying bundle from $local_src…" mkdir -p "bundles/$BUNDLE" (cd "$local_src" && git archive HEAD 2>/dev/null || tar cf - --exclude node_modules --exclude .git .) \ | tar xf - -C "bundles/$BUNDLE" else echo "Cloning $repo…" git clone "$repo" "bundles/$BUNDLE" fi fi echo "Building the bundle…" (cd "bundles/$BUNDLE" && npm ci --no-audit --no-fund && npm run build) # ── config ──────────────────────────────────────────────────────────────── if [[ ! -f cfg/nodecg.json ]]; then printf '{\n\t"port": %s\n}\n' "$port" > cfg/nodecg.json echo "Wrote cfg/nodecg.json" fi if [[ ! -f cfg/$BUNDLE.json ]]; then node - "$obs_password" > "cfg/$BUNDLE.json" <<'EOF' const password = process.argv[2] ?? ""; process.stdout.write(JSON.stringify({ locale: "en", obs: { enabled: true, url: "ws://127.0.0.1:4455", password }, }, null, "\t") + "\n"); EOF echo "Wrote cfg/$BUNDLE.json" fi # ── OBS scene collection ────────────────────────────────────────────────── node "bundles/$BUNDLE/scripts/generate-obs-scenes.mjs" \ --config "cfg/$BUNDLE.json" \ --nodecg-url "http://localhost:$port" \ --name VenueGfx \ --out obs/VenueGfx.json # ── done ────────────────────────────────────────────────────────────────── cat <