mnemosyne/README.md

273 lines
8.4 KiB
Markdown

# 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 <id\|title>` | Mark a task complete |
| `/edit <id> <field> <value>` | Update a single field |
| `/disable <id\|title>` | Hide a task without deleting it |
| `/delete <id\|title>` | 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
**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