All configuration (including secrets) now lives in a single file: bin/config.yaml. The separate .env file has been eliminated. Changes: - config.go: Added SMTPSettings struct + AI.APIKey to Config; removed godotenv import, Environment struct, and all os.Getenv() calls - config.yaml: Added smtp section (host/port/username/password) and ai.api_key field with placeholder values - main.go: Reads SMTP and API key from cfg instead of env - smtp.go: Changed Port field from string to int - otp_test.go: Updated Port values to int - .env.example: Deleted (all config is in config.yaml) - .gitignore: Removed .env.example; kept .env for safety - go.mod/go.sum: Removed github.com/joho/godotenv dependency - install.sh: No longer creates .env or uses EnvironmentFile; warns about placeholder values in config.yaml instead
219 lines
8.7 KiB
Bash
Executable file
219 lines
8.7 KiB
Bash
Executable file
#!/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 ""
|