Files
Linux_post_install/DOC/howto/communication.md
T
Your Name a4761df3f6
gates / consistency-and-conventions (push) Successful in 2m3s
feat: telegram listener text-prefix map — <word> <text> routes to a mapped app
Generalizes the Telegram listener with a configurable text-prefix map
(telegram_prefixes.env): any non-command message '<word> <text>' runs
the mapped command with <text> appended as ONE quoted argument — e.g.
opencode=opencode turns 'opencode check cpu' into opencode "check cpu".

Routing order per message: text-prefix map → built-in Gemini ai bridge →
/command map → Unknown command. A mapped word shadows the Gemini bridge.

The prefix verb is reworked: bare = list map + bridge word; 'prefix
<word> <cmd...>' = map; 'prefix <word>' = show; 'prefix -r <word>' = remove.
The Gemini trigger word itself is now set via 'pos config telegram'
(TELEGRAM_AI_PREFIX).

Also extracted run_and_reply() to share the /command-map (60s) and
prefix (120s) execution semantics; fixed a latent set -e abort on
invalid templates in prefix_map_set's check_syntax call.

Verified: 27/27 routing-harness assertions, full CLI verb suite,
dispatch smoke, pos config telegram render, bash -n, make gen && make check,
make lint 0 FAIL / 0 WARN, shellcheck -S style (0 new findings).
2026-09-04 08:10:59 -04:00

