Skip to content

Repository files navigation

Evangelotron

evangelotron — announce your Linux distribution to anyone within technical reach.

$ evangelotron --distro "$(< /etc/arch-release)" --dry-run
I use Arch btw
evangelotron: dry run selected local

Claimer: The opinions expressed and implied by this tool absolutely ARE those of the creator and contributing community.

Evangelotron is a local-first Linux CLI. It detects or accepts a distribution name, renders I use <distro> btw, and can announce that fact through local audio, explicitly authorized Google Cast receivers, and a configured Home Assistant event. The implementation is serious because the premise has already done enough damage.

Installation

Python 3.11 or newer on Linux is required. Install from a checked-out source tree; this repository does not yet claim PyPI, AUR, or distro-package availability:

$ python -m pip install .

The core package has no mandatory Python runtime dependencies. Direct Cast support is an optional extra:

$ python -m pip install '.[cast]'

For development, install the editable package and tools, optionally including Cast support:

$ python -m pip install -e '.[dev]'
$ python -m pip install -e '.[cast,dev]'

System capabilities are separate from Python dependencies. Local or Cast audio needs espeak-ng; local playback additionally needs one of pw-play, paplay, or aplay. Evangelotron probes these programs but never installs them automatically.

The source tree contains man/evangelotron.1. Python packaging does not portably install it as a system man page; later distro packaging may place it under the appropriate system man directory without requiring privileged pip behavior.

Correcting the btw command

An unrelated project already uses btw as both a package and command name. Evangelotron therefore installs its executable as evangelotron.

The intended command name for this project is still btw. Configure it in your shell:

alias btw='evangelotron'

Evangelotron does not modify shell configuration automatically. If another btw command is already installed, rename or re-alias that command before assigning btw to Evangelotron.

Quick start

Autodetect the distro and use configured default sinks:

$ evangelotron

Use a distro explicitly or preserve the Arch empty-marker joke:

$ evangelotron --distro Fedora --dry-run
I use Fedora btw
evangelotron: dry run selected local

$ evangelotron --distro "$(< /etc/arch-release)" --dry-run
I use Arch btw
evangelotron: dry run selected local

Select exactly the requested sink classes, or all classes enabled under [announce]:

$ evangelotron --local
$ evangelotron --cast
$ evangelotron --home-assistant
$ evangelotron --local --cast
$ evangelotron --all

--all never means every device visible on the LAN. --dry-run performs local parsing and capability probes but never synthesizes, plays, discovers Cast, contacts Home Assistant, starts a media server, or reserves cooldown.

Output controls are:

  • --json: one announcement result object on stdout;
  • --quiet: suppress successful human output, not errors;
  • --verbose: safe operational diagnostics on stderr.

Inspect local capabilities without announcing or networking:

$ evangelotron doctor

Explicitly add bounded, non-mutating checks of the configured HA endpoint and configured Cast target resolution:

$ evangelotron doctor --network

Doctor warnings are nonfatal. Doctor returns 0 when no check fails and 1 when diagnostics complete with one or more failures. Usage errors remain 2, unexpected internal errors remain 70, and Ctrl-C returns 130.

Announcement exit codes remain:

  • 0: at least one selected sink succeeded and none failed, with skips allowed;
  • 2: CLI usage or configuration error;
  • 3: partial announcement success;
  • 4: no selected announcement sink succeeded;
  • 5: distro detection failure;
  • 6: cooldown prevented execution;
  • 70: unexpected internal error.

Configuration

Configuration uses ${XDG_CONFIG_HOME:-~/.config}/btw/config.toml. A missing file uses validated defaults and is not created automatically.

[announce]
local = true
cast = false
home_assistant = false
cooldown_seconds = 3

[speech]
provider = "auto"
voice = "en-gb"

# Piper execution is not implemented.
# [speech.piper]
# model = "/absolute/path/to/voice.onnx"

[local]
player = "auto"

[cast]
enabled = false
interrupt_active_media = false
discovery_timeout_seconds = 5
playback_timeout_seconds = 30
http_lifetime_seconds = 90
targets = []
known_hosts = []

[home_assistant]
enabled = false
url = "http://homeassistant.local:8123"
event_type = "btw_announce"
token_env = "BTW_HA_TOKEN"
allow_insecure_http = true

[message]
template = "I use {distro} btw"

Unknown keys warn; malformed TOML, invalid types, invalid URLs, and unsafe values fail before announcement side effects. The message template supports only {distro}. The canonical terminal/JSON/HA text remains I use Arch btw; speech providers receive the deliberately narrow I use Arch, by the way.

Cooldown state uses ${XDG_RUNTIME_DIR}/btw/ where available and otherwise ${XDG_CACHE_HOME:-~/.cache}/btw/. One invocation reserves once, and the lock is released before synthesis or network work. --force bypasses only cooldown timing; it does not bypass configuration, authorization, or security policy.

Local audio setup

Install espeak-ng and at least one supported default-output player using your system's normal administration process. Automatic player selection is:

  1. pw-play (PipeWire);
  2. paplay (PulseAudio-compatible);
  3. aplay (ALSA).

No shell command is constructed from message data, and it does not change system volume or select a non-default device.

