From 20e28907de7c4f86fbe50c527d2f3f029bbc0587 Mon Sep 17 00:00:00 2001 From: cclohmar Date: Thu, 23 Apr 2026 20:19:57 +0000 Subject: [PATCH] install.sh: download from git repository instead of local copy; update README with deployment guide install.sh now fetches bin/inboxer, bin/config.yaml, and bin/prompt.txt from https://git.lohmar.co.uk/cclohmar/inboxer/raw/branch/main/bin/ so the script works as a standalone remote installer: curl -sSL https://git.lohmar.co.uk/cclohmar/inboxer/raw/branch/main/install.sh | sudo bash Supports both curl and wget; validates downloads are not HTML error pages. README.md rewritten with: - One-line install command - Manual build & deploy instructions - Full configuration reference table - Service management commands - Architecture overview --- README.md | 170 +++++++++++++++++++++++++++++++++++++++++++++++------ install.sh | 120 +++++++++++++++++++++---------------- 2 files changed, 220 insertions(+), 70 deletions(-) diff --git a/README.md b/README.md index a4fdc92..acecac9 100644 --- a/README.md +++ b/README.md @@ -1,35 +1,167 @@ # inBOXER -Email classification and organization tool using IMAP and AI. +AI-powered email classification and organization tool. Connects to your +IMAP mailbox, classifies incoming emails via DeepSeek AI, and moves them +into organised folders automatically. -## Overview +## One-Line Install -inBOXER is a Go application that: -- Connects to your IMAP email account -- Uses DeepSeek AI to classify incoming emails -- Automatically moves emails to appropriate folders (Important, eCommerce, Other, Spam) -- Provides a web interface for configuration and monitoring +```bash +curl -sSL https://git.lohmar.co.uk/cclohmar/inboxer/raw/branch/main/install.sh | sudo bash +``` + +This will: +- Download the binary, config, and prompt file from the repository +- Create the `inboxer` system user +- Install everything under `/opt/inboxer/` +- Create and start a systemd service + +After install, edit the configuration to set your credentials: + +```bash +sudo nano /opt/inboxer/bin/config.yaml +``` + +Required settings: +- `ai.api_key` -- your DeepSeek API key +- `smtp.host`, `smtp.port`, `smtp.username`, `smtp.password` -- SMTP credentials for sending OTP login emails +- `server.session_secret` -- change from the default + +Then start the service: + +```bash +sudo systemctl start inboxer +``` + +## Manual Install + +### Prerequisites + +- Go 1.21+ (for building from source) +- A DeepSeek API key ([platform.deepseek.com](https://platform.deepseek.com/)) +- SMTP credentials for sending OTP emails + +### Build from Source + +```bash +git clone https://git.lohmar.co.uk/cclohmar/inboxer.git +cd inboxer +make build +``` + +The binary is written to `bin/inboxer`. Copy the entire `bin/` directory +to your target machine or deploy via `install.sh`. + +### Run Directly + +```bash +cp .env.example .env +# edit .env with your credentials + +make run +``` + +The web interface will be available at `http://localhost:8080`. + +### Deploy Manually to /opt/inboxer + +```bash +sudo mkdir -p /opt/inboxer/{bin,data,logs} +sudo cp bin/inboxer /opt/inboxer/bin/ +sudo cp bin/config.yaml /opt/inboxer/bin/ +sudo cp bin/prompt.txt /opt/inboxer/bin/ +``` + +Edit `/opt/inboxer/bin/config.yaml` to set credentials and adjust paths: +- `database.path: /opt/inboxer/data/db.sqlite` +- `logging.file: /opt/inboxer/logs/inboxer.log` + +Create the systemd service at `/etc/systemd/system/inboxer.service`: + +```ini +[Unit] +Description=inBOXER - AI-Powered Email Classifier +After=network-online.target + +[Service] +Type=simple +User=inboxer +Group=inboxer +WorkingDirectory=/opt/inboxer +ExecStart=/opt/inboxer/bin/inboxer +Restart=on-failure +RestartSec=10 + +[Install] +WantedBy=multi-user.target +``` + +Enable and start: + +```bash +sudo systemctl daemon-reload +sudo systemctl enable inboxer +sudo systemctl start inboxer +``` + +## Configuration + +All configuration lives in a single file: `bin/config.yaml`. + +| Section | Key | Description | +|---------|-----|-------------| +| `server` | `port` | Web interface port (default: 8080) | +| `server` | `host` | Bind address (default: 0.0.0.0) | +| `server` | `session_secret` | Session encryption key -- change in production | +| `database` | `path` | SQLite database file path | +| `smtp` | `host` | SMTP server hostname | +| `smtp` | `port` | SMTP server port (typically 587 for STARTTLS) | +| `smtp` | `username` | SMTP login username | +| `smtp` | `password` | SMTP login password | +| `ai` | `api_key` | DeepSeek API key | +| `ai` | `model` | DeepSeek model (default: deepseek-chat) | +| `ai` | `prompt_file` | Path to classification prompt template | +| `folders` | `*` | IMAP folder names for classified emails | + +## Service Management + +```bash +sudo systemctl status inboxer # check status +sudo systemctl restart inboxer # restart after config change +sudo systemctl stop inboxer # stop the service +sudo journalctl -u inboxer -f # follow the logs +``` ## Features -- **Email + OTP Authentication**: Secure login without passwords -- **AI-Powered Classification**: Uses DeepSeek LLM for intelligent email sorting +- **Email + OTP Authentication**: Login with just your email address +- **AI-Powered Classification**: DeepSeek LLM sorts email into 7 categories +- **7 Classification Folders**: Important, eCommerce, Notifications, Finance, + Social, Other, Spam - **Mobile-First Web Interface**: Responsive design for all devices -- **Modular Architecture**: Clean separation of concerns (auth, IMAP, AI, database, worker) - **Test Mode**: Preview AI decisions without moving emails - -## Quick Start - -1. Clone the repository -2. Configure `.env` with your API keys and credentials -3. Run `make build` to compile the binary -4. Run `make run` to start the application -5. Access the web interface at `http://localhost:8080` +- **Empty-Inbox Guarantee**: Every email is processed unconditionally ## Architecture +``` +src/ + cmd/ - Entry point (main.go) + pkg/config/ - Configuration loader + internal/ + auth/ - OTP authentication & SMTP sender + imap/ - IMAP client (fetch, move, create folders) + ai/ - DeepSeek API client & classifier + db/ - SQLite database (GORM) + web/ - HTTP handlers & templates + worker/ - Background email processing + web/ + templates/ - Go HTML templates (mobile-first) +bin/ - Pre-built binary & configuration files +``` + See `PROJECT_PLAN.md` for detailed architecture and development phases. ## License -See `docs/LICENSE.md` for license information. \ No newline at end of file +See `docs/LICENSE.md` for license information. diff --git a/install.sh b/install.sh index b28d574..baeba4e 100755 --- a/install.sh +++ b/install.sh @@ -2,32 +2,28 @@ # # inBOXER Install Script # ====================== -# Deploys the inBOXER binary and configuration to /opt/inboxer -# Creates a systemd service for production deployment. +# Downloads the latest inBOXER release from the git repository and +# deploys it to /opt/inboxer as a systemd service. # Supports Debian (apt) and RHEL (yum/dnf) families. # -# Usage: sudo ./install.sh +# Usage: +# curl -sSL https://git.lohmar.co.uk/cclohmar/inboxer/raw/branch/main/install.sh | sudo bash # set -euo pipefail # --- Configuration ----------------------------------------------------------- INSTALL_DIR="/opt/inboxer" +BIN_DIR="${INSTALL_DIR}/bin" +DATA_DIR="${INSTALL_DIR}/data" +LOGS_DIR="${INSTALL_DIR}/logs" + 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 +# Repository raw content base URL +REPO_BASE="https://git.lohmar.co.uk/cclohmar/inboxer/raw/branch/main" # --- Terminal colours -------------------------------------------------------- RED='\033[0;31m' @@ -50,26 +46,22 @@ if ! command -v systemctl &>/dev/null; then 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 +# Prefer curl; fall back to wget +DOWNLOADER="" +if command -v curl &>/dev/null; then + DOWNLOADER="curl -sSL" +elif command -v wget &>/dev/null; then + DOWNLOADER="wget -q -O" +else + error "Neither curl nor wget found. Install one of them and re-run." + exit 1 +fi # --- 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 ---------------------------------------------- @@ -79,7 +71,6 @@ 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 @@ -91,34 +82,64 @@ else --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" +# --- Download helper --------------------------------------------------------- +download() { + local src_url="$1" + local dest_path="$2" + local mode="$3" -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" + local tmpfile + tmpfile="$(mktemp)" + + if [[ "${DOWNLOADER}" == curl* ]]; then + curl -sSL "${src_url}" -o "${tmpfile}" + else + wget -q -O "${tmpfile}" "${src_url}" + fi + + # Check that the downloaded content is non-empty and not an HTML error page + if [[ ! -s "${tmpfile}" ]]; then + rm -f "${tmpfile}" + error "Downloaded empty file from ${src_url}" + return 1 + fi + + # Gitea returns HTML on 404; detect by checking first bytes + if head -c 100 "${tmpfile}" | grep -qi " ${dest_path}" +} + +# --- Download release files -------------------------------------------------- +info "Downloading inBOXER release from ${REPO_BASE}/bin/ ..." + +download "${REPO_BASE}/bin/inboxer" "${BIN_DIR}/inboxer" 755 +download "${REPO_BASE}/bin/config.yaml" "${BIN_DIR}/config.yaml" 644 +download "${REPO_BASE}/bin/prompt.txt" "${BIN_DIR}/prompt.txt" 644 # 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 +Documentation=https://git.lohmar.co.uk/cclohmar/inboxer After=network-online.target Wants=network-online.target @@ -149,12 +170,9 @@ UNITEOF 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 "${DATA_DIR}" chmod 750 "${LOGS_DIR}" - -# config.yaml contains secrets so restrict access chmod 640 "${BIN_DIR}/config.yaml" # --- Register & start service ----------------------------------------------- @@ -195,15 +213,15 @@ 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 +info " Required settings:" +info " - ai.api_key (DeepSeek API key)" +info " - smtp.host / port (SMTP server for OTP emails)" +info " - smtp.username / pass (SMTP login credentials)" +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 ""