github robert-dean/deadair v0.13.0
deadair 0.13.0

latest releases: v0.89.6, v0.89.5, v0.89.4...
one month ago
  • A break the model nearly got right is now put back to it once, rather than going straight to the station's fallback phrasings. When a script is refused for something the model can act on — it did not sound like the character, it used wording the persona forbids, it reached for a signature just used, it read a sample line back, it named the wrong part of the day, or it answered with nothing — the same writer is asked again, told which rule it broke and shown what was refused. Refusals about the records themselves (a break about neither of them, a record announced on the wrong side, an invented year) are not retried: those mean the model misread what it was given, and asking again invites it to invent something that fits. The second ask waits only as long as the break's own deadline allows, so it can never make the station late, and both attempts are recorded, so the refusal and the rewrite are both in the history.
  • The audio chain's log level is a station setting. stream.logLevel is Liquidsoap's own 1-5, offered as a named menu ("Normal", "Debug — for diagnosing a fault") and materialized into radio.env as LOG_LEVEL, so the config watch restarts Liquidsoap with it like any other stream setting. It was reachable only as a container variable before, which on unraid means editing the template and which nothing in the console could show — and on 2026-09-16 the diagnosis of an audio chain that took a record and never resolved it ended at "raise the level and wait", because the layer that stopped only logs at 4. Two things the help text says because they are not guessable: a restart CLEARS a stuck chain, so this is for leaving on and waiting for the next occurrence rather than for turning up during one; and at 4 and above the log holds the playout bridge secret in plain text, which is why reading logs is platform.manage. The resolver reads it through numberFrom rather than numberOr, because Number('') is 0 and finite, so a blanked box would have resolved to zero and clamped to 1 — a station that quietly stopped reporting its own faults.
  • The production image rotates the audio chain's and track shim's logs. Neither was bounded by anything: Liquidsoap has no rotation setting at any version and the shim's supervisor holds one append redirection across every restart, so both grew for as long as the station ran. Measured on the live station, liquidsoap.log was 15.5 MB after thirteen days at the default log level, on the same volume as the database, and already past the 8 MiB a download reads — so more than half of it could not be reached from the console at all. A new log-rotate service runs logrotate on a timer with copytruncate, which is required rather than preferred: neither writer reopens its log, so a rotation that renamed the file would leave both appending to an inode with no name and the console showing a log frozen at the moment of the rotation. su deadair deadair is what lets it work on an unraid share made with the usual 777, and the service takes any existing log to 99:100 first because su then refuses a file that user does not own. The segments stay uncompressed with logrotate's .1 naming because the app reads them back as one history. LOG_ROTATE_INTERVAL_S (300, 0 disables), STREAM_LOG_MAX_SIZE (8M) and STREAM_LOG_KEEP (3).
  • The audio chain and track shim logs are read back across their rotated segments rather than only out of the active file. Nothing rotated them before, so the reader knew about one file; the image rotates them now, and a reader that still knew about one would show an operator LESS after rotation than before, and almost nothing in the minutes after a rotation — which is exactly when somebody is looking. readSegmentedTail walks name.log, name.log.1, name.log.2 … newest first until the byte budget runs out, so the console's view is the newest 512 KiB (a tail) or 8 MiB (a download) of retained history wherever it happens to live, which is what the unbounded file used to give it. measureSegments sums the set for the size beside the source, because an operator told "8 MB" next to 32 MB of disk is being told the wrong thing about their own storage. The segments must stay uncompressed for this to hold, and the rotation config says so from the other side.

Don't miss a new deadair release

NewReleases is sending notifications on new releases.