Files
Pete/config.example.toml
prosolis c69fbb63db games: a blackjack table you can actually sit down at
The engine, the escrow and the wire were all in place; nothing had a browser on
the end of it. This is that end: a lobby, a table, and the five endpoints between
them.

The browser holds no game. It sends intents and gets back a view — the cards it
is entitled to see, and the script of how they arrived, one event per card off
the shoe. The dealer's hole card is not in the payload at all until the reveal,
because a field the client is told to ignore is a field somebody reads in
devtools. The shoe lives in game_live_hands, which also means a redeploy
mid-hand no longer costs a player their stake: the hand is still there when they
come back.

The money is ordered so nothing can be spent twice. The stake leaves the stack in
the same statement that checks it exists, before a card is dealt. Every new hand
is seated with a plain INSERT, so a double-clicked Deal is decided by the primary
key rather than by a read that raced — it loses, gets its chips back, and the
hand in progress is untouched. A double takes its raise up front and hands it
straight back if the engine refuses the move.

Cards are dealt rather than swapped in — they fly out of the shoe and turn over,
which was a requirement and not a flourish. The faces and the chips are still
plain; that's next.
2026-07-13 23:20:42 -07:00

291 lines
9.1 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)"
# The casino (games.parodia.dev). Signed-in only — there is real money in it — so
# it needs [web.auth] above, and web.auth.cookie_domain if the games host is a
# different subdomain from the news one. Chips are 1:1 with gogobee euros and
# cross the border through the escrow endpoints, which gogobee polls; matrix_server
# is how a player is named to that ledger (@<authentik username>:<matrix_server>).
[web.games]
enabled = false
host = "games.parodia.dev"
matrix_server = "parodia.dev"
# 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