#!/usr/bin/env bash # # Install the RDM card reader as a service that starts at boot and keeps the # card on the reader available over HTTP for the web page to claim. # # Run as root on the Raspberry Pi: # # sudo ./deploy/install.sh --origin https://your.web.app # # Options: # --origin URL web app origin allowed to read the API (repeatable) # --port PORT HTTP port for the bridge (default 8765) # --dir PATH install location (default /opt/rdm-card-reader) # --user NAME service account (default rdmcard) # --no-service install and configure, but do not enable the service # set -euo pipefail ORIGINS=() BRIDGE_PORT=8765 INSTALL_DIR=/opt/rdm-card-reader SERVICE_USER=rdmcard INSTALL_SERVICE=1 SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" while [[ $# -gt 0 ]]; do case "$1" in --origin) ORIGINS+=("$2"); shift 2 ;; --port) BRIDGE_PORT="$2"; shift 2 ;; --dir) INSTALL_DIR="$2"; shift 2 ;; --user) SERVICE_USER="$2"; shift 2 ;; --no-service) INSTALL_SERVICE=0; shift ;; -h|--help) sed -n '2,20p' "$0"; exit 0 ;; *) echo "unknown option: $1" >&2; exit 2 ;; esac done log() { printf '\n\033[1m==> %s\033[0m\n' "$*"; } warn() { printf '\033[33mwarning: %s\033[0m\n' "$*" >&2; } if [[ $EUID -ne 0 ]]; then echo "Please run as root: sudo $0 $*" >&2 exit 1 fi # --------------------------------------------------------------------------- # 1. System packages # --------------------------------------------------------------------------- log "Checking system packages" apt-get update -qq apt-get install -y --no-install-recommends python3 python3-venv python3-pip # --------------------------------------------------------------------------- # 2. Install the application # --------------------------------------------------------------------------- log "Installing to $INSTALL_DIR" mkdir -p "$INSTALL_DIR" # Copy the source tree (excluding local build/test artifacts). for item in src tests pyproject.toml README.md LICENSE deploy; do if [[ -e "$SOURCE_DIR/$item" ]]; then cp -a "$SOURCE_DIR/$item" "$INSTALL_DIR/" fi done log "Building the virtual environment" python3 -m venv "$INSTALL_DIR/.venv" "$INSTALL_DIR/.venv/bin/pip" install --quiet --upgrade pip "$INSTALL_DIR/.venv/bin/pip" install --quiet "$INSTALL_DIR" # --------------------------------------------------------------------------- # 3. Service account # --------------------------------------------------------------------------- log "Creating service account '$SERVICE_USER'" if ! id -u "$SERVICE_USER" >/dev/null 2>&1; then # No login shell, no home directory: this account only reads a serial port. useradd --system --no-create-home --shell /usr/sbin/nologin "$SERVICE_USER" fi # Serial access requires the dialout group. usermod -aG dialout "$SERVICE_USER" echo " $SERVICE_USER is in group dialout" # --------------------------------------------------------------------------- # 4. Stable device name # --------------------------------------------------------------------------- log "Installing the udev rule (/dev/rdm-reader)" install -m 0644 "$SOURCE_DIR/deploy/99-rdm-reader.rules" \ /etc/udev/rules.d/99-rdm-reader.rules udevadm control --reload-rules udevadm trigger --subsystem-match=tty || true sleep 1 if [[ -e /dev/rdm-reader ]]; then echo " /dev/rdm-reader -> $(readlink -f /dev/rdm-reader)" else warn "/dev/rdm-reader does not exist yet." warn "The reader may not be plugged in, or it uses a different chip." warn "Run '$INSTALL_DIR/.venv/bin/rdm-card-reader --list-ports --verbose-ports'" warn "then edit /etc/udev/rules.d/99-rdm-reader.rules to match your adapter." fi # --------------------------------------------------------------------------- # 5. Service # --------------------------------------------------------------------------- log "Installing the systemd unit" UNIT=/etc/systemd/system/rdm-card-reader.service install -m 0644 "$SOURCE_DIR/deploy/rdm-card-reader.service" "$UNIT" # Rewrite the ExecStart line with the real install dir, port and origins. ORIGIN_ARGS="" for origin in "${ORIGINS[@]:-}"; do [[ -n "$origin" ]] && ORIGIN_ARGS+=" --allow-origin $origin" done python3 - "$UNIT" "$INSTALL_DIR" "$BRIDGE_PORT" "$ORIGIN_ARGS" "$SERVICE_USER" <<'PY' import re, sys unit, install_dir, port, origin_args, user = sys.argv[1:6] text = open(unit).read() command = ( f"{install_dir}/.venv/bin/rdm-card-reader --serve " f"--port /dev/rdm-reader --bridge-port {port} --mode login{origin_args}" ) # Replace the whole (possibly line-continued) ExecStart definition. text = re.sub(r"ExecStart=[\s\S]*?(?=\n\n|\nRestart=)", f"ExecStart={command}", text, count=1) text = re.sub(r"^User=.*$", f"User={user}", text, count=1, flags=re.M) open(unit, "w").write(text) PY # Make the venv readable by the service account. chown -R "$SERVICE_USER:$SERVICE_USER" "$INSTALL_DIR" if [[ "$INSTALL_SERVICE" -eq 1 ]]; then log "Enabling and starting the service" systemctl daemon-reload systemctl enable rdm-card-reader.service systemctl restart rdm-card-reader.service sleep 2 systemctl --no-pager --lines=15 status rdm-card-reader.service || true else log "Skipping service start (--no-service)" fi # --------------------------------------------------------------------------- # Done # --------------------------------------------------------------------------- cat <