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
This commit is contained in:
Claus Lohmar 2026-04-23 20:19:57 +00:00
parent 6a6177c8fe
commit 20e28907de
2 changed files with 220 additions and 70 deletions

170
README.md
View file

@ -1,35 +1,167 @@
# inBOXER # 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: ```bash
- Connects to your IMAP email account curl -sSL https://git.lohmar.co.uk/cclohmar/inboxer/raw/branch/main/install.sh | sudo bash
- 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 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 ## Features
- **Email + OTP Authentication**: Secure login without passwords - **Email + OTP Authentication**: Login with just your email address
- **AI-Powered Classification**: Uses DeepSeek LLM for intelligent email sorting - **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 - **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 - **Test Mode**: Preview AI decisions without moving emails
- **Empty-Inbox Guarantee**: Every email is processed unconditionally
## 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`
## Architecture ## 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. See `PROJECT_PLAN.md` for detailed architecture and development phases.
## License ## License
See `docs/LICENSE.md` for license information. See `docs/LICENSE.md` for license information.

View file

@ -2,32 +2,28 @@
# #
# inBOXER Install Script # inBOXER Install Script
# ====================== # ======================
# Deploys the inBOXER binary and configuration to /opt/inboxer # Downloads the latest inBOXER release from the git repository and
# Creates a systemd service for production deployment. # deploys it to /opt/inboxer as a systemd service.
# Supports Debian (apt) and RHEL (yum/dnf) families. # 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 set -euo pipefail
# --- Configuration ----------------------------------------------------------- # --- Configuration -----------------------------------------------------------
INSTALL_DIR="/opt/inboxer" INSTALL_DIR="/opt/inboxer"
BIN_DIR="${INSTALL_DIR}/bin"
DATA_DIR="${INSTALL_DIR}/data"
LOGS_DIR="${INSTALL_DIR}/logs"
SERVICE_USER="inboxer" SERVICE_USER="inboxer"
SERVICE_GROUP="inboxer" SERVICE_GROUP="inboxer"
SERVICE_NAME="inboxer" SERVICE_NAME="inboxer"
SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service" SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service"
BIN_DIR="${INSTALL_DIR}/bin" # Repository raw content base URL
DATA_DIR="${INSTALL_DIR}/data" REPO_BASE="https://git.lohmar.co.uk/cclohmar/inboxer/raw/branch/main"
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 -------------------------------------------------------- # --- Terminal colours --------------------------------------------------------
RED='\033[0;31m' RED='\033[0;31m'
@ -50,26 +46,22 @@ if ! command -v systemctl &>/dev/null; then
exit 1 exit 1
fi fi
REQUIRED_FILES=( # Prefer curl; fall back to wget
"${REPO_DIR}/bin/inboxer" DOWNLOADER=""
"${REPO_DIR}/bin/config.yaml" if command -v curl &>/dev/null; then
"${REPO_DIR}/bin/prompt.txt" DOWNLOADER="curl -sSL"
) elif command -v wget &>/dev/null; then
for f in "${REQUIRED_FILES[@]}"; do DOWNLOADER="wget -q -O"
if [[ ! -f "$f" ]]; then else
error "Required file not found: $f" error "Neither curl nor wget found. Install one of them and re-run."
error "Run this script from the inBOXER repository root." exit 1
exit 1 fi
fi
done
# --- OS Detection (informational) ------------------------------------------- # --- OS Detection (informational) -------------------------------------------
if [[ -f /etc/os-release ]]; then if [[ -f /etc/os-release ]]; then
# shellcheck source=/dev/null # shellcheck source=/dev/null
. /etc/os-release . /etc/os-release
info "Detected OS: ${NAME} ${VERSION_ID}" info "Detected OS: ${NAME} ${VERSION_ID}"
else
info "Could not detect OS version (no /etc/os-release)."
fi fi
# --- Create system user & group ---------------------------------------------- # --- Create system user & group ----------------------------------------------
@ -79,7 +71,6 @@ if getent group "${SERVICE_GROUP}" &>/dev/null; then
info "Group '${SERVICE_GROUP}' already exists." info "Group '${SERVICE_GROUP}' already exists."
else else
groupadd --system "${SERVICE_GROUP}" groupadd --system "${SERVICE_GROUP}"
info "Group '${SERVICE_GROUP}' created."
fi fi
if getent passwd "${SERVICE_USER}" &>/dev/null; then if getent passwd "${SERVICE_USER}" &>/dev/null; then
@ -91,34 +82,64 @@ else
--shell /sbin/nologin \ --shell /sbin/nologin \
--comment "inBOXER Service User" \ --comment "inBOXER Service User" \
"${SERVICE_USER}" "${SERVICE_USER}"
info "User '${SERVICE_USER}' created."
fi fi
# --- Create directory structure ---------------------------------------------- # --- Create directory structure ----------------------------------------------
info "Creating directories under ${INSTALL_DIR}..." info "Creating directories under ${INSTALL_DIR}..."
mkdir -p "${BIN_DIR}" "${DATA_DIR}" "${LOGS_DIR}" mkdir -p "${BIN_DIR}" "${DATA_DIR}" "${LOGS_DIR}"
# --- Install binary & config files ------------------------------------------- # --- Download helper ---------------------------------------------------------
info "Installing binary..." download() {
install -m 755 "${REPO_DIR}/bin/inboxer" "${BIN_DIR}/inboxer" local src_url="$1"
local dest_path="$2"
local mode="$3"
info "Installing configuration..." local tmpfile
install -m 644 "${REPO_DIR}/bin/config.yaml" "${BIN_DIR}/config.yaml" tmpfile="$(mktemp)"
install -m 644 "${REPO_DIR}/bin/prompt.txt" "${BIN_DIR}/prompt.txt"
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 "<html\|<!DOCTYPE"; then
rm -f "${tmpfile}"
error "File not found at ${src_url}"
return 1
fi
install -m "${mode}" "${tmpfile}" "${dest_path}"
rm -f "${tmpfile}"
info " ${src_url##*/} -> ${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 # Adjust config paths for /opt/inboxer deployment
info "Adjusting configuration paths for 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|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" 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 --------------------------------------------- # --- Create systemd service unit ---------------------------------------------
info "Creating systemd service unit at ${SERVICE_FILE} ..." info "Creating systemd service unit at ${SERVICE_FILE} ..."
cat > "${SERVICE_FILE}" << UNITEOF cat > "${SERVICE_FILE}" << UNITEOF
[Unit] [Unit]
Description=inBOXER - AI-Powered Email Classifier Description=inBOXER - AI-Powered Email Classifier
Documentation=https://github.com/cclohmar/inboxer Documentation=https://git.lohmar.co.uk/cclohmar/inboxer
After=network-online.target After=network-online.target
Wants=network-online.target Wants=network-online.target
@ -149,12 +170,9 @@ UNITEOF
info "Setting file ownership and permissions..." info "Setting file ownership and permissions..."
chown -R "${SERVICE_USER}:${SERVICE_GROUP}" "${INSTALL_DIR}" chown -R "${SERVICE_USER}:${SERVICE_GROUP}" "${INSTALL_DIR}"
# Directory permissions
chmod 755 "${BIN_DIR}" chmod 755 "${BIN_DIR}"
chmod 750 "${DATA_DIR}" # database file is sensitive chmod 750 "${DATA_DIR}"
chmod 750 "${LOGS_DIR}" chmod 750 "${LOGS_DIR}"
# config.yaml contains secrets so restrict access
chmod 640 "${BIN_DIR}/config.yaml" chmod 640 "${BIN_DIR}/config.yaml"
# --- Register & start service ----------------------------------------------- # --- Register & start service -----------------------------------------------
@ -195,15 +213,15 @@ info " Logs: ${LOGS_DIR}/"
echo "" echo ""
info " * Edit config.yaml with your credentials before first start:" info " * Edit config.yaml with your credentials before first start:"
info " sudo nano ${BIN_DIR}/config.yaml" 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 "" 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 " ! config.yaml still contains placeholder values!"
warn " Edit ${BIN_DIR}/config.yaml before the service will function." warn " Edit ${BIN_DIR}/config.yaml before the service will function."
echo "" echo ""