Raise / ack / resolve all rendered with the same title and body on
ntfy and SMTP, so a recovery looked identical to the original alert.
Webhook was already fine because the JSON envelope carries 'event'.
ntfy:
Title '[raised · warning] dev backup_failed' (was '[warning] …')
Tags 'raised,warning,backup_failed' (was 'warning,backup_failed')
Body 'Resolved · <message>' / 'Acknowledged · <message>' on those events
SMTP:
Subject '[restic-manager] [raised · warning] dev: backup_failed'
Plus: cmd/_fake_alert now accepts the ref as a positional argument
(go run ./cmd/_fake_alert steve-001) instead of silently ignoring
unknown positional args. Refuses ambiguous '-ref X positional Y'.
Self-hosted ntfy that doesn't expose a token-mint endpoint can still
authenticate over HTTP Basic. Add Username + Password fields to
NtfyConfig; the channel sends 'Authorization: Basic …' when token is
empty and username is set. Token wins when both are configured.
Form-side: two new optional fields next to the access token, with
the same write-only placeholder treatment as smtp_password (blank
on edit means 'keep stored value'). Username is round-tripped on
edit; password is masked.
Fixes flagged in spec review of f0a323e: ntfy POSTs need explicit
Content-Type: text/plain (the spec calls for it; ntfy works without
but explicit beats inferred); trim trailing slashes from server URL
to avoid double-slash when operators paste 'https://ntfy.sh/'.