Files
meshcore-gui/install_scripts/install_bridge.sh
2026-03-09 17:53:29 +01:00

239 lines
7.2 KiB
Bash
Executable File

#!/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"