#!/bin/sh set -e # ───────────────────────────────────────────────────────────────────────────── # DragRace Runner Installer # Usage: curl -sSL https://get.dragrace.dev | sh # ───────────────────────────────────────────────────────────────────────────── DRAGRACE_VERSION="${DRAGRACE_VERSION:-latest}" DRAGRACE_REPO="${DRAGRACE_REPO:-dragrace-dev/runner}" DRAGRACE_BASE_URL="https://github.com/${DRAGRACE_REPO}/releases/download" INSTALL_DIR="/usr/local/bin" CONFIG_DIR="/etc/dragrace" CONFIG_FILE="${CONFIG_DIR}/runner.yaml" WORK_DIR="/var/dragrace" INSTALL_MODE="" # "test" or "full" # Colors RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[0;33m' BLUE='\033[0;34m' BOLD='\033[1m' NC='\033[0m' # No Color info() { printf "%b\n" "${BLUE}ℹ${NC} $1"; } ok() { printf "%b\n" "${GREEN}✅${NC} $1"; } warn() { printf "%b\n" "${YELLOW}⚠️${NC} $1"; } error() { printf "%b\n" "${RED}❌${NC} $1"; exit 1; } # ─── Banner ───────────────────────────────────────────────────────────────── printf "%b" "${BOLD}" echo " ____ ____ " echo " | _ \ _ __ __ _ __ _| _ \ __ _ ___ ___ " echo " | | | | '__/ _\` |/ _\` | |_) / _\` |/ __/ _ \\" echo " | |_| | | | (_| | (_| | _ < (_| | (_| __/" echo " |____/|_| \__,_|\__, |_| \_\__,_|\___\___|" echo " |___/ " printf "%b\n" "${NC}" echo " Runner Installer v${DRAGRACE_VERSION}" echo "" # ─── OS / Arch Detection ──────────────────────────────────────────────────── detect_platform() { OS="$(uname -s | tr '[:upper:]' '[:lower:]')" ARCH="$(uname -m)" case "$OS" in linux) OS="linux" ;; darwin) OS="darwin" ;; *) error "Unsupported OS: $OS" ;; esac case "$ARCH" in x86_64|amd64) ARCH="amd64" ;; aarch64|arm64) ARCH="arm64" ;; *) error "Unsupported architecture: $ARCH" ;; esac info "Detected platform: ${OS}/${ARCH}" } # ─── Prerequisites ────────────────────────────────────────────────────────── check_prerequisites() { # Docker (optional in test mode) if command -v docker > /dev/null 2>&1; then ok "Docker found: $(docker --version | head -1)" elif [ "$INSTALL_MODE" = "full" ]; then error "Docker is required for full runner mode. Install it from https://docs.docker.com/get-docker/" else warn "Docker not found. You can still use --executor process for local testing." fi # Git if ! command -v git > /dev/null 2>&1; then error "Git is required but not installed." fi ok "Git found: $(git --version)" # Root check (for Linux service install — full mode only) if [ "$INSTALL_MODE" = "full" ] && [ "$OS" = "linux" ] && [ "$(id -u)" -ne 0 ]; then warn "Not running as root. You may need to run with sudo for service installation." fi } # ─── Download Binary ──────────────────────────────────────────────────────── download_binary() { BINARY_NAME="dragrace-runner-${OS}-${ARCH}" # Resolve "latest" version via GitHub API if [ "$DRAGRACE_VERSION" = "latest" ]; then info "Resolving latest version from GitHub..." GITHUB_API_URL="https://api.github.com/repos/${DRAGRACE_REPO}/releases/latest" if command -v curl > /dev/null 2>&1; then DRAGRACE_VERSION=$(curl -fsSL "$GITHUB_API_URL" | grep '"tag_name"' | sed -E 's/.*"v?([^"]+)".*/\1/') elif command -v wget > /dev/null 2>&1; then DRAGRACE_VERSION=$(wget -qO- "$GITHUB_API_URL" | grep '"tag_name"' | sed -E 's/.*"v?([^"]+)".*/\1/') else error "Neither curl nor wget found" fi if [ -z "$DRAGRACE_VERSION" ]; then error "Could not resolve latest version from GitHub" fi info "Latest version: ${DRAGRACE_VERSION}" fi DOWNLOAD_URL="${DRAGRACE_BASE_URL}/v${DRAGRACE_VERSION}/${BINARY_NAME}" info "Downloading runner binary..." info "URL: ${DOWNLOAD_URL}" if command -v curl > /dev/null 2>&1; then curl -fsSL -o "/tmp/${BINARY_NAME}" "${DOWNLOAD_URL}" || error "Download failed. Check that version v${DRAGRACE_VERSION} exists." elif command -v wget > /dev/null 2>&1; then wget -q -O "/tmp/${BINARY_NAME}" "${DOWNLOAD_URL}" || error "Download failed. Check that version v${DRAGRACE_VERSION} exists." else error "Neither curl nor wget found" fi chmod +x "/tmp/${BINARY_NAME}" # Install location depends on mode if [ "$INSTALL_MODE" = "test" ]; then # Test mode: install to ~/.local/bin (no sudo needed) INSTALL_DIR="$HOME/.local/bin" mkdir -p "${INSTALL_DIR}" mv "/tmp/${BINARY_NAME}" "${INSTALL_DIR}/dragrace-runner" elif [ -w "${INSTALL_DIR}" ]; then mv "/tmp/${BINARY_NAME}" "${INSTALL_DIR}/dragrace-runner" else sudo mv "/tmp/${BINARY_NAME}" "${INSTALL_DIR}/dragrace-runner" fi ok "Binary installed to ${INSTALL_DIR}/dragrace-runner (v${DRAGRACE_VERSION})" } # ─── Configuration ────────────────────────────────────────────────────────── configure_runner() { echo "" printf "%b\n" "${BOLD}🔧 Runner Configuration${NC}" echo "────────────────────────────────────────" # API Key echo "" printf "%b\n" "Paste your API key (format: ${BOLD}client_id:client_secret${NC})" echo "Create one at: https://dragrace.dev/settings/runner-keys" printf "→ API Key: " read -r API_KEY < /dev/tty # Parse client_id:secret CLIENT_ID="${API_KEY%%:*}" CLIENT_SECRET="${API_KEY#*:}" if [ "$CLIENT_ID" = "$CLIENT_SECRET" ] || [ -z "$CLIENT_ID" ] || [ -z "$CLIENT_SECRET" ]; then error "Invalid API key format. Expected: client_id:client_secret" fi # Scope echo "" echo "Runner scope (how this runner will be used):" echo " [p] Public — available to everyone" echo " [o] Organization — available to one org's challenges" echo " [c] Challenge — dedicated to one challenge" printf "→ Scope [p/o/c] (default: p): " read -r SCOPE_CHOICE < /dev/tty case "${SCOPE_CHOICE:-p}" in p|P|public) SCOPE_TYPE="public" SCOPE_TARGET="" ;; o|O|org|organization) SCOPE_TYPE="organization" printf "→ Organization ID: " read -r SCOPE_TARGET < /dev/tty [ -z "$SCOPE_TARGET" ] && error "Organization ID is required" ;; c|C|challenge) SCOPE_TYPE="challenge" printf "→ Challenge ID: " read -r SCOPE_TARGET < /dev/tty [ -z "$SCOPE_TARGET" ] && error "Challenge ID is required" ;; *) error "Invalid scope choice: $SCOPE_CHOICE" ;; esac # NATS URL echo "" printf "→ NATS URL (default: nats://nats.dragrace.dev:4222): " read -r NATS_URL < /dev/tty NATS_URL="${NATS_URL:-nats://nats.dragrace.dev:4222}" # Create config directory if [ -w "$(dirname "${CONFIG_DIR}")" ]; then mkdir -p "${CONFIG_DIR}" "${WORK_DIR}" else sudo mkdir -p "${CONFIG_DIR}" "${WORK_DIR}" fi # Write config file cat > "/tmp/runner.yaml" << EOF # DragRace Runner Configuration # Edit this file and restart the service to reconfigure nats_url: "${NATS_URL}" client_id: "${CLIENT_ID}" client_secret: "${CLIENT_SECRET}" scope: type: "${SCOPE_TYPE}" target_id: "${SCOPE_TARGET}" work_dir: "${WORK_DIR}" EOF if [ -w "${CONFIG_DIR}" ]; then mv "/tmp/runner.yaml" "${CONFIG_FILE}" else sudo mv "/tmp/runner.yaml" "${CONFIG_FILE}" sudo chmod 600 "${CONFIG_FILE}" fi ok "Configuration saved to ${CONFIG_FILE}" } # ─── Service Installation ─────────────────────────────────────────────────── install_service() { echo "" printf "%b\n" "${BOLD}🔧 Service Installation${NC}" echo "────────────────────────────────────────" if [ "$OS" = "linux" ]; then install_systemd_service elif [ "$OS" = "darwin" ]; then install_launchd_service fi } install_systemd_service() { SERVICE_FILE="/etc/systemd/system/dragrace-runner.service" cat > "/tmp/dragrace-runner.service" << EOF [Unit] Description=DragRace Runner After=network.target docker.service Requires=docker.service [Service] Type=simple EnvironmentFile=-${CONFIG_DIR}/runner.env ExecStart=${INSTALL_DIR}/dragrace-runner Restart=always RestartSec=10 Environment=RUNNER_CONFIG=${CONFIG_FILE} [Install] WantedBy=multi-user.target EOF # Generate env file from yaml config cat > "/tmp/runner.env" << EOF NATS_URL=${NATS_URL} RUNNER_CLIENT_ID=${CLIENT_ID} RUNNER_CLIENT_SECRET=${CLIENT_SECRET} RUNNER_SCOPE=${SCOPE_TYPE} RUNNER_SCOPE_TARGET=${SCOPE_TARGET} RUNNER_WORK_DIR=${WORK_DIR} EOF sudo mv "/tmp/dragrace-runner.service" "${SERVICE_FILE}" sudo mv "/tmp/runner.env" "${CONFIG_DIR}/runner.env" sudo chmod 600 "${CONFIG_DIR}/runner.env" sudo systemctl daemon-reload sudo systemctl enable dragrace-runner sudo systemctl start dragrace-runner ok "systemd service installed and started" info "Commands:" info " sudo systemctl status dragrace-runner # check status" info " sudo journalctl -u dragrace-runner -f # view logs" info " sudo systemctl restart dragrace-runner # restart" } install_launchd_service() { PLIST_FILE="$HOME/Library/LaunchAgents/dev.dragrace.runner.plist" mkdir -p "$HOME/Library/LaunchAgents" cat > "${PLIST_FILE}" << EOF Label dev.dragrace.runner ProgramArguments ${INSTALL_DIR}/dragrace-runner EnvironmentVariables NATS_URL ${NATS_URL} RUNNER_CLIENT_ID ${CLIENT_ID} RUNNER_CLIENT_SECRET ${CLIENT_SECRET} RUNNER_SCOPE ${SCOPE_TYPE} RUNNER_SCOPE_TARGET ${SCOPE_TARGET} RUNNER_WORK_DIR ${WORK_DIR} RunAtLoad KeepAlive StandardOutPath /tmp/dragrace-runner.log StandardErrorPath /tmp/dragrace-runner.err EOF launchctl unload "${PLIST_FILE}" 2>/dev/null || true launchctl load "${PLIST_FILE}" ok "launchd service installed and started" info "Commands:" info " launchctl list | grep dragrace # check status" info " tail -f /tmp/dragrace-runner.log # view logs" info " launchctl unload ${PLIST_FILE} # stop" info " launchctl load ${PLIST_FILE} # start" } # ─── Mode Selection ───────────────────────────────────────────────────────── select_mode() { # Support non-interactive mode via --test-only flag for arg in "$@"; do case "$arg" in --test-only) INSTALL_MODE="test"; return ;; --full) INSTALL_MODE="full"; return ;; esac done printf "%b\n" "${BOLD}How will you use the runner?${NC}" echo "" echo " [1] 🧪 Test mode — Test challenges locally (no account needed)" echo " [2] 🏁 Full runner — Connect to DragRace and run official jobs" echo "" printf "→ Choice [1/2] (default: 1): " read -r MODE_CHOICE < /dev/tty case "${MODE_CHOICE:-1}" in 1|test) INSTALL_MODE="test" ;; 2|full) INSTALL_MODE="full" ;; *) error "Invalid choice: $MODE_CHOICE" ;; esac } # ─── Main ─────────────────────────────────────────────────────────────────── main() { select_mode "$@" detect_platform check_prerequisites download_binary if [ "$INSTALL_MODE" = "full" ]; then configure_runner install_service echo "" echo "────────────────────────────────────────" printf "%b\n" "${GREEN}${BOLD}🏁 DragRace Runner installed successfully!${NC}" echo "" info "Config file: ${CONFIG_FILE}" info "To reconfigure: edit ${CONFIG_FILE} and restart the service" else echo "" echo "────────────────────────────────────────" printf "%b\n" "${GREEN}${BOLD}🧪 DragRace Runner installed (test mode)!${NC}" echo "" info "Binary: ${INSTALL_DIR}/dragrace-runner" # Check if INSTALL_DIR is in PATH case ":$PATH:" in *":${INSTALL_DIR}:"*) ;; *) warn "${INSTALL_DIR} is not in your PATH. Add it with:" echo " export PATH=\"${INSTALL_DIR}:\$PATH\"" echo "" ;; esac info "Get started:" echo " dragrace-runner test --challenge ./my-challenge --executor process" echo " dragrace-runner test -c ./challenge -s ./solution --executor process -E ROW_COUNT=1000" fi echo "" } main "$@"