$ evangelotron --local --dry-run
$ evangelotron --distro Fedora --local
evangelotron: announced Fedora via local

Cast setup

Install the optional dependency, discover without playback, then manually copy an owned receiver's stable UUID into configuration:

$ python -m pip install '.[cast]'
$ evangelotron discover cast --timeout 5
UUID                                  NAME             MODEL             ADDRESS
aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa  Kitchen display  Google Nest Mini  192.168.1.20
[announce]
cast = true

[cast]
enabled = true
targets = ["aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"]

UUIDs are preferred. Exact friendly names are accepted only when unique; ambiguity fails instead of guessing. Discovery and known_hosts are resolution hints, not authorization, and discovered inventories are not persisted.

A real Cast announcement performs bounded discovery only to resolve configured targets. One synthesized WAV is shared by local and Cast sinks. A short-lived stdlib HTTP server binds to a route-reachable address and ephemeral port, then serves only that WAV at a random unguessable path. Receivers must be able to reach the host; firewalls, client isolation, VLANs, VPNs, or model-specific behavior may prevent fetching.

Active or unknown media is skipped by default. Setting interrupt_active_media = true explicitly permits replacement. Evangelotron never changes Cast volume and uses no cloud TTS. Automated coverage is comprehensive, but broad real-device compatibility has not been validated across receiver models.

The opt-in live test controls only the explicitly supplied target:

$ BTW_LIVE_CAST_TEST=1 BTW_LIVE_CAST_TARGET=AUTHORIZED_UUID pytest -m live_cast

Home Assistant event setup

Home Assistant support sends one custom event to the configured endpoint. It does not assume a TTS provider, entity, or automation topology.

[announce]
home_assistant = true

[home_assistant]
enabled = true
url = "https://homeassistant.example.test:8123"
event_type = "btw_announce"
token_env = "BTW_HA_TOKEN"
allow_insecure_http = false

Set the referenced environment variable only in the invoking environment. Or replace token_env with an absolute token_file path and restrict the file, for example to mode 0600. Token values and file contents are never printed.

The schema-version 1 event payload is:

{
  "schema_version": 1,
  "source": "btw",
  "message": "I use Arch btw",
  "distro": "Arch",
  "hostname": "workstation",
  "direct_cast_targets": []
}

Combined Cast/HA runs populate direct_cast_targets only with stable UUIDs of receivers actually addressed by direct playback. HA-only runs retain [].

Example automation contract:

alias: BTW announcement
triggers:
  - trigger: event
    event_type: btw_announce

actions: []  # The Home Assistant owner decides what happens.

evangelotron doctor --network uses authenticated GET /api/ and never fires the event endpoint. TLS verification remains enabled. Plain HTTP works only when explicitly allowed and always produces a doctor warning because bearer tokens can be observed by parties able to inspect that network path.

Authorization and security

Only configure devices and services that you own or are authorized to control.

Discovery is not authorization. --all is not uncontrolled broadcast mode. Home Assistant traffic goes only to its configured endpoint, and direct Cast playback goes only to configured UUIDs or exact unambiguous names. The project has no telemetry, update checks, cloud TTS, automatic package installation, automatic volume manipulation, daemon, or remote-control service.

See SECURITY.md for the implemented threat model, secret handling, temporary-server boundary, cleanup behavior, and reporting guidance.

Troubleshooting

Start locally:

$ evangelotron doctor

Only when you intend to contact configured local services, add:

$ evangelotron doctor --network

Common findings:

  • espeak-ng executable not found: install it through the host distribution;
  • no local player: install PipeWire, PulseAudio-compatible, or ALSA tooling;
  • Piper configured: Piper execution remains intentionally unimplemented;
  • Cast extra missing: reinstall from source with '.[cast]';
  • Cast unresolved: confirm the configured UUID/name and discovery routing;
  • Cast WAV fetch fails: inspect host firewall and client-isolation policy;
  • HA token source missing: set the referenced variable or provide the file;
  • HA plain HTTP warning: prefer locally trusted HTTPS where available;
  • cooldown active: wait, or deliberately use --force for that invocation.

evangelotron doctor is strictly local. evangelotron doctor --network is diagnostic and non-mutating: it does not announce, synthesize, play, reserve cooldown, start a Cast media server, change volume, or fire a Home Assistant event.

Contributing

Install development tooling and run the same local gates as CI:

$ python -m pip install -e '.[cast,dev]'
$ python -m compileall -q src tests
$ ruff check .
$ pytest -m 'not live_cast'

Normal tests use deterministic fakes or loopback-only fixtures. Never enable a live Cast test in automation. Keep core runtime dependencies empty, preserve explicit network authorization, and avoid adding deferred sinks or release machinery without an accepted design change.

The public package namespace, distro packaging, version 0.1.0, tag, and release remain human/release-phase decisions. No release is claimed by this development tree.

Evangelotron does not collect telemetry. It has better things to announce.

License

Copyright (C) 2026 James Wylie

Evangelotron is free software licensed under the GNU General Public License version 3 or, at your option, any later version. You can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

See LICENSE for the complete license text.

About

Unnecessarily announce your Linux distribution through your terminal, local speakers, smart devices, and anything else unfortunate enough to be within technical reach.

Topics

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages