Initial build: Asterisk voicemail transcription + portal
- mailcmd replacement (vm_mailcmd.py): faster-whisper transcription (CPU int8), extractive summary + intent tags + spoken-digit number extraction, multipart/alternative HTML email, fail-safe relay of original message - Telegram DM delivery (vm_telegram.py) with per-mailbox routing - Caller-ID -> name (vm_contacts.py): file / google / carddav backends - SQLite store (vm_store.py) with content-addressed audio - FastAPI portal (vm_web.py): PIN login, list/play/delete, per-user settings, zero JS, loopback-only behind Apache TLS - Backfill importer (vm_import.py) for existing spool recordings - systemd unit, Apache vhost + certbot TLS, install.sh - Docs: INSTALL, CONFIGURATION, ARCHITECTURE, OPERATIONS, SECURITY, TESTING Verified end-to-end on mail.txt3.net: 157 historical messages backfilled, live voicemail -> transcribed -> stored -> visible at https://vm.txt3.net.
This commit is contained in:
159
docs/CONFIGURATION.md
Normal file
159
docs/CONFIGURATION.md
Normal file
@ -0,0 +1,159 @@
|
||||
# Configuration
|
||||
|
||||
All config lives in `/opt/vm-transcribe/`. Templates are committed as
|
||||
`*.example`; the live files are created by `install.sh` or by you. Secret-bearing
|
||||
files (`telegram.conf`, `contacts.conf`) are mode `640 root:asterisk` and git-
|
||||
ignored.
|
||||
|
||||
Most settings can also be set via environment variables, which is how the systemd
|
||||
unit wires production values. Env vars override file values when both are present
|
||||
unless noted.
|
||||
|
||||
---
|
||||
|
||||
## Environment variables (shared)
|
||||
|
||||
| Variable | Default | Used by | Meaning |
|
||||
|---|---|---|---|
|
||||
| `VM_DB` | `/var/lib/vm-transcribe/voicemail.db` | store, web, import | SQLite path. Created with WAL mode if missing. |
|
||||
| `VM_AUDIO_DIR` | `/var/lib/vm-transcribe/audio` | store, web, import | Where copied recordings live (`<sha[:2]>/<sha>.wav`). |
|
||||
| `VM_LOG` | `/var/log/asterisk/vm_mailcmd.log` | mailcmd | Log path. |
|
||||
| `VM_MODEL_CACHE` | `/opt/vm-transcribe/models` | mailcmd, import | Whisper model download/load dir. |
|
||||
| `VM_WHISPER_MODEL` | `base.en` | mailcmd, import | Model name. `tiny`/`base`/`small` trade speed for accuracy. |
|
||||
| `VM_SPOOL` | `/var/spool/asterisk/voicemail` | import | Spool root to scan. |
|
||||
| `VM_ASTERISK_CONF` | `/etc/asterisk/voicemail.conf` | web, auth | For PIN lookup. |
|
||||
|
||||
---
|
||||
|
||||
## voicemail.conf (Asterisk)
|
||||
|
||||
One line is all the pipeline needs:
|
||||
|
||||
```ini
|
||||
mailcmd=/opt/vm-transcribe/venv/bin/python3 /opt/vm-transcribe/vm_mailcmd.py
|
||||
```
|
||||
|
||||
Place it in the `[general]` section (or any context). After editing:
|
||||
|
||||
```bash
|
||||
sudo asterisk -rx 'voicemail reload'
|
||||
```
|
||||
|
||||
`install.sh` backs up the file and adds this line automatically.
|
||||
|
||||
---
|
||||
|
||||
## telegram.conf
|
||||
|
||||
```ini
|
||||
[telegram]
|
||||
enabled = yes
|
||||
token = 123456789:AAH... ; from @BotFather
|
||||
default_chat_id = ; fallback for unmapped mailboxes (blank = none)
|
||||
send_audio = yes ; voice note with caption
|
||||
send_transcript = yes ; full transcript as a follow-up message
|
||||
timeout = 20 ; seconds per send attempt
|
||||
|
||||
[mailbox:1001]
|
||||
chat_id = 123456789 ; single recipient
|
||||
|
||||
[mailbox:1002]
|
||||
chat_id = 5551, 5552 ; comma list fans out to several people
|
||||
send_transcript = no ; any global key can be overridden per mailbox
|
||||
|
||||
[mailbox:1003]
|
||||
chat_id = -1001234567890 ; negative = group chat
|
||||
send_audio = no ; text-only DM
|
||||
```
|
||||
|
||||
**Routing rules** (evaluated per voicemail):
|
||||
|
||||
1. Match the `[mailbox:N]` section for the voicemail's mailbox number.
|
||||
2. `chat_id` may be a single id, a comma-separated list (fan-out), or a group id
|
||||
(negative).
|
||||
3. If no mailbox section matches, fall back to `default_chat_id`.
|
||||
4. If `enabled = no` or no route resolves, nothing is sent — email still goes out.
|
||||
5. The file is re-read on every voicemail; no reload needed after editing.
|
||||
|
||||
**Delivery fallback chain** (per chat id, tried in order): `sendVoice` →
|
||||
`sendDocument` → `sendMessage` (text-only). So a Telegram outage or a bad audio
|
||||
file still delivers the summary.
|
||||
|
||||
> Contact resolution note: a user must message the bot once before it can DM
|
||||
> them. Telegram does not permit bots to initiate conversations.
|
||||
|
||||
---
|
||||
|
||||
## contacts.conf
|
||||
|
||||
```ini
|
||||
[contacts]
|
||||
backends = file ; space/comma separated: file google carddav
|
||||
cache_ttl = 86400 ; seconds; cache hits AND misses
|
||||
cache_file = /var/lib/vm-transcribe/contacts-cache.json
|
||||
|
||||
# --- 'file' backend (recommended): a vCard or CSV export -------------
|
||||
[file]
|
||||
path = /var/lib/vm-transcribe/contacts.vcf
|
||||
# CSV (Google export "All contact data") is auto-detected:
|
||||
# path = /var/lib/vm-transcribe/contacts.csv
|
||||
|
||||
# --- 'google' backend: People API via OAuth ---------------------------
|
||||
[google]
|
||||
token_file = /var/lib/vm-transcribe/google-token.json
|
||||
# Obtain a token with the People API (contacts.readonly) scope. See
|
||||
# docs/SECURITY.md. No app password here — OAuth only.
|
||||
|
||||
# --- 'carddav' backend: app password, Nextcloud/Fastmail/iCloud --------
|
||||
[carddav]
|
||||
# Google rejects app passwords (basic auth disabled 2024-09-30); a Google
|
||||
# URL here is refused with a log warning and the backend is skipped.
|
||||
url = https://contacts.fastmail.com/dav/addressbooks/user/xxx/Default
|
||||
username = jp@txt3.com
|
||||
app_password = ; app-specific password, not your login password
|
||||
```
|
||||
|
||||
**Matching** is on the **last 9 digits** of the caller ID, so `+447700900123`,
|
||||
`07700900123` and `447700900123` all collapse to the same contact. Names resolve
|
||||
best-effort; on any backend error the caller ID is used as-is.
|
||||
|
||||
---
|
||||
|
||||
## Portal settings (per mailbox, in the SQLite store)
|
||||
|
||||
These are edited from the portal's **Settings** page by each user; they are not
|
||||
config files. Every value defaults to `yes`.
|
||||
|
||||
| Setting | Effect |
|
||||
|---|---|
|
||||
| `summarise` | Run the summariser (if off, the raw transcript is shown). |
|
||||
| `telegram` | Forward new voicemails to the mailbox's Telegram route (requires telegram.conf). |
|
||||
| `email_notify` | Send the enriched email (if off, the message is stored but not emailed). |
|
||||
| `highlight` | Colour the caller chip when a name is resolved from contacts. |
|
||||
| `max_inbox` | Keep at most N messages; oldest beyond N are auto-deleted (0 = unlimited). |
|
||||
|
||||
---
|
||||
|
||||
## vm-portal.service
|
||||
|
||||
Production values are passed as environment in the unit file:
|
||||
|
||||
```ini
|
||||
Environment=VM_DB=/var/lib/vm-transcribe/voicemail.db
|
||||
Environment=VM_AUDIO_DIR=/var/lib/vm-transcribe/audio
|
||||
Environment=VM_ASTERISK_CONF=/etc/asterisk/voicemail.conf
|
||||
Environment=VM_SESSION_HOURS=12
|
||||
```
|
||||
|
||||
Login hardening (in-process):
|
||||
|
||||
| Var | Default | Meaning |
|
||||
|---|---|---|
|
||||
| `VM_MAX_FAILS` | `5` | Failed attempts per (mailbox, IP) before locking. |
|
||||
| `VM_LOCK_MINUTES` | `15` | Lock duration. |
|
||||
| `VM_SESSION_HOURS` | `12` | Session lifetime. |
|
||||
| `VM_INSECURE_COOKIE` | unset | Set to `1` only for plain-HTTP testing; normally cookies are `Secure`. |
|
||||
| `VM_BASE_PATH` | `""` | Set to e.g. `/voicemail` if the app is served under a sub-path. |
|
||||
|
||||
> A service restart clears the in-memory lockout counters. Testing lockout will
|
||||
> lock you out of your own verification for `VM_LOCK_MINUTES`.
|
||||
Reference in New Issue
Block a user