mirror of
https://github.com/pe1hvh/meshcore-gui.git
synced 2026-03-28 17:42:38 +01:00
239 lines
7.2 KiB
Bash
Executable File
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"
|