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:
parent
6a6177c8fe
commit
20e28907de
2 changed files with 220 additions and 70 deletions
170
README.md
170
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.
|
||||
See `docs/LICENSE.md` for license information.
|
||||
|
|
|
|||
120
install.sh
120
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 "<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
|
||||
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 ""
|
||||
|
|
|
|||
Loading…
Reference in a new issue