117 lines
4.2 KiB
Bash
Executable File
117 lines
4.2 KiB
Bash
Executable File
#!/bin/bash
|
|
# Install a WarpEngine store on a Batocera box.
|
|
#
|
|
# This installer belongs to the engine, so it takes the store as a parameter:
|
|
# point STORE_CONFIG at a config.json — a local path or a URL — and it sets up
|
|
# that store. A store repository's own install.sh is a three-line wrapper that
|
|
# supplies its config; see README.
|
|
#
|
|
# STORE_CONFIG=./config.json ./install.sh
|
|
# STORE_CONFIG=https://git.example.org/tools/my-store/raw/branch/master/config.json \
|
|
# bash <(curl -fsSL https://git.teletypegames.org/tools/warp-engine-batocera-store/raw/branch/master/install.sh)
|
|
#
|
|
set -euo pipefail
|
|
|
|
STORE_ROOT="${BATOCERA_STORE_ROOT:-/userdata/system/batocera-store}"
|
|
PORTS_DIR="${BATOCERA_PORTS_DIR:-/userdata/roms/ports}"
|
|
ENGINE_RAW_BASE="${ENGINE_RAW_BASE:-https://git.teletypegames.org/tools/warp-engine-batocera-store/raw/branch/master}"
|
|
STORE_CONFIG="${STORE_CONFIG:-}"
|
|
# Only set when run as a file; piped from curl "$0" is `bash`, and then the
|
|
# engine has to come from the forge rather than from whatever the cwd holds.
|
|
SRC_DIR=""
|
|
if [ -f "$0" ]; then
|
|
SRC_DIR="$(cd "$(dirname "$(readlink -f "$0")")" && pwd)"
|
|
fi
|
|
|
|
say() { echo "[install] $*"; }
|
|
die() { echo "[install] error: $*" >&2; exit 1; }
|
|
|
|
command -v python3 >/dev/null 2>&1 || die "python3 is required"
|
|
[ -n "$STORE_CONFIG" ] || die "STORE_CONFIG is required (path or URL of a store config.json)"
|
|
[ -d /userdata ] || say "warning: /userdata not found — this does not look like a Batocera box"
|
|
|
|
fetch() { # fetch <source> <dest>; source may be a local path or a URL
|
|
case "$1" in
|
|
http://*|https://*) curl -fsSL "$1" -o "$2" ;;
|
|
*) [ -f "$1" ] || die "no such file: $1"; cp "$1" "$2" ;;
|
|
esac
|
|
}
|
|
|
|
# Stage the config first: the store id in it decides where everything lands.
|
|
TMP_CFG="$(mktemp)"
|
|
trap 'rm -f "$TMP_CFG"' EXIT
|
|
say "reading store config from $STORE_CONFIG"
|
|
fetch "$STORE_CONFIG" "$TMP_CFG"
|
|
|
|
STORE_META="$(python3 -c '
|
|
import json, sys
|
|
cfg = json.load(open(sys.argv[1])).get("store") or {}
|
|
sid = cfg.get("id") or ""
|
|
if not sid or "/" in sid:
|
|
sys.exit("store.id is required and must not contain a slash")
|
|
print(sid, cfg.get("name") or sid)
|
|
' "$TMP_CFG")" || die "cannot read $STORE_CONFIG"
|
|
|
|
STORE_ID="${STORE_META%% *}"
|
|
STORE_NAME="${STORE_META#* }"
|
|
|
|
STORE_HOME="$STORE_ROOT/$STORE_ID"
|
|
PORT_NAME="${BATOCERA_PORT_NAME:-$STORE_NAME}"
|
|
LAUNCHER="$STORE_ROOT/$STORE_ID-store"
|
|
|
|
mkdir -p "$STORE_HOME" "$PORTS_DIR"
|
|
|
|
if [ -n "$SRC_DIR" ] && [ -f "$SRC_DIR/store.py" ]; then
|
|
say "installing the engine from $SRC_DIR"
|
|
install -m 0755 "$SRC_DIR/store.py" "$STORE_HOME/store.py"
|
|
else
|
|
say "downloading the engine from $ENGINE_RAW_BASE"
|
|
curl -fsSL "$ENGINE_RAW_BASE/store.py" -o "$STORE_HOME/store.py"
|
|
chmod 0755 "$STORE_HOME/store.py"
|
|
fi
|
|
|
|
# The config is the store's identity, so it is always refreshed; state.json,
|
|
# catalog.json and the log stay put, which makes re-running the upgrade path.
|
|
if [ -f "$STORE_HOME/config.json" ]; then
|
|
say "updating $STORE_HOME/config.json (previous copy kept as config.json.bak)"
|
|
cp "$STORE_HOME/config.json" "$STORE_HOME/config.json.bak"
|
|
fi
|
|
install -m 0644 "$TMP_CFG" "$STORE_HOME/config.json"
|
|
|
|
# A short launcher so the CLI is one path, not a python invocation.
|
|
cat > "$LAUNCHER" <<EOF
|
|
#!/bin/bash
|
|
# $STORE_NAME — CLI wrapper around the shared batocera-store engine.
|
|
export BATOCERA_STORE_HOME="$STORE_HOME"
|
|
exec "$STORE_HOME/store.py" --config "$STORE_HOME/config.json" "\$@"
|
|
EOF
|
|
chmod 0755 "$LAUNCHER"
|
|
|
|
# The Ports entry: EmulationStation gives launched scripts no console, so send
|
|
# everything to a log file the user can read over SSH.
|
|
cat > "$PORTS_DIR/$PORT_NAME.sh" <<EOF
|
|
#!/bin/bash
|
|
# $STORE_NAME — syncs the catalog into this box's ROM folders.
|
|
exec >>"$STORE_HOME/store.log" 2>&1
|
|
echo "=== \$(date -Iseconds) sync started ==="
|
|
"$LAUNCHER" sync-from-es
|
|
echo "=== \$(date -Iseconds) sync finished (exit \$?) ==="
|
|
EOF
|
|
chmod 0755 "$PORTS_DIR/$PORT_NAME.sh"
|
|
say "ports entry: $PORTS_DIR/$PORT_NAME.sh"
|
|
|
|
say "running the first sync"
|
|
"$LAUNCHER" --verbose sync --no-restart
|
|
|
|
cat <<EOF
|
|
|
|
[install] done.
|
|
|
|
Menu: Ports -> "$PORT_NAME" (re-run any time to pull new releases)
|
|
CLI: $LAUNCHER list|sync|remove
|
|
Config: $STORE_HOME/config.json
|
|
Log: $STORE_HOME/store.log
|
|
|
|
Restart EmulationStation to see the games: batocera-es-swissknife --restart
|
|
EOF
|