Skip to content

feat: replace JSON config with .env format and auto-generate API token#22

Merged
AlexanderWagnerDev merged 10 commits into
mainfrom
claude/auto-api-token-env-config-kzbpmb
Jun 30, 2026
Merged

feat: replace JSON config with .env format and auto-generate API token#22
AlexanderWagnerDev merged 10 commits into
mainfrom
claude/auto-api-token-env-config-kzbpmb

Conversation

@AlexanderWagnerDev

@AlexanderWagnerDev AlexanderWagnerDev commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Config format changed from JSON to .env: config.example.json is replaced by config.example.env; the server now looks for config.env by default (instead of config.json). The new format uses flat KEY=VALUE pairs, comments (#), and optional quotes.
  • API token is now auto-generated: On first startup the server generates a cryptographically secure token (128-bit OS entropy via keygen_secret), prints it to stderr, and writes it back as API_TOKEN=... into the config file so it persists across restarts. The -t CLI flag and LRTMP2_API_TOKEN environment override are removed — users can no longer set the token manually.
  • docker-compose.yml updated: The LRTMP2_API_TOKEN environment variable requirement is removed since the token is now managed automatically.

Changes

File Change
src/config.rs Replace serde_json JSON parser with a simple .env line parser; add config_write_token() to persist the generated token in-place
src/main.rs Remove -t flag; auto-generate token on startup if api_token is empty
config.example.env New example config in .env format (no API_TOKEN entry — generated automatically)
config.example.json Deleted
docker-compose.yml Remove LRTMP2_API_TOKEN requirement

Test plan

  • cargo test — all 22 tests pass (6 new tests cover .env parsing and config_write_token)
  • Fresh start with no config.env: server generates and prints token, writes API_TOKEN=... to the file
  • Subsequent start with existing config.env containing API_TOKEN: server reuses stored token without regenerating
  • Existing non-token config entries (e.g. HTTP_BIND) are preserved when the token is written back

Generated by Claude Code

Summary by CodeRabbit

  • New Features
    • Switched to .env-style configuration by default (config.env), with environment and CLI overrides still supported.
    • API token is now persisted in the database and auto-generated on first startup (printed once).
  • Bug Fixes
    • Improved config parsing: KEY=VALUE, supports quoted values (including =), and skips blank lines/comments.
    • Values provided for the API token in the config file are ignored in favor of the stored token.
  • Chores
    • Updated example configuration and compose setup; removed the dedicated API-token CLI option and compose token injection.

The API token is now generated automatically using the OS RNG on first
startup and written back into the config file, so it persists across
restarts without any manual step. Users can no longer set the token
manually: the -t CLI flag and LRTMP2_API_TOKEN env override are removed.

Config files change from JSON to a flat KEY=VALUE (.env) format.
config.example.json is replaced by config.example.env; the default
file the server looks for is config.env instead of config.json.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XxGDerSY7BbB6SEKVaVCK5
Copilot AI review requested due to automatic review settings June 30, 2026 10:57

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Switches config loading from JSON to .env parsing, stores the API token in the database, and changes startup to load or generate that token automatically. The default config path and CLI options are updated, and example/config files plus tests are refreshed.

Changes

Config migration and API token lifecycle

Layer / File(s) Summary
.env parsing and config loading
src/config.rs, config.example.env, docker-compose.yml
Switches the config module to .env parsing, maps recognized keys into ServerConfig, updates config tests, adds the example env file, and stops passing LRTMP2_API_TOKEN in compose.
Token storage in settings table
src/db.rs
Adds a settings table and public token read/write helpers backed by that table.
Startup token loading and generation
src/main.rs, src/server.rs
Removes the CLI API token option, changes the default config path, and loads or generates the API token during server creation.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐇 Hop, hop—now dotenv leads the way,
The token blooms from the DB today.
No JSON jars, no CLI token spell,
Just config, cache, and startup working well.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the two main changes: switching config to .env format and auto-generating the API token.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/auto-api-token-env-config-kzbpmb

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/config.rs`:
- Around line 74-104: The config parsing in the `load_env`/match block silently
ignores malformed values for known keys like `TLS_ENABLED`,
`REQUIRE_STREAM_KEY`, and `LOG_LEVEL`. Update this parsing logic to explicitly
warn or return an error when a recognized key has an invalid value instead of
falling back to defaults. Use the existing `config` assignment branches and the
`val.parse::<i32>()` / boolean match arms to detect bad inputs and surface them
clearly.
- Line 169: The config file write path currently uses std::fs::write without
tightening permissions, so newly created token-bearing files may be
world-readable. Update the config-writing logic in the config save/write flow to
create files with owner-only permissions on Unix (read/write for the owner only)
and add the corresponding Windows ACL handling or documented fallback in the
same config persistence path. Use the existing config write site around the
write-and-map_err call so the restrictive permissions are applied whenever
config.env is created or overwritten.
- Line 142: The config loading in the existing file read path is masking read
failures by falling back to an empty string, which can lead to accidental
overwrites later in the config flow. In `src/config.rs`, update the logic around
`std::fs::read_to_string` so unreadable configs are handled as an error instead
of being treated as empty content, and make sure the code that writes back the
config only runs when the file was successfully read. Use the surrounding config
load/update path to preserve all existing entries and avoid rewriting from
partial state.
- Around line 60-63: Guard the quote-stripping logic in the config parsing path
so one-character values like KEY=" or KEY=' do not reach the slice in the
quote-handling branch. Update the logic around the val normalization in
src/config.rs to check that the string is at least 2 characters long before
removing surrounding quotes, and keep the existing behavior for properly wrapped
values while leaving malformed inputs to fail cleanly instead of panicking.

In `@src/main.rs`:
- Around line 72-77: The token generation path in main currently writes the raw
bearer token to stderr, which can end up in captured process logs. Update the
token-printing logic in main to avoid emitting the token value directly; instead
only show the config path and brief usage instructions, or gate any raw token
reveal behind an explicit one-time interactive action.
- Around line 67-78: The token bootstrap in config_load/main currently only
regenerates when config.api_token is empty, so placeholder or stale values still
get trusted. Update the startup validation around config.api_token to reject
non-empty tokens that are clearly invalid (or trigger the same auto-generation
flow), and make the error message tell users to remove the API_TOKEN line from
the config if they want automatic generation. Use the existing config_load and
config_write_token flow, and keep the check in the branch that currently handles
empty tokens.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ea91af36-4161-4ceb-8fa9-659394f786ce

📥 Commits

Reviewing files that changed from the base of the PR and between 778acfd and b257494.

📒 Files selected for processing (5)
  • config.example.env
  • config.example.json
  • docker-compose.yml
  • src/config.rs
  • src/main.rs
💤 Files with no reviewable changes (2)
  • docker-compose.yml
  • config.example.json

Comment thread src/config.rs Outdated
Comment thread src/config.rs Outdated
Comment thread src/config.rs Outdated
pub fn config_write_token(path: &str, token: &str) -> Result<(), String> {
let line = format!("API_TOKEN={token}");

let existing = std::fs::read_to_string(path).unwrap_or_default();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Don’t treat unreadable configs as empty files.

unwrap_or_default() masks every read failure; a transient read error can cause Line 169 to rewrite the file with only API_TOKEN, dropping the rest of the config.

Proposed fix
-    let existing = std::fs::read_to_string(path).unwrap_or_default();
+    let existing = match std::fs::read_to_string(path) {
+        Ok(existing) => existing,
+        Err(e) if e.kind() == std::io::ErrorKind::NotFound => String::new(),
+        Err(e) => return Err(format!("Cannot read config file {path}: {e}")),
+    };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let existing = std::fs::read_to_string(path).unwrap_or_default();
let existing = match std::fs::read_to_string(path) {
Ok(existing) => existing,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => String::new(),
Err(e) => return Err(format!("Cannot read config file {path}: {e}")),
};
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/config.rs` at line 142, The config loading in the existing file read path
is masking read failures by falling back to an empty string, which can lead to
accidental overwrites later in the config flow. In `src/config.rs`, update the
logic around `std::fs::read_to_string` so unreadable configs are handled as an
error instead of being treated as empty content, and make sure the code that
writes back the config only runs when the file was successfully read. Use the
surrounding config load/update path to preserve all existing entries and avoid
rewriting from partial state.

Comment thread src/config.rs Outdated
out.push('\n');
}