337 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# How-To: `pos communication`
Messaging and alerts over Telegram and Matrix, plus Android mirroring. Tools:
`telegram-sender`, `telegram-listener`, `matrix-sender`, `matrix-listener`,
`scrcpy`.
| Tool | What it does |
|------|--------------|
| `pos communication telegram sender` | Send messages/files/links/stickers, test, config (token + chat id) |
| `pos communication telegram listener` | Bot listener: map `/command` → bash and run it from chat (systemd user daemon) |
| `pos communication matrix sender` | Send messages to a Matrix room via the client-server API (send, test, login) |
| `pos communication matrix listener` | Matrix listener: map `/command` → bash and run it from room messages (systemd user daemon) |
| `pos communication scrcpy` | Mirror/control an Android device over USB or WiFi (scrcpy+adb) |
`telegram-sender` is the workhorse: it backs the whole **notify system**
health digests, backup alerts, firewall changes — and can be used directly.
`matrix-sender` plugs into the same system as a second platform.
---
## `pos communication telegram sender`
### One-time setup
```bash
pos config telegram
# edit TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID (masked input), then test:
pos communication telegram sender test
# config lives in ~/.config/linux_post_install/telegram.env (chmod 600)
```
The bot token comes from @BotFather, the chat ID from @userinfobot (or by
starting a chat and reading it). `pos config telegram` shows the current values
(token masked).
### Send
```bash
pos communication telegram sender send "hello from my server" # plain text
pos communication telegram sender send --markdown "**bold** ok" # parse as markdown
pos communication telegram sender send --help # list all flags
pos communication telegram sender send /path/to/report.pdf # auto-detects file
```
**Recipes:**
- **Alert when a backup finishes** — automatic: `pos system backup` calls
`notify_send` (below) on success *and* failure.
- **Warn before a service update:**
```bash
pos communication telegram sender send "Maintenance: docker compose down in 2min"
```
- **On-call file drop:** `pos communication telegram sender send ~/log/nginx-error.log`
**Troubleshooting:**
- "Not configured (no token or chat id)" → run `pos config telegram` and set both values.
- Send succeeds but nothing arrives → the chat must have started the bot
(press `Start` / send `/start` once).
- **Markdown silently empty** → Telegram uses its own MarkdownV2; unmatched
syntax makes the message vanish. Use `--markdown` only when the text is
Telegram-safe (the health digest output is).
- **Webhook vs getUpdates:** the sender uses polling (`getUpdates`), so a
webhook registered on the bot (e.g. via BotFather) blocks sends; remove it
with BotFather's `/deletewebhook`.
---
## `pos communication telegram listener`
Turns your bot into a two-way remote control: map `/command` → bash, then
message the bot from your phone to run it.
```bash
pos communication telegram listener # edit the /command → bash map
pos communication telegram listener --status # service state + mapped commands
pos communication telegram listener --enable # install the systemd user daemon
pos communication telegram listener --disable # remove it
```
- **Map file:** `~/.config/linux_post_install/telegram_commands.env` (chmod 600),
one `/cmd=bash command` per line — re-read on every message, so edits apply
instantly. Example:
```
/status=pos system health
/temp=sensors | grep -i 'Tctl\|package id 0'
/update=cd /path/to/repo && git pull
```
- **Bot command menu:** the mapped commands are pushed to the bot's `/` menu
(`setMyCommands`) after every map edit, on `--enable`, and at daemon start
(force it anytime with `--sync-commands`). Add a short description with the
`/cmd::description=bash command` syntax — e.g.
`/backup::Encrypted nightly backup=@quiet pos system backup $HOME/Documents` — or it
falls back to the bash command. Telegram only registers lowercase `[a-z0-9_]`
names (132 chars); `/Status` or `/my-cmd` are skipped from the menu but still
work when typed. An empty map clears the menu.
- **Owner-only:** the bot only reacts to `TELEGRAM_CHAT_ID` (your own chat);
others are ignored. `/help` lists mapped commands; unknown → "Unknown command".
- **Runs as you:** mapped commands execute as your user with a 60s timeout,
stdout + stderr are replied to the chat (truncated ~3800 chars; empty → `OK`).
`sudo` inside a command needs a NOPASSWD rule.
- **Text-prefix map (apps):** `~/.config/linux_post_install/telegram_prefixes.env`
(chmod 600), one `<word>=command` per line — a non-command message
`<word> <text>` runs the app with `<text>` appended as ONE quoted argument,
e.g. `opencode=opencode` turns "opencode check cpu" into `opencode "check cpu"`.
First match wins (file order), case-insensitive, word must be space-delimited.
Manage it with `pos communication telegram listener prefix <word> <command...>`
(bare `prefix` lists, `prefix <word>` shows one, `prefix -r <word>` removes):
```bash
pos communication telegram listener prefix opencode opencode
# then message: opencode check cpu → runs opencode "check cpu"
pos communication telegram listener prefix ai "pos ai gemini ask --session telegram-\$TELEGRAM_CHAT_ID"
# overrides the built-in Gemini bridge for the word 'ai'
```
Routing order per non-command message: text-prefix map → AI bridge →
`/command` map → "Unknown command".
- **`@quiet` prefix:** a map value starting with `@quiet ` runs the command but
does NOT reply — for commands that already send their own notification, so
you don't get it twice. `pos system backup` self-notifies, so
`/backup=@quiet pos system backup $HOME/Documents` sends the backup notification
and nothing else. `pos system health` is console-only (it never sends), so map
it WITHOUT `@quiet` — `/status=pos system health` replies with the dashboard.
- **Daemon lifecycle:** the service is a systemd **user** unit; it stops at
logout unless you enable linger: `sudo loginctl enable-linger $(whoami)`.
`--enable` prints this warning if linger is off.
**Troubleshooting:**
- Bot doesn't answer → send `/help`; if silent, check the service with
`systemctl --user status pos-telegram-listener.service`.
- A webhook on the bot blocks `getUpdates` → remove it with BotFather's
`/deletewebhook`.
- Needs `jq` (in preinstall PACKAGES).
---
## The notify system (`notify_send`)
Every tool that announces something sends through `lib/notify.sh` instead of
hard-coding Telegram:
```bash
# ~/.config/linux_post_install/notify.env
NOTIFY_PLATFORM=telegram # default; comma-separated to fan out to all
```
- `notify_send "msg"` → delivers to every platform in `NOTIFY_PLATFORM`
(current senders: `telegram`, `matrix`).
- **Silent-fail:** no platform configured → one WARN line, exit 0, never
breaks the calling tool.
- New platform: implement `bin/pos-communication-<p> send
<value> [--markdown]`, then list it in `NOTIFY_PLATFORM`. Details:
[DOC/DEV.md → Alerting](../DEV.md).
---
## `pos communication matrix sender`
Send plain-text (or markdown-formatted) messages to a Matrix room via the
homeserver's client-server API (v3). No bot is needed — it uses a regular
account's access token.
### One-time setup
```bash
pos config matrix
# set MATRIX_HOMESERVER (https://matrix.example.org) and MATRIX_ROOM_ID
# (room id or alias like #ops:example.org), then grab a token:
pos communication matrix sender login --user @you:example.org
# password is prompted (masked) — access token + user id are saved
pos communication matrix sender test
# config lives in ~/.config/linux_post_install/matrix.env (chmod 600)
```
`login` calls the homeserver's password endpoint and stores the resulting
access token in `matrix.env` (masked by `pos config matrix`). Tokens from any
Matrix client (Element, `synctl`…) work too — set `MATRIX_ACCESS_TOKEN`
directly. Requires network access to your homeserver.
### Send
```bash
pos communication matrix sender send "Backup finished" # plain text
pos communication matrix sender send --markdown "**bold** ok" # org.matrix.custom.html
pos communication matrix sender send "hi" --room '#ops:example.org' # one-shot override
pos communication matrix sender test # canned test message
```
`--markdown` sends with `org.matrix.custom.html` (best-effort markdown →
HTML: `**bold**`, `` `code` ``, links, headers, lists). Room ids and aliases
are URL-encoded automatically.
**Troubleshooting:**
- "No access token" → run `pos communication matrix sender login` first.
- Login fails → is the homeserver URL right (`pos config matrix`) and is the
account password correct? Homeservers may require the full
`@user:example.org` id.
- Nothing arrives → the room id/alias must exist and the account must be a
member. Public aliases like `#pos:example.org` work when the account has
joined.
---
## `pos communication matrix listener`
Turns any room you're in into a remote control for your server: message your
own account a `/command` and the mapped bash runs.
```bash
pos communication matrix listener # edit the /command → bash map
pos communication matrix listener --status # service state + mapped commands
pos communication matrix listener --enable # install the systemd user daemon
pos communication matrix listener --disable # remove it
```
- **Map file:** `~/.config/linux_post_install/matrix_commands.env` (chmod 600),
one `/cmd=bash command` per line — re-read on every message, so edits apply
instantly. Example:
```
/status=pos system health
/temp=sensors | grep -i 'Tctl\|package id 0'
/update=cd /path/to/repo && git pull
```
- **Self-messaging:** the listener reacts to messages **from your own user id**
(`MATRIX_USER_ID`) — in practice that means a second device (or another
account) sending the commands. If `MATRIX_ROOM_ID` is set it only watches
that room, otherwise every room you've joined. `/` and `!` both work
(`!status` = `/status`). `/help` lists mapped commands; unknown → "Unknown
command".
- **Runs as you:** mapped commands execute as your user with a 60s timeout,
stdout + stderr are replied to the room as a thread reply to your message
(truncated ~3800 chars; empty → `OK`). `sudo` inside a command needs a
NOPASSWD rule.
- **`@quiet` prefix:** a map value starting with `@quiet ` runs the command but
does NOT reply — for commands that already send their own notification
(`pos system backup` self-notifies, e.g. `/backup=@quiet pos system backup $HOME/Documents`).
`pos system health` is console-only (never sends), so map it WITHOUT `@quiet`.
- **`ai …` bridge:** non-command messages starting with `ai ` are answered by
`pos ai gemini` (per-room memory session; `ai /reset` clears it) — replying
with the model's answer, markdown stripped.
- **Daemon lifecycle:** the service is a systemd **user** unit; it stops at
logout unless you enable linger: `sudo loginctl enable-linger $(whoami)`.
`--enable` prints this warning if linger is off.
**Troubleshooting:**
- Doesn't answer → check the service with
`systemctl --user status pos-matrix-listener.service`; the daemon logs to
`~/.local/share/linux_post_install/logs/pos.log`.
- "Unknown command" → send `/help` for the mapped list.
- Needs `jq` (in preinstall PACKAGES).
---
## `pos communication scrcpy`
Mirror and control an Android device from the PC — the phone's screen in a
window, controlled with mouse + keyboard (scrcpy by Genymobile, no root, no
phone app). Works over USB or WiFi.
### One-time setup
```bash
# phone: Settings → About → tap "Build number" 7× → Developer options → enable
# "USB debugging"; plug it in and accept the "Allow USB debugging" dialog
pos communication scrcpy devices # confirm the phone shows as "device"
pos communication scrcpy info # model / Android version
pos config scrcpy # optional defaults (serial, size, fps, ...)
```
Requires `scrcpy` + `adb`. `adb` is in preinstall PACKAGES; `scrcpy` usually is
**not** in apt on Debian/Ubuntu (needs contrib/universe and is older there), so
install the current GitHub release (bundles `adb`) with the optional app
`apps/media/scrcpy.sh` (or `./install.sh --apps`).
### Mirror
```bash
pos communication scrcpy # USB device, config defaults
pos communication scrcpy --turn-screen-off # pass any scrcpy flag through
pos communication scrcpy --no-audio --always-on-top
pos communication scrcpy --new-display=1920x1080 # mirror to a NEW phone display
```
`--new-display` mirrors a fresh virtual display instead of the phone's real
screen (nice for watching the phone screen content elsewhere): no `=…` uses the
main display's size/dpi, or force size/dpi — `1920x1080`, `1920x1080/420`,
`/240`. Set it persistently with `SCRCPY_NEW_DISPLAY` in `pos config scrcpy`.
Audio is forwarded to the desktop **by default** (scrcpy default); `--no-audio`
disables it, or set `SCRCPY_AUDIO=false` in `pos config scrcpy`.
The window needs a display — over ssh use `ssh -X` (and a phone already
reachable over WiFi, see below). `scrcpy --help` lists every flag; the wrapper
forwards flags verbatim.
### Wireless (no USB cable)
```bash
pos communication scrcpy tcpip 5555 # switch the USB device to WiFi adb
# unplug the phone, then:
pos communication scrcpy connect 192.168.1.42:5555 # connect + mirror
```
`tcpip` prints the exact `connect` command with the phone's detected IP. Set
`SCRCPY_SERIAL` in `pos config scrcpy` so later bare `pos communication scrcpy`
goes straight to that device.
### Record / screenshot / files
```bash
pos communication scrcpy record --headless # record to ~/Videos/scrcpy/, no window
pos communication scrcpy record clip.mp4 # explicit file
pos communication scrcpy screenshot # PNG to ~/Videos/scrcpy/
pos communication scrcpy push ~/app.apk # → /sdcard/Download/
pos communication scrcpy pull /sdcard/DCIM/Camera ~/photos
```
These all work headless — handy on the homelab box for grabbing a phone's
screen/file without a desktop.
**Troubleshooting:**
- "no device connected" → is USB debugging on, is the "Allow USB debugging"
dialog accepted, and does `pos communication scrcpy devices` show the serial?
- Device shows `offline`/`unauthorized` → re-accept the USB debugging dialog on
the phone (unplug/replug); `adb kill-server` may help.
- "not reachable" after `tcpip` → the phone's WiFi IP changed; re-check with
`adb devices` or run `tcpip` again while plugged in.
- Mirror window is blank / no audio → older apt scrcpy lacks features; install
the latest with the `apps/media/scrcpy.sh` app installer.
- `ssh -X` mirror is slow → prefer WiFi or a wired LAN; bump `SCRCPY_MAX_FPS`
down or set `SCRCPY_BIT_RATE` lower in `pos config scrcpy`.
---
## Related
- Reference + config file details: [DOC/POS.md → communication](../POS.md)
- Alerting contract: [DOC/DEV.md → Alerting](../DEV.md)
- Health dashboard + scheduled digest: [system.md](system.md)