Files
Pete/config.example.toml
prosolis cb84e1d549 games: a news session that travels to the games box
preferred_username was being read from the ID token and thrown away after
serving as a display-name fallback. It is the whole identity story: MAS imports
it as the Matrix localpart, so it is also who the player is in the euro economy.
Keep it in the session, and derive @user:server from it.

The session cookie was host-only, so a sign-in on news never reached games.
Widen it with an opt-in web.auth.cookie_domain — but only the session cookie:
the OAuth round-trip cookie pairs with a redirect back to the host that started
the login and stays where it was set. And because the redirect must return to
that host, the redirect_uri is now derived per-request for hosts inside the
cookie domain, with the configured URL as the fallback for anything else — a
Host header we don't own is never echoed into a redirect.
2026-07-13 23:04:09 -07:00

281 lines
8.6 KiB
TOML

[matrix]
homeserver = "https://matrix.example.org"
user_id = "@pete:matrix.example.org"
password = "${PETE_PASSWORD}"
pickle_key = "${PETE_PICKLE_KEY}"
display_name = "Pete"
data_dir = "./data"
admin_room = "!adminroomid:matrix.example.org"
# Matrix user IDs allowed to run in-room commands like !post. Empty disables
# commands entirely (channel rooms may be public, so empty != "anyone").
admins = ["@you:matrix.example.org"]
[matrix.channels]
tech = "!techroomid:matrix.example.org"
politics = "!politicsroomid:matrix.example.org"
gaming = "!gamingroomid:matrix.example.org"
lego = "!legoroomid:matrix.example.org"
[posting]
enabled = true # master switch for auto-posting news to Matrix; false = web-only (commands still work)
min_interval_seconds = 300
burst_cap_count = 3
burst_cap_window_seconds = 1800
daily_cap_total = 5 # hard global cap across ALL channels (rolling 24h); 0 disables
[posting.round_robin]
enabled = false # when true, replaces immediate posting with paced rotation
interval_hours = 4 # one story per N hours, cycling through channels in sorted order
[storage]
db_path = "./data/pete.db"
recent_window_hours = 24
[web]
enabled = true
listen_addr = ":8080"
site_title = "Pete"
base_url = "https://news.parodia.dev"
# OIDC subjects allowed to view the owner-facing source-health dashboard at
# /status (per-feed poll status, failures, content stats). Requires web.auth
# below. Empty = /status returns 404 for everyone. Find a user's subject in the
# server logs ("auth: user signed in" sub=...) after they sign in once.
admin_subs = []
# Optional OIDC sign-in (Authentik). When enabled, signed-in users get their
# preferences (hidden feeds, weather location, toggles) stored server-side keyed
# by their OIDC subject and synced across devices. Anonymous visitors keep using
# browser localStorage — the site stays public. If the provider is unreachable
# at startup, Pete logs a warning and serves anonymously rather than refusing to
# boot. Create an OAuth2/OIDC provider + application in Authentik, set the
# redirect URI to <base_url>/auth/callback, then fill these in.
[web.auth]
enabled = false
issuer = "https://authentik.parodia.dev/application/o/pete/"
client_id = "${PETE_OIDC_CLIENT_ID}"
client_secret = "${PETE_OIDC_CLIENT_SECRET}"
redirect_url = "https://news.parodia.dev/auth/callback"
# HMAC key that signs the session cookie. Generate with: openssl rand -hex 32
session_secret = "${PETE_SESSION_SECRET}"
# Share the session across sibling hosts, so signing in on news.parodia.dev also
# signs you in on games.parodia.dev. This widens the cookie to every host under
# the domain, so leave it empty to keep the session host-only. Each host that
# starts a login also needs its own redirect URI registered in Authentik
# (<host>/auth/callback) — the login round-trip returns to the host it began on.
cookie_domain = ""
# Optional Web Push digests. When enabled, signed-in users can opt in (from the
# feed-settings panel) to a periodic "N new stories" notification, delivered via
# a service worker so it also works when the site is installed as a PWA. Push is
# signed-in only, so it needs web.auth above; it does nothing otherwise.
# Generate the VAPID keypair once with: pete -genvapid
# then paste the two keys here (the private key is a secret — treat it like a
# password). subject identifies you to the push service (a mailto: or https: URL).
[web.push]
enabled = false
vapid_public_key = "${PETE_VAPID_PUBLIC_KEY}"
vapid_private_key = "${PETE_VAPID_PRIVATE_KEY}"
subject = "mailto:admin@parodia.dev"
# How often the sender wakes to look for new stories per subscriber (minutes).
interval_minutes = 360
# Smallest number of new (non-hidden) stories that triggers a digest, so a lone
# item doesn't ping everyone.
min_stories = 3
# Server-side neural read-aloud (Piper, https://github.com/rhasspy/piper).
# When enabled, the reader's "Listen" button streams real Piper voices instead
# of the browser's robotic Web Speech voice. Signed-in only, so it needs
# [web.auth] on too. Install the piper binary and one or more voice models
# (<id>.onnx + <id>.onnx.json) into voices_dir first.
[web.tts]
enabled = false
piper_bin = "/opt/piper/piper" # path to the piper executable
voices_dir = "/opt/piper/voices" # dir holding <id>.onnx (+ .onnx.json) models
default = "en_US-amy-medium" # voice id selected until the reader picks another
# List the voices to offer, in menu order. Omit the whole [[web.tts.voices]]
# list to auto-discover every *.onnx in voices_dir (labelled by filename).
[[web.tts.voices]]
id = "en_US-amy-medium"
label = "Amy (US, female)"
[[web.tts.voices]]
id = "en_US-ryan-high"
label = "Ryan (US, male, HQ)"
# Every enabled source MUST set direct_route to a key from [matrix.channels] above.
# There is no automatic classification — Pete posts each story to its configured channel.
# Optional: language = "en" drops feed items whose per-item <language> tag is
# set and doesn't match (prefix). Useful for multilingual feeds like Politico
# Europe that publish the same story in en / fr / de side-by-side. Items with
# no language tag pass through unchanged.
[[sources]]
name = "The Guardian — World"
feed_url = "https://www.theguardian.com/world/rss"
tier = 1
poll_interval_minutes = 20
direct_route = "politics"
enabled = true
# Optional: override the User-Agent for this feed only. Leave unset to use
# Pete's honest bot UA; set a browser string for feeds whose WAF blocks bots.
# user_agent = "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0"
[[sources]]
name = "The Guardian — Politics"
feed_url = "https://www.theguardian.com/politics/rss"
tier = 1
poll_interval_minutes = 20
direct_route = "politics"
enabled = true
[[sources]]
name = "The Guardian — US News"
feed_url = "https://www.theguardian.com/us-news/rss"
tier = 1
poll_interval_minutes = 20
direct_route = "politics"
enabled = true
[[sources]]
name = "Ars Technica"
feed_url = "https://feeds.arstechnica.com/arstechnica/index"
tier = 1
poll_interval_minutes = 20
direct_route = "tech"
enabled = true
[[sources]]
name = "Time Extension"
feed_url = "https://www.timeextension.com/feeds/news"
tier = 1
poll_interval_minutes = 20
direct_route = "gaming"
enabled = true
[[sources]]
name = "Pitchfork"
feed_url = "https://pitchfork.com/feed/feed-news/rss"
tier = 1
poll_interval_minutes = 60
direct_route = "music"
enabled = true
[[sources]]
name = "The Quietus"
feed_url = "https://thequietus.com/feed"
tier = 1
poll_interval_minutes = 60
direct_route = "music"
enabled = true
[[sources]]
name = "Consequence"
feed_url = "https://consequence.net/feed/"
tier = 1
poll_interval_minutes = 60
direct_route = "music"
enabled = true
[[sources]]
name = "Brooklyn Vegan"
feed_url = "https://www.brooklynvegan.com/feed/"
tier = 1
poll_interval_minutes = 60
direct_route = "music"
enabled = true
[[sources]]
name = "NME — Music"
feed_url = "https://www.nme.com/news/music/feed"
tier = 1
poll_interval_minutes = 60
direct_route = "music"
enabled = true
[[sources]]
name = "BBC Newsround"
feed_url = "http://feeds.bbci.co.uk/newsround/rss.xml"
tier = 1
poll_interval_minutes = 30
direct_route = "kids"
enabled = true
[[sources]]
name = "DOGO News"
feed_url = "https://www.dogonews.com/articles.rss"
tier = 1
poll_interval_minutes = 60
direct_route = "kids"
enabled = true
[[sources]]
name = "Science News Explores"
feed_url = "https://www.snexplores.org/feed"
tier = 1
poll_interval_minutes = 60
direct_route = "kids"
enabled = true
[[sources]]
name = "NASA for Students"
feed_url = "https://www.nasa.gov/rss/dyn/educationnews.rss"
tier = 1
poll_interval_minutes = 60
direct_route = "kids"
enabled = true
[[sources]]
name = "Post-Punk.com"
feed_url = "https://post-punk.com/feed/"
tier = 1
poll_interval_minutes = 60
direct_route = "music"
enabled = true
[[sources]]
name = "Naked Capitalism"
feed_url = "https://www.nakedcapitalism.com/feed"
tier = 1
poll_interval_minutes = 60
direct_route = "finance"
enabled = true
[[sources]]
name = "Wolf Street"
feed_url = "https://wolfstreet.com/feed/"
tier = 1
poll_interval_minutes = 60
direct_route = "finance"
enabled = true
[[sources]]
name = "MarketBeat"
feed_url = "https://www.marketbeat.com/feed/"
tier = 1
poll_interval_minutes = 60
direct_route = "finance"
enabled = true
[[sources]]
name = "Seeking Alpha"
feed_url = "https://seekingalpha.com/feed.xml"
tier = 2
poll_interval_minutes = 60
direct_route = "finance"
enabled = true
[[sources]]
name = "Brickset"
feed_url = "https://brickset.com/feed"
tier = 1
poll_interval_minutes = 60
direct_route = "lego"
enabled = true
[[sources]]
name = "The Brick Fan"
feed_url = "https://www.thebrickfan.com/feed/"
tier = 1
poll_interval_minutes = 60
direct_route = "lego"
enabled = true