std::fs::write(path, out).map_err(|e| format!("Cannot write config file {path}: {e}"))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Create token-bearing config files with restrictive permissions.

This path can create config.env on first startup and now writes a bearer token. Ensure newly created files are owner-readable/writable only on Unix, and document/handle equivalent Windows ACL behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/config.rs` at line 169, The config file write path currently uses
std::fs::write without tightening permissions, so newly created token-bearing
files may be world-readable. Update the config-writing logic in the config
save/write flow to create files with owner-only permissions on Unix (read/write
for the owner only) and add the corresponding Windows ACL handling or documented
fallback in the same config persistence path. Use the existing config write site
around the write-and-map_err call so the restrictive permissions are applied
whenever config.env is created or overwritten.

Comment thread src/main.rs Outdated
Comment on lines +67 to +78
if config.api_token.is_empty() {
let token = keygen::keygen_secret("tk_")?;
let cfg_path = &config.config_file;
config_write_token(cfg_path, &token)
.map_err(|e| format!("Could not persist API token: {e}"))?;
eprintln!(
"============================================================\n\
Generated API token (saved to {cfg_path}):\n\
{token}\n\
============================================================"
);
config.api_token = token;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Keep validating loaded tokens before trusting them.

config_load still accepts any API_TOKEN, and this branch only regenerates when it is empty. A stale short/placeholder token will now start successfully. Restore a usability check for non-empty configured tokens, or reject them with guidance to remove the line for auto-generation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main.rs` around lines 67 - 78, The token bootstrap in config_load/main
currently only regenerates when config.api_token is empty, so placeholder or
stale values still get trusted. Update the startup validation around
config.api_token to reject non-empty tokens that are clearly invalid (or trigger
the same auto-generation flow), and make the error message tell users to remove
the API_TOKEN line from the config if they want automatic generation. Use the
existing config_load and config_write_token flow, and keep the check in the
branch that currently handles empty tokens.

