#!/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" <&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" <&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)"