#!/bin/bash # Install OrderPy Bridge daemon (systemd). Run with sudo. set -e INSTALL_PREFIX="${INSTALL_PREFIX:-/opt/orderpy-bridge}" CONFIG_DIR="/etc/orderpy-bridge" DATA_DIR="/var/lib/orderpy-bridge" SERVICE_USER="orderpy-bridge" SERVICE_GROUP="orderpy-bridge" # Optional colors and formatting (disable if not a tty or TERM is dumb) if [[ -t 1 ]] && [[ "${TERM:-dumb}" != "dumb" ]]; then RED="$(tput setaf 1 2>/dev/null || true)" GREEN="$(tput setaf 2 2>/dev/null || true)" YELLOW="$(tput setaf 3 2>/dev/null || true)" BOLD="$(tput bold 2>/dev/null || true)" SGR0="$(tput sgr0 2>/dev/null || true)" else RED="" GREEN="" YELLOW="" BOLD="" SGR0="" fi # Preflight: print check label (no newline), then call ok_line or fail_line with result (overwrites line with \r) preflight_check() { printf " %-28s " "$1"; } ok_line() { printf "\r %-28s ${GREEN}[ OK ]${SGR0} %s\n" "$PREFLIGHT_LABEL" "$1"; } fail_line() { printf "\r %-28s ${RED}[ FAIL ]${SGR0} %s\n" "$PREFLIGHT_LABEL" "$1" >&2; return 1; } # Resolve orderpy-bridge repo root (directory containing bridge_core and daemon/) SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" BRIDGE_ROOT="$SCRIPT_DIR" while [[ -n "$BRIDGE_ROOT" ]]; do if [[ -d "$BRIDGE_ROOT/bridge_core" ]] && [[ -f "$BRIDGE_ROOT/daemon/main.py" ]]; then break fi BRIDGE_ROOT="${BRIDGE_ROOT%/*}" [[ "$BRIDGE_ROOT" == "${BRIDGE_ROOT%/*}" ]] && BRIDGE_ROOT="" done if [[ -z "$BRIDGE_ROOT" ]] || [[ ! -d "$BRIDGE_ROOT/bridge_core" ]]; then echo "${RED}Error:${SGR0} Run from orderpy-bridge repo (bridge_core and daemon/ must exist)." >&2 exit 1 fi if [[ "$(id -u)" -ne 0 ]]; then echo "${RED}Error:${SGR0} This script must be run with sudo." >&2 exit 1 fi # --- Preflight checks --- echo "" echo "${BOLD}Preflight checks${SGR0}" PREFLIGHT_FAIL=0 # Python 3.11+ PREFLIGHT_LABEL="Python 3.11+" preflight_check "$PREFLIGHT_LABEL" PYVER="" if command -v python3 >/dev/null 2>&1; then PYVER="$(python3 -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")' 2>/dev/null)" || true fi if [[ -z "$PYVER" ]]; then fail_line "python3 not found. Install Python 3.11+ (e.g. apt install python3 python3-venv)." || PREFLIGHT_FAIL=1 else MAJOR="${PYVER%%.*}"; MINOR="${PYVER#*.}"; MINOR="${MINOR%%.*}" if [[ "$MAJOR" -lt 3 ]] || { [[ "$MAJOR" -eq 3 ]] && [[ "$MINOR" -lt 11 ]]; }; then fail_line "Python $PYVER found; 3.11+ required. Install a newer python3." || PREFLIGHT_FAIL=1 else ok_line "Python $PYVER" fi fi # python3-venv PREFLIGHT_LABEL="python3-venv" preflight_check "$PREFLIGHT_LABEL" if python3 -m venv --help >/dev/null 2>&1; then ok_line "available" else fail_line "not available. Install it (e.g. apt install python3-venv)." || PREFLIGHT_FAIL=1 fi # pip or ensurepip (venv will get pip via ensurepip or get-pip.py) PREFLIGHT_LABEL="pip / ensurepip" preflight_check "$PREFLIGHT_LABEL" if python3 -m pip --version >/dev/null 2>&1 || python3 -c 'import ensurepip' 2>/dev/null; then ok_line "available" else fail_line "pip not found. Install python3-pip or python3-venv (e.g. apt install python3-venv python3-pip)." || PREFLIGHT_FAIL=1 fi # systemd PREFLIGHT_LABEL="systemd" preflight_check "$PREFLIGHT_LABEL" if command -v systemctl >/dev/null 2>&1 && [[ -d /run/systemd/system ]]; then ok_line "available" else fail_line "not found or not running. This installer is for systemd-based systems." || PREFLIGHT_FAIL=1 fi # Write access to target dirs PREFLIGHT_LABEL="Write access (prefix)" preflight_check "$PREFLIGHT_LABEL" PARENT="$(dirname "$INSTALL_PREFIX")" if [[ -w "$PARENT" ]] || [[ ! -e "$PARENT" ]]; then ok_line "Can install to $INSTALL_PREFIX" else fail_line "No write access to $(dirname "$INSTALL_PREFIX"). Set INSTALL_PREFIX or fix permissions." || PREFLIGHT_FAIL=1 fi PREFLIGHT_LABEL="Config directory" preflight_check "$PREFLIGHT_LABEL" if [[ -w /etc ]] || [[ -d "$CONFIG_DIR" && -w "$CONFIG_DIR" ]]; then ok_line "/etc/orderpy-bridge can be created" else fail_line "Cannot create $CONFIG_DIR. Run with sudo." || PREFLIGHT_FAIL=1 fi echo "" if [[ "$PREFLIGHT_FAIL" -ne 0 ]]; then echo "${RED}${BOLD}Preflight failed.${SGR0} Fix the issues above and run the script again." >&2 exit 1 fi echo "${GREEN}All preflight checks passed.${SGR0}" echo "" # Installation runs in background; we show a progress bar until it finishes. ENV_FILE="$CONFIG_DIR/orderpy-bridge.env" INSTALL_LOG="$(mktemp)" INSTALL_EXIT_FILE="$(mktemp)" trap 'rm -f "$INSTALL_LOG" "$INSTALL_EXIT_FILE"' EXIT do_install() { set +e # User/group if ! getent group "$SERVICE_GROUP" >/dev/null; then groupadd --system "$SERVICE_GROUP" fi if ! getent passwd "$SERVICE_USER" >/dev/null; then useradd --system --no-create-home --gid "$SERVICE_GROUP" "$SERVICE_USER" fi # Directories install -d -m 755 "$CONFIG_DIR" install -d -m 750 -o "$SERVICE_USER" -g "$SERVICE_GROUP" "$DATA_DIR" install -d -m 755 "$(dirname "$INSTALL_PREFIX")" install -d -m 755 -o "$SERVICE_USER" -g "$SERVICE_GROUP" "$INSTALL_PREFIX" # Config: env file only if missing if [[ ! -f "$ENV_FILE" ]]; then install -m 640 -o root -g "$SERVICE_GROUP" "$BRIDGE_ROOT/daemon/orderpy-bridge.env.example" "$ENV_FILE" fi # Application files cp -r "$BRIDGE_ROOT/bridge_core" "$INSTALL_PREFIX/" install -m 644 "$BRIDGE_ROOT/daemon/main.py" "$INSTALL_PREFIX/" install -m 644 "$BRIDGE_ROOT/daemon/requirements.txt" "$INSTALL_PREFIX/" chown -R "$SERVICE_USER:$SERVICE_GROUP" "$INSTALL_PREFIX" # Python venv: ensure pip is available, then install deps and verify uvicorn if [[ ! -x "$INSTALL_PREFIX/venv/bin/uvicorn" ]]; then python3 -m venv "$INSTALL_PREFIX/venv" VENV_PYTHON="$INSTALL_PREFIX/venv/bin/python" # Ensure pip in venv (some minimal python3-venv installs create venv without pip) if ! "$VENV_PYTHON" -m pip --version >/dev/null 2>&1; then if "$VENV_PYTHON" -m ensurepip --upgrade 2>/dev/null; then : # pip now available else GET_PIP="$(mktemp)" if curl -sSf -o "$GET_PIP" "https://bootstrap.pypa.io/get-pip.py" && "$VENV_PYTHON" "$GET_PIP"; then rm -f "$GET_PIP" else rm -f "$GET_PIP" echo "FATAL: Could not install pip into venv (ensurepip failed and get-pip.py failed or unavailable)." >&2 exit 1 fi fi fi "$VENV_PYTHON" -m pip install --disable-pip-version-check -q -r "$INSTALL_PREFIX/requirements.txt" || { echo "FATAL: pip install -r requirements.txt failed." >&2 exit 1 } if [[ ! -x "$INSTALL_PREFIX/venv/bin/uvicorn" ]]; then echo "FATAL: uvicorn not found after pip install. Check requirements.txt and network." >&2 exit 1 fi chown -R "$SERVICE_USER:$SERVICE_GROUP" "$INSTALL_PREFIX/venv" fi # systemd unit install -m 644 "$BRIDGE_ROOT/daemon/systemd/orderpy-bridge.service" /etc/systemd/system/ systemctl daemon-reload exit 0 } ( do_install; r=$?; echo $r > "$INSTALL_EXIT_FILE"; exit $r ) >> "$INSTALL_LOG" 2>&1 & INSTALL_PID=$! # Progress bar (indeterminate: moving cursor) BAR_W=24 bar_pos=0 printf "\n Installing to %s\n " "$INSTALL_PREFIX" while kill -0 "$INSTALL_PID" 2>/dev/null; do bar="" for (( i=0; i/dev/null || true EXIT_CODE=0 [[ -f "$INSTALL_EXIT_FILE" ]] && read -r EXIT_CODE < "$INSTALL_EXIT_FILE" || true if [[ "$EXIT_CODE" -ne 0 ]]; then printf "\r [%*s] ${RED}Installation failed.${SGR0}\n" "$BAR_W" " " echo "" echo "Last lines of install log:" tail -n 20 "$INSTALL_LOG" | sed 's/^/ /' cp "$INSTALL_LOG" /tmp/orderpy-bridge-install.log 2>/dev/null || true echo "" echo "Full log: /tmp/orderpy-bridge-install.log" exit "$EXIT_CODE" fi printf "\r [%s] ${GREEN}Done!${SGR0} \n\n" "$(printf '%*s' "$BAR_W" '' | tr ' ' '=')" echo "If this is a fresh install, edit $ENV_FILE and set ORDERPY_CLOUD_URL, ORDERPY_ALLOWED_ORIGINS." echo "" echo "Next steps:" echo " sudo systemctl enable --now orderpy-bridge" echo " sudo systemctl status orderpy-bridge"