| bin | ||
| config | ||
| lib/Mnemosyne | ||
| nginx | ||
| scripts | ||
| share | ||
| systemd | ||
| t | ||
| cpanfile | ||
| LICENSE | ||
| README.md | ||
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
- Your Telegram chat ID (@userinfobot will tell you)
- Perl 5.26+, nginx, certbot, sqlite3
Deployment
1. Clone the repo
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.
sudo bash scripts/install.sh
This will:
- Install system packages (Perl, cpanminus, sqlite3, gcc) via
dnforapt-get - Install CPAN dependencies from
cpanfileviacpanm - Create a
mnemosynesystem 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/mnemosynefor 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
sudo nano /etc/mnemosyne/mnemosyne.conf
Fields to fill in:
[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:
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:
sudo certbot --nginx -d bot.example.com
Verify the nginx config still works after certbot modifies it:
sudo nginx -t && sudo systemctl reload nginx
6. Start the bot
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:
sudo -u mnemosyne /opt/mnemosyne/bin/mnemosyne-webhook \
--config /etc/mnemosyne/mnemosyne.conf
To remove the webhook (e.g. before switching servers):
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:
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:
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:
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:
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:
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
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