mnemosyne/scripts/install.sh
2026-06-04 19:33:35 -04:00

193 lines
9.3 KiB
Bash
Executable File

#!/usr/bin/env bash
# Mnemosyne install script — idempotent; safe to re-run.
# Does NOT clobber an existing database or config file.
#
# Usage (as root or with sudo):
# bash scripts/install.sh [--app-dir /opt/mnemosyne] [--config-dir /etc/mnemosyne]
# [--data-dir /var/lib/mnemosyne]
#
# After running:
# 1. Edit /etc/mnemosyne/mnemosyne.conf (fill in token, secret, URL, chat ID)
# 2. Copy and adjust nginx/mnemosyne.conf.example → /etc/nginx/conf.d/mnemosyne.conf
# 3. Obtain a Let's Encrypt cert: certbot --nginx -d your.subdomain.com
# 4. Register the Telegram webhook: /opt/mnemosyne/bin/mnemosyne-webhook --config /etc/mnemosyne/mnemosyne.conf
# 5. Start the bot: systemctl start mnemosyne-bot
set -euo pipefail
# ── Defaults ────────────────────────────────────────────────────────────────
APP_DIR="/opt/mnemosyne"
CONFIG_DIR="/etc/mnemosyne"
DATA_DIR="/var/lib/mnemosyne"
SERVICE_USER="mnemosyne"
SERVICE_GROUP="mnemosyne"
SYSTEMD_DIR="/etc/systemd/system"
CRON_FILE="/etc/cron.d/mnemosyne"
# ── Argument parsing ─────────────────────────────────────────────────────────
while [[ $# -gt 0 ]]; do
case "$1" in
--app-dir) APP_DIR="$2"; shift 2 ;;
--config-dir) CONFIG_DIR="$2"; shift 2 ;;
--data-dir) DATA_DIR="$2"; shift 2 ;;
*) echo "Unknown argument: $1" >&2; exit 1 ;;
esac
done
# ── Must run as root ─────────────────────────────────────────────────────────
if [[ $EUID -ne 0 ]]; then
echo "Error: this script must be run as root." >&2
exit 1
fi
# ── Detect source directory (where this script lives) ───────────────────────
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
echo "==> Mnemosyne installer"
echo " Source: $REPO_DIR"
echo " App: $APP_DIR"
echo " Config: $CONFIG_DIR"
echo " Data: $DATA_DIR"
echo ""
# ── Detect distro family ─────────────────────────────────────────────────────
detect_distro() {
if command -v dnf &>/dev/null || command -v yum &>/dev/null; then
echo "rhel"
elif command -v apt-get &>/dev/null; then
echo "debian"
else
echo "unknown"
fi
}
DISTRO="$(detect_distro)"
# ── 1. System packages ───────────────────────────────────────────────────────
echo "==> Installing system packages (distro: $DISTRO)"
case "$DISTRO" in
rhel)
PKG_MGR="dnf"
command -v yum &>/dev/null && ! command -v dnf &>/dev/null && PKG_MGR="yum"
$PKG_MGR install -y perl perl-App-cpanminus perl-DBI sqlite perl-local-lib 2>&1 | grep -E '(Installed|already|Error)' || true
# DBD::SQLite needs C compiler for XS build
$PKG_MGR install -y gcc make perl-devel 2>&1 | grep -E '(Installed|already|Error)' || true
;;
debian)
apt-get install -y perl cpanminus libdbi-perl sqlite3 gcc make 2>&1 | grep -E '(Setting up|already|Error)' || true
;;
*)
echo " Warning: unknown distro. Install Perl, cpanminus, DBI, and SQLite manually." >&2
;;
esac
# ── 2. CPAN dependencies ─────────────────────────────────────────────────────
echo "==> Installing CPAN dependencies"
# Install into system Perl (running as root) or local::lib if preferred
cpanm --notest --quiet --installdeps "$REPO_DIR" || {
echo " Warning: cpanm reported errors. Check output above." >&2
}
# ── 3. System user/group ─────────────────────────────────────────────────────
echo "==> Creating system user: $SERVICE_USER"
if ! getent group "$SERVICE_GROUP" &>/dev/null; then
groupadd --system "$SERVICE_GROUP"
echo " Created group $SERVICE_GROUP"
fi
if ! id "$SERVICE_USER" &>/dev/null; then
useradd --system --gid "$SERVICE_GROUP" --no-create-home \
--home-dir "$DATA_DIR" --shell /sbin/nologin "$SERVICE_USER"
echo " Created user $SERVICE_USER"
else
echo " User $SERVICE_USER already exists — skipping"
fi
# ── 4. Directories ───────────────────────────────────────────────────────────
echo "==> Creating directories"
install -d -m 755 "$APP_DIR"
install -d -m 750 -o "$SERVICE_USER" -g "$SERVICE_GROUP" "$DATA_DIR"
install -d -m 750 "$CONFIG_DIR"
# ── 5. Copy application files ────────────────────────────────────────────────
echo "==> Installing application to $APP_DIR"
# Use rsync if available (preserves structure, skips .git), otherwise cp
if command -v rsync &>/dev/null; then
rsync -a --exclude='.git' --exclude='config/mnemosyne.conf' \
"$REPO_DIR/" "$APP_DIR/"
else
cp -rp "$REPO_DIR/." "$APP_DIR/"
rm -rf "$APP_DIR/.git"
rm -f "$APP_DIR/config/mnemosyne.conf"
fi
chmod +x "$APP_DIR/bin/"*
chown -R root:"$SERVICE_GROUP" "$APP_DIR"
chmod -R o-rwx "$APP_DIR"
# ── 6. Config file ───────────────────────────────────────────────────────────
echo "==> Config"
if [[ -f "$CONFIG_DIR/mnemosyne.conf" ]]; then
echo " $CONFIG_DIR/mnemosyne.conf already exists — not overwriting"
else
install -m 640 -o root -g "$SERVICE_GROUP" \
"$REPO_DIR/config/mnemosyne.conf.example" \
"$CONFIG_DIR/mnemosyne.conf"
echo ""
echo " *** ACTION REQUIRED ***"
echo " Edit $CONFIG_DIR/mnemosyne.conf and fill in:"
echo " bot.token — from @BotFather"
echo " bot.webhook_secret — run: openssl rand -hex 32"
echo " bot.webhook_url — your public HTTPS webhook URL"
echo " bot.allowed_chat_ids — your Telegram chat ID (@userinfobot)"
echo " db.path — default: $DATA_DIR/mnemosyne.db"
echo " app.timezone — IANA timezone (e.g. America/New_York)"
echo ""
fi
# ── 7. Systemd service ───────────────────────────────────────────────────────
echo "==> Installing systemd service"
# Rewrite paths in the service template
sed "s|/opt/mnemosyne|$APP_DIR|g; s|/etc/mnemosyne|$CONFIG_DIR|g" \
"$REPO_DIR/systemd/mnemosyne-bot.service" \
> "$SYSTEMD_DIR/mnemosyne-bot.service"
chmod 644 "$SYSTEMD_DIR/mnemosyne-bot.service"
systemctl daemon-reload
systemctl enable mnemosyne-bot
echo " Enabled mnemosyne-bot.service (not started — configure first)"
# ── 8. Cron entry for digest ─────────────────────────────────────────────────
echo "==> Installing cron job for digest"
cat > "$CRON_FILE" <<CRON
# Mnemosyne morning digest — fires every 5 min; the script checks digest_time itself.
*/5 * * * * $SERVICE_USER $APP_DIR/bin/mnemosyne-digest --config $CONFIG_DIR/mnemosyne.conf 2>&1 | logger -t mnemosyne-digest
CRON
chmod 644 "$CRON_FILE"
echo " Installed $CRON_FILE"
# ── 9. Backup cron (optional) ────────────────────────────────────────────────
echo "==> Installing daily backup cron"
BACKUP_DIR="/var/backups/mnemosyne"
install -d -m 750 -o "$SERVICE_USER" -g "$SERVICE_GROUP" "$BACKUP_DIR"
cat >> "$CRON_FILE" <<CRON
# Daily backup at 02:15
15 2 * * * $SERVICE_USER MNEMOSYNE_BACKUP_DIR=$BACKUP_DIR $APP_DIR/scripts/backup.sh --config $CONFIG_DIR/mnemosyne.conf 2>&1 | logger -t mnemosyne-backup
CRON
echo " Backup cron added (target: $BACKUP_DIR)"
# ── Done ─────────────────────────────────────────────────────────────────────
echo ""
echo "==> Installation complete."
echo ""
echo "Next steps:"
echo " 1. Edit $CONFIG_DIR/mnemosyne.conf"
echo " 2. Copy nginx/mnemosyne.conf.example → /etc/nginx/conf.d/mnemosyne.conf"
echo " and adjust server_name + port"
echo " 3. Obtain TLS cert: certbot --nginx -d your.subdomain.example.com"
echo " 4. Reload nginx: systemctl reload nginx"
echo " 5. Register webhook: $APP_DIR/bin/mnemosyne-webhook --config $CONFIG_DIR/mnemosyne.conf"
echo " 6. Start bot: systemctl start mnemosyne-bot"
echo " 7. Check logs: journalctl -u mnemosyne-bot -f"
echo ""
echo " Note (RHEL/AlmaLinux): if SELinux is enforcing, you may need:"
echo " setsebool -P httpd_can_network_connect 1"
echo " (allows nginx to proxy to the local bot port)"