- Python 64.4%
- Nix 18%
- Shell 17.6%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| .forgejo | ||
| .envrc | ||
| .gitignore | ||
| AGENTS.md | ||
| daily-briefing.py | ||
| daily-briefing.sh | ||
| flake.lock | ||
| flake.nix | ||
| nixos-module.nix | ||
| package.nix | ||
| quotes.txt | ||
| README.md | ||
| setup.sh | ||
| test-media.sh | ||
daily-briefing
A personal morning briefing script that assembles weather, news, and media download summaries into a spoken TTS audio clip and pushes everything to ntfy.
What it does
Each run:
- Weather — fetches current conditions and today's forecast from open-meteo, then generates a conversational report via Ollama
- News — pulls yesterday's unread articles from FreshRSS (per category/label), summarises each category using a local Ollama model, and assembles a conversational spoken summary
- Media — checks Sonarr and Radarr history for TV episodes and movies downloaded yesterday
- Thought for the day — picks a quote from
quotes.txt, rotating through the full list without repeating until all have been used - TTS — generates a WAV file using pocket-tts cloned from a Fern Brady voice sample
- ntfy — sends a markdown text notification and a separate audio file attachment to the
dailybriefingtopic
Requirements
- Nix with flakes enabled, or Python 3.13 plus
requests curl,jq,ffmpeg(for WAV→MP3 conversion before upload)- A running Ollama instance with a suitable model (default:
qwen3.6:35b-a3b-q8_0) - A running FreshRSS instance with the Google Reader API enabled
- Running Sonarr and Radarr instances
- An ntfy server
- For the NixOS module: sops-nix and an age-encrypted secrets file
Setup
direnv allow
This loads the flake-defined development environment with Python, requests, pocket-tts, curl, and jq.
If you prefer not to use direnv:
nix develop
The legacy ./setup.sh path remains available for non-Nix environments and now exits early with usage guidance when a flake-capable Nix install is present.
Voice sample
Place a WAV voice sample at:
voice_samples/fern_brady.wav
Environment file (standalone mode)
Create a single briefing.env file in the project directory:
# ntfy
NTFY_BASE_URL=https://ntfy.example.com
NTFY_API_KEY=your-api-key
# Weather (open-meteo)
WEATHER_POSTCODE=SW1A1AA
WEATHER_LAT=51.5074
WEATHER_LON=-0.1278
# FreshRSS
FRESHRSS_URL=https://freshrss.example.com
FRESHRSS_USER=your-username
FRESHRSS_PASS=your-password
# Sonarr / Radarr
SONARR_URL=http://192.168.x.x:8989
SONARR_API_KEY=your-sonarr-api-key
RADARR_URL=http://192.168.x.x:7878
RADARR_API_KEY=your-radarr-api-key
# Ollama (optional overrides)
# OLLAMA_URL=http://192.168.x.x:11434
# OLLAMA_MODEL=qwen3.6:35b
# Audio (optional overrides)
# FFMPEG_BIN=/path/to/ffmpeg # default: ffmpeg
# MP3_BITRATE=96k # default: 96k
The audio attachment is generated as a WAV by pocket-tts, then converted to
MP3 with ffmpeg before being uploaded to ntfy. If ffmpeg is unavailable
the WAV is sent instead.
NixOS service
The flake exports nixosModules.default, which turns the settings from the
environment file into NixOS options and keeps credentials in sops. Import it
in a host configuration alongside the flake's other modules:
{
imports = [ inputs.daily-briefing.nixosModules.default ];
services.daily-briefing = {
enable = true;
sopsFile = ./secrets/daily-briefing.yaml;
ntfyBaseUrl = "https://ntfy.example.com";
weatherPostcode = "SW1A1AA";
weatherLatitude = "51.5074";
weatherLongitude = "-0.1278";
freshRssUrl = "https://freshrss.example.com";
freshRssUser = "briefing";
sonarrUrl = "http://192.168.1.10:8989";
radarrUrl = "http://192.168.1.10:7878";
ollamaUrl = "http://192.168.1.10:11434";
};
}
The module creates a daily-briefing system user and a persistent timer at
07:00 by default. Put these keys in the encrypted sops YAML (values omitted):
daily-briefing/ntfy-api-key: ...
daily-briefing/freshrss-password: ...
daily-briefing/sonarr-api-key: ...
daily-briefing/radarr-api-key: ...
daily-briefing/hf-token: ...
Copy voice_samples/fern_brady.wav into the module's
/var/lib/daily-briefing/voice_samples/ directory and make it readable by the
service user. The module copies quotes.txt there on first run and keeps the
quote history in the same directory.
Attic cache workflow
.forgejo/workflows/attic-cache.yml validates the flake on pull requests and
builds the package closure on main, pushing it to the nixos Attic cache.
Configure the repository secret ATTIC_PUSH_TOKEN with permission to push to
that cache. Consumers must configure https://attic.nimmog.uk/nixos as a Nix
substituter and trust its public signing key before they can use the cached
closure.
Running
nix run
You can also run ./daily-briefing.sh, which uses python3 when available and otherwise falls back to nix run.
Run those commands from the repository root so the script can read briefing.env, voice_samples/, and the quote history files. On Debian media1, ./daily-briefing.sh prefers nix run when Nix is installed, so it uses the flake's pinned Python/TTS/runtime dependencies before the host migrates to NixOS. nix run also works directly on any other non-NixOS machine with flakes enabled.
Use ./daily-briefing.sh --dry-run to generate the briefing locally without posting text or audio notifications to ntfy.
Typically scheduled as a cron job or systemd timer each morning.
TTS Notes
- The script prefers the local
voice_samples/fern_brady.wavcloning sample when available. - On Nix,
pocket-ttsmay require accepted Hugging Face terms and local auth for voice-cloning weights. - If cloned voice generation is unavailable, the script falls back to a built-in Pocket TTS voice.
- Override the fallback voice with
POCKET_TTS_FALLBACK_VOICE, or point directly at a binary withPOCKET_TTS_BIN.
Current Operational Checks
When debugging missing briefings, verify these first:
- FreshRSS credentials in
briefing.envstill work against the Google Reader API. - Ollama at the configured host responds quickly enough for both weather and news summarisation.
pocket-ttscan either access the cloned-voice weights or produce audio with the configured fallback voice.- A systemd timer or other scheduler actually exists on the target machine.
Files
| File | Description |
|---|---|
daily-briefing.py |
Main script |
daily-briefing.sh |
Wrapper that runs the script directly or via nix run |
flake.nix |
Nix runtime and development shell |
.envrc |
direnv entrypoint for the flake shell |
setup.sh |
Legacy non-Nix setup script |
test-media.sh |
Standalone test for Sonarr/Radarr fetching |
quotes.txt |
Pool of quotes for "thought of the day" |
unused_quotes.txt |
Remaining quotes not yet used (auto-managed) |
quote_history.json |
Log of which quote was used on which date (auto-managed) |
voice_samples/ |
Voice WAV samples for pocket-tts |