0.6.5: HTML Telegram digest + fix silently-empty beet sections + watermark-art resilience
The daily digest is now formatted with Telegram HTML: bold section headers
with <pre>-aligned columns, colored circle emoji as status dots, a branded
header line, and a top blockquote callout (all clear vs N issues). All free
text is &/</> escaped so a stray angle bracket in a log snippet can't break
the parse and swallow the whole message. notify-telegram.sh gains an --html
flag (parse_mode=HTML) used by the digest send; plain-text callers are
unchanged. Truncation is now tag-safe in HTML mode: cut on a line boundary,
re-close an open <pre>, and use an escaped marker -- the old literal
"...<truncated>" tail would itself have 400'd the send.
Two real bugs surfaced while testing:
- pipeline-status.sh pins its own PATH, which lacked /opt/venv/bin where
beet lives, so every beet probe ("added today", "Library by format", the
mp3-now count) has been silently empty behind 2>/dev/null since the cron
migration. PATH now includes the venv; both sections show real numbers.
- strip-watermark-art.py aborted its entire weekly run when metaflac stalled
on ONE file (seen today: a healthy 190KB cover took >15s under disk
contention, TimeoutExpired killed the job). Per-file timeout is now 60s
and a timeout skips that file with a warning instead of failing the run.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,17 @@
|
||||
# Writes a one-screen summary to ${ALEMBIC_CONFIG_DIR:-/config}/logs/STATUS.log (overwritten daily)
|
||||
# and ships it to Telegram via notify-telegram.sh.
|
||||
#
|
||||
# Formatting note: the digest now uses Telegram HTML entities (<b>, <i>, <pre>,
|
||||
# <blockquote>) — the brand's chrome/dot-badge language translated into what
|
||||
# Telegram can actually render (no color, no custom font, so status "dots"
|
||||
# become colored circle emoji and section cards become bold header + <pre>
|
||||
# block, matching the web UI's h2 + mono table pattern). This REQUIRES
|
||||
# notify-telegram.sh to be called with --html (parse_mode=HTML) — sent as
|
||||
# plain text the tags would show up literally.
|
||||
# All free-text going through mark_ok/mark_warn/mark_skip/mark_info/section is
|
||||
# escaped (esc()) for &, <, > so a stray angle bracket in a log line can't
|
||||
# break the HTML parse and swallow the whole message.
|
||||
#
|
||||
# Reports on:
|
||||
# - Sibling service reachability (slskd, navidrome) via HTTP, not docker ps —
|
||||
# alembic has no Docker socket access
|
||||
@@ -27,7 +38,10 @@
|
||||
# the opposite of what a health report should do. It writes no library state,
|
||||
# so there is nothing to leave half-applied.
|
||||
set -u
|
||||
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||
# /opt/venv/bin first: `beet` lives in the app venv. Without it every beet
|
||||
# probe here ("added today", "Library by format", the mp3-now count) silently
|
||||
# came up empty behind its 2>/dev/null.
|
||||
PATH=/opt/venv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||
|
||||
OUT=${ALEMBIC_CONFIG_DIR:-/config}/logs/STATUS.log
|
||||
TODAY=$(date +%Y%m%d)
|
||||
@@ -43,13 +57,33 @@ ND_PASS="${ND_PASS:-}"
|
||||
OK=0
|
||||
WARN=0
|
||||
SKIP=0
|
||||
PRE_OPEN=0
|
||||
|
||||
mark_ok() { OK=$((OK+1)); printf " ✓ %s\n" "$*"; }
|
||||
mark_warn() { WARN=$((WARN+1)); printf " ⚠ %s\n" "$*"; }
|
||||
mark_skip() { SKIP=$((SKIP+1)); printf " ⊘ %s\n" "$*"; }
|
||||
mark_info() { printf " %s\n" "$*"; }
|
||||
# Escapes &, <, > so free text (log snippets, exception messages, filenames)
|
||||
# can't be mistaken for an HTML entity by Telegram's parser.
|
||||
esc() {
|
||||
local s=$1
|
||||
s=${s//&/&}
|
||||
s=${s//</<}
|
||||
s=${s//>/>}
|
||||
printf '%s' "$s"
|
||||
}
|
||||
|
||||
section() { printf "\n▎ %s\n" "$*"; }
|
||||
# Status lines double as the brand's badge-dot language (success/warning/muted)
|
||||
# translated into Telegram's only color channel: emoji.
|
||||
mark_ok() { OK=$((OK+1)); printf " 🟢 %s\n" "$(esc "$*")"; }
|
||||
mark_warn() { WARN=$((WARN+1)); printf " 🟠 %s\n" "$(esc "$*")"; }
|
||||
mark_skip() { SKIP=$((SKIP+1)); printf " ⚪ %s\n" "$(esc "$*")"; }
|
||||
mark_info() { printf " %s\n" "$(esc "$*")"; }
|
||||
|
||||
# Bold section header (proportional text, reads like the web app's <h2>),
|
||||
# followed by a <pre> block so the printf-padded columns below it actually
|
||||
# line up in a fixed-width font. Closes the previous section's <pre> first.
|
||||
section() {
|
||||
if [[ "$PRE_OPEN" -eq 1 ]]; then printf "</pre>\n"; fi
|
||||
printf "\n<b>▎ %s</b>\n<pre>" "$(esc "$*")"
|
||||
PRE_OPEN=1
|
||||
}
|
||||
|
||||
# syslog / `logger` is not present in the container image. Only call it if it
|
||||
# exists, so these lines don't spew "logger: command not found" into the job
|
||||
@@ -208,10 +242,13 @@ dedup_today_status() {
|
||||
mark_ok "$(printf '%-13s %d new tracks in beets' 'added today' "$added_total")"
|
||||
if [[ "$added_total" -gt 0 ]]; then
|
||||
# Per-playlist breakdown. Empty $grouping (bandcamp/manual) shown as "(none)".
|
||||
# Piped through the same &/</> escaping as mark_*, since this prints
|
||||
# straight into the <pre> block without going through esc() otherwise.
|
||||
echo "$ADDED_TODAY" \
|
||||
| awk '{ if ($0 == "") print "(none)"; else print }' \
|
||||
| sort | uniq -c | sort -rn \
|
||||
| awk '{ g=$2; for(i=3;i<=NF;i++) g=g" "$i; printf " %3d %s\n", $1, g }'
|
||||
| awk '{ g=$2; for(i=3;i<=NF;i++) g=g" "$i; printf " %3d %s\n", $1, g }' \
|
||||
| sed 's/&/\&/g; s/</\</g; s/>/\>/g'
|
||||
fi
|
||||
|
||||
# 3. Maintenance freshness
|
||||
@@ -432,16 +469,34 @@ except Exception as e:
|
||||
beet ls -f '$format' 2>/dev/null | sort | uniq -c | sort -rn \
|
||||
| awk '{printf " %-6s %d tracks\n", $2, $1}'
|
||||
|
||||
[[ "$PRE_OPEN" -eq 1 ]] && printf "</pre>\n"
|
||||
|
||||
} > "$OUT"
|
||||
|
||||
# ---- Build header banner and prepend ----
|
||||
# Bold brand mark (⚗️ is literally the "ALEMBIC" unicode glyph — no generic
|
||||
# music-note stand-in needed), a hostname chip in <code>, and an italic tally
|
||||
# that reuses the same 🟢/🟠/⚪ dots as the body. When something needs
|
||||
# attention, a <blockquote> callout mirrors the web app's .notice.warning
|
||||
# panel; an all-clear run gets the calm .notice.success equivalent instead.
|
||||
HOSTNAME_SHORT=$(hostname -s)
|
||||
DATE_HUMAN=$(TZ="${TZ:-UTC}" date '+%a %Y-%m-%d %H:%M %Z')
|
||||
BANNER_TITLE="🎵 Music pipeline · $HOSTNAME_SHORT · $DATE_HUMAN"
|
||||
BANNER_TALLY=" $OK OK · $WARN warn · $SKIP skip"
|
||||
sed -i "1c\\
|
||||
${BANNER_TITLE}\\
|
||||
${BANNER_TALLY}" "$OUT"
|
||||
HEADER="⚗️ <b>Alembic</b> · music pipeline · <code>${HOSTNAME_SHORT}</code> · ${DATE_HUMAN}
|
||||
<i>🟢 ${OK} OK 🟠 ${WARN} warn ⚪ ${SKIP} skip</i>"
|
||||
if (( WARN > 0 )); then
|
||||
HEADER="${HEADER}
|
||||
<blockquote>🟠 <b>${WARN} issue(s)</b> need attention — see below</blockquote>"
|
||||
else
|
||||
HEADER="${HEADER}
|
||||
<blockquote>🟢 All clear — nothing needs attention.</blockquote>"
|
||||
fi
|
||||
|
||||
# Swap the __HEADER_PLACEHOLDER__ line for the (2-3 line) banner above. A
|
||||
# straight prepend, not sed 1c — the banner's line count varies with the
|
||||
# blockquote, which sed's `c` command can't take as a variable easily.
|
||||
tail -n +2 "$OUT" > "${OUT}.body"
|
||||
{ printf '%s\n' "$HEADER"; cat "${OUT}.body"; } > "$OUT"
|
||||
rm -f "${OUT}.body"
|
||||
|
||||
# Always mirror the one-line summary to syslog so journalctl shows last-run
|
||||
# state even if Telegram is broken. Warnings get a separate WARN tag.
|
||||
@@ -450,10 +505,12 @@ if (( WARN > 0 )); then
|
||||
slog "WARN: pipeline-status flagged $WARN issue(s) — see $OUT"
|
||||
fi
|
||||
|
||||
# Send to Telegram. If it fails, log the failure to syslog so the absence of
|
||||
# a message in the chat has a corresponding journal entry to grep for.
|
||||
# Send to Telegram with --html: the digest is Telegram-HTML (see header
|
||||
# comment), so notify-telegram.sh must send it with parse_mode=HTML. If the
|
||||
# send fails, log to syslog so the absence of a message in the chat has a
|
||||
# corresponding journal entry to grep for.
|
||||
if [[ -x ${PIPELINE_DIR:-/app/pipeline}/lib/notify-telegram.sh ]]; then
|
||||
if ! ${PIPELINE_DIR:-/app/pipeline}/lib/notify-telegram.sh < "$OUT" 2>/tmp/tg-err; then
|
||||
if ! ${PIPELINE_DIR:-/app/pipeline}/lib/notify-telegram.sh --html < "$OUT" 2>/tmp/tg-err; then
|
||||
slog "WARN: Telegram send failed: $(cat /tmp/tg-err 2>/dev/null | head -c 200)"
|
||||
rm -f /tmp/tg-err
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user