#!/usr/bin/env bash # # inBOXER Install Script # ====================== # Deploys the inBOXER binary and configuration to /opt/inboxer # Creates a systemd service for production deployment. # Supports Debian (apt) and RHEL (yum/dnf) families. # # Usage: sudo ./install.sh # set -euo pipefail # ─── Configuration ─────────────────────────────────────────────────────────── INSTALL_DIR="/opt/inboxer" SERVICE_USER="inboxer" SERVICE_GROUP="inboxer" SERVICE_NAME="inboxer" SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service" BIN_DIR="${INSTALL_DIR}/bin" DATA_DIR="${INSTALL_DIR}/data" LOGS_DIR="${INSTALL_DIR}/logs" # Detect script/repo location SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" if [[ "$(basename "${SCRIPT_DIR}")" == "bin" ]]; then REPO_DIR="$(dirname "${SCRIPT_DIR}")" else REPO_DIR="${SCRIPT_DIR}" fi # ─── Terminal colours ──────────────────────────────────────────────────────── RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' info() { echo -e "${GREEN}[INFO]${NC} $*"; } warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } error() { echo -e "${RED}[ERROR]${NC} $*" >&2; } # ─── Pre-flight checks ─────────────────────────────────────────────────────── if [[ $EUID -ne 0 ]]; then error "This script must be run as root (use sudo)." exit 1 fi if ! command -v systemctl &>/dev/null; then error "systemd is required but not found on this system." exit 1 fi REQUIRED_FILES=( "${REPO_DIR}/bin/inboxer" "${REPO_DIR}/bin/config.yaml" "${REPO_DIR}/bin/prompt.txt" ) for f in "${REQUIRED_FILES[@]}"; do if [[ ! -f "$f" ]]; then error "Required file not found: $f" error "Run this script from the inBOXER repository root." exit 1 fi done # ─── OS Detection (informational) ─────────────────────────────────────────── if [[ -f /etc/os-release ]]; then # shellcheck source=/dev/null . /etc/os-release info "Detected OS: ${NAME} ${VERSION_ID}" else info "Could not detect OS version (no /etc/os-release)." fi # ─── Create system user & group ────────────────────────────────────────────── info "Creating system user '${SERVICE_USER}'..." if getent group "${SERVICE_GROUP}" &>/dev/null; then info "Group '${SERVICE_GROUP}' already exists." else groupadd --system "${SERVICE_GROUP}" info "Group '${SERVICE_GROUP}' created." fi if getent passwd "${SERVICE_USER}" &>/dev/null; then info "User '${SERVICE_USER}' already exists." else useradd --system \ --no-create-home \ --gid "${SERVICE_GROUP}" \ --shell /sbin/nologin \ --comment "inBOXER Service User" \ "${SERVICE_USER}" info "User '${SERVICE_USER}' created." fi # ─── Create directory structure ────────────────────────────────────────────── info "Creating directories under ${INSTALL_DIR}..." mkdir -p "${BIN_DIR}" "${DATA_DIR}" "${LOGS_DIR}" # ─── Install binary & config files ─────────────────────────────────────────── info "Installing binary..." install -m 755 "${REPO_DIR}/bin/inboxer" "${BIN_DIR}/inboxer" info "Installing configuration..." install -m 644 "${REPO_DIR}/bin/config.yaml" "${BIN_DIR}/config.yaml" install -m 644 "${REPO_DIR}/bin/prompt.txt" "${BIN_DIR}/prompt.txt" # Adjust config paths for /opt/inboxer deployment info "Adjusting configuration paths for deployment..." sed -i 's|path: "bin/db.sqlite"|path: "'"${DATA_DIR}"'/db.sqlite"|' "${BIN_DIR}/config.yaml" sed -i 's|file: "bin/inboxer.log"|file: "'"${LOGS_DIR}"'/inboxer.log"|' "${BIN_DIR}/config.yaml" # prompt_file: "bin/prompt.txt" works as-is relative to the working directory, # since WorkingDirectory=/opt/inboxer resolves it to /opt/inboxer/bin/prompt.txt # ─── Create systemd service unit ───────────────────────────────────────────── info "Creating systemd service unit at ${SERVICE_FILE} ..." cat > "${SERVICE_FILE}" << UNITEOF [Unit] Description=inBOXER – AI-Powered Email Classifier Documentation=https://github.com/cclohmar/inboxer After=network-online.target Wants=network-online.target [Service] Type=simple User=${SERVICE_USER} Group=${SERVICE_GROUP} WorkingDirectory=${INSTALL_DIR} ExecStart=${BIN_DIR}/inboxer Restart=on-failure RestartSec=10 # Logging StandardOutput=append:${LOGS_DIR}/stdout.log StandardError=append:${LOGS_DIR}/stderr.log # Security hardening NoNewPrivileges=true ProtectSystem=full ProtectHome=true PrivateTmp=true [Install] WantedBy=multi-user.target UNITEOF # ─── Set file permissions ──────────────────────────────────────────────────── info "Setting file ownership and permissions..." chown -R "${SERVICE_USER}:${SERVICE_GROUP}" "${INSTALL_DIR}" # Directory permissions chmod 755 "${BIN_DIR}" chmod 750 "${DATA_DIR}" # database file is sensitive chmod 750 "${LOGS_DIR}" # config.yaml contains secrets so restrict access chmod 640 "${BIN_DIR}/config.yaml" # ─── Register & start service ─────────────────────────────────────────────── info "Reloading systemd daemon..." systemctl daemon-reload info "Enabling ${SERVICE_NAME} service (starts on boot)..." systemctl enable "${SERVICE_NAME}" info "Starting ${SERVICE_NAME} service..." systemctl start "${SERVICE_NAME}" # Brief pause so the service can initialise sleep 2 # ─── Verify ────────────────────────────────────────────────────────────────── if systemctl is-active --quiet "${SERVICE_NAME}"; then info "Service '${SERVICE_NAME}' is running." systemctl status "${SERVICE_NAME}" --no-pager else warn "Service '${SERVICE_NAME}' did not start. Check logs:" warn " journalctl -u ${SERVICE_NAME} --no-pager" systemctl status "${SERVICE_NAME}" --no-pager || true fi # ─── Summary ───────────────────────────────────────────────────────────────── echo "" info "═════════════════════════════════════════════════" info " inBOXER Installation Complete" info "═════════════════════════════════════════════════" echo "" info " Install directory: ${INSTALL_DIR}" info " Binary: ${BIN_DIR}/inboxer" info " Configuration: ${BIN_DIR}/config.yaml" info " Prompt file: ${BIN_DIR}/prompt.txt" info " Data (SQLite): ${DATA_DIR}/" info " Logs: ${LOGS_DIR}/" echo "" info " ⚙ Edit config.yaml with your credentials before first start:" info " sudo nano ${BIN_DIR}/config.yaml" info "" info " Required settings:" info " - ai.api_key (DeepSeek API key)" info " - smtp.host/port (SMTP server for OTP emails)" info " - smtp.username (SMTP login)" info " - smtp.password (SMTP password)" info " - server.session_secret (change from the default)" echo "" if grep -q "your_deepseek_api_key_here\|your.smtp.host\|change-me-in-production" "${BIN_DIR}/config.yaml" 2>/dev/null; then warn " ⚠ config.yaml still contains placeholder values!" warn " Edit ${BIN_DIR}/config.yaml before the service will function." echo "" fi info " Service management:" info " sudo systemctl status ${SERVICE_NAME}" info " sudo systemctl restart ${SERVICE_NAME}" info " sudo systemctl stop ${SERVICE_NAME}" info " sudo journalctl -u ${SERVICE_NAME} -f" echo "" info " Web interface: http://$(hostname -s 2>/dev/null || echo "localhost"):8080" info "═════════════════════════════════════════════════" echo ""