#!/bin/sh # Installs horizon-server (docs/server.md) on Linux or macOS: the latest Horizon release for this computer, # downloaded from GitHub Releases and checked against the SHA-256 GitHub publishes for it. # # curl -fsSL https://raw.githubusercontent.com/ludovic111/horizon/main/packaging/server/install.sh | sh # curl -fsSL .../install.sh | sh -s -- --version v0.5.0 --dir /opt/horizon --bin /usr/local/bin # # It needs curl, tar or unzip, and sha256sum or shasum; it never asks for sudo (pick folders you can write). # Releases go to /releases/, /current points at the one in use, and /horizon-server # runs it. The game files you prepare from your own Minecraft stay in /game-files across updates. # Nothing of Mojang's is downloaded. set -eu REPO=ludovic111/horizon VERSION=latest DIR="${XDG_DATA_HOME:-$HOME/.local/share}/horizon" BIN="$HOME/.local/bin" die() { echo "horizon install: $*" >&2; exit 1; } while [ $# -gt 0 ]; do case "$1" in --version) [ $# -ge 2 ] || die "--version needs a tag"; VERSION=$2; shift 2 ;; --dir) [ $# -ge 2 ] || die "--dir needs a folder"; DIR=$2; shift 2 ;; --bin) [ $# -ge 2 ] || die "--bin needs a folder"; BIN=$2; shift 2 ;; -h|--help) sed -n '2,12p' "$0" 2>/dev/null || true; exit 0 ;; *) die "unknown option $1" ;; esac done case "$(uname -s)-$(uname -m)" in Linux-x86_64) ASSET=horizon-linux-x86_64.tar.gz ;; Darwin-arm64) ASSET=horizon-macos-arm64.zip ;; *) die "no Horizon release for $(uname -s) $(uname -m) yet: build it from source (docs/server.md)" ;; esac command -v curl >/dev/null || die "curl is needed" if command -v sha256sum >/dev/null; then SHA="sha256sum" elif command -v shasum >/dev/null; then SHA="shasum -a 256" else die "sha256sum or shasum is needed"; fi if [ "$VERSION" = latest ]; then API="https://api.github.com/repos/$REPO/releases/latest" else API="https://api.github.com/repos/$REPO/releases/tags/$VERSION"; fi JSON=$(curl -fsSL -H "Accept: application/vnd.github+json" "$API") || die "no release $VERSION on GitHub" TAG=$(printf '%s\n' "$JSON" | sed -n 's/^ "tag_name": "\([^"]*\)".*/\1/p' | head -n 1) [ -n "$TAG" ] || die "could not read the release's tag" # The asset's download address and digest, from its block in the release's JSON. FIELDS=$(printf '%s\n' "$JSON" | awk -v asset="$ASSET" ' /"name": / { line = $0; sub(/.*"name": "/, "", line); sub(/".*/, "", line); current = line } current == asset && /"digest": / { line = $0; sub(/.*"digest": "sha256:/, "", line); sub(/".*/, "", line); digest = line } current == asset && /"browser_download_url": / { line = $0; sub(/.*"browser_download_url": "/, "", line); sub(/".*/, "", line); url = line } END { print url; print digest }') URL=$(printf '%s\n' "$FIELDS" | sed -n 1p) DIGEST=$(printf '%s\n' "$FIELDS" | sed -n 2p) [ -n "$URL" ] || die "release $TAG has no $ASSET" [ -n "$DIGEST" ] || die "release $TAG publishes no SHA-256 for $ASSET" TMP=$(mktemp -d) trap 'rm -rf "$TMP"' EXIT echo "Downloading Horizon $TAG ($ASSET)..." curl -fL --progress-bar -o "$TMP/$ASSET" "$URL" GOT=$($SHA "$TMP/$ASSET" | cut -d' ' -f1) [ "$GOT" = "$DIGEST" ] || die "$ASSET doesn't match its SHA-256 (got $GOT, expected $DIGEST): not installed" mkdir -p "$TMP/x" case "$ASSET" in *.tar.gz) tar -xzf "$TMP/$ASSET" -C "$TMP/x" ;; *.zip) command -v unzip >/dev/null || die "unzip is needed"; unzip -q "$TMP/$ASSET" -d "$TMP/x" ;; esac SOURCE=$(dirname "$(find "$TMP/x" -name horizon-server -type f | head -n 1)") [ -x "$SOURCE/horizon-server" ] || die "$ASSET holds no horizon-server (a release from before it existed?)" RELEASE="$DIR/releases/$TAG" mkdir -p "$DIR/releases" "$DIR/game-files/horizon-client" "$BIN" rm -rf "$RELEASE" mv "$SOURCE" "$RELEASE" # Prepared game files live outside the release, so updates and rollbacks keep them. mkdir -p "$RELEASE/.local" ln -sfn "$DIR/game-files/horizon-client" "$RELEASE/.local/horizon-client" ln -sfn "$RELEASE" "$DIR/current" # Small launchers rather than links: the programs find their release folder from where they run. for tool in horizon-server horizon-prepare; do printf '#!/bin/sh\nexec "%s/current/%s" "$@"\n' "$DIR" "$tool" > "$BIN/$tool" chmod +x "$BIN/$tool" done echo "Installed Horizon $TAG in $RELEASE; horizon-server and horizon-prepare are in $BIN." case ":$PATH:" in *":$BIN:"*) ;; *) echo "Add $BIN to your PATH, for example: echo 'export PATH=\"$BIN:\$PATH\"' >> ~/.profile" ;; esac if [ -z "$(ls -A "$DIR/game-files/horizon-client" 2>/dev/null)" ]; then echo "Next, prepare the game files from your own Minecraft 26.3 (it needs Java 25 and the game installed once" echo "by the official launcher; see docs/server.md):" echo " horizon-prepare --prepare --server-jar /path/to/server.jar" fi echo "Then set up a server: mkdir my-server && cd my-server && horizon-server init"