From fd8bc4b56a24994036546f3991c9db263eec7dff Mon Sep 17 00:00:00 2001 From: jkingsman Date: Wed, 25 Mar 2026 08:09:55 -0700 Subject: [PATCH 1/4] First draft of install script --- README_ADVANCED.md | 33 ++++ scripts/install_service.sh | 328 +++++++++++++++++++++++++++++++++++++ 2 files changed, 361 insertions(+) create mode 100755 scripts/install_service.sh diff --git a/README_ADVANCED.md b/README_ADVANCED.md index 64aba19..0d6b432 100644 --- a/README_ADVANCED.md +++ b/README_ADVANCED.md @@ -46,6 +46,39 @@ Accept the browser warning, or use [mkcert](https://github.com/FiloSottile/mkcer ## Systemd Service +Two paths are available depending on your comfort level with Linux system administration. + +### Simple install (recommended for most users) + +Run the installer script from the repo root. It runs as your current user, installs from wherever you cloned the repo, and prints a quick-reference cheatsheet when done — no separate service account or path juggling required. + +```bash +bash scripts/install_service.sh +``` + +The script interactively asks which transport to use (serial auto-detect, serial with explicit port, TCP, or BLE), whether to enable the bot system, and whether to set up HTTP Basic Auth. It handles dependency installation (`uv sync`), fetches a prebuilt frontend, adds your user to the `dialout` group if needed, writes the systemd unit file, and enables the service. After installation, normal operations work without any `sudo -u` gymnastics: + +```bash +# Update to latest and restart +cd /path/to/repo +git pull +uv sync +cd frontend && npm install && npm run build && cd .. +sudo systemctl restart remoteterm + +# Refresh prebuilt frontend only (skips local build) +python3 scripts/fetch_prebuilt_frontend.py +sudo systemctl restart remoteterm + +# View live logs +sudo journalctl -u remoteterm -f + +# Service control +sudo systemctl start|stop|restart|status remoteterm +``` + +### Hardened install (production / multi-user systems) + Assumes you are running from `/opt/remoteterm`; adjust paths if you deploy elsewhere. ```bash diff --git a/scripts/install_service.sh b/scripts/install_service.sh new file mode 100755 index 0000000..34af0e1 --- /dev/null +++ b/scripts/install_service.sh @@ -0,0 +1,328 @@ +#!/usr/bin/env bash +# install_service.sh +# +# Sets up RemoteTerm for MeshCore as a persistent systemd service running as +# the current user from the current repo directory. No separate service account +# is needed. After installation, git pull and rebuilds work without any sudo -u +# gymnastics. +# +# Run from anywhere inside the repo: +# bash scripts/install_service.sh + +set -e + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +CYAN='\033[0;36m' +BOLD='\033[1m' +NC='\033[0m' + +SERVICE_NAME="remoteterm" +REPO_DIR="$(cd "$(dirname "$0")/.." && pwd)" +CURRENT_USER="$(id -un)" +SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service" + +echo -e "${BOLD}=== RemoteTerm for MeshCore — Service Installer ===${NC}" +echo + +# ── sanity checks ────────────────────────────────────────────────────────────── + +if [ "$(uname -s)" != "Linux" ]; then + echo -e "${RED}Error: this script is for Linux (systemd) only.${NC}" + exit 1 +fi + +if ! command -v systemctl &>/dev/null; then + echo -e "${RED}Error: systemd not found. This script requires a systemd-based Linux system.${NC}" + exit 1 +fi + +if ! command -v uv &>/dev/null; then + echo -e "${RED}Error: 'uv' not found. Install it first:${NC}" + echo " curl -LsSf https://astral.sh/uv/install.sh | sh" + exit 1 +fi + +if ! command -v python3 &>/dev/null; then + echo -e "${RED}Error: python3 is required but was not found.${NC}" + exit 1 +fi + +UV_BIN="$(command -v uv)" +UVICORN_BIN="$REPO_DIR/.venv/bin/uvicorn" + +echo -e " Installing as user : ${CYAN}${CURRENT_USER}${NC}" +echo -e " Repo directory : ${CYAN}${REPO_DIR}${NC}" +echo -e " Service name : ${CYAN}${SERVICE_NAME}${NC}" +echo -e " uv : ${CYAN}${UV_BIN}${NC}" +echo + +# ── transport selection ──────────────────────────────────────────────────────── + +echo -e "${BOLD}─── Transport ───────────────────────────────────────────────────────${NC}" +echo "How is your MeshCore radio connected?" +echo " 1) Serial — auto-detect port (default)" +echo " 2) Serial — specify port manually" +echo " 3) TCP (network connection)" +echo " 4) BLE (Bluetooth)" +echo +read -rp "Select transport [1-4] (default: 1): " TRANSPORT_CHOICE +TRANSPORT_CHOICE="${TRANSPORT_CHOICE:-1}" +echo + +NEED_DIALOUT=false +SERIAL_PORT="" +TCP_HOST="" +TCP_PORT="" +BLE_ADDRESS="" +BLE_PIN="" + +case "$TRANSPORT_CHOICE" in + 1) + echo -e "${GREEN}Serial auto-detect selected.${NC}" + NEED_DIALOUT=true + ;; + 2) + read -rp "Serial port path (default: /dev/ttyUSB0): " SERIAL_PORT + SERIAL_PORT="${SERIAL_PORT:-/dev/ttyUSB0}" + echo -e "${GREEN}Serial port: ${SERIAL_PORT}${NC}" + NEED_DIALOUT=true + ;; + 3) + read -rp "TCP host (IP address or hostname): " TCP_HOST + while [ -z "$TCP_HOST" ]; do + echo -e "${RED}TCP host is required.${NC}" + read -rp "TCP host: " TCP_HOST + done + read -rp "TCP port (default: 4000): " TCP_PORT + TCP_PORT="${TCP_PORT:-4000}" + echo -e "${GREEN}TCP: ${TCP_HOST}:${TCP_PORT}${NC}" + ;; + 4) + read -rp "BLE device address (e.g. AA:BB:CC:DD:EE:FF): " BLE_ADDRESS + while [ -z "$BLE_ADDRESS" ]; do + echo -e "${RED}BLE address is required.${NC}" + read -rp "BLE device address: " BLE_ADDRESS + done + read -rsp "BLE PIN: " BLE_PIN + echo + while [ -z "$BLE_PIN" ]; do + echo -e "${RED}BLE PIN is required.${NC}" + read -rsp "BLE PIN: " BLE_PIN + echo + done + echo -e "${GREEN}BLE: ${BLE_ADDRESS}${NC}" + ;; + *) + echo -e "${YELLOW}Invalid selection — defaulting to serial auto-detect.${NC}" + TRANSPORT_CHOICE=1 + NEED_DIALOUT=true + ;; +esac +echo + +# ── bots ────────────────────────────────────────────────────────────────────── + +echo -e "${BOLD}─── Bot System ──────────────────────────────────────────────────────${NC}" +echo -e "${YELLOW}Warning:${NC} The bot system executes arbitrary Python code on the server." +echo "It is not recommended on untrusted networks. You can always enable" +echo "it later by editing the service file." +echo +read -rp "Enable bots? [y/N]: " ENABLE_BOTS +ENABLE_BOTS="${ENABLE_BOTS:-N}" +echo + +ENABLE_AUTH="N" +AUTH_USERNAME="" +AUTH_PASSWORD="" + +if [[ "$ENABLE_BOTS" =~ ^[Yy] ]]; then + echo -e "${GREEN}Bots enabled.${NC}" + echo + + echo -e "${BOLD}─── HTTP Basic Auth ─────────────────────────────────────────────────${NC}" + echo "With bots enabled, HTTP Basic Auth is strongly recommended if this" + echo "service will be accessible beyond your local machine." + echo + read -rp "Set up HTTP Basic Auth? [Y/n]: " ENABLE_AUTH + ENABLE_AUTH="${ENABLE_AUTH:-Y}" + echo + + if [[ "$ENABLE_AUTH" =~ ^[Yy] ]]; then + read -rp "Username: " AUTH_USERNAME + while [ -z "$AUTH_USERNAME" ]; do + echo -e "${RED}Username cannot be empty.${NC}" + read -rp "Username: " AUTH_USERNAME + done + read -rsp "Password: " AUTH_PASSWORD + echo + while [ -z "$AUTH_PASSWORD" ]; do + echo -e "${RED}Password cannot be empty.${NC}" + read -rsp "Password: " AUTH_PASSWORD + echo + done + echo -e "${GREEN}Basic Auth configured for user '${AUTH_USERNAME}'.${NC}" + echo -e "${YELLOW}Note:${NC} Basic Auth credentials are not safe over plain HTTP." + echo "See README_ADVANCED.md for HTTPS setup." + fi +else + echo -e "${GREEN}Bots disabled.${NC}" +fi +echo + +# ── python dependencies ──────────────────────────────────────────────────────── + +echo -e "${YELLOW}Installing Python dependencies (uv sync)...${NC}" +cd "$REPO_DIR" +uv sync +echo -e "${GREEN}Dependencies ready.${NC}" +echo + +# ── prebuilt frontend ────────────────────────────────────────────────────────── + +echo -e "${YELLOW}Fetching prebuilt frontend...${NC}" +python3 "$REPO_DIR/scripts/fetch_prebuilt_frontend.py" +echo + +# ── data directory ───────────────────────────────────────────────────────────── + +mkdir -p "$REPO_DIR/data" + +# ── serial port access ───────────────────────────────────────────────────────── + +if [ "$NEED_DIALOUT" = true ]; then + if ! id -nG "$CURRENT_USER" | grep -qw dialout; then + echo -e "${YELLOW}Adding ${CURRENT_USER} to the 'dialout' group for serial port access...${NC}" + sudo usermod -aG dialout "$CURRENT_USER" + echo -e "${GREEN}Done. You may need to log out and back in for this to take effect for${NC}" + echo -e "${GREEN}manual runs; the service itself handles it via SupplementaryGroups.${NC}" + echo + else + echo -e "${GREEN}User ${CURRENT_USER} is already in the 'dialout' group.${NC}" + echo + fi +fi + +# ── systemd service file ─────────────────────────────────────────────────────── + +echo -e "${YELLOW}Writing systemd service file to ${SERVICE_FILE}...${NC}" + +generate_service_file() { + echo "[Unit]" + echo "Description=RemoteTerm for MeshCore" + echo "After=network.target" + echo "" + echo "[Service]" + echo "Type=simple" + echo "User=${CURRENT_USER}" + echo "WorkingDirectory=${REPO_DIR}" + echo "ExecStart=${UVICORN_BIN} app.main:app --host 0.0.0.0 --port 8000" + echo "Restart=always" + echo "RestartSec=5" + echo "Environment=MESHCORE_DATABASE_PATH=${REPO_DIR}/data/meshcore.db" + + # Transport + case "$TRANSPORT_CHOICE" in + 2) echo "Environment=MESHCORE_SERIAL_PORT=${SERIAL_PORT}" ;; + 3) + echo "Environment=MESHCORE_TCP_HOST=${TCP_HOST}" + echo "Environment=MESHCORE_TCP_PORT=${TCP_PORT}" + ;; + 4) + echo "Environment=MESHCORE_BLE_ADDRESS=${BLE_ADDRESS}" + echo "Environment=MESHCORE_BLE_PIN=${BLE_PIN}" + ;; + esac + + # Bots + if [[ ! "$ENABLE_BOTS" =~ ^[Yy] ]]; then + echo "Environment=MESHCORE_DISABLE_BOTS=true" + fi + + # Basic auth + if [[ "$ENABLE_BOTS" =~ ^[Yy] ]] && [[ "$ENABLE_AUTH" =~ ^[Yy] ]]; then + echo "Environment=MESHCORE_BASIC_AUTH_USERNAME=${AUTH_USERNAME}" + echo "Environment=MESHCORE_BASIC_AUTH_PASSWORD=${AUTH_PASSWORD}" + fi + + # Serial group access + if [ "$NEED_DIALOUT" = true ]; then + echo "SupplementaryGroups=dialout" + fi + + echo "" + echo "[Install]" + echo "WantedBy=multi-user.target" +} + +generate_service_file | sudo tee "$SERVICE_FILE" > /dev/null + +echo -e "${GREEN}Service file written.${NC}" +echo + +# ── enable and start ─────────────────────────────────────────────────────────── + +echo -e "${YELLOW}Enabling and starting ${SERVICE_NAME}...${NC}" +sudo systemctl daemon-reload +sudo systemctl enable --now "$SERVICE_NAME" +echo + +# ── status check ─────────────────────────────────────────────────────────────── + +echo -e "${YELLOW}Service status:${NC}" +sudo systemctl status "$SERVICE_NAME" --no-pager -l || true +echo + +# ── summary ──────────────────────────────────────────────────────────────────── + +echo -e "${GREEN}${BOLD}=== Installation complete! ===${NC}" +echo +echo -e "RemoteTerm is running at ${CYAN}http://$(hostname -I | awk '{print $1}'):8000${NC}" +echo + +case "$TRANSPORT_CHOICE" in + 1) echo -e " Transport : ${CYAN}Serial (auto-detect)${NC}" ;; + 2) echo -e " Transport : ${CYAN}Serial (${SERIAL_PORT})${NC}" ;; + 3) echo -e " Transport : ${CYAN}TCP (${TCP_HOST}:${TCP_PORT})${NC}" ;; + 4) echo -e " Transport : ${CYAN}BLE (${BLE_ADDRESS})${NC}" ;; +esac + +if [[ "$ENABLE_BOTS" =~ ^[Yy] ]]; then + echo -e " Bots : ${YELLOW}Enabled${NC}" + if [[ "$ENABLE_AUTH" =~ ^[Yy] ]]; then + echo -e " Basic Auth: ${GREEN}Enabled (user: ${AUTH_USERNAME})${NC}" + else + echo -e " Basic Auth: ${YELLOW}Not configured${NC}" + fi +else + echo -e " Bots : ${GREEN}Disabled${NC} (edit ${SERVICE_FILE} to enable)" +fi +echo + +echo -e "${YELLOW}Note:${NC} A prebuilt frontend has been fetched and installed. It may lag" +echo "behind the latest code. To build the frontend from source for the most" +echo "up-to-date features, run:" +echo +echo -e " ${CYAN}cd ${REPO_DIR}/frontend && npm install && npm run build${NC}" +echo + +echo -e "${BOLD}─── Quick Reference ─────────────────────────────────────────────────${NC}" +echo +echo -e "${YELLOW}Update to latest and restart:${NC}" +echo -e " cd ${REPO_DIR}" +echo -e " git pull" +echo -e " uv sync" +echo -e " cd frontend && npm install && npm run build && cd .." +echo -e " sudo systemctl restart ${SERVICE_NAME}" +echo +echo -e "${YELLOW}Refresh prebuilt frontend only (skips local build):${NC}" +echo -e " python3 ${REPO_DIR}/scripts/fetch_prebuilt_frontend.py" +echo -e " sudo systemctl restart ${SERVICE_NAME}" +echo +echo -e "${YELLOW}View live logs (useful for troubleshooting):${NC}" +echo -e " sudo journalctl -u ${SERVICE_NAME} -f" +echo +echo -e "${YELLOW}Service control:${NC}" +echo -e " sudo systemctl start|stop|restart|status ${SERVICE_NAME}" +echo -e "${BOLD}─────────────────────────────────────────────────────────────────────${NC}" From 983a37f68f4e856a7f5e5488ab4e15c48ee2dc73 Mon Sep 17 00:00:00 2001 From: jkingsman Date: Thu, 26 Mar 2026 16:46:27 -0700 Subject: [PATCH 2/4] Idempotentify and remove the explicit setup instructions in the advanced readme --- README_ADVANCED.md | 59 ++------------------------------------ scripts/install_service.sh | 11 +++++-- 2 files changed, 11 insertions(+), 59 deletions(-) diff --git a/README_ADVANCED.md b/README_ADVANCED.md index 0d6b432..129a263 100644 --- a/README_ADVANCED.md +++ b/README_ADVANCED.md @@ -58,6 +58,8 @@ bash scripts/install_service.sh The script interactively asks which transport to use (serial auto-detect, serial with explicit port, TCP, or BLE), whether to enable the bot system, and whether to set up HTTP Basic Auth. It handles dependency installation (`uv sync`), fetches a prebuilt frontend, adds your user to the `dialout` group if needed, writes the systemd unit file, and enables the service. After installation, normal operations work without any `sudo -u` gymnastics: +You can also rerun the script later to change transport, bot, or auth settings. If the service is already running, the installer stops it, rewrites the unit file, reloads systemd, and starts it again with the new configuration. + ```bash # Update to latest and restart cd /path/to/repo @@ -77,63 +79,6 @@ sudo journalctl -u remoteterm -f sudo systemctl start|stop|restart|status remoteterm ``` -### Hardened install (production / multi-user systems) - -Assumes you are running from `/opt/remoteterm`; adjust paths if you deploy elsewhere. - -```bash -# Create service user -sudo useradd -r -m -s /bin/false remoteterm - -# Install to /opt/remoteterm -sudo mkdir -p /opt/remoteterm -sudo cp -r . /opt/remoteterm/ -sudo chown -R remoteterm:remoteterm /opt/remoteterm - -# Install dependencies -cd /opt/remoteterm -sudo -u remoteterm uv venv -sudo -u remoteterm uv sync - -# If deploying from a source checkout, build the frontend first -sudo -u remoteterm bash -lc 'cd /opt/remoteterm/frontend && npm install && npm run build' - -# If deploying from the release zip artifact, frontend/prebuilt is already present -``` - -Create `/etc/systemd/system/remoteterm.service` with: - -```ini -[Unit] -Description=RemoteTerm for MeshCore -After=network.target - -[Service] -Type=simple -User=remoteterm -Group=remoteterm -WorkingDirectory=/opt/remoteterm -ExecStart=/opt/remoteterm/.venv/bin/uvicorn app.main:app --host 0.0.0.0 --port 8000 -Restart=always -RestartSec=5 -Environment=MESHCORE_DATABASE_PATH=/opt/remoteterm/data/meshcore.db -# Uncomment and set if auto-detection doesn't work: -# Environment=MESHCORE_SERIAL_PORT=/dev/ttyUSB0 -SupplementaryGroups=dialout - -[Install] -WantedBy=multi-user.target -``` - -Then install and start it: - -```bash -sudo systemctl daemon-reload -sudo systemctl enable --now remoteterm -sudo systemctl status remoteterm -sudo journalctl -u remoteterm -f -``` - ## Debug Logging And Bug Reports If you're experiencing issues or opening a bug report, please start the backend with debug logging enabled. Debug mode provides a much more detailed breakdown of radio communication, packet processing, and other internal operations, which makes it significantly easier to diagnose problems. diff --git a/scripts/install_service.sh b/scripts/install_service.sh index 34af0e1..da905ea 100755 --- a/scripts/install_service.sh +++ b/scripts/install_service.sh @@ -206,6 +206,12 @@ fi # ── systemd service file ─────────────────────────────────────────────────────── +if sudo systemctl is-active --quiet "$SERVICE_NAME"; then + echo -e "${YELLOW}${SERVICE_NAME} is currently running; stopping it before applying changes...${NC}" + sudo systemctl stop "$SERVICE_NAME" + echo +fi + echo -e "${YELLOW}Writing systemd service file to ${SERVICE_FILE}...${NC}" generate_service_file() { @@ -263,9 +269,10 @@ echo # ── enable and start ─────────────────────────────────────────────────────────── -echo -e "${YELLOW}Enabling and starting ${SERVICE_NAME}...${NC}" +echo -e "${YELLOW}Reloading systemd and applying ${SERVICE_NAME}...${NC}" sudo systemctl daemon-reload -sudo systemctl enable --now "$SERVICE_NAME" +sudo systemctl enable "$SERVICE_NAME" +sudo systemctl start "$SERVICE_NAME" echo # ── status check ─────────────────────────────────────────────────────────────── From 88c99e0983a0e6608074ec7a7cbc469e7aacf685 Mon Sep 17 00:00:00 2001 From: jkingsman Date: Thu, 26 Mar 2026 16:50:48 -0700 Subject: [PATCH 3/4] Add note in readme --- README.md | 2 + README_ADVANCED.md | 4 +- scripts/install_service.sh | 96 ++++++++++++++++++++++++++++++++++---- 3 files changed, 91 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 28a8ee4..238c64c 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,8 @@ Access the app at http://localhost:8000. Source checkouts expect a normal frontend build in `frontend/dist`. +On Linux, if you want this installed as a persistent `systemd` service that starts on boot and restarts automatically on failure, run `bash scripts/install_service.sh` from the repo root. + ## Path 1.5: Use The Prebuilt Release Zip Release zips can be found as an asset within the [releases listed here](https://github.com/jkingsman/Remote-Terminal-for-MeshCore/releases). This can be beneficial on resource constrained systems that cannot cope with the RAM-hungry frontend build process. diff --git a/README_ADVANCED.md b/README_ADVANCED.md index 129a263..2b0684c 100644 --- a/README_ADVANCED.md +++ b/README_ADVANCED.md @@ -50,13 +50,13 @@ Two paths are available depending on your comfort level with Linux system admini ### Simple install (recommended for most users) -Run the installer script from the repo root. It runs as your current user, installs from wherever you cloned the repo, and prints a quick-reference cheatsheet when done — no separate service account or path juggling required. +On Linux systems, this is the recommended installation method if you want RemoteTerm set up as a persistent systemd service that starts automatically on boot and restarts automatically if it crashes. Run the installer script from the repo root. It runs as your current user, installs from wherever you cloned the repo, and prints a quick-reference cheatsheet when done — no separate service account or path juggling required. ```bash bash scripts/install_service.sh ``` -The script interactively asks which transport to use (serial auto-detect, serial with explicit port, TCP, or BLE), whether to enable the bot system, and whether to set up HTTP Basic Auth. It handles dependency installation (`uv sync`), fetches a prebuilt frontend, adds your user to the `dialout` group if needed, writes the systemd unit file, and enables the service. After installation, normal operations work without any `sudo -u` gymnastics: +The script interactively asks which transport to use (serial auto-detect, serial with explicit port, TCP, or BLE), whether to build the frontend locally or download a prebuilt copy, whether to enable the bot system, and whether to set up HTTP Basic Auth. It handles dependency installation (`uv sync`), validates `node`/`npm` for local builds, adds your user to the `dialout` group if needed, writes the systemd unit file, and enables the service. After installation, normal operations work without any `sudo -u` gymnastics: You can also rerun the script later to change transport, bot, or auth settings. If the service is already running, the installer stops it, rewrites the unit file, reloads systemd, and starts it again with the new configuration. diff --git a/scripts/install_service.sh b/scripts/install_service.sh index da905ea..31340a2 100755 --- a/scripts/install_service.sh +++ b/scripts/install_service.sh @@ -22,6 +22,7 @@ SERVICE_NAME="remoteterm" REPO_DIR="$(cd "$(dirname "$0")/.." && pwd)" CURRENT_USER="$(id -un)" SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service" +FRONTEND_MODE="prebuilt" echo -e "${BOLD}=== RemoteTerm for MeshCore — Service Installer ===${NC}" echo @@ -58,6 +59,24 @@ echo -e " Service name : ${CYAN}${SERVICE_NAME}${NC}" echo -e " uv : ${CYAN}${UV_BIN}${NC}" echo +version_major() { + local version="$1" + version="${version#v}" + printf '%s' "${version%%.*}" +} + +require_minimum_version() { + local tool_name="$1" + local detected_version="$2" + local minimum_major="$3" + local major + major="$(version_major "$detected_version")" + if ! [[ "$major" =~ ^[0-9]+$ ]] || [ "$major" -lt "$minimum_major" ]; then + echo -e "${RED}Error: ${tool_name} ${minimum_major}+ is required for a local frontend build, but found ${detected_version}.${NC}" + exit 1 + fi +} + # ── transport selection ──────────────────────────────────────────────────────── echo -e "${BOLD}─── Transport ───────────────────────────────────────────────────────${NC}" @@ -122,6 +141,33 @@ case "$TRANSPORT_CHOICE" in esac echo +# ── frontend install mode ────────────────────────────────────────────────────── + +echo -e "${BOLD}─── Frontend Assets ─────────────────────────────────────────────────${NC}" +echo "How should the frontend be installed?" +echo " 1) Download prebuilt frontend (default, fastest)" +echo " 2) Build locally with npm (latest code, requires node/npm)" +echo +read -rp "Select frontend mode [1-2] (default: 1): " FRONTEND_CHOICE +FRONTEND_CHOICE="${FRONTEND_CHOICE:-1}" +echo + +case "$FRONTEND_CHOICE" in + 1) + FRONTEND_MODE="prebuilt" + echo -e "${GREEN}Using prebuilt frontend download.${NC}" + ;; + 2) + FRONTEND_MODE="build" + echo -e "${GREEN}Using local frontend build.${NC}" + ;; + *) + FRONTEND_MODE="prebuilt" + echo -e "${YELLOW}Invalid selection — defaulting to prebuilt frontend download.${NC}" + ;; +esac +echo + # ── bots ────────────────────────────────────────────────────────────────────── echo -e "${BOLD}─── Bot System ──────────────────────────────────────────────────────${NC}" @@ -179,10 +225,35 @@ uv sync echo -e "${GREEN}Dependencies ready.${NC}" echo -# ── prebuilt frontend ────────────────────────────────────────────────────────── +# ── frontend assets ──────────────────────────────────────────────────────────── -echo -e "${YELLOW}Fetching prebuilt frontend...${NC}" -python3 "$REPO_DIR/scripts/fetch_prebuilt_frontend.py" +if [ "$FRONTEND_MODE" = "build" ]; then + if ! command -v node &>/dev/null; then + echo -e "${RED}Error: node is required for a local frontend build but was not found.${NC}" + echo -e "${YELLOW}Tip:${NC} Re-run the installer and choose the prebuilt frontend option, or install Node.js 18+ and npm 9+." + exit 1 + fi + if ! command -v npm &>/dev/null; then + echo -e "${RED}Error: npm is required for a local frontend build but was not found.${NC}" + echo -e "${YELLOW}Tip:${NC} Re-run the installer and choose the prebuilt frontend option, or install Node.js 18+ and npm 9+." + exit 1 + fi + + NODE_VERSION="$(node -v)" + NPM_VERSION="$(npm -v)" + require_minimum_version "Node.js" "$NODE_VERSION" 18 + require_minimum_version "npm" "$NPM_VERSION" 9 + + echo -e "${YELLOW}Building frontend locally with Node ${NODE_VERSION} and npm ${NPM_VERSION}...${NC}" + ( + cd "$REPO_DIR/frontend" + npm install + npm run build + ) +else + echo -e "${YELLOW}Fetching prebuilt frontend...${NC}" + python3 "$REPO_DIR/scripts/fetch_prebuilt_frontend.py" +fi echo # ── data directory ───────────────────────────────────────────────────────────── @@ -294,6 +365,11 @@ case "$TRANSPORT_CHOICE" in 3) echo -e " Transport : ${CYAN}TCP (${TCP_HOST}:${TCP_PORT})${NC}" ;; 4) echo -e " Transport : ${CYAN}BLE (${BLE_ADDRESS})${NC}" ;; esac +if [ "$FRONTEND_MODE" = "build" ]; then + echo -e " Frontend : ${GREEN}Built locally${NC}" +else + echo -e " Frontend : ${YELLOW}Prebuilt download${NC}" +fi if [[ "$ENABLE_BOTS" =~ ^[Yy] ]]; then echo -e " Bots : ${YELLOW}Enabled${NC}" @@ -307,12 +383,14 @@ else fi echo -echo -e "${YELLOW}Note:${NC} A prebuilt frontend has been fetched and installed. It may lag" -echo "behind the latest code. To build the frontend from source for the most" -echo "up-to-date features, run:" -echo -echo -e " ${CYAN}cd ${REPO_DIR}/frontend && npm install && npm run build${NC}" -echo +if [ "$FRONTEND_MODE" = "prebuilt" ]; then + echo -e "${YELLOW}Note:${NC} A prebuilt frontend has been fetched and installed. It may lag" + echo "behind the latest code. To build the frontend from source for the most" + echo "up-to-date features later, run:" + echo + echo -e " ${CYAN}cd ${REPO_DIR}/frontend && npm install && npm run build${NC}" + echo +fi echo -e "${BOLD}─── Quick Reference ─────────────────────────────────────────────────${NC}" echo From 094058bad70f6d2061064be3999cbf6b42c84ea6 Mon Sep 17 00:00:00 2001 From: jkingsman Date: Thu, 26 Mar 2026 16:59:53 -0700 Subject: [PATCH 4/4] Tweak install script --- scripts/install_service.sh | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/scripts/install_service.sh b/scripts/install_service.sh index 31340a2..f51a091 100755 --- a/scripts/install_service.sh +++ b/scripts/install_service.sh @@ -22,7 +22,7 @@ SERVICE_NAME="remoteterm" REPO_DIR="$(cd "$(dirname "$0")/.." && pwd)" CURRENT_USER="$(id -un)" SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service" -FRONTEND_MODE="prebuilt" +FRONTEND_MODE="build" echo -e "${BOLD}=== RemoteTerm for MeshCore — Service Installer ===${NC}" echo @@ -145,8 +145,8 @@ echo echo -e "${BOLD}─── Frontend Assets ─────────────────────────────────────────────────${NC}" echo "How should the frontend be installed?" -echo " 1) Download prebuilt frontend (default, fastest)" -echo " 2) Build locally with npm (latest code, requires node/npm)" +echo " 1) Build locally with npm (default, latest code, requires node/npm)" +echo " 2) Download prebuilt frontend (fastest)" echo read -rp "Select frontend mode [1-2] (default: 1): " FRONTEND_CHOICE FRONTEND_CHOICE="${FRONTEND_CHOICE:-1}" @@ -154,16 +154,16 @@ echo case "$FRONTEND_CHOICE" in 1) - FRONTEND_MODE="prebuilt" - echo -e "${GREEN}Using prebuilt frontend download.${NC}" - ;; - 2) FRONTEND_MODE="build" echo -e "${GREEN}Using local frontend build.${NC}" ;; - *) + 2) FRONTEND_MODE="prebuilt" - echo -e "${YELLOW}Invalid selection — defaulting to prebuilt frontend download.${NC}" + echo -e "${GREEN}Using prebuilt frontend download.${NC}" + ;; + *) + FRONTEND_MODE="build" + echo -e "${YELLOW}Invalid selection — defaulting to local frontend build.${NC}" ;; esac echo