NextExpense/install.sh

284 lines
9.7 KiB
Bash
Executable file

#!/usr/bin/env bash
#
# ReceiptNext Installer
# =====================
#
# Usage:
# sudo ./install.sh — Full install (clone, build, configure, service)
# sudo ./install.sh -update — Pull latest, rebuild, restart
#
# Installs to /opt/receiptnext/ and sets up a systemd service.
#
set -euo pipefail
INSTALL_DIR="/opt/receiptnext"
SERVICE_NAME="receiptnext"
REPO_URL="https://git.lohmar.co.uk/cclohmar/ReceiptNext.git"
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m'
info() { echo -e "${GREEN}[✓]${NC} $1"; }
warn() { echo -e "${YELLOW}[!]${NC} $1"; }
err() { echo -e "${RED}[✗]${NC} $1"; }
ask() { echo -en "${CYAN}${NC} $1"; }
if [ "$EUID" -ne 0 ]; then
err "Please run as root: sudo ./install.sh"
exit 1
fi
# ──────────────────────────────────────────────────────────────────
# UPDATE MODE
# ──────────────────────────────────────────────────────────────────
if [ "${1:-}" = "-update" ]; then
echo ""
echo " ╔═══════════════════════════════════════╗"
echo " ║ ReceiptNext — Update Mode ║"
echo " ╚═══════════════════════════════════════╝"
echo ""
if [ ! -d "$INSTALL_DIR" ]; then
err "$INSTALL_DIR does not exist. Run install.sh without -update first."
exit 1
fi
cd "$INSTALL_DIR"
if [ -d .git ]; then
info "Pulling latest code..."
git pull
else
warn "Not a git repository — re-cloning..."
cd /tmp
rm -rf receiptnext-update
git clone "$REPO_URL" receiptnext-update
rsync -a --delete receiptnext-update/ "$INSTALL_DIR/"
rm -rf receiptnext-update
cd "$INSTALL_DIR"
fi
info "Rebuilding binary..."
CGO_ENABLED=0 go build -ldflags="-s -w" -o receiptnext .
info "Restarting service..."
systemctl restart "$SERVICE_NAME"
sleep 2
if systemctl is-active --quiet "$SERVICE_NAME"; then
info "Update complete — ReceiptNext is running"
else
warn "Service did not start. Check: systemctl status $SERVICE_NAME"
fi
exit 0
fi
# ──────────────────────────────────────────────────────────────────
# FRESH INSTALL
# ──────────────────────────────────────────────────────────────────
echo ""
echo " ╔═══════════════════════════════════════╗"
echo " ║ ReceiptNext Installer ║"
echo " ╚═══════════════════════════════════════╝"
echo ""
# ── 1. Clone repo ────────────────────────────────────────────────
if [ -d "$INSTALL_DIR" ]; then
warn "$INSTALL_DIR already exists — pulling latest..."
cd "$INSTALL_DIR"
git pull
else
info "Cloning repository to $INSTALL_DIR..."
git clone "$REPO_URL" "$INSTALL_DIR"
cd "$INSTALL_DIR"
fi
# ── 2. Build binary ──────────────────────────────────────────────
info "Building binary (pure Go, no CGO)..."
CGO_ENABLED=0 go build -ldflags="-s -w" -o receiptnext .
info "Binary built: $INSTALL_DIR/receiptnext"
# ── 3. Create runtime directories ────────────────────────────────
mkdir -p storage
chmod 755 templates static storage
# ── 4. AI Provider ───────────────────────────────────────────────
echo ""
echo " ── AI Provider ──"
echo " Which AI should process receipt images?"
echo " 1) Google Gemini (cloud API, needs API key)"
echo " 2) OpenAI / Compatible (OpenAI, Perplexity, Groq, etc.)"
echo " 3) Ollama (local, installs automatically)"
echo ""
ask " Choose [1-3] (default: 1): "
read -r AI_CHOICE
AI_CHOICE="${AI_CHOICE:-1}"
case "$AI_CHOICE" in
2)
AI_PROVIDER="openai"
ask " OpenAI API key: "
read -r OPENAI_API_KEY
ask " Model [gpt-4o-mini]: "
read -r AI_MODEL
AI_MODEL="${AI_MODEL:-gpt-4o-mini}"
ask " Base URL [https://api.openai.com/v1]: "
read -r AI_BASE_URL
AI_BASE_URL="${AI_BASE_URL:-https://api.openai.com/v1}"
;;
3)
AI_PROVIDER="ollama"
AI_MODEL="qwen3.5:2b"
info "Installing Ollama..."
if ! command -v ollama &>/dev/null; then
curl -fsSL https://ollama.com/install.sh | sh 2>&1 | tail -3
else
info "Ollama already installed"
fi
info "Pulling model $AI_MODEL (this may take a while)..."
ollama pull "$AI_MODEL" 2>&1 | tail -3
AI_BASE_URL="http://localhost:11434"
;;
*)
AI_PROVIDER="gemini"
ask " Gemini API key: "
read -r GEMINI_API_KEY
;;
esac
# ── 5. SMTP Configuration ────────────────────────────────────────
echo ""
echo " ── Email (SMTP) ──"
echo " Required for sending OTP codes and expense reports."
echo " Leave blank to skip (OTP codes will be logged to console)."
echo ""
ask " SMTP host: "
read -r SMTP_HOST
if [ -n "$SMTP_HOST" ]; then
ask " SMTP port [587]: "
read -r SMTP_PORT
SMTP_PORT="${SMTP_PORT:-587}"
ask " SMTP user: "
read -r SMTP_USER
ask " SMTP password: "
read -r SMTP_PASS
fi
# ── 6. General settings ──────────────────────────────────────────
echo ""
echo " ── General ──"
ask " Public URL [http://localhost:8080]: "
read -r BASE_URL
BASE_URL="${BASE_URL:-http://localhost:8080}"
ask " Web server port [8080]: "
read -r PORT
PORT="${PORT:-8080}"
# ── 7. Write .env ────────────────────────────────────────────────
info "Creating .env..."
cat > "$INSTALL_DIR/.env" << ENVEOF
# ReceiptNext Configuration
# Generated by install.sh on $(date)
PORT=${PORT}
BASE_URL=${BASE_URL}
AI_PROVIDER=${AI_PROVIDER}
ENVEOF
case "$AI_PROVIDER" in
openai)
cat >> "$INSTALL_DIR/.env" << ENVEOF
OPENAI_API_KEY=${OPENAI_API_KEY}
AI_MODEL=${AI_MODEL}
AI_BASE_URL=${AI_BASE_URL}
ENVEOF
;;
ollama)
cat >> "$INSTALL_DIR/.env" << ENVEOF
AI_MODEL=${AI_MODEL}
AI_BASE_URL=${AI_BASE_URL}
ENVEOF
;;
gemini)
cat >> "$INSTALL_DIR/.env" << ENVEOF
GEMINI_API_KEY=${GEMINI_API_KEY}
ENVEOF
;;
esac
if [ -n "$SMTP_HOST" ]; then
cat >> "$INSTALL_DIR/.env" << ENVEOF
SMTP_HOST=${SMTP_HOST}
SMTP_PORT=${SMTP_PORT}
SMTP_USER=${SMTP_USER}
SMTP_PASS=${SMTP_PASS}
ENVEOF
fi
chmod 600 "$INSTALL_DIR/.env"
info "Configuration saved to $INSTALL_DIR/.env"
# ── 8. Systemd service ───────────────────────────────────────────
info "Creating systemd service..."
cat > "/etc/systemd/system/${SERVICE_NAME}.service" << SERVEOF
[Unit]
Description=ReceiptNext — AI-Powered Expense Tracker
Documentation=https://git.lohmar.co.uk/cclohmar/ReceiptNext
After=network.target ollama.service
Wants=ollama.service
[Service]
Type=simple
User=root
WorkingDirectory=${INSTALL_DIR}
ExecStart=${INSTALL_DIR}/receiptnext
Restart=always
RestartSec=5
EnvironmentFile=${INSTALL_DIR}/.env
StandardOutput=append:/var/log/receiptnext.log
StandardError=append:/var/log/receiptnext.log
[Install]
WantedBy=multi-user.target
SERVEOF
systemctl daemon-reload
systemctl enable "${SERVICE_NAME}"
info "Service created: /etc/systemd/system/${SERVICE_NAME}.service"
# ── 9. Start ─────────────────────────────────────────────────────
info "Starting ReceiptNext..."
systemctl restart "${SERVICE_NAME}" 2>/dev/null || true
sleep 2
echo ""
echo " ╔═══════════════════════════════════════╗"
echo " ║ Installation Complete! ║"
echo " ╚═══════════════════════════════════════╝"
echo ""
echo " Install: $INSTALL_DIR"
echo " Binary: $INSTALL_DIR/receiptnext"
echo " Config: $INSTALL_DIR/.env"
echo " Templates: $INSTALL_DIR/templates/"
echo " Static: $INSTALL_DIR/static/"
echo " Storage: $INSTALL_DIR/storage/"
echo " Service: systemctl status $SERVICE_NAME"
echo " Logs: journalctl -u $SERVICE_NAME -f"
echo ""
if systemctl is-active --quiet "${SERVICE_NAME}"; then
info "ReceiptNext is running on port ${PORT}"
echo " Open http://localhost:${PORT} (or your server IP)"
else
warn "Service did not start. Check: systemctl status ${SERVICE_NAME}"
journalctl -u "${SERVICE_NAME}" -n 20 --no-pager
fi
echo ""
echo " ── Update ──"
echo " To update later: sudo ./install.sh -update"
echo ""