Comment thread src/main.rs Outdated
claude added 2 commits June 30, 2026 11:08
The token can no longer be set via config file or environment variable.
On first startup it is generated (128-bit OS entropy) and persisted in a
new `settings` table in the SQLite database; on subsequent starts it is
read back from there. This removes config_write_token and all API_TOKEN
handling from config.rs and main.rs.

Also fixes CodeRabbit findings:
- Quote-stripping guard: val.len() >= 2 check prevents panic on KEY="
- Warn on invalid values for TLS_ENABLED, LOG_LEVEL, etc. instead of
  silently falling back to defaults

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XxGDerSY7BbB6SEKVaVCK5
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XxGDerSY7BbB6SEKVaVCK5

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/db.rs`:
- Around line 188-198: token_get currently hides SQLite read failures by
converting them into None, which causes ServerApp::create to treat DB errors as
missing tokens and regenerate one. Update token_get in the db module to return a
Result<Option<String>> instead of swallowing errors, and adjust
ServerApp::create to regenerate/persist a bearer token only when token_get
returns Ok(None), while propagating any Err from the read path.
- Around line 200-207: Avoid overwriting an already bootstrapped API token in
token_set; the current INSERT ... ON CONFLICT DO UPDATE path can replace the
winning token and leave the loser using an unpersisted value. Update the token
persistence flow in src/db.rs so it behaves like get-or-create: check for an
existing api_token first, only insert when missing, and if another process wins
the race, read back the stored token and use that instead of keeping the locally
generated one.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 217e9067-5619-415a-98a1-94b4d503f966

📥 Commits

Reviewing files that changed from the base of the PR and between b257494 and cf687d5.

📒 Files selected for processing (4)
  • src/config.rs
  • src/db.rs
  • src/main.rs
  • src/server.rs

Comment thread src/db.rs Outdated
Comment thread src/db.rs Outdated
claude added 2 commits June 30, 2026 11:15
- token_get now returns Result<Option<String>> so DB read errors are
  propagated instead of silently becoming "no token found"
- token_set uses INSERT OR IGNORE instead of ON CONFLICT DO UPDATE to
  avoid overwriting a concurrently bootstrapped token; callers read back
  the winner's token when their insert was ignored
- Add docstrings to token_get and token_set for coverage threshold

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XxGDerSY7BbB6SEKVaVCK5

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/db.rs (1)

88-91: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Allow bootstrapping to repair an empty api_token row.

token_get() treats val='' as missing, but INSERT OR IGNORE refuses to write when the api_token row already exists. A DB containing settings(key='api_token', val='') will fail startup permanently instead of generating a token.

🐛 Proposed fix
-        let rows = conn
-            .execute(
-                "INSERT OR IGNORE INTO settings(key,val) VALUES('api_token',?)",
-                rusqlite::params![token],
-            )
+        let rows = conn
+            .execute(
+                "INSERT INTO settings(key,val) VALUES('api_token',?)
+                 ON CONFLICT(key) DO UPDATE SET val=excluded.val
+                 WHERE settings.val=''",
+                rusqlite::params![token],
+            )
             .map_err(|e| format!("DB error persisting API token: {e}"))?;

Also applies to: 200-215

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/db.rs` around lines 88 - 91, The settings bootstrap path currently treats
api_token with an empty val as missing, but the existing INSERT OR IGNORE path
never updates that row, so startup can get stuck forever. Update the
bootstrapping logic around token_get() and the settings table handling in
src/db.rs so that an existing api_token row with val='' is repaired by writing a
newly generated token instead of being ignored. Ensure the fix targets the
api_token key specifically and replaces the empty value on startup.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/db.rs`:
- Around line 88-91: The settings bootstrap path currently treats api_token with
an empty val as missing, but the existing INSERT OR IGNORE path never updates
that row, so startup can get stuck forever. Update the bootstrapping logic
around token_get() and the settings table handling in src/db.rs so that an
existing api_token row with val='' is repaired by writing a newly generated
token instead of being ignored. Ensure the fix targets the api_token key
specifically and replaces the empty value on startup.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b6ea7536-fe53-44e3-83a0-6d4a287072a8

📥 Commits

Reviewing files that changed from the base of the PR and between fe7216f and 08a381b.

📒 Files selected for processing (2)
  • src/db.rs
  • src/server.rs

claude added 3 commits June 30, 2026 11:18
INSERT OR IGNORE would permanently skip generation if the settings table
already had api_token='' (e.g. after a corrupted write). Switch to
ON CONFLICT DO UPDATE WHERE val='' so an empty row is overwritten with a
freshly generated token while a real token is still never overwritten.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XxGDerSY7BbB6SEKVaVCK5
…onfig

These three settings had no business being user-configurable:
- WEB_ROOT: there is no configurable web UI path
- REQUIRE_STREAM_KEY: stream keys are always required
- RTMP_CHUNK_SIZE: protocol internal, no reason for operators to tune this

Removed from ServerConfig, Default, apply_kv, and config.example.env.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XxGDerSY7BbB6SEKVaVCK5
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XxGDerSY7BbB6SEKVaVCK5
@AlexanderWagnerDev AlexanderWagnerDev merged commit 31c8e3e into main Jun 30, 2026
2 checks passed
@AlexanderWagnerDev AlexanderWagnerDev deleted the claude/auto-api-token-env-config-kzbpmb branch June 30, 2026 11:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants