chore: interactive install.sh — prompts user for credentials, installs to /opt/rx

- install.sh copies binary + assets to /opt/rx/
- Prompts for SMTP, Gemini, port, URL interactively
- Creates .env file with chmod 600
- Creates systemd service pointing to /opt/rx/
- Handles both pre-built and dist/ binaries
This commit is contained in:
Claus Lohmar 2026-05-30 14:23:42 +00:00
parent f50048e833
commit b780ac1e1f

View file

@ -1,129 +1,135 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# #
# install.sh — ReceiptNext one-command installer for bare-metal servers # install.sh — ReceiptNext installer
# #
# Usage: # Copies the binary and assets to /opt/rx/, prompts for configuration,
# curl -sL https://git.lohmar.co.uk/cclohmar/ReceiptNext/releases/download/v1.0.0/install.sh | bash # and sets up a systemd service.
# #
# Or from a local clone: # Usage: sudo ./install.sh
# ./install.sh
# #
# What it does:
# 1. Creates receiptnext user if missing
# 2. Creates /opt/receiptnext with templates/, static/, storage/
# 3. Installs binary to /usr/local/bin/receiptnext
# 4. Creates /etc/receiptnext/.env from .env.example (if not exists)
# 5. Installs systemd service
# 6. Starts the service
# 7. Shows status and instructions
set -euo pipefail set -euo pipefail
BINARY="receiptnext" INSTALL_DIR="/opt/rx"
INSTALL_DIR="/opt/${BINARY}" BIN_PATH="/opt/rx/receiptnext"
BIN_DIR="/usr/local/bin" SERVICE_NAME="receiptnext"
ETC_DIR="/etc/${BINARY}"
SERVICE_FILE="contrib/${BINARY}.service"
LOG_FILE="/var/log/${BINARY}.log"
RED='\033[0;31m' RED='\033[0;31m'
GREEN='\033[0;32m' GREEN='\033[0;32m'
YELLOW='\033[1;33m' YELLOW='\033[1;33m'
NC='\033[0m' # No Color CYAN='\033[0;36m'
NC='\033[0m'
info() { echo -e "${GREEN}[✓]${NC} $1"; } info() { echo -e "${GREEN}[✓]${NC} $1"; }
warn() { echo -e "${YELLOW}[!]${NC} $1"; } warn() { echo -e "${YELLOW}[!]${NC} $1"; }
error() { echo -e "${RED}[✗]${NC} $1"; } prompt() { echo -en "${CYAN}${NC} $1"; }
# Check if running as root
if [ "$EUID" -ne 0 ]; then if [ "$EUID" -ne 0 ]; then
error "Please run as root: sudo ./install.sh" echo -e "${RED}Please run as root: sudo ./install.sh${NC}"
exit 1 exit 1
fi fi
echo "" echo ""
echo " ╔═══════════════════════════════════════╗" echo " ╔═══════════════════════════════════════╗"
echo " ║ ReceiptNext Installer ║" echo " ║ ReceiptNext Installer ║"
echo " ║ AI-Powered Expense Tracker ║"
echo " ╚═══════════════════════════════════════╝" echo " ╚═══════════════════════════════════════╝"
echo "" echo ""
# ── Step 1: Build or download binary ────────────────────────────── # ── Detect binary ────────────────────────────────────────────────
if [ -f "./${BINARY}" ]; then SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
info "Binary found locally" BINARY=""
elif [ -f "go.mod" ]; then for f in "$SCRIPT_DIR/receiptnext" "$SCRIPT_DIR/dist/receiptnext-linux-amd64" "$SCRIPT_DIR/dist/receiptnext-linux-arm64"; do
info "Building from source..." if [ -f "$f" ] && [ -x "$f" ]; then
if ! command -v go &>/dev/null; then BINARY="$f"
error "Go is required to build from source. Install go 1.22+ and re-run." break
exit 1
fi fi
go build -ldflags="-s -w" -o "${BINARY}" . done
info "Build complete"
else if [ -z "$BINARY" ]; then
# Try to download the latest release echo -e "${RED}No binary found. Build it first: go build -o receiptnext .${NC}"
TAG=$(curl -s https://git.lohmar.co.uk/api/v1/repos/cclohmar/ReceiptNext/releases/latest \ exit 1
| python3 -c "import json,sys; print(json.load(sys.stdin).get('tag_name','v1.0.0'))" 2>/dev/null || echo "v1.0.0")
ARCH=$(uname -m)
case "$ARCH" in
x86_64) ARCH="amd64" ;;
aarch64) ARCH="arm64" ;;
*) error "Unsupported architecture: $ARCH"; exit 1 ;;
esac
URL="https://git.lohmar.co.uk/cclohmar/ReceiptNext/releases/download/${TAG}/${BINARY}-linux-${ARCH}"
info "Downloading ${URL}..."
curl -sL -o "${BINARY}" "${URL}"
info "Download complete"
fi fi
chmod +x "${BINARY}" BINARY_NAME=$(basename "$BINARY")
info "Using binary: $BINARY"
# ── Step 2: Create system user ──────────────────────────────────── # ── Create install directory ─────────────────────────────────────
if ! id -u ${BINARY} &>/dev/null 2>&1; then mkdir -p "$INSTALL_DIR"/{templates,static/css,static/icons,storage}
useradd -r -s /bin/false -d "${INSTALL_DIR}" ${BINARY} info "Created $INSTALL_DIR"
info "Created system user '${BINARY}'"
fi
# ── Step 3: Create install directory ────────────────────────────── # ── Copy binary and assets ───────────────────────────────────────
mkdir -p "${INSTALL_DIR}"/{templates,static/css,static/icons,storage} cp "$BINARY" "$BIN_PATH"
chmod +x "$BIN_PATH"
cp -r "$SCRIPT_DIR/templates/." "$INSTALL_DIR/templates/"
cp -r "$SCRIPT_DIR/static/." "$INSTALL_DIR/static/"
info "Copied binary + assets to $INSTALL_DIR"
# Copy templates and static assets # ── Prompt for configuration ─────────────────────────────────────
cp -r templates/* "${INSTALL_DIR}/templates/" echo ""
cp -r static/* "${INSTALL_DIR}/static/" echo " ── Configuration ──"
cp -r storage/. "${INSTALL_DIR}/storage/" 2>/dev/null || true echo " (Press Enter to skip any field)"
echo ""
# ── Step 4: Install binary ──────────────────────────────────────── read -p " SMTP host [$SMTP_HOST]: " SMTP_HOST_IN
cp "${BINARY}" "${BIN_DIR}/${BINARY}" SMTP_HOST="${SMTP_HOST_IN:-$SMTP_HOST}"
chmod +x "${BIN_DIR}/${BINARY}" read -p " SMTP port [${SMTP_PORT:-587}]: " SMTP_PORT_IN
info "Installed binary to ${BIN_DIR}/${BINARY}" SMTP_PORT="${SMTP_PORT_IN:-${SMTP_PORT:-587}}"
read -p " SMTP user [$SMTP_USER]: " SMTP_USER_IN
SMTP_USER="${SMTP_USER_IN:-$SMTP_USER}"
read -p " SMTP password [$SMTP_PASS]: " SMTP_PASS_IN
SMTP_PASS="${SMTP_PASS_IN:-$SMTP_PASS}"
read -p " Gemini API key [$GEMINI_API_KEY]: " GEMINI_IN
GEMINI_API_KEY="${GEMINI_IN:-$GEMINI_API_KEY}"
read -p " Public server URL [${BASE_URL:-http://localhost:8080}]: " BASE_URL_IN
BASE_URL="${BASE_URL_IN:-${BASE_URL:-http://localhost:8080}}"
read -p " Port for web server [${PORT:-8080}]: " PORT_IN
PORT="${PORT_IN:-${PORT:-8080}}"
# ── Step 5: Create config directory ─────────────────────────────── # ── Write .env ───────────────────────────────────────────────────
mkdir -p "${ETC_DIR}" cat > "$INSTALL_DIR/.env" << ENVEOF
if [ ! -f "${ETC_DIR}/.env" ]; then # ReceiptNext Configuration
if [ -f ".env.example" ]; then # Created by install.sh on $(date)
cp .env.example "${ETC_DIR}/.env"
info "Created ${ETC_DIR}/.env — edit it with your credentials"
else
warn "No .env.example found — create ${ETC_DIR}/.env manually"
fi
else
info "${ETC_DIR}/.env already exists — keeping existing config"
fi
# ── Step 6: Install systemd service ─────────────────────────────── SMTP_HOST=${SMTP_HOST}
if [ -f "${SERVICE_FILE}" ]; then SMTP_PORT=${SMTP_PORT}
cp "${SERVICE_FILE}" /etc/systemd/system/ SMTP_USER=${SMTP_USER}
systemctl daemon-reload SMTP_PASS=${SMTP_PASS}
systemctl enable ${BINARY} GEMINI_API_KEY=${GEMINI_API_KEY}
info "Systemd service installed and enabled" BASE_URL=${BASE_URL}
fi PORT=${PORT}
ENVEOF
# ── Step 7: Set permissions ─────────────────────────────────────── chmod 600 "$INSTALL_DIR/.env"
chown -R ${BINARY}:${BINARY} "${INSTALL_DIR}" info "Created $INSTALL_DIR/.env"
chown ${BINARY}:${BINARY} "${ETC_DIR}/.env" 2>/dev/null || true
touch "${LOG_FILE}" && chown ${BINARY}:${BINARY} "${LOG_FILE}"
# ── Step 8: Start service ───────────────────────────────────────── # ── Create systemd service ───────────────────────────────────────
systemctl start ${BINARY} || warn "Service failed to start — check 'systemctl status ${BINARY}'" 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
[Service]
Type=simple
User=root
WorkingDirectory=${INSTALL_DIR}
ExecStart=${BIN_PATH}
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 "Systemd service created: ${SERVICE_NAME}"
# ── Start service ────────────────────────────────────────────────
systemctl start "${SERVICE_NAME}" 2>/dev/null || true
sleep 2 sleep 2
echo "" echo ""
@ -131,23 +137,17 @@ echo " ╔═══════════════════════
echo " ║ Installation Complete! ║" echo " ║ Installation Complete! ║"
echo " ╚═══════════════════════════════════════╝" echo " ╚═══════════════════════════════════════╝"
echo "" echo ""
echo " Config: ${ETC_DIR}/.env" echo " Install dir: $INSTALL_DIR"
echo " Binary: ${BIN_DIR}/${BINARY}" echo " Config: $INSTALL_DIR/.env"
echo " Data: ${INSTALL_DIR}" echo " Binary: $BIN_PATH"
echo " Logs: ${LOG_FILE}" echo " Service: systemctl status $SERVICE_NAME"
echo " Service: systemctl status ${BINARY}" echo " Logs: journalctl -u $SERVICE_NAME -f"
echo " Restart: systemctl restart ${BINARY}"
echo " Log tail: journalctl -u ${BINARY} -f"
echo "" echo ""
if systemctl is-active --quiet ${BINARY}; then if systemctl is-active --quiet "${SERVICE_NAME}"; then
info "ReceiptNext is running!" info "ReceiptNext is running on port ${PORT}"
echo " Open http://YOUR_SERVER_IP:8080 in your browser" echo " Open http://localhost:${PORT} (or your server IP)"
echo ""
echo " First steps:"
echo " 1. Edit ${ETC_DIR}/.env with your SMTP + AI credentials"
echo " 2. Restart: systemctl restart ${BINARY}"
echo ""
else else
warn "Service is not running. Check: systemctl status ${BINARY}" warn "Service did not start. Check: systemctl status ${SERVICE_NAME}"
echo " Logs: journalctl -u ${SERVICE_NAME} -n 50 --no-pager"
fi fi