infra: .deb packaging via scripts/build-deb.sh

Add scripts/build-deb.sh using fakeroot and dpkg-deb to produce a
Debian package. Includes DEBIAN/control, postinst (venv setup, systemd
enable), prerm (systemd stop), and postrm (cleanup). Add make deb
target. Update install-service.sh and uninstall-service.sh for current
paths and Python version.
This commit is contained in:
Stacy Olivas
2026-03-17 17:42:54 -07:00
parent 5b6f2829b8
commit c7f2bdbf72
3 changed files with 291 additions and 5 deletions
+52 -2
View File
@@ -17,7 +17,7 @@
#
# Prerequisites:
# - Linux system with systemd OR macOS
# - Python 3.7+ installed
# - Python 3.9+ installed
# - sudo access (script will prompt if needed)
# - Run from the meshcore-bot directory
@@ -128,6 +128,29 @@ print_error() {
echo -e "${RED}${NC} $1"
}
# Function to ask yes/no question
ask_yes_no() {
local prompt="$1"
local default="${2:-n}"
local response
if [[ "$default" == "y" ]]; then
prompt="${prompt} [Y/n]: "
else
prompt="${prompt} [y/N]: "
fi
while true; do
read -p "$(echo -e "${YELLOW}${prompt}${NC}")" response
response="${response:-$default}"
case "$response" in
[Yy]|[Yy][Ee][Ss]) return 0 ;;
[Nn]|[Nn][Oo]) return 1 ;;
*) echo "Please answer yes or no." ;;
esac
done
}
if [[ "$UPGRADE_MODE" == true ]]; then
print_section "MeshCore Bot Service Upgrader"
print_info "Running in UPGRADE mode - will update files and dependencies"
@@ -202,7 +225,7 @@ fi
# Check if Python 3 is available
if ! command -v python3 &> /dev/null; then
print_error "Python 3 is not installed or not in PATH"
print_error "Please install Python 3.7 or higher before running this script"
print_error "Please install Python 3.9 or higher before running this script"
exit 1
fi
@@ -505,6 +528,33 @@ $VENV_PYTHON -m pip install --quiet -r "$INSTALL_DIR/requirements.txt" || {
}
print_success "Installed all Python dependencies"
# Optional extras
echo ""
print_info "Optional feature packages are available:"
echo " • Profanity filter (better-profanity, unidecode) — drop/censor offensive messages"
echo " • Geocoding extras (pycountry, us) — improved country/state name resolution"
echo ""
if ask_yes_no "Install profanity filter packages? (recommended if using the profanity filter feature)" "n"; then
print_info "Installing profanity filter packages..."
"$INSTALL_DIR/venv/bin/pip" install --quiet "better-profanity>=0.7.0" "unidecode>=1.3.0" || {
print_warning "Failed to install profanity filter packages (non-fatal)"
}
print_success "Installed profanity filter packages"
else
print_info "Skipping profanity filter packages"
fi
if ask_yes_no "Install geocoding extras? (recommended if using location/path commands)" "n"; then
print_info "Installing geocoding extras..."
"$INSTALL_DIR/venv/bin/pip" install --quiet "pycountry>=23.12.0" "us>=2.0.0" || {
print_warning "Failed to install geocoding extras (non-fatal)"
}
print_success "Installed geocoding extras"
else
print_info "Skipping geocoding extras"
fi
print_section "Step 5: Setting File Permissions"
print_info "Configuring file ownership and permissions for security"
print_info "The service user will own all files, with appropriate read/write permissions"
+231
View File
@@ -0,0 +1,231 @@
#!/usr/bin/env bash
# build-deb.sh — Build a .deb package for meshcore-bot
# Usage: ./scripts/build-deb.sh [version]
#
# Produces: dist/meshcore-bot_<version>_all.deb
# Requirements: dpkg-deb, fakeroot (sudo apt install fakeroot)
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
# ── Version ──────────────────────────────────────────────────────────────────
VERSION="${1:-}"
if [[ -z "${VERSION}" ]]; then
VERSION="$(python3 -c "import tomllib; d=tomllib.load(open('${PROJECT_ROOT}/pyproject.toml','rb')); print(d['project']['version'])" 2>/dev/null || \
python3 -c "import tomli; d=tomli.load(open('${PROJECT_ROOT}/pyproject.toml','rb')); print(d['project']['version'])" 2>/dev/null || \
grep -Po '(?<=^version = ")([^"]+)' "${PROJECT_ROOT}/pyproject.toml" || echo "0.9.0")"
fi
PACKAGE_NAME="meshcore-bot"
ARCH="all"
INSTALL_ROOT="/opt/meshcore-bot"
CONF_DIR="/etc/meshcore-bot"
LOG_DIR="/var/log/meshcore-bot"
DATA_DIR="/var/lib/meshcore-bot"
SYSTEMD_DIR="/lib/systemd/system"
BUILD_DIR="${PROJECT_ROOT}/dist/deb-build/${PACKAGE_NAME}_${VERSION}_${ARCH}"
OUT_DIR="${PROJECT_ROOT}/dist"
echo "==> Building ${PACKAGE_NAME} ${VERSION} (arch=${ARCH})"
echo " Project root : ${PROJECT_ROOT}"
echo " Build dir : ${BUILD_DIR}"
echo " Output dir : ${OUT_DIR}"
echo ""
# ── Clean & create staging tree ──────────────────────────────────────────────
rm -rf "${BUILD_DIR}"
mkdir -p \
"${BUILD_DIR}/DEBIAN" \
"${BUILD_DIR}${INSTALL_ROOT}" \
"${BUILD_DIR}${CONF_DIR}" \
"${BUILD_DIR}${LOG_DIR}" \
"${BUILD_DIR}${DATA_DIR}" \
"${BUILD_DIR}${SYSTEMD_DIR}" \
"${OUT_DIR}"
# ── Copy application files ────────────────────────────────────────────────────
echo "==> Copying application files…"
rsync -a \
--exclude='.git' \
--exclude='.github' \
--exclude='__pycache__' \
--exclude='*.pyc' \
--exclude='*.egg-info' \
--exclude='.venv' \
--exclude='venv' \
--exclude='dist' \
--exclude='local' \
--exclude='*.db' \
--exclude='*.log' \
--exclude='config.ini' \
--exclude='node_modules' \
"${PROJECT_ROOT}/" "${BUILD_DIR}${INSTALL_ROOT}/"
# ── Default config ────────────────────────────────────────────────────────────
echo "==> Installing default config…"
cp "${PROJECT_ROOT}/config.ini.example" "${BUILD_DIR}${CONF_DIR}/config.ini"
# Symlink so the app finds it in the install root
ln -sf "${CONF_DIR}/config.ini" "${BUILD_DIR}${INSTALL_ROOT}/config.ini"
# ── systemd unit ──────────────────────────────────────────────────────────────
echo "==> Installing systemd unit…"
cat > "${BUILD_DIR}${SYSTEMD_DIR}/${PACKAGE_NAME}.service" << 'UNIT'
[Unit]
Description=MeshCore Bot - Mesh Network Bot Service
Documentation=https://github.com/your-repo/meshcore-bot
After=network.target
Wants=network.target
[Service]
Type=simple
User=meshcore
Group=meshcore
WorkingDirectory=/opt/meshcore-bot
ExecStart=/opt/meshcore-bot/venv/bin/python /opt/meshcore-bot/meshcore_bot.py --config /etc/meshcore-bot/config.ini
ExecReload=/bin/kill -HUP $MAINPID
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
SyslogIdentifier=meshcore-bot
Environment=PYTHONPATH=/opt/meshcore-bot
Environment=PYTHONUNBUFFERED=1
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/opt/meshcore-bot
ReadWritePaths=/etc/meshcore-bot
ReadWritePaths=/var/log/meshcore-bot
ReadWritePaths=/var/lib/meshcore-bot
LimitNOFILE=65536
MemoryMax=512M
StartLimitInterval=60
StartLimitBurst=3
[Install]
WantedBy=multi-user.target
UNIT
# ── DEBIAN/control ─────────────────────────────────────────────────────────────
echo "==> Writing DEBIAN/control…"
# Compute rough installed size (kB)
INSTALLED_SIZE=$(du -s "${BUILD_DIR}${INSTALL_ROOT}" | awk '{print $1}')
cat > "${BUILD_DIR}/DEBIAN/control" << EOF
Package: ${PACKAGE_NAME}
Version: ${VERSION}
Section: net
Priority: optional
Architecture: ${ARCH}
Installed-Size: ${INSTALLED_SIZE}
Depends: python3 (>= 3.9), python3-pip, python3-venv, adduser
Recommends: systemd
Suggests: sqlite3
Maintainer: MeshCore Bot Team <noreply@example.com>
Description: MeshCore Bot - Mesh Network Automation Bot
A feature-rich bot for MeshCore mesh radio networks.
Supports command handling, scheduled messages, web viewer,
Discord/Telegram bridges, inbound webhooks, and more.
Homepage: https://github.com/your-repo/meshcore-bot
EOF
# ── DEBIAN/conffiles ──────────────────────────────────────────────────────────
echo "${CONF_DIR}/config.ini" > "${BUILD_DIR}/DEBIAN/conffiles"
# ── DEBIAN/postinst ───────────────────────────────────────────────────────────
cat > "${BUILD_DIR}/DEBIAN/postinst" << 'POSTINST'
#!/bin/bash
set -e
INSTALL_ROOT="/opt/meshcore-bot"
CONF_DIR="/etc/meshcore-bot"
LOG_DIR="/var/log/meshcore-bot"
DATA_DIR="/var/lib/meshcore-bot"
SERVICE_USER="meshcore"
# Create service user if it doesn't exist
if ! id -u "${SERVICE_USER}" >/dev/null 2>&1; then
adduser --system --group --no-create-home \
--home "${INSTALL_ROOT}" \
--shell /usr/sbin/nologin \
--gecos "MeshCore Bot service account" \
"${SERVICE_USER}"
fi
# Create directories
install -d -o "${SERVICE_USER}" -g "${SERVICE_USER}" -m 0755 "${LOG_DIR}"
install -d -o "${SERVICE_USER}" -g "${SERVICE_USER}" -m 0755 "${DATA_DIR}"
install -d -o root -g root -m 0755 "${CONF_DIR}"
chown -R "${SERVICE_USER}:${SERVICE_USER}" "${INSTALL_ROOT}"
# Create virtualenv and install dependencies
if [ ! -d "${INSTALL_ROOT}/venv" ]; then
echo "Creating Python virtualenv…"
python3 -m venv "${INSTALL_ROOT}/venv"
fi
echo "Installing Python dependencies…"
"${INSTALL_ROOT}/venv/bin/pip" install --quiet --upgrade pip
"${INSTALL_ROOT}/venv/bin/pip" install --quiet -e "${INSTALL_ROOT}[all]"
# Enable and start systemd service
if command -v systemctl >/dev/null 2>&1; then
systemctl daemon-reload
systemctl enable meshcore-bot.service || true
echo "Service enabled. Start with: sudo systemctl start meshcore-bot"
fi
echo ""
echo "meshcore-bot installed successfully."
echo "Edit /etc/meshcore-bot/config.ini before starting the service."
POSTINST
chmod 0755 "${BUILD_DIR}/DEBIAN/postinst"
# ── DEBIAN/prerm ──────────────────────────────────────────────────────────────
cat > "${BUILD_DIR}/DEBIAN/prerm" << 'PRERM'
#!/bin/bash
set -e
if command -v systemctl >/dev/null 2>&1; then
systemctl stop meshcore-bot.service 2>/dev/null || true
systemctl disable meshcore-bot.service 2>/dev/null || true
fi
PRERM
chmod 0755 "${BUILD_DIR}/DEBIAN/prerm"
# ── DEBIAN/postrm ─────────────────────────────────────────────────────────────
cat > "${BUILD_DIR}/DEBIAN/postrm" << 'POSTRM'
#!/bin/bash
set -e
if [ "$1" = "purge" ]; then
rm -rf /opt/meshcore-bot/venv
rm -rf /var/lib/meshcore-bot
if command -v systemctl >/dev/null 2>&1; then
systemctl daemon-reload || true
fi
fi
POSTRM
chmod 0755 "${BUILD_DIR}/DEBIAN/postrm"
# ── Set permissions ───────────────────────────────────────────────────────────
echo "==> Setting permissions…"
find "${BUILD_DIR}" -type d -exec chmod 0755 {} \;
find "${BUILD_DIR}" -type f -exec chmod 0644 {} \;
chmod 0755 "${BUILD_DIR}/DEBIAN/postinst" "${BUILD_DIR}/DEBIAN/prerm" "${BUILD_DIR}/DEBIAN/postrm"
# Make Python entry point executable
chmod 0755 "${BUILD_DIR}${INSTALL_ROOT}/meshcore_bot.py" 2>/dev/null || true
# ── Build the .deb ────────────────────────────────────────────────────────────
DEB_FILE="${OUT_DIR}/${PACKAGE_NAME}_${VERSION}_${ARCH}.deb"
echo "==> Building .deb: ${DEB_FILE}"
fakeroot dpkg-deb --build "${BUILD_DIR}" "${DEB_FILE}"
echo ""
echo "Done! Package: ${DEB_FILE}"
echo ""
echo "Install with: sudo dpkg -i ${DEB_FILE}"
echo " sudo apt-get install -f # fix dependencies if needed"
+8 -3
View File
@@ -487,9 +487,14 @@ echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━
echo -e "${BLUE}️ Additional Notes${NC}"
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo ""
print_info "Python packages installed via pip are not automatically removed"
print_info "If you want to remove them, run:"
echo " ${YELLOW}pip3 uninstall -r requirements.txt${NC}"
if [[ "$INSTALL_EXISTS" == true ]] && [ ! -d "$INSTALL_DIR" ]; then
print_info "The virtual environment was removed with the installation directory"
print_info "All Python packages installed for this bot have been removed"
else
print_info "Python packages are installed inside the virtual environment at:"
echo " ${YELLOW}$INSTALL_DIR/venv${NC}"
print_info "Removing the installation directory above also removes all packages"
fi
echo ""
if [[ "$INSTALL_EXISTS" == true ]] && [ -d "$INSTALL_DIR" ]; then