# Mnemosyne A self-hosted, single-user task manager built around long-timeframe recurring tasks. Its job is to keep things **on the radar** — recurring obligations, periodic maintenance, and someday/maybe items — without the bloat of a full project management system. The primary interface is a Telegram bot. Every morning it delivers a **Day at a Glance** digest: what's overdue, what's due today, what's coming up in the next week, and a rotation of floating reminders. Everything else is query-on-demand. No mid-day push notifications, ever. The name is Mnemosyne — the Greek personification of memory. The app remembers so you don't have to. --- ## Task classes Five scheduling models cover most recurring-task patterns: | Class | Description | Example | |---|---|---| | `monthly_date` | Fixed day of the month | Pay rent on the 1st | | `monthly_weekday` | Nth weekday of the month | First Thursday, last Friday | | `every_n_period` | Every N days/weeks/months from an anchor date | Every 2 weeks from 2026-01-06 | | `interval` | Every N days, clock resets on completion | Change oil every 90 days | | `floating` | No fixed date; surfaces by priority rotation | Call parents, learn Spanish | Missed dated tasks roll into an **Overdue** section rather than disappearing. Slippage is a feature, not an error. --- ## Telegram commands | Command | What it does | |---|---| | `/today` or `/glance` | Day at a Glance digest on demand | | `/list [filter]` | Browse tasks; filter by class, priority, or `inactive` | | `/add` | Create a task via guided wizard | | `/done ` | Mark a task complete | | `/edit ` | Update a single field | | `/disable ` | Hide a task without deleting it | | `/delete ` | Permanently delete (with confirmation) | | `/settime HH:MM` | Change the morning digest delivery time | | `/help` | List commands | Actionable tasks in digests and list results get inline **Mark Done** buttons. Tapping one completes the task immediately and replaces the button with **Undo** in case of misclicks. --- ## Stack - **Perl** — application logic - **SQLite** — single-file database via `DBD::SQLite`; WAL mode - **Mojolicious** — webhook receiver and outbound Telegram API calls (`Mojo::UserAgent`) - **nginx** — TLS termination and reverse proxy; bot binds to localhost only - **systemd** — long-running webhook service with `Restart=always` - **cron** — morning digest (runs every 5 minutes; the script checks the configured time) --- ## Prerequisites - A Linux server with a public IP (AlmaLinux 10 / RHEL family recommended; Debian/Ubuntu also supported) - A domain or subdomain with an A record pointing at the server - A Telegram bot token from [@BotFather](https://t.me/BotFather) - Your Telegram chat ID ([@userinfobot](https://t.me/userinfobot) will tell you) - Perl 5.26+, nginx, certbot, sqlite3 --- ## Deployment ### 1. Clone the repo ```bash git clone https://git.castlehollow.com/rodger/mnemosyne.git cd mnemosyne ``` ### 2. Run the install script Must be run as root. Safe to re-run — it will not overwrite an existing database or config file. ```bash sudo bash scripts/install.sh ``` This will: - Install system packages (Perl, cpanminus, sqlite3, gcc) via `dnf` or `apt-get` - Install CPAN dependencies from `cpanfile` via `cpanm` - Create a `mnemosyne` system user and group - Deploy application files to `/opt/mnemosyne` - Install the systemd service (enabled but **not started** until you configure it) - Install a cron job at `/etc/cron.d/mnemosyne` for the digest and daily backup Default paths (override with flags if needed): | Flag | Default | |---|---| | `--app-dir` | `/opt/mnemosyne` | | `--config-dir` | `/etc/mnemosyne` | | `--data-dir` | `/var/lib/mnemosyne` | ### 3. Edit the config file ```bash sudo nano /etc/mnemosyne/mnemosyne.conf ``` Fields to fill in: ```ini [bot] token = YOUR_BOT_TOKEN_HERE # from @BotFather webhook_secret = ... # openssl rand -hex 32 webhook_url = https://bot.example.com/hook/YOUR_RANDOM_PATH listen_port = 8765 allowed_chat_ids = 123456789 # your Telegram chat ID [db] path = /var/lib/mnemosyne/mnemosyne.db [app] timezone = America/New_York # any IANA timezone name digest_time = 06:30 ``` The `webhook_url` path segment (`/hook/YOUR_RANDOM_PATH`) should be a hard-to-guess string — it acts as a second layer of security alongside the `webhook_secret` header. Generate one with `openssl rand -hex 16`. ### 4. Configure nginx Copy the example config and adjust the server name and port: ```bash sudo cp /opt/mnemosyne/nginx/mnemosyne.conf.example \ /etc/nginx/conf.d/mnemosyne.conf sudo nano /etc/nginx/conf.d/mnemosyne.conf # Replace mnemosyne.example.com with your subdomain # Replace 8765 with your listen_port if you changed it sudo nginx -t && sudo systemctl reload nginx ``` ### 5. Obtain a TLS certificate Telegram requires HTTPS. Use certbot: ```bash sudo certbot --nginx -d bot.example.com ``` Verify the nginx config still works after certbot modifies it: ```bash sudo nginx -t && sudo systemctl reload nginx ``` ### 6. Start the bot ```bash sudo systemctl start mnemosyne-bot sudo systemctl status mnemosyne-bot sudo journalctl -u mnemosyne-bot -f ``` ### 7. Register the Telegram webhook This tells Telegram where to send updates. Run it once, and again any time the `webhook_url` changes: ```bash sudo -u mnemosyne /opt/mnemosyne/bin/mnemosyne-webhook \ --config /etc/mnemosyne/mnemosyne.conf ``` To remove the webhook (e.g. before switching servers): ```bash sudo -u mnemosyne /opt/mnemosyne/bin/mnemosyne-webhook \ --config /etc/mnemosyne/mnemosyne.conf --delete ``` ### 8. Verify Send `/today` to your bot. You should receive a digest (or an "all clear" message if no tasks are due). --- ## AlmaLinux / RHEL-specific notes **IPv6 not routed (common on Linode/VPS)** — if the bot logs "Connect timeout" when trying to reach `api.telegram.org`, IPv6 is configured on the interface but not actually routed. Fix by telling the system resolver to prefer IPv4: ```bash echo 'precedence ::ffff:0:0/96 100' | sudo tee -a /etc/gai.conf sudo systemctl restart mnemosyne-bot ``` **SELinux** — if nginx cannot connect to the local bot port, allow it: ```bash sudo setsebool -P httpd_can_network_connect 1 ``` **Firewall** — the bot port (`8765` by default) does not need to be open to the internet; nginx proxies to it locally. Only ports 80 and 443 need to be reachable. **Perl modules** — some CPAN modules require `perl-devel` and `gcc` for XS compilation. The install script handles this, but if you install manually: ```bash sudo dnf install -y perl-devel gcc make sudo cpanm --notest --installdeps . ``` --- ## Backup and restore The install script sets up a daily backup cron at 02:15. You can also run it manually: ```bash sudo -u mnemosyne /opt/mnemosyne/scripts/backup.sh \ --config /etc/mnemosyne/mnemosyne.conf ``` Backups land in `/var/backups/mnemosyne/` and are retained for 30 days by default. Override with `--keep-days N`. The backup uses SQLite's Online Backup API, so it's safe to run while the bot is live — no downtime required. **Restore:** ```bash sudo systemctl stop mnemosyne-bot sudo cp /var/backups/mnemosyne/mnemosyne-YYYYMMDD-HHMMSS.db \ /var/lib/mnemosyne/mnemosyne.db sudo chown mnemosyne:mnemosyne /var/lib/mnemosyne/mnemosyne.db sudo systemctl start mnemosyne-bot ``` --- ## Updating ```bash cd /path/to/mnemosyne-repo git pull sudo bash scripts/install.sh # re-runs safely; does not touch config or database sudo systemctl restart mnemosyne-bot ``` --- ## Repository layout ``` mnemosyne/ ├── bin/ │ ├── mnemosyne-bot # webhook receiver (Mojolicious app, run by systemd) │ ├── mnemosyne-digest # morning digest sender (run by cron) │ └── mnemosyne-webhook # one-shot webhook registration helper ├── lib/Mnemosyne/ │ ├── Config.pm # INI config loader │ ├── DB.pm # SQLite connection, schema, migration │ ├── Digest.pm # Day at a Glance builder and renderer │ ├── Schedule.pm # per-class due/overdue/upcoming resolver │ ├── Task.pm # task CRUD + completions │ ├── Telegram.pm # Bot API client and keyboard factories │ └── Webhook.pm # update routing, command and callback handlers ├── config/ │ └── mnemosyne.conf.example ├── nginx/ │ └── mnemosyne.conf.example ├── scripts/ │ ├── backup.sh │ ├── install.sh │ └── print-digest # debug: render a digest to the terminal ├── share/ │ └── schema.sql ├── systemd/ │ └── mnemosyne-bot.service └── t/ # test suite ``` --- ## License MIT