v0.7.1
v0.7.1 (2026-07-18)
This release is published under the MIT License.
Bug Fixes
- Apply config changes reliably and keep quotas across restarts (
ddede03)
Editing configuration through the UI did not take effect. Three separate faults, each of which alone was enough to break it:
- Docker could not save at all. Nacho writes config by renaming a temporary file over the target, and that rename fails with EBUSY against a bind-mounted file:
StorageError: Cannot save /app/config.yaml: [Errno 16] Resource busy: '/app/.config.yaml.qk9ed65s.tmp' -> '/app/config.yaml'
The documented compose file also mounted it read-only. Mount the directory, read-write, and run as a user that can write it.
-
The image shipped --no-reload, which makes trigger_reload a no-op. Restarting is how a config change is applied, so this stranded every edit until the container was restarted by hand.
-
Restarting reset the state that enforces quotas. Rate-limit windows and key cool-downs live only in memory, so each edit handed out a fresh burst allowance and released every quarantined key — also true of any ordinary deploy or crash.
Persist limiter windows and cool-downs across restarts. Limiters are rebuilt lazily so the rate always comes from the current configuration and only the consumed window carries over; a limit raised while the process was down therefore applies immediately. Entries are keyed by a hash of the limiter name because that name embeds the upstream credential and client IPs, and the file is written 0600 via a temporary file and rename. A missing, corrupt, or version-mismatched file is ignored: cold counters are a degradation, refusing to start is an outage.
Also debounce reload triggers. A single save emits several change events and every restart drops in-flight requests, including streaming responses.
The state file lives beside the configuration, taken from the config manager rather than CONFIG_PATH — the path can arrive as a constructor argument, and falling back to the working directory made two instances started from one directory share quotas. That bug surfaced as e2e tests leaking rate-limit state between runs.
Not persisted: queued requests. Each owns an asyncio.Future tied to a client socket that does not survive the restart, so replaying one would spend upstream quota on a response nobody is waiting for. Shutdown already fails them so clients can retry.
Verified end to end in Docker: a UI save now returns 200, persists to the host, triggers "WatchFiles detected changes in 'watch.txt'. Reloading...", and writes .nya_state.json on the way out. 28 new tests, checked by mutation: disabling restoration fails 4 of them, and removing the name hashing fails the credential-leak test.
Documentation
- Document the data directory mount for Docker (
15e37cf)
The documented docker run reproduced every fault that made config edits fail in containers: it bind-mounted config.yaml as a single file, mounted it read-only, and passed --no-reload.
Mount the directory instead. Saving from the /config UI writes a temporary file and renames it over the target, which fails with EBUSY against a bind-mounted file, so every save returned a 500 before the read-only flag was even reached. Add --user, because the image runs as uid 100 and cannot otherwise write a directory the operator owns, and note that Docker Desktop maps ownership itself. Drop --no-reload, which stranded any edit that did manage to save.
Also describe what the directory now holds: config.yaml plus .nya_state.json, which carries rate-limit windows and key cool-downs across the restart that applies a configuration change.
Applied to the Chinese and Japanese READMEs as well, since all three carried the same command.
Verified by running the documented command verbatim against a build of this tree: the container starts, a UI save returns 200 and persists to ./data on the host, the reload fires ("WatchFiles detected changes in 'watch.txt'"), .nya_state.json is written 0600 beside the config, and the service stays healthy afterwards.
- Show key_blocking in the provider example configs (
b02fc8d)
The credential quarantine setting shipped with the starter config in nya/config.yaml and is listed in the README feature table, but none of the provider examples mentioned it, so anyone copying one as a starting point had no sign the option exists.
Add the block to the openai, gemini, and novelai examples, mirroring the starter config's values (disabled, 403, 300s) rather than asserting per-provider status codes. Left disabled deliberately: quarantining on a wrongly chosen status code removes keys from rotation, which should be an explicit choice.
Verified: all five configs validate against nya/schema.json and load through ConfigManager, which reports key_blocking for every API.
Detailed Changes: v0.7.0...v0.7.1