Distribute binary in repo + add install.sh for /opt/inboxer deployment
This commit is contained in:
parent
766e3e3de6
commit
a9216459fc
4 changed files with 243 additions and 20 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -1,8 +1,7 @@
|
|||
# Environment
|
||||
.env
|
||||
|
||||
# Build artifacts
|
||||
bin/inboxer
|
||||
# Build artifacts (binary is distributed with the repo)
|
||||
bin/*.log
|
||||
bin/db.sqlite
|
||||
bin/db.sqlite-wal
|
||||
|
|
|
|||
|
|
@ -1,18 +0,0 @@
|
|||
You are an email classification assistant. Analyze the email content and categorize it into one of these folders:
|
||||
|
||||
1. **Important** - Urgent emails, work-related, personal correspondence, bills, appointments, time-sensitive matters
|
||||
2. **eCommerce** - Shopping confirmations, order updates, shipping notifications, receipts, promotional offers from stores
|
||||
3. **Spam** - Unsolicited commercial emails, phishing attempts, scams, obvious junk mail
|
||||
4. **Other** - Everything else that doesn't fit the above categories
|
||||
|
||||
Consider the sender, subject, and email body. Respond with a JSON object in this exact format:
|
||||
{
|
||||
"folder": "Important|eCommerce|Spam|Other",
|
||||
"score": 1-100,
|
||||
"context": "Brief explanation of why this classification was chosen"
|
||||
}
|
||||
|
||||
Email to classify:
|
||||
From: {sender}
|
||||
Subject: {subject}
|
||||
Body: {body}
|
||||
BIN
bin/inboxer
Executable file
BIN
bin/inboxer
Executable file
Binary file not shown.
242
install.sh
Executable file
242
install.sh
Executable file
|
|
@ -0,0 +1,242 @@
|
|||
#!/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 .env template ────────────────────────────────────────────────────
|
||||
ENV_FILE="${INSTALL_DIR}/.env"
|
||||
if [[ ! -f "${ENV_FILE}" ]]; then
|
||||
info "Creating .env template at ${ENV_FILE} ..."
|
||||
cat > "${ENV_FILE}" << 'ENVEOF'
|
||||
# inBOXER Environment Configuration
|
||||
# ====================================
|
||||
# Set your credentials below. The service reads these variables on startup.
|
||||
# You can also set them directly in the systemd EnvironmentFile or via
|
||||
# the shell environment (godotenv.Load() checks both the .env file and os.Getenv).
|
||||
|
||||
# DeepSeek API key – used to classify incoming emails
|
||||
DEEPSEEK_API_KEY=your_deepseek_api_key_here
|
||||
|
||||
# SMTP credentials – used to send one-time passwords (OTP) via email
|
||||
SMTP_HOST=your.smtp.host
|
||||
SMTP_PORT=587
|
||||
SMTP_USER=your-email@example.com
|
||||
SMTP_PASS=your-smtp-password
|
||||
|
||||
# (Optional) Override the session_secret from config.yaml.
|
||||
# APP_SECRET=change-me-in-production
|
||||
ENVEOF
|
||||
info ".env template created."
|
||||
else
|
||||
info ".env already exists, keeping existing file."
|
||||
fi
|
||||
|
||||
# ─── 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}
|
||||
EnvironmentFile=${ENV_FILE}
|
||||
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}"
|
||||
|
||||
# .env must be readable by the service user (and owner-only for secrets)
|
||||
chmod 640 "${ENV_FILE}"
|
||||
|
||||
# ─── 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}/"
|
||||
info " Environment: ${ENV_FILE}"
|
||||
echo ""
|
||||
info " Edit the environment file with your credentials:"
|
||||
info " sudo nano ${ENV_FILE}"
|
||||
echo ""
|
||||
if grep -q "your_deepseek_api_key_here" "${ENV_FILE}" 2>/dev/null; then
|
||||
warn " ⚠ The .env file still contains placeholder values!"
|
||||
warn " Edit ${ENV_FILE} 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 ""
|
||||
Loading…
Reference in a new issue