- deps guards before -h|--help in docker-health/ps, network-scan, usb-server, media-mp3/mp4 (--dry-run pre-scan kept); system-firewall gains usage()/--help; autostart/usb-automount get flags.sh + template - install.sh: normalize N-M range syntax in --steps - bin/pos: INTERACTIVE_CMDS += docker-compose docker-vbox network-hotspot - common.sh: canonical XDG-aware CONFIG_DIR + DIM color var; notify.sh stderr fallback; ent_plugin_* registry renames (runtime plugin API kept) - docker-compose SCALE_DIR/CONFIG_ENV env seams; ffmpeg in PACKAGES; scrcpy.sh exec bit - docs: health is console-only (--send/--markdown removed), POS.md file refs for config/tree/entertainment, DEV.md no-guard exception, docmap/ filetable regenerated (make gen), hand-maintained line rows bumped - add scripts/lint-conventions.sh gate + Makefile lint target; record all VERIFIED outcomes in MAINTENANCE.md; AGENT_TODO Done entry (2026-08-14) - gates: make gen/check/lint all green (0 FAIL, 0 WARN); bash -n sweep clean; restricted-PATH dep tests + step-matrix dry-runs verified
14 KiB
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
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
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 backupcallsnotify_send(below) on success and failure. - Warn before a service update:
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 telegramand set both values. - Send succeeds but nothing arrives → the chat must have started the bot
(press
Start/ send/startonce). - Markdown silently empty → Telegram uses its own MarkdownV2; unmatched
syntax makes the message vanish. Use
--markdownonly 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.
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 commandper 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 commandsyntax — 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 (1–32 chars);/Statusor/my-cmdare 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./helplists 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).sudoinside a command needs a NOPASSWD rule. @quietprefix: a map value starting with@quietruns the command but does NOT reply — for commands that already send their own notification, so you don't get it twice.pos system backupself-notifies, so/backup=@quiet pos system backup $HOME/Documentssends the backup notification and nothing else.pos system healthis console-only (it never sends), so map it WITHOUT@quiet—/status=pos system healthreplies 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).--enableprints this warning if linger is off.
Troubleshooting:
- Bot doesn't answer → send
/help; if silent, check the service withsystemctl --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:
# ~/.config/linux_post_install/notify.env
NOTIFY_PLATFORM=telegram # default; comma-separated to fan out to all
notify_send "msg"→ delivers to every platform inNOTIFY_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 inNOTIFY_PLATFORM. Details: DOC/DEV.md → Alerting.
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
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
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 loginfirst. - Login fails → is the homeserver URL right (
pos config matrix) and is the account password correct? Homeservers may require the full@user:example.orgid. - Nothing arrives → the room id/alias must exist and the account must be a
member. Public aliases like
#pos:example.orgwork 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.
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 commandper 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. IfMATRIX_ROOM_IDis set it only watches that room, otherwise every room you've joined./and!both work (!status=/status)./helplists 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).sudoinside a command needs a NOPASSWD rule. @quietprefix: a map value starting with@quietruns the command but does NOT reply — for commands that already send their own notification (pos system backupself-notifies, e.g./backup=@quiet pos system backup $HOME/Documents).pos system healthis console-only (never sends), so map it WITHOUT@quiet.ai …bridge: non-command messages starting withaiare answered bypos ai gemini(per-room memory session;ai /resetclears 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).--enableprints 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
/helpfor 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
# 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
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)
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
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 devicesshow the serial? - Device shows
offline/unauthorized→ re-accept the USB debugging dialog on the phone (unplug/replug);adb kill-servermay help. - "not reachable" after
tcpip→ the phone's WiFi IP changed; re-check withadb devicesor runtcpipagain while plugged in. - Mirror window is blank / no audio → older apt scrcpy lacks features; install
the latest with the
apps/media/scrcpy.shapp installer. ssh -Xmirror is slow → prefer WiFi or a wired LAN; bumpSCRCPY_MAX_FPSdown or setSCRCPY_BIT_RATElower inpos config scrcpy.
Related
- Reference + config file details: DOC/POS.md → communication
- Alerting contract: DOC/DEV.md → Alerting
- Health dashboard + scheduled digest: system.md