326 lines
17 KiB
Bash
326 lines
17 KiB
Bash
# infra/env/example.env — every knob the infra scripts read, with placeholder
|
|
# values. THIS IS A TEMPLATE, NOT A DEPLOYABLE FILE.
|
|
#
|
|
# Sourced by every infra/*.sh script (as their first argument) and, once
|
|
# 05-deploy.sh has generated /etc/fly/fly.env from it, by the systemd
|
|
# EnvironmentFile= stanzas that need it.
|
|
#
|
|
# The real env files are NOT in this repo. This repo is public and a real env
|
|
# file names the host, the container id, the LAN address, the channel and the
|
|
# `pass` entry. Keep the real ones outside the checkout and pass the path:
|
|
#
|
|
# infra/provision.sh /etc/fly/env/fly-pokemon.env
|
|
# FLY_ENV_DIR=/etc/fly/env infra/05-deploy.sh fly-pokemon.env <tarball>
|
|
#
|
|
# See infra/env/README.md. Every value below is a placeholder except the ones
|
|
# that are genuinely repo-wide decisions (stream geometry, the feature flags,
|
|
# the chat deny-list path) — diff a real env against this file after a bump to
|
|
# spot new knobs.
|
|
#
|
|
# Angle-bracket values (`<release-ctid>`, `<twitch-channel>`, …) are
|
|
# placeholders and every script will refuse them or fail loudly; empty values
|
|
# mean "unset is a valid choice for this knob".
|
|
|
|
# --- where the operator's own values live -----------------------------------
|
|
# Not read by the scripts; recorded here so the next operator knows where to
|
|
# look. The real env files, the release records, the live cpuset numbers, the
|
|
# host notes and the claim-log path live in the operator's infra repo under
|
|
# `services/flybrain/`.
|
|
|
|
# --- host-side locations (read by the scripts, not by systemd) --------------
|
|
# PVE_HOST: ssh target of the Proxmox host, used only by
|
|
# infra/bin/dev-stage-deploy.sh (which runs from the operator box). No
|
|
# default: the script refuses to guess a hostname.
|
|
PVE_HOST=<pve-host-ssh-target>
|
|
# HOST_STAGE_DIR: where host-side artifacts are staged before `pct push`
|
|
# (release tarballs, the NVIDIA .run, the VirtualGL .deb). Defaults to /root
|
|
# in the scripts, because they run as root on the host.
|
|
HOST_STAGE_DIR=
|
|
# AGENT_CLAIM_LOG: the host's serialisation file — one agent at a time, claim
|
|
# the container before touching it, release it when done (CLAUDE.md,
|
|
# infra/README.md "Claim the container ID"). Scripts that would claim a
|
|
# container SKIP the claim when this is unset rather than inventing a path.
|
|
AGENT_CLAIM_LOG=
|
|
# Offsite copy, read by infra/bin/fly-backup-stage (installed on the host by
|
|
# hand — see infra/README.md). BACKUP_HOST has no default and the script
|
|
# refuses to run without it.
|
|
BACKUP_HOST=<backup-user>@<backup-host>
|
|
BACKUP_ROOT=<backup-path>
|
|
FLY_BACKUP_DIR=
|
|
|
|
# --- container identity -----------------------------------------------------
|
|
CTID=<release-ctid>
|
|
HOSTNAME=fly-pokemon
|
|
# The convention these scripts assume is DHCP plus a router-side MAC
|
|
# reservation, not an in-guest static address: this value is what you ASK the
|
|
# router to reserve, not something set inside the container. `dhcp` is a valid
|
|
# sentinel meaning "whatever the lease gives it — check `pct list` / arp".
|
|
IP=<release-ct-ip>
|
|
ONBOOT=1
|
|
STARTUP_ORDER=4
|
|
STARTUP_UP=60
|
|
|
|
# ROLE gates infra/05-deploy.sh (refuses ROLE=release unless the source tree is
|
|
# exactly at a clean annotated vX.Y.Z tag — infra/lib/common.sh's
|
|
# require_release_tag, infra/build/tag-release.sh) and is otherwise
|
|
# informational. "release": nothing experimental runs there, no ad-hoc
|
|
# processes, tagged commits only. "dev": any commit, clean or not. Only those
|
|
# two are valid; load_env defaults to "dev".
|
|
ROLE=release
|
|
|
|
# --- sizing (docs/design/infra.md section 1) --------------------------------
|
|
# CORES must match the width of CPUSET below. If you widen or narrow the
|
|
# cpuset, change this too.
|
|
CORES=8
|
|
MEMORY_MB=8192
|
|
# 0, not the design's 2048: a container swap allocation buys nothing on a host
|
|
# with no swap of its own — there is no backing store for it. Per-unit
|
|
# MemoryMax plus bin/fly-watchdog is then the whole OOM story. Kept as a
|
|
# variable rather than deleted because 01-create-ct.sh passes it to
|
|
# `pct create --swap`.
|
|
SWAP_MB=0
|
|
ROOTFS_GB=24
|
|
STATE_MP_GB=16 # mp0, SSD pool, /srv/fly/state
|
|
MEDIA_MP_GB=600 # mp1, bulk array, /srv/fly/media (ZFS quota)
|
|
|
|
# --- GPU (docs/design/gpu.md) -----------------------------------------------
|
|
# GPU=1 switches on the whole GPU path: 01-create-ct.sh converges the NVIDIA
|
|
# passthrough block and the cpuset into the container's conf, 02-base.sh
|
|
# installs the matching userspace driver in the container, flycast encodes with
|
|
# h264_nvenc, and verify.sh runs its GPU section. GPU=0 makes every one of
|
|
# those steps a clean no-op.
|
|
GPU=0
|
|
|
|
# nvenc | x264. Read by bin/flycast-launch out of /etc/fly/fly.env. nvenc falls
|
|
# back to x264 automatically and once, if the session cannot be opened (the
|
|
# card is shared with another guest — gpu.md section 5), and exports
|
|
# fly_encoder_backend so the degradation is visible. Keep it consistent with
|
|
# GPU above: a container with no passthrough has no NVENC session to open.
|
|
FLY_ENCODER=x264
|
|
|
|
# NVENC preset, p1 (fastest) .. p7 (best). p4 is the starting point; gpu.md
|
|
# section 4 says try p6/p7 once NVENC is in play. Unused while FLY_ENCODER=x264.
|
|
FLY_NVENC_PRESET=p4
|
|
# x264 preset, the FLY_ENCODER=x264 counterpart, written into /etc/fly/fly.env
|
|
# as FLY_X264_PRESET and read by bin/flycast-launch's encoder_x264(), which
|
|
# defaults to veryfast when unset. A faster preset is the cheapest way to hand
|
|
# CPU back to the sim when the box cannot hold real time; it costs quality per
|
|
# bit at the same bitrate, and gpu.md section 4's quality rails were shot at
|
|
# veryfast, so a faster preset means they no longer describe what goes out.
|
|
X264_PRESET=veryfast
|
|
|
|
# The host's driver version. The in-container userspace must match the host
|
|
# kernel module EXACTLY when GPU=1 — see infra/docs/runbook.md "GPU driver
|
|
# version lockstep". 02-base.sh installs
|
|
# ${HOST_STAGE_DIR:-/root}/NVIDIA-Linux-x86_64-$NVIDIA_VERSION.run and verify.sh
|
|
# asserts the container, this value and the host's nvidia-smi all agree.
|
|
NVIDIA_VERSION=<driver-version>
|
|
|
|
# WHOLE physical cores on ONE socket, SMT siblings deliberately excluded.
|
|
# 01-create-ct.sh writes this as `lxc.cgroup2.cpuset.cpus` and 05-deploy.sh
|
|
# partitions it between the units via lib/common.sh's cpuset_partition. Two
|
|
# measured results shape it: the sim must own whole physical cores (a thread
|
|
# landing on an SMT sibling costs about a fifth of real time), and the browser
|
|
# must not share a core with the encoder (the compositor starves). Read
|
|
# `lscpu -e` on your own host to find which cpu numbers are whole cores on one
|
|
# socket; CPUMEMS pins the pages to the socket the threads run on.
|
|
CPUSET=<whole-cores-on-one-socket>
|
|
CPUMEMS=0
|
|
|
|
# RAYON_NUM_THREADS for flysim, written into /etc/fly/fly.env by 05-deploy.sh.
|
|
# More is not better: past the knee, extra threads are the signature of
|
|
# memory-bandwidth saturation and real time gets WORSE. With FLY_LIF_CUDA=1 the
|
|
# sweep and the propagation leave the CPU entirely and this drops to about 2.
|
|
# The measured numbers for a given box are a record, not a constant — they live
|
|
# in the operator's infra repo.
|
|
RAYON_THREADS=3
|
|
# ENCODER_CORES for flycast, written into its AllowedCPUs= drop-in by
|
|
# cpuset_partition (default 2 when unset). flycast gets its own group rather
|
|
# than sharing the page group — see lib/common.sh's cpuset_partition header.
|
|
ENCODER_CORES=2
|
|
|
|
# --- CUDA LIF backend -------------------------------------------------------
|
|
# 1 makes 05-deploy.sh write FLY_LIF_CUDA=1 into /etc/fly/fly.env, which is what
|
|
# flysim reads to attach the GPU LIF backend at startup; 0 (the default for any
|
|
# env file that does not set it) runs the CPU kernel exactly as before.
|
|
#
|
|
# Three things have to be true for this to do anything, and all three are
|
|
# checked rather than assumed:
|
|
#
|
|
# 1. the binary was built with the feature — FLY_CARGO_FEATURES=cuda
|
|
# infra/build/build-flysim.sh. A binary WITHOUT the feature ignores this
|
|
# variable SILENTLY, which is why a deploy record checks flysim's log for
|
|
# the "cuda backend attached" line rather than trusting the env file;
|
|
# 2. GPU=1 above, so the container has /dev/nvidia* and the userspace driver;
|
|
# 3. the card is the one the committed PTX targets — sm_75. Another
|
|
# architecture needs cuda/build-ptx.sh re-run with ARCH=.
|
|
#
|
|
# Bit-exact with the CPU kernel, so it changes neither the kernel version string
|
|
# nor the compatibility string, and a checkpoint moves between a CUDA build and
|
|
# a CPU build in both directions with no migration.
|
|
FLY_LIF_CUDA=0
|
|
|
|
# Chromium flag profile: `default` (the --disable-gpu 2D-canvas build, paper
|
|
# fly), `gpu` (config/chromium-flags.gpu — measured insufficient on its own: on
|
|
# Xvfb it gets no GL at all), or `vgl` (config/chromium-flags.vgl plus
|
|
# `vglrun -d egl0`, gpu.md section 3 option (d)), which is how the page gets
|
|
# hardware WebGL and about a third of a core back.
|
|
#
|
|
# Leave it unset on a release container: `vgl` requires --disable-gpu-sandbox,
|
|
# because VirtualGL's faker cannot open its second X11 connection inside a
|
|
# sandboxed GPU process. The page renderer's sandbox is untouched, but the GPU
|
|
# process loses its namespace sandbox while holding an ioctl handle on a kernel
|
|
# module shared with another guest. Fine on a dev box; a deliberate decision
|
|
# for a 24/7 public stream.
|
|
CHROMIUM_PROFILE=
|
|
|
|
# VirtualGL, required by CHROMIUM_PROFILE=vgl (02-base.sh installs it when GPU=1
|
|
# and this is set). Debian has no virtualgl package, so this is an upstream .deb
|
|
# staged on the host by hand as
|
|
# ${HOST_STAGE_DIR:-/root}/virtualgl_${VIRTUALGL_VERSION}_amd64.deb.
|
|
# VIRTUALGL_DEB_SHA256 is mandatory and 02-base.sh refuses to install anything
|
|
# else. Upstream publishes no checksum file, only a GPG signature inside the
|
|
# .deb's own `_gpgorigin` ar member — verify that against the upstream key
|
|
# before writing a hash here (the procedure is in the VirtualGL spike record).
|
|
VIRTUALGL_VERSION=
|
|
VIRTUALGL_DEB_SHA256=
|
|
|
|
# --- game selection ---------------------------------------------------------
|
|
# FLY_GAME on the flysim unit is set from this. pokemon-red | platformer. The
|
|
# GAME id folds into the flysim compatibility string, so a checkpoint from one
|
|
# game is rejected by a run of the other.
|
|
GAME=pokemon-red
|
|
# The ROM itself is never in git, never on the host, never on stream, never
|
|
# linked. This is only its sha256, which is what flysim checks the file it
|
|
# loads against (FLY_ROM/FLY_ROM_SHA256 in /etc/fly/fly.env, written by
|
|
# 05-deploy.sh from this value). Stage the ROM in the container as
|
|
# /srv/fly/rom/<sha>.gb, 0400 fly:fly.
|
|
#
|
|
# Leaving this EMPTY is a trap worth knowing about: 05-deploy.sh then writes no
|
|
# FLY_ROM at all, flysim falls back to its own default path
|
|
# (/srv/fly/rom/pokemon-red.gb, which no script here creates), and systemd walks
|
|
# the unit into the start limiter — but only on the first flysim RESTART after a
|
|
# deploy regenerates fly.env, which is the worst possible time to find it.
|
|
ROM_SHA256=
|
|
REWARD_ADAPTER=fly-pokered-rstdp-v1
|
|
# Sustained button holds vs menu-driven pacing is a reviewed change, not a
|
|
# config tweak (docs/design/infra.md section 6). Keep the name here so the env
|
|
# file is the single place that selects it. default | platformer.
|
|
DECODER_PRESET=default
|
|
|
|
# --- twitch -----------------------------------------------------------------
|
|
# Empty is valid and means "this container never pushes anywhere outside
|
|
# itself" — the shape a throwaway dev box wants.
|
|
TWITCH_CHANNEL=<twitch-channel>
|
|
# The `pass` entry holding the stream key. Only the NAME is here; the secret
|
|
# never is. 06-secrets.sh reads it on the operator box and installs it as a
|
|
# systemd credential.
|
|
PASS_KEY=twitch/<channel>-key
|
|
|
|
# --- flybridge (services/bridge/src/config.ts) ------------------------------
|
|
# 05-deploy.sh writes these into /etc/fly/fly.env, which flybridge.service reads
|
|
# through EnvironmentFile=. None of them is a secret: the Twitch app id/secret
|
|
# arrive as the systemd credential twitch-app (06-secrets.sh) and the OAuth
|
|
# tokens as /var/lib/flybridge/tokens.json, mode 0600.
|
|
#
|
|
# The bot and the broadcaster may be the SAME account — supported
|
|
# (services/bridge/src/auth.ts builds one auth provider per role precisely so a
|
|
# single account holding both roles cannot lose one of its two tokens) — but
|
|
# then the fly's own chat replies come from the channel's own name. Give the bot
|
|
# its own account and change this one line.
|
|
TWITCH_BOT_USER=<twitch-bot-user>
|
|
|
|
# The only game-specific word in flybridge's config: substituted into every chat
|
|
# template (docs/stream-mvp-plan.md, "keep game words out of code"). ASCII on
|
|
# purpose, not an accented spelling: the sanitizers on both sides would pass it,
|
|
# but the on-screen CHAT panel is set in a pixel face with no guarantee of a
|
|
# precomposed accented glyph, and an unrenderable glyph on the broadcast is
|
|
# worse than a missing accent. It is NOT the ROM's file name.
|
|
GAME_TITLE="Pokemon Red"
|
|
|
|
# Channel Points and Predictions are Affiliate-gated by Twitch. With
|
|
# FEATURE_REDEMPTIONS=1 on a non-Affiliate channel the API answers 403; the
|
|
# bridge degrades that one specific 403 to "redemptions disabled" and keeps
|
|
# chat, follows and raids running
|
|
# (src/redemptions.ts's isChannelPointsAffiliateRefusal), so flipping this to 1
|
|
# early is safe — it logs and carries on until the channel qualifies.
|
|
FEATURE_REDEMPTIONS=0
|
|
# Predictions stay OFF and their scope stays unused: src/predictions.ts is a
|
|
# deliberate stub.
|
|
FEATURE_PREDICTIONS=0
|
|
# The bridge end of the on-screen CHAT panel. FLY_CHAT_ENABLED below is the
|
|
# other end (flysim's), and the one to reach for in a hurry — it needs no Twitch
|
|
# credentials to take effect.
|
|
FEATURE_ONSCREEN_CHAT=1
|
|
# Quiet mode: the bridge speaks only when spoken to. No startup or recovery
|
|
# notice, no explainer rotation, no follow/raid thanks; commands and the
|
|
# on-screen CHAT panel are unaffected.
|
|
FEATURE_QUIET=1
|
|
|
|
# --- EventSub self-healing (services/bridge/src/subscription-health.ts) -----
|
|
# A connected websocket is not a working subscription: both EventSub sockets can
|
|
# drop, Twitch can refuse the re-created channel.chat.message subscription with
|
|
# "number of websocket transports limit exceeded" (429), and the bridge then
|
|
# stays `active (running)` with NO chat subscription. It exits 75 when that
|
|
# subscription has been unconfirmed for this long, and flybridge.service
|
|
# restarts it 15 s later with a fresh transport. 60 s is generous: a healthy
|
|
# reconnect confirms in well under a second. Lower it and a slow reconnect
|
|
# becomes a needless restart; raise it and the channel stays dead longer.
|
|
EVENTSUB_GRACE_MS=60000
|
|
# At most one startup-or-recovery notice in chat per this window, counted across
|
|
# process restarts, so a bad half hour on Twitch's side cannot turn the
|
|
# self-healing restarts into chat spam.
|
|
NOTICE_MIN_INTERVAL_MS=600000
|
|
|
|
# --- stream output ----------------------------------------------------------
|
|
# 1920x1080 / 6000 kbps is the current decision (docs/streaming-plan.md section
|
|
# 3's dated note). Not yet consumed by infra/units/xvfb.service,
|
|
# infra/config/chromium-flags or infra/units/flycast.service, which still
|
|
# hardcode these same numbers directly (05-deploy.sh does not template them into
|
|
# /etc/fly/fly.env yet) — this is the single documented source of truth for the
|
|
# three numbers until that wiring lands.
|
|
STREAM_WIDTH=1920
|
|
STREAM_HEIGHT=1080
|
|
STREAM_KBPS=6000
|
|
|
|
# --- on-screen chat (docs/control-api.md [chat]) ----------------------------
|
|
# The persistent CHAT panel in the right rail. 1 = on, 0 = the kill switch:
|
|
# POST /chat answers 403 and the feed header omits `chat`, so the panel blanks
|
|
# without touching flybridge or the page.
|
|
#
|
|
# Chat never reaches the simulation. It is sanitized by flybridge, sanitized
|
|
# AGAIN by flysim, deny-list filtered and rate-limited there, and carried in a
|
|
# 12-line ring in the feed header.
|
|
FLY_CHAT_ENABLED=1
|
|
# Operator-maintained deny list, one pattern per line. 05-deploy.sh installs
|
|
# infra/config/chat-deny.txt here ONCE and never overwrites it; edit it in the
|
|
# container and run `systemctl kill -s HUP flysim` to re-read it (flysim also
|
|
# re-reads it once a minute by itself).
|
|
CHAT_DENY_LIST=/srv/fly/chat-deny.txt
|
|
|
|
# --- macro buttons ----------------------------------------------------------
|
|
# What the fly's channels mean (docs/design/macros.md section 12,
|
|
# docs/control-api.md).
|
|
# raw: the population decoder's button mask goes to the emulator.
|
|
# macros: the scene's macros are buttons of their own. Every macro type has its
|
|
# own neuron population (macro_<type>, mushroom body output neurons plus
|
|
# brain motor neurons) and its own channel in the readout's second
|
|
# exclusive group; the scene decides which of those channels exist and
|
|
# the decoder picks among exactly those, on the direction group's own
|
|
# hold, hysteresis and fatigue. The eight real buttons are decoded as
|
|
# ever, and while a macro runs it owns the pad.
|
|
# "palette" and "plan" are the two modes section 12 replaced; flysim still reads
|
|
# either as "macros", with a warning, for one release.
|
|
FLY_MACRO_MODE=raw
|
|
# How long a macro leaves a target alone after a walk to it aborted "blocked" or
|
|
# "timeout" (macros.md section 12.1). Session state, so a restart offers every
|
|
# target once more. Unset means the default, 10.
|
|
# FLY_MACRO_BLOCKED_MINUTES=10
|
|
|
|
# --- push mode --------------------------------------------------------------
|
|
# local: flypush.service stays disabled, everything else identical to prod.
|
|
# twitch: flypush.service is enabled by 07-enable.sh.
|
|
#
|
|
# Never go live without the operator's explicit approval for that run
|
|
# (CLAUDE.md). `local` is the safe default and local MediaMTX demos are fine.
|
|
PUSH_TARGET=local
|