mirror of
https://github.com/pe1hvh/meshcore-gui.git
synced 2026-08-07 01:13:18 +02:00
Initial clean code
This commit is contained in:
Executable
+293
@@ -0,0 +1,293 @@
|
||||
#!/usr/bin/env bash
|
||||
# ============================================================================
|
||||
# MeshCore GUI — BLE Installer
|
||||
# ============================================================================
|
||||
#
|
||||
# Installs the BLE PIN agent, reconnect module, systemd service
|
||||
# and D-Bus policy. Automatically detects the correct paths and user.
|
||||
#
|
||||
# Usage:
|
||||
# bash install_scripts/install_ble_stable.sh # from project root
|
||||
# cd install_scripts && bash install_ble_stable.sh # from install_scripts/
|
||||
#
|
||||
# Optional:
|
||||
# bash install_scripts/install_ble_stable.sh --uninstall
|
||||
#
|
||||
# Requirements:
|
||||
# - meshcore-gui project with venv/ directory
|
||||
# - sudo access (for systemd and D-Bus config)
|
||||
# - Linux with BlueZ (Bluetooth)
|
||||
#
|
||||
# ============================================================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ── Colors ──
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
info() { echo -e "${BLUE}[INFO]${NC} $*"; }
|
||||
ok() { echo -e "${GREEN}[OK]${NC} $*"; }
|
||||
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
|
||||
error() { echo -e "${RED}[ERROR]${NC} $*"; exit 1; }
|
||||
|
||||
# ── Resolve project root ──
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
if [[ "$(basename "${SCRIPT_DIR}")" == "install_scripts" ]]; then
|
||||
PROJECT_DIR="$(dirname "${SCRIPT_DIR}")"
|
||||
else
|
||||
PROJECT_DIR="${SCRIPT_DIR}"
|
||||
fi
|
||||
|
||||
# ── Uninstall mode ──
|
||||
if [[ "${1:-}" == "--uninstall" ]]; then
|
||||
info "Removing meshcore-gui service and configuration..."
|
||||
sudo systemctl stop meshcore-gui 2>/dev/null || true
|
||||
sudo systemctl disable meshcore-gui 2>/dev/null || true
|
||||
sudo rm -f /etc/systemd/system/meshcore-gui.service
|
||||
sudo rm -f /etc/dbus-1/system.d/meshcore-ble.conf
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl reset-failed 2>/dev/null || true
|
||||
ok "Service and configuration removed"
|
||||
info "Python files in your project have NOT been removed."
|
||||
info "Remove manually if desired:"
|
||||
info " rm meshcore_gui/ble/ble_agent.py"
|
||||
info " rm meshcore_gui/ble/ble_reconnect.py"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ── Detect environment ──
|
||||
info "Detecting environment..."
|
||||
|
||||
if [[ ! -f "${PROJECT_DIR}/meshcore_gui.py" ]] && [[ ! -d "${PROJECT_DIR}/meshcore_gui" ]]; then
|
||||
error "Cannot find meshcore_gui.py or meshcore_gui/ in ${PROJECT_DIR}
|
||||
Run this script from the project directory or from install_scripts/."
|
||||
fi
|
||||
|
||||
CURRENT_USER="$(whoami)"
|
||||
VENV_PYTHON="${PROJECT_DIR}/venv/bin/python"
|
||||
|
||||
# Check venv
|
||||
if [[ ! -x "${VENV_PYTHON}" ]]; then
|
||||
error "Virtual environment not found at: ${VENV_PYTHON}
|
||||
Create it first:
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate
|
||||
pip install meshcore nicegui bleak meshcoredecoder"
|
||||
fi
|
||||
|
||||
# Determine the entry point
|
||||
if [[ -f "${PROJECT_DIR}/meshcore_gui.py" ]]; then
|
||||
ENTRY_POINT="meshcore_gui.py"
|
||||
elif [[ -d "${PROJECT_DIR}/meshcore_gui" ]]; then
|
||||
ENTRY_POINT="-m meshcore_gui"
|
||||
else
|
||||
error "Cannot determine entry point."
|
||||
fi
|
||||
|
||||
# Check BLE address argument
|
||||
BLE_ADDRESS="${BLE_ADDRESS:-}"
|
||||
if [[ -z "${BLE_ADDRESS}" ]]; then
|
||||
echo ""
|
||||
echo -e "${YELLOW}BLE MAC address not specified.${NC}"
|
||||
echo "You can specify it in two ways:"
|
||||
echo ""
|
||||
echo " 1. As an environment variable:"
|
||||
echo " BLE_ADDRESS=FF:05:D6:71:83:8D bash $0"
|
||||
echo ""
|
||||
echo " 2. Enter manually:"
|
||||
read -rp " BLE MAC address (e.g. FF:05:D6:71:83:8D): " BLE_ADDRESS
|
||||
echo ""
|
||||
fi
|
||||
|
||||
if [[ -z "${BLE_ADDRESS}" ]]; then
|
||||
error "No BLE MAC address specified. Aborted."
|
||||
fi
|
||||
|
||||
# Summary
|
||||
echo ""
|
||||
echo "═══════════════════════════════════════════════════"
|
||||
echo " MeshCore GUI — BLE Stability Installer"
|
||||
echo "═══════════════════════════════════════════════════"
|
||||
echo " Project dir: ${PROJECT_DIR}"
|
||||
echo " User: ${CURRENT_USER}"
|
||||
echo " Python: ${VENV_PYTHON}"
|
||||
echo " Entry point: ${ENTRY_POINT}"
|
||||
echo " BLE address: ${BLE_ADDRESS}"
|
||||
echo "═══════════════════════════════════════════════════"
|
||||
echo ""
|
||||
read -rp "Continue? [y/N] " confirm
|
||||
if [[ "${confirm}" != "y" && "${confirm}" != "Y" ]]; then
|
||||
info "Aborted."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ── Step 1: Upgrade meshcore library ──
|
||||
info "Step 1/6: Upgrading meshcore library..."
|
||||
"${PROJECT_DIR}/venv/bin/pip" install --upgrade meshcore --quiet 2>/dev/null || \
|
||||
"${PROJECT_DIR}/venv/bin/pip" install --upgrade meshcore
|
||||
MESHCORE_VERSION=$("${PROJECT_DIR}/venv/bin/pip" show meshcore 2>/dev/null | grep "^Version:" | awk '{print $2}')
|
||||
ok "meshcore version: ${MESHCORE_VERSION:-unknown}"
|
||||
|
||||
# ── Step 2: Check that dbus_fast is available ──
|
||||
info "Step 2/6: Checking dbus_fast dependency..."
|
||||
if "${VENV_PYTHON}" -c "import dbus_fast" 2>/dev/null; then
|
||||
ok "dbus_fast available (included with bleak)"
|
||||
else
|
||||
warn "dbus_fast not found, installing..."
|
||||
"${PROJECT_DIR}/venv/bin/pip" install dbus-fast --quiet
|
||||
ok "dbus_fast installed"
|
||||
fi
|
||||
|
||||
# ── Step 3: Check Python files ──
|
||||
info "Step 3/6: Checking Python files..."
|
||||
|
||||
BLE_DIR="${PROJECT_DIR}/meshcore_gui/ble"
|
||||
if [[ ! -d "${BLE_DIR}" ]]; then
|
||||
error "Directory ${BLE_DIR} not found."
|
||||
fi
|
||||
|
||||
# Check if the files are already in place
|
||||
AGENT_OK=false
|
||||
RECONNECT_OK=false
|
||||
[[ -f "${BLE_DIR}/ble_agent.py" ]] && AGENT_OK=true
|
||||
[[ -f "${BLE_DIR}/ble_reconnect.py" ]] && RECONNECT_OK=true
|
||||
|
||||
if $AGENT_OK && $RECONNECT_OK; then
|
||||
ok "ble_agent.py and ble_reconnect.py are already installed"
|
||||
else
|
||||
# Check if they are alongside the project files
|
||||
if [[ -f "${PROJECT_DIR}/meshcore_gui/ble/ble_agent.py" ]]; then
|
||||
ok "Files already present in project"
|
||||
else
|
||||
if ! $AGENT_OK; then
|
||||
error "ble_agent.py not found in ${BLE_DIR}/
|
||||
Copy this file manually to ${BLE_DIR}/"
|
||||
fi
|
||||
if ! $RECONNECT_OK; then
|
||||
error "ble_reconnect.py not found in ${BLE_DIR}/
|
||||
Copy this file manually to ${BLE_DIR}/"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Verify Python syntax
|
||||
info "Verifying Python syntax..."
|
||||
"${VENV_PYTHON}" -c "
|
||||
import ast, sys
|
||||
errors = []
|
||||
for f in ['${BLE_DIR}/ble_agent.py', '${BLE_DIR}/ble_reconnect.py', '${BLE_DIR}/worker.py']:
|
||||
try:
|
||||
ast.parse(open(f).read())
|
||||
except SyntaxError as e:
|
||||
errors.append(f'{f}: {e}')
|
||||
if errors:
|
||||
print('SYNTAX ERRORS:')
|
||||
for e in errors:
|
||||
print(f' {e}')
|
||||
sys.exit(1)
|
||||
print('OK')
|
||||
" || error "Syntax errors found in Python files"
|
||||
ok "All Python files are syntactically correct"
|
||||
|
||||
# ── Step 4: Remove old bt-agent service ──
|
||||
info "Step 4/6: Cleaning up old services..."
|
||||
if systemctl is-active --quiet bt-agent 2>/dev/null; then
|
||||
sudo systemctl stop bt-agent
|
||||
sudo systemctl disable bt-agent
|
||||
ok "bt-agent.service stopped and disabled"
|
||||
elif systemctl list-unit-files | grep -q bt-agent 2>/dev/null; then
|
||||
sudo systemctl disable bt-agent 2>/dev/null || true
|
||||
ok "bt-agent.service disabled"
|
||||
else
|
||||
ok "bt-agent.service was already absent"
|
||||
fi
|
||||
|
||||
# Stop existing meshcore-gui service if running
|
||||
if systemctl is-active --quiet meshcore-gui 2>/dev/null; then
|
||||
sudo systemctl stop meshcore-gui
|
||||
ok "Existing meshcore-gui.service stopped"
|
||||
fi
|
||||
|
||||
# ── Step 5: Install D-Bus policy ──
|
||||
info "Step 5/6: Installing D-Bus policy..."
|
||||
DBUS_CONF="/etc/dbus-1/system.d/meshcore-ble.conf"
|
||||
|
||||
sudo tee "${DBUS_CONF}" > /dev/null << DBUS_EOF
|
||||
<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
|
||||
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
|
||||
<busconfig>
|
||||
<!-- Allow user '${CURRENT_USER}' to interact with BlueZ for BLE pairing agent -->
|
||||
<policy user="${CURRENT_USER}">
|
||||
<allow send_destination="org.bluez"/>
|
||||
<allow send_interface="org.bluez.Agent1"/>
|
||||
<allow send_interface="org.bluez.AgentManager1"/>
|
||||
</policy>
|
||||
</busconfig>
|
||||
DBUS_EOF
|
||||
|
||||
ok "D-Bus policy installed for user '${CURRENT_USER}'"
|
||||
|
||||
# ── Step 6: Install systemd service ──
|
||||
info "Step 6/6: Installing systemd service..."
|
||||
SERVICE_FILE="/etc/systemd/system/meshcore-gui.service"
|
||||
|
||||
sudo tee "${SERVICE_FILE}" > /dev/null << SERVICE_EOF
|
||||
[Unit]
|
||||
Description=MeshCore GUI (BLE)
|
||||
After=bluetooth.target
|
||||
Wants=bluetooth.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=${CURRENT_USER}
|
||||
WorkingDirectory=${PROJECT_DIR}
|
||||
ExecStart=${VENV_PYTHON} ${ENTRY_POINT} ${BLE_ADDRESS} --debug-on
|
||||
Restart=on-failure
|
||||
RestartSec=30
|
||||
Environment=DBUS_SYSTEM_BUS_ADDRESS=unix:path=/var/run/dbus/system_bus_socket
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
SERVICE_EOF
|
||||
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable meshcore-gui
|
||||
ok "meshcore-gui.service installed and enabled"
|
||||
|
||||
# ── Done ──
|
||||
echo ""
|
||||
echo "═══════════════════════════════════════════════════"
|
||||
echo -e " ${GREEN}Installation complete!${NC}"
|
||||
echo "═══════════════════════════════════════════════════"
|
||||
echo ""
|
||||
echo " Commands:"
|
||||
echo " sudo systemctl start meshcore-gui # Start"
|
||||
echo " sudo systemctl stop meshcore-gui # Stop"
|
||||
echo " sudo systemctl restart meshcore-gui # Restart"
|
||||
echo " sudo systemctl status meshcore-gui # Status"
|
||||
echo " journalctl -u meshcore-gui -f # Live logs"
|
||||
echo ""
|
||||
echo " Uninstall:"
|
||||
echo " bash install_scripts/install_ble_stable.sh --uninstall"
|
||||
echo ""
|
||||
echo "═══════════════════════════════════════════════════"
|
||||
|
||||
# Optionally start immediately
|
||||
echo ""
|
||||
read -rp "Start service now? [y/N] " start_now
|
||||
if [[ "${start_now}" == "y" || "${start_now}" == "Y" ]]; then
|
||||
sudo systemctl start meshcore-gui
|
||||
sleep 2
|
||||
if systemctl is-active --quiet meshcore-gui; then
|
||||
ok "Service is running!"
|
||||
echo ""
|
||||
info "View live logs: journalctl -u meshcore-gui -f"
|
||||
else
|
||||
warn "Service could not start. Check logs:"
|
||||
echo " journalctl -u meshcore-gui --no-pager -n 20"
|
||||
fi
|
||||
fi
|
||||
Executable
+238
@@ -0,0 +1,238 @@
|
||||
#!/usr/bin/env bash
|
||||
# =============================================================================
|
||||
# MeshCore Bridge — systemd Service Installer
|
||||
# =============================================================================
|
||||
#
|
||||
# Installs meshcore_bridge as a systemd daemon service.
|
||||
#
|
||||
# Usage:
|
||||
# sudo bash install_scripts/install_bridge.sh # from project root
|
||||
# cd install_scripts && sudo bash install_bridge.sh # from install_scripts/
|
||||
# sudo bash install_scripts/install_bridge.sh --uninstall
|
||||
#
|
||||
# What this script does:
|
||||
# 1. Copies meshcore_bridge.py and meshcore_bridge/ to /opt/meshcore-bridge/
|
||||
# 2. Copies bridge_config.yaml to /etc/meshcore/ (if not already present)
|
||||
# 3. Creates a systemd service unit file
|
||||
# 4. Reloads systemd and enables the service
|
||||
#
|
||||
# After installation:
|
||||
# sudo nano /etc/meshcore/bridge_config.yaml # edit configuration
|
||||
# sudo systemctl start meshcore-bridge # start the service
|
||||
# sudo systemctl enable meshcore-bridge # auto-start on boot
|
||||
# sudo systemctl status meshcore-bridge # check status
|
||||
# journalctl -u meshcore-bridge -f # follow logs
|
||||
#
|
||||
# Author: PE1HVH
|
||||
# SPDX-License-Identifier: MIT
|
||||
# Copyright: (c) 2026 PE1HVH
|
||||
# =============================================================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SERVICE_NAME="meshcore-bridge"
|
||||
INSTALL_DIR="/opt/meshcore-bridge"
|
||||
CONFIG_DIR="/etc/meshcore"
|
||||
CONFIG_FILE="${CONFIG_DIR}/bridge_config.yaml"
|
||||
SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service"
|
||||
|
||||
# ── Resolve project root ──
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
if [[ "$(basename "${SCRIPT_DIR}")" == "install_scripts" ]]; then
|
||||
PROJECT_DIR="$(dirname "${SCRIPT_DIR}")"
|
||||
else
|
||||
PROJECT_DIR="${SCRIPT_DIR}"
|
||||
fi
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
info() { echo -e "${GREEN}[INFO]${NC} $*"; }
|
||||
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
|
||||
error() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
|
||||
|
||||
# ── Check root ──
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
error "This script must be run as root (sudo)."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ── Uninstall mode ──
|
||||
if [[ "${1:-}" == "--uninstall" ]]; then
|
||||
info "Uninstalling ${SERVICE_NAME}..."
|
||||
|
||||
if systemctl is-active --quiet "${SERVICE_NAME}" 2>/dev/null; then
|
||||
info "Stopping service..."
|
||||
systemctl stop "${SERVICE_NAME}"
|
||||
fi
|
||||
|
||||
if systemctl is-enabled --quiet "${SERVICE_NAME}" 2>/dev/null; then
|
||||
info "Disabling service..."
|
||||
systemctl disable "${SERVICE_NAME}"
|
||||
fi
|
||||
|
||||
if [[ -f "${SERVICE_FILE}" ]]; then
|
||||
info "Removing service file..."
|
||||
rm -f "${SERVICE_FILE}"
|
||||
systemctl daemon-reload
|
||||
fi
|
||||
|
||||
if [[ -d "${INSTALL_DIR}" ]]; then
|
||||
info "Removing installation directory..."
|
||||
rm -rf "${INSTALL_DIR}"
|
||||
fi
|
||||
|
||||
warn "Configuration preserved at ${CONFIG_DIR}/"
|
||||
info "Uninstall complete."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ── Install mode ──
|
||||
info "Installing ${SERVICE_NAME}..."
|
||||
|
||||
# Verify source files exist
|
||||
if [[ ! -f "${PROJECT_DIR}/meshcore_bridge.py" ]]; then
|
||||
error "meshcore_bridge.py not found in ${PROJECT_DIR}"
|
||||
error "Run this script from the project directory or from install_scripts/."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -d "${PROJECT_DIR}/meshcore_bridge" ]]; then
|
||||
error "meshcore_bridge/ directory not found in ${PROJECT_DIR}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Detect Python interpreter
|
||||
PYTHON_BIN=""
|
||||
for candidate in python3.12 python3.11 python3.10 python3; do
|
||||
if command -v "${candidate}" &>/dev/null; then
|
||||
PYTHON_BIN="$(command -v "${candidate}")"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ -z "${PYTHON_BIN}" ]]; then
|
||||
error "Python 3.10+ not found. Install Python first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PYTHON_VERSION=$("${PYTHON_BIN}" -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')")
|
||||
info "Using Python ${PYTHON_VERSION} at ${PYTHON_BIN}"
|
||||
|
||||
# Check dependencies
|
||||
info "Checking dependencies..."
|
||||
"${PYTHON_BIN}" -c "import yaml" 2>/dev/null || {
|
||||
error "pyyaml not installed. Run: pip install pyyaml"
|
||||
exit 1
|
||||
}
|
||||
"${PYTHON_BIN}" -c "from meshcore_gui.core.shared_data import SharedData" 2>/dev/null || {
|
||||
error "meshcore_gui not importable. Ensure it is installed or on PYTHONPATH."
|
||||
exit 1
|
||||
}
|
||||
info "All dependencies satisfied."
|
||||
|
||||
# Create install directory
|
||||
info "Copying files to ${INSTALL_DIR}/..."
|
||||
mkdir -p "${INSTALL_DIR}"
|
||||
cp "${PROJECT_DIR}/meshcore_bridge.py" "${INSTALL_DIR}/"
|
||||
cp -r "${PROJECT_DIR}/meshcore_bridge" "${INSTALL_DIR}/"
|
||||
chmod +x "${INSTALL_DIR}/meshcore_bridge.py"
|
||||
|
||||
# Copy config (preserve existing)
|
||||
mkdir -p "${CONFIG_DIR}"
|
||||
if [[ -f "${CONFIG_FILE}" ]]; then
|
||||
warn "Config already exists at ${CONFIG_FILE} — not overwriting."
|
||||
warn "New template saved as ${CONFIG_FILE}.new"
|
||||
cp "${PROJECT_DIR}/bridge_config.yaml" "${CONFIG_FILE}.new"
|
||||
else
|
||||
info "Installing config template at ${CONFIG_FILE}"
|
||||
cp "${PROJECT_DIR}/bridge_config.yaml" "${CONFIG_FILE}"
|
||||
fi
|
||||
|
||||
# Detect meshcore_gui location for PYTHONPATH
|
||||
MESHCORE_GUI_DIR=""
|
||||
MESHCORE_GUI_PARENT=$("${PYTHON_BIN}" -c "
|
||||
import meshcore_gui, os
|
||||
print(os.path.dirname(os.path.dirname(meshcore_gui.__file__)))
|
||||
" 2>/dev/null || true)
|
||||
|
||||
if [[ -n "${MESHCORE_GUI_PARENT}" ]]; then
|
||||
MESHCORE_GUI_DIR="${MESHCORE_GUI_PARENT}"
|
||||
info "meshcore_gui found at ${MESHCORE_GUI_DIR}"
|
||||
fi
|
||||
|
||||
# Build PYTHONPATH
|
||||
PYTHONPATH_VALUE="${INSTALL_DIR}"
|
||||
if [[ -n "${MESHCORE_GUI_DIR}" ]]; then
|
||||
PYTHONPATH_VALUE="${INSTALL_DIR}:${MESHCORE_GUI_DIR}"
|
||||
fi
|
||||
|
||||
# Create systemd service file
|
||||
info "Creating systemd service..."
|
||||
cat > "${SERVICE_FILE}" << EOF
|
||||
[Unit]
|
||||
Description=MeshCore Bridge — Cross-Frequency Message Bridge Daemon
|
||||
Documentation=file://${INSTALL_DIR}/BRIDGE.md
|
||||
After=network.target
|
||||
Wants=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=${PYTHON_BIN} ${INSTALL_DIR}/meshcore_bridge.py --config=${CONFIG_FILE}
|
||||
WorkingDirectory=${INSTALL_DIR}
|
||||
Environment="PYTHONPATH=${PYTHONPATH_VALUE}"
|
||||
|
||||
# Restart policy
|
||||
Restart=on-failure
|
||||
RestartSec=10
|
||||
StartLimitIntervalSec=300
|
||||
StartLimitBurst=5
|
||||
|
||||
# Security hardening
|
||||
NoNewPrivileges=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=read-only
|
||||
ReadWritePaths=/home /var/log
|
||||
PrivateTmp=true
|
||||
|
||||
# Logging
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
SyslogIdentifier=${SERVICE_NAME}
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
# Reload systemd
|
||||
info "Reloading systemd daemon..."
|
||||
systemctl daemon-reload
|
||||
|
||||
# Copy documentation
|
||||
if [[ -f "${PROJECT_DIR}/BRIDGE.md" ]]; then
|
||||
cp "${PROJECT_DIR}/BRIDGE.md" "${INSTALL_DIR}/"
|
||||
fi
|
||||
|
||||
# ── Summary ──
|
||||
echo
|
||||
info "============================================="
|
||||
info " Installation complete!"
|
||||
info "============================================="
|
||||
echo
|
||||
info "Files installed:"
|
||||
info " Application: ${INSTALL_DIR}/"
|
||||
info " Config: ${CONFIG_FILE}"
|
||||
info " Service: ${SERVICE_FILE}"
|
||||
echo
|
||||
info "Next steps:"
|
||||
info " 1. Edit configuration: sudo nano ${CONFIG_FILE}"
|
||||
info " 2. Start the service: sudo systemctl start ${SERVICE_NAME}"
|
||||
info " 3. Enable auto-start: sudo systemctl enable ${SERVICE_NAME}"
|
||||
info " 4. Check status: sudo systemctl status ${SERVICE_NAME}"
|
||||
info " 5. Follow logs: journalctl -u ${SERVICE_NAME} -f"
|
||||
info " 6. Open dashboard: http://localhost:9092"
|
||||
echo
|
||||
info "To uninstall: sudo bash install_scripts/install_bridge.sh --uninstall"
|
||||
Executable
+233
@@ -0,0 +1,233 @@
|
||||
#!/usr/bin/env bash
|
||||
# ============================================================================
|
||||
# MeshCore Observer — systemd Service Installer
|
||||
# ============================================================================
|
||||
#
|
||||
# Installs a systemd service for the MeshCore Observer daemon.
|
||||
# Automatically detects the venv and current user.
|
||||
#
|
||||
# Usage:
|
||||
# bash install_scripts/install_observer.sh # from project root
|
||||
# cd install_scripts && bash install_observer.sh # from install_scripts/
|
||||
#
|
||||
# Optional:
|
||||
# bash install_scripts/install_observer.sh --uninstall
|
||||
#
|
||||
# Requirements:
|
||||
# - meshcore-gui project with venv/ directory
|
||||
# - nicegui and pyyaml installed in the venv
|
||||
# - sudo access (for systemd)
|
||||
#
|
||||
# Author: PE1HVH
|
||||
# SPDX-License-Identifier: MIT
|
||||
# Copyright: (c) 2026 PE1HVH
|
||||
# ============================================================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ── Colors ──
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
info() { echo -e "${BLUE}[INFO]${NC} $*"; }
|
||||
ok() { echo -e "${GREEN}[OK]${NC} $*"; }
|
||||
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
|
||||
error() { echo -e "${RED}[ERROR]${NC} $*"; exit 1; }
|
||||
|
||||
SERVICE_NAME="meshcore-observer"
|
||||
|
||||
# ── Resolve project root ──
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
if [[ "$(basename "${SCRIPT_DIR}")" == "install_scripts" ]]; then
|
||||
PROJECT_DIR="$(dirname "${SCRIPT_DIR}")"
|
||||
else
|
||||
PROJECT_DIR="${SCRIPT_DIR}"
|
||||
fi
|
||||
|
||||
# ── Uninstall mode ──
|
||||
if [[ "${1:-}" == "--uninstall" ]]; then
|
||||
info "Removing ${SERVICE_NAME} service..."
|
||||
sudo systemctl stop "${SERVICE_NAME}" 2>/dev/null || true
|
||||
sudo systemctl disable "${SERVICE_NAME}" 2>/dev/null || true
|
||||
sudo rm -f "/etc/systemd/system/${SERVICE_NAME}.service"
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl reset-failed 2>/dev/null || true
|
||||
ok "Service removed"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ── Detect environment ──
|
||||
info "Detecting environment..."
|
||||
|
||||
if [[ ! -f "${PROJECT_DIR}/meshcore_observer.py" ]] && [[ ! -d "${PROJECT_DIR}/meshcore_observer" ]]; then
|
||||
error "Cannot find meshcore_observer.py or meshcore_observer/ in ${PROJECT_DIR}
|
||||
Run this script from the project directory or from install_scripts/."
|
||||
fi
|
||||
|
||||
CURRENT_USER="$(whoami)"
|
||||
VENV_PYTHON="${PROJECT_DIR}/venv/bin/python"
|
||||
|
||||
# Check venv
|
||||
if [[ ! -x "${VENV_PYTHON}" ]]; then
|
||||
# Try parent directory venv (observer may be in meshcore-gui project)
|
||||
PARENT_VENV="$(dirname "${PROJECT_DIR}")/venv/bin/python"
|
||||
if [[ -x "${PARENT_VENV}" ]]; then
|
||||
VENV_PYTHON="${PARENT_VENV}"
|
||||
warn "Using parent directory venv: ${VENV_PYTHON}"
|
||||
else
|
||||
error "Virtual environment not found at: ${VENV_PYTHON}
|
||||
Create it first:
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate
|
||||
pip install nicegui pyyaml"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── Check dependencies ──
|
||||
info "Checking dependencies..."
|
||||
|
||||
"${VENV_PYTHON}" -c "import nicegui" 2>/dev/null || {
|
||||
error "nicegui not installed in venv. Run:
|
||||
source venv/bin/activate
|
||||
pip install nicegui"
|
||||
}
|
||||
|
||||
"${VENV_PYTHON}" -c "import yaml" 2>/dev/null || {
|
||||
error "pyyaml not installed in venv. Run:
|
||||
source venv/bin/activate
|
||||
pip install pyyaml"
|
||||
}
|
||||
|
||||
ok "All dependencies satisfied"
|
||||
|
||||
# ── Detect NODE_PATH for meshcore-decoder (MQTT auth) ──
|
||||
NODE_PATH_VALUE=""
|
||||
if command -v node &>/dev/null; then
|
||||
NPM_GLOBAL="$(npm root -g 2>/dev/null || true)"
|
||||
if [[ -n "${NPM_GLOBAL}" ]] && [[ -d "${NPM_GLOBAL}" ]]; then
|
||||
NODE_PATH_VALUE="${NPM_GLOBAL}"
|
||||
info "Node.js global modules: ${NPM_GLOBAL}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── Optional settings ──
|
||||
WEB_PORT="${WEB_PORT:-9093}"
|
||||
ARCHIVE_DIR="${ARCHIVE_DIR:-~/.meshcore-gui/archive}"
|
||||
DEBUG_ON="${DEBUG_ON:-}"
|
||||
|
||||
if [[ -z "${DEBUG_ON}" ]]; then
|
||||
read -rp "Enable debug logging? [y/N] " dbg
|
||||
if [[ "${dbg}" == "y" || "${dbg}" == "Y" ]]; then
|
||||
DEBUG_ON="yes"
|
||||
else
|
||||
DEBUG_ON="no"
|
||||
fi
|
||||
fi
|
||||
|
||||
DEBUG_FLAG=""
|
||||
if [[ "${DEBUG_ON}" == "yes" ]]; then
|
||||
DEBUG_FLAG="--debug-on"
|
||||
fi
|
||||
|
||||
# ── Config file ──
|
||||
CONFIG_FLAG=""
|
||||
CONFIG_FILE="${PROJECT_DIR}/observer_config.yaml"
|
||||
if [[ -f "${CONFIG_FILE}" ]]; then
|
||||
CONFIG_FLAG="--config=${CONFIG_FILE}"
|
||||
info "Using config: ${CONFIG_FILE}"
|
||||
else
|
||||
info "No observer_config.yaml found — using defaults"
|
||||
fi
|
||||
|
||||
# ── Summary ──
|
||||
echo ""
|
||||
echo "═══════════════════════════════════════════════════"
|
||||
echo " MeshCore Observer — Service Installer"
|
||||
echo "═══════════════════════════════════════════════════"
|
||||
echo " Project dir: ${PROJECT_DIR}"
|
||||
echo " User: ${CURRENT_USER}"
|
||||
echo " Python: ${VENV_PYTHON}"
|
||||
echo " Archive dir: ${ARCHIVE_DIR}"
|
||||
echo " Web port: ${WEB_PORT}"
|
||||
echo " Config: ${CONFIG_FILE}"
|
||||
echo " Debug: ${DEBUG_ON}"
|
||||
if [[ -n "${NODE_PATH_VALUE}" ]]; then
|
||||
echo " NODE_PATH: ${NODE_PATH_VALUE}"
|
||||
fi
|
||||
echo "═══════════════════════════════════════════════════"
|
||||
echo ""
|
||||
read -rp "Continue? [y/N] " confirm
|
||||
if [[ "${confirm}" != "y" && "${confirm}" != "Y" ]]; then
|
||||
info "Aborted."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ── Install systemd service ──
|
||||
info "Installing systemd service..."
|
||||
SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service"
|
||||
|
||||
# Build optional Environment line for NODE_PATH
|
||||
ENV_LINE=""
|
||||
if [[ -n "${NODE_PATH_VALUE}" ]]; then
|
||||
ENV_LINE="Environment=\"NODE_PATH=${NODE_PATH_VALUE}\""
|
||||
fi
|
||||
|
||||
sudo tee "${SERVICE_FILE}" > /dev/null << SERVICE_EOF
|
||||
[Unit]
|
||||
Description=MeshCore Observer — Read-Only Archive Monitor Dashboard
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=${CURRENT_USER}
|
||||
WorkingDirectory=${PROJECT_DIR}
|
||||
ExecStart=${VENV_PYTHON} meshcore_observer.py ${CONFIG_FLAG} --port=${WEB_PORT} ${DEBUG_FLAG}
|
||||
Restart=on-failure
|
||||
RestartSec=30
|
||||
${ENV_LINE}
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
SERVICE_EOF
|
||||
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable "${SERVICE_NAME}"
|
||||
ok "${SERVICE_NAME}.service installed and enabled"
|
||||
|
||||
# ── Done ──
|
||||
echo ""
|
||||
echo "═══════════════════════════════════════════════════"
|
||||
echo -e " ${GREEN}Installation complete!${NC}"
|
||||
echo "═══════════════════════════════════════════════════"
|
||||
echo ""
|
||||
echo " Commands:"
|
||||
echo " sudo systemctl start ${SERVICE_NAME} # Start"
|
||||
echo " sudo systemctl stop ${SERVICE_NAME} # Stop"
|
||||
echo " sudo systemctl restart ${SERVICE_NAME} # Restart"
|
||||
echo " sudo systemctl status ${SERVICE_NAME} # Status"
|
||||
echo " journalctl -u ${SERVICE_NAME} -f # Live logs"
|
||||
echo ""
|
||||
echo " Dashboard: http://localhost:${WEB_PORT}"
|
||||
echo ""
|
||||
echo " Uninstall:"
|
||||
echo " bash install_scripts/install_observer.sh --uninstall"
|
||||
echo ""
|
||||
echo "═══════════════════════════════════════════════════"
|
||||
|
||||
# Optionally start immediately
|
||||
echo ""
|
||||
read -rp "Start service now? [y/N] " start_now
|
||||
if [[ "${start_now}" == "y" || "${start_now}" == "Y" ]]; then
|
||||
sudo systemctl start "${SERVICE_NAME}"
|
||||
sleep 2
|
||||
if systemctl is-active --quiet "${SERVICE_NAME}"; then
|
||||
ok "Service is running!"
|
||||
echo ""
|
||||
info "View live logs: journalctl -u ${SERVICE_NAME} -f"
|
||||
else
|
||||
warn "Service could not start. Check logs:"
|
||||
echo " journalctl -u ${SERVICE_NAME} --no-pager -n 20"
|
||||
fi
|
||||
fi
|
||||
Executable
+241
@@ -0,0 +1,241 @@
|
||||
#!/usr/bin/env bash
|
||||
# ============================================================================
|
||||
# MeshCore GUI — Serial Installer
|
||||
# ============================================================================
|
||||
#
|
||||
# Installs a systemd service for the serial-based MeshCore GUI.
|
||||
# Automatically detects paths and the current user.
|
||||
#
|
||||
# Usage:
|
||||
# bash install_scripts/install_serial.sh # from project root
|
||||
# cd install_scripts && bash install_serial.sh # from install_scripts/
|
||||
#
|
||||
# Optional:
|
||||
# bash install_scripts/install_serial.sh --uninstall
|
||||
#
|
||||
# Requirements:
|
||||
# - meshcore-gui project with venv/ directory
|
||||
# - sudo access (for systemd)
|
||||
#
|
||||
# ============================================================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ── Colors ──
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
info() { echo -e "${BLUE}[INFO]${NC} $*"; }
|
||||
ok() { echo -e "${GREEN}[OK]${NC} $*"; }
|
||||
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
|
||||
error() { echo -e "${RED}[ERROR]${NC} $*"; exit 1; }
|
||||
|
||||
# ── Resolve project root ──
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
if [[ "$(basename "${SCRIPT_DIR}")" == "install_scripts" ]]; then
|
||||
PROJECT_DIR="$(dirname "${SCRIPT_DIR}")"
|
||||
else
|
||||
PROJECT_DIR="${SCRIPT_DIR}"
|
||||
fi
|
||||
|
||||
# ── Uninstall mode ──
|
||||
if [[ "${1:-}" == "--uninstall" ]]; then
|
||||
info "Removing meshcore-gui service..."
|
||||
sudo systemctl stop meshcore-gui 2>/dev/null || true
|
||||
sudo systemctl disable meshcore-gui 2>/dev/null || true
|
||||
sudo rm -f /etc/systemd/system/meshcore-gui.service
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl reset-failed 2>/dev/null || true
|
||||
ok "Service removed"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ── Detect environment ──
|
||||
info "Detecting environment..."
|
||||
|
||||
if [[ ! -f "${PROJECT_DIR}/meshcore_gui.py" ]] && [[ ! -d "${PROJECT_DIR}/meshcore_gui" ]]; then
|
||||
error "Cannot find meshcore_gui.py or meshcore_gui/ in ${PROJECT_DIR}
|
||||
Run this script from the project directory or from install_scripts/."
|
||||
fi
|
||||
|
||||
CURRENT_USER="$(whoami)"
|
||||
VENV_PYTHON="${PROJECT_DIR}/venv/bin/python"
|
||||
|
||||
# Check venv
|
||||
if [[ ! -x "${VENV_PYTHON}" ]]; then
|
||||
error "Virtual environment not found at: ${VENV_PYTHON}
|
||||
Create it first:
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate
|
||||
pip install meshcore nicegui meshcoredecoder"
|
||||
fi
|
||||
|
||||
# Determine the entry point
|
||||
if [[ -f "${PROJECT_DIR}/meshcore_gui.py" ]]; then
|
||||
ENTRY_POINT="meshcore_gui.py"
|
||||
elif [[ -d "${PROJECT_DIR}/meshcore_gui" ]]; then
|
||||
ENTRY_POINT="-m meshcore_gui"
|
||||
else
|
||||
error "Cannot determine entry point."
|
||||
fi
|
||||
|
||||
# Serial port (env or prompt)
|
||||
SERIAL_PORT="${SERIAL_PORT:-}"
|
||||
if [[ -z "${SERIAL_PORT}" ]]; then
|
||||
echo ""
|
||||
echo -e "${YELLOW}Serial device not specified.${NC}"
|
||||
echo "You can specify it in two ways:"
|
||||
echo ""
|
||||
echo " 1. As an environment variable:"
|
||||
echo " SERIAL_PORT=/dev/ttyACM0 bash $0"
|
||||
echo ""
|
||||
echo " 2. Enter manually:"
|
||||
read -rp " Serial device (e.g. /dev/ttyACM0 or /dev/ttyUSB0): " SERIAL_PORT
|
||||
echo ""
|
||||
fi
|
||||
|
||||
if [[ -z "${SERIAL_PORT}" ]]; then
|
||||
error "No serial device specified. Aborted."
|
||||
fi
|
||||
|
||||
# Optional settings
|
||||
BAUD="${BAUD:-115200}"
|
||||
SERIAL_CX_DLY="${SERIAL_CX_DLY:-0.1}"
|
||||
WEB_PORT="${WEB_PORT:-8081}"
|
||||
DEBUG_ON="${DEBUG_ON:-}"
|
||||
|
||||
if [[ -z "${DEBUG_ON}" ]]; then
|
||||
read -rp "Enable debug logging? [y/N] " dbg
|
||||
if [[ "${dbg}" == "y" || "${dbg}" == "Y" ]]; then
|
||||
DEBUG_ON="yes"
|
||||
else
|
||||
DEBUG_ON="no"
|
||||
fi
|
||||
fi
|
||||
|
||||
DEBUG_FLAG=""
|
||||
if [[ "${DEBUG_ON}" == "yes" ]]; then
|
||||
DEBUG_FLAG="--debug-on"
|
||||
fi
|
||||
|
||||
# Warn about dialout group (Linux)
|
||||
if ! id -nG "${CURRENT_USER}" | grep -qw "dialout"; then
|
||||
warn "User '${CURRENT_USER}' is not in the 'dialout' group."
|
||||
warn "Serial access may fail. Fix with:"
|
||||
warn " sudo usermod -aG dialout ${CURRENT_USER}"
|
||||
warn " (then log out/in)"
|
||||
fi
|
||||
|
||||
# Summary
|
||||
echo ""
|
||||
echo "═══════════════════════════════════════════════════"
|
||||
echo " MeshCore GUI — Serial Installer"
|
||||
echo "═══════════════════════════════════════════════════"
|
||||
echo " Project dir: ${PROJECT_DIR}"
|
||||
echo " User: ${CURRENT_USER}"
|
||||
echo " Python: ${VENV_PYTHON}"
|
||||
echo " Entry point: ${ENTRY_POINT}"
|
||||
echo " Serial port: ${SERIAL_PORT}"
|
||||
echo " Baudrate: ${BAUD}"
|
||||
echo " CX delay: ${SERIAL_CX_DLY}"
|
||||
echo " Web port: ${WEB_PORT}"
|
||||
echo " Debug: ${DEBUG_ON}"
|
||||
echo "═══════════════════════════════════════════════════"
|
||||
echo ""
|
||||
read -rp "Continue? [y/N] " confirm
|
||||
if [[ "${confirm}" != "y" && "${confirm}" != "Y" ]]; then
|
||||
info "Aborted."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ── Step 1: Upgrade meshcore library ──
|
||||
info "Step 1/3: Upgrading meshcore library..."
|
||||
"${PROJECT_DIR}/venv/bin/pip" install --upgrade meshcore --quiet 2>/dev/null || \
|
||||
"${PROJECT_DIR}/venv/bin/pip" install --upgrade meshcore
|
||||
MESHCORE_VERSION=$("${PROJECT_DIR}/venv/bin/pip" show meshcore 2>/dev/null | grep "^Version:" | awk '{print $2}')
|
||||
ok "meshcore version: ${MESHCORE_VERSION:-unknown}"
|
||||
|
||||
# ── Step 2: Verify Python syntax ──
|
||||
info "Step 2/3: Verifying Python syntax..."
|
||||
"${VENV_PYTHON}" -c "
|
||||
import ast, sys
|
||||
files = [
|
||||
'${PROJECT_DIR}/meshcore_gui.py',
|
||||
'${PROJECT_DIR}/meshcore_gui/ble/worker.py',
|
||||
'${PROJECT_DIR}/meshcore_gui/ble/commands.py',
|
||||
]
|
||||
errors = []
|
||||
for f in files:
|
||||
try:
|
||||
ast.parse(open(f).read())
|
||||
except Exception as e:
|
||||
errors.append(f'{f}: {e}')
|
||||
if errors:
|
||||
print('SYNTAX ERRORS:')
|
||||
for e in errors:
|
||||
print(f' {e}')
|
||||
sys.exit(1)
|
||||
print('OK')
|
||||
" || error "Syntax errors found in Python files"
|
||||
ok "Python files are syntactically correct"
|
||||
|
||||
# ── Step 3: Install systemd service ──
|
||||
info "Step 3/3: Installing systemd service..."
|
||||
SERVICE_FILE="/etc/systemd/system/meshcore-gui.service"
|
||||
|
||||
sudo tee "${SERVICE_FILE}" > /dev/null << SERVICE_EOF
|
||||
[Unit]
|
||||
Description=MeshCore GUI (Serial)
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=${CURRENT_USER}
|
||||
WorkingDirectory=${PROJECT_DIR}
|
||||
ExecStart=${VENV_PYTHON} ${ENTRY_POINT} ${SERIAL_PORT} ${DEBUG_FLAG} --port=${WEB_PORT} --baud=${BAUD} --serial-cx-dly=${SERIAL_CX_DLY}
|
||||
Restart=on-failure
|
||||
RestartSec=30
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
SERVICE_EOF
|
||||
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable meshcore-gui
|
||||
ok "meshcore-gui.service installed and enabled"
|
||||
|
||||
# ── Done ──
|
||||
echo ""
|
||||
echo "═══════════════════════════════════════════════════"
|
||||
echo -e " ${GREEN}Installation complete!${NC}"
|
||||
echo "═══════════════════════════════════════════════════"
|
||||
echo ""
|
||||
echo " Commands:"
|
||||
echo " sudo systemctl start meshcore-gui # Start"
|
||||
echo " sudo systemctl stop meshcore-gui # Stop"
|
||||
echo " sudo systemctl restart meshcore-gui # Restart"
|
||||
echo " sudo systemctl status meshcore-gui # Status"
|
||||
echo " journalctl -u meshcore-gui -f # Live logs"
|
||||
echo ""
|
||||
echo " Uninstall:"
|
||||
echo " bash install_scripts/install_serial.sh --uninstall"
|
||||
echo ""
|
||||
echo "═══════════════════════════════════════════════════"
|
||||
|
||||
# Optionally start immediately
|
||||
echo ""
|
||||
read -rp "Start service now? [y/N] " start_now
|
||||
if [[ "${start_now}" == "y" || "${start_now}" == "Y" ]]; then
|
||||
sudo systemctl start meshcore-gui
|
||||
sleep 2
|
||||
if systemctl is-active --quiet meshcore-gui; then
|
||||
ok "Service is running!"
|
||||
echo ""
|
||||
info "View live logs: journalctl -u meshcore-gui -f"
|
||||
else
|
||||
warn "Service could not start. Check logs:"
|
||||
echo " journalctl -u meshcore-gui --no-pager -n 20"
|
||||
fi
|
||||
fi
|
||||
Executable
+30
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env bash
|
||||
# ============================================================================
|
||||
# MeshCore GUI — Virtual Environment Setup
|
||||
# ============================================================================
|
||||
#
|
||||
# Creates a venv and installs core Python dependencies.
|
||||
#
|
||||
# Usage:
|
||||
# bash install_scripts/install_venv.sh # from project root
|
||||
# cd install_scripts && bash install_venv.sh # from install_scripts/
|
||||
#
|
||||
# ============================================================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ── Resolve project root ──
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
if [[ "$(basename "${SCRIPT_DIR}")" == "install_scripts" ]]; then
|
||||
PROJECT_DIR="$(dirname "${SCRIPT_DIR}")"
|
||||
else
|
||||
PROJECT_DIR="${SCRIPT_DIR}"
|
||||
fi
|
||||
|
||||
cd "${PROJECT_DIR}"
|
||||
|
||||
echo "Creating virtual environment in ${PROJECT_DIR}/venv ..."
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate
|
||||
pip install nicegui meshcore bleak meshcoredecoder
|
||||
echo "Done. Activate with: source ${PROJECT_DIR}/venv/bin/activate"
|
||||
Reference in New Issue
Block a user