Skip to content

feat: support MOSTRO_NSEC_PRIVKEY env var for Nostr private key#713

Merged
grunch merged 6 commits into
mainfrom
feat/nsec-env-var
May 13, 2026
Merged

feat: support MOSTRO_NSEC_PRIVKEY env var for Nostr private key#713
grunch merged 6 commits into
mainfrom
feat/nsec-env-var

Conversation

@AndreaDiazCorreia
Copy link
Copy Markdown
Member

@AndreaDiazCorreia AndreaDiazCorreia commented Apr 24, 2026

Motivation

Storing the Nostr private key inline in settings.toml makes it easy to leak it accidentally: config files end up in logs, backups, bug reports, and chat messages when operators ask for help. It also makes integration with Docker secrets, systemd credentials, and similar tools harder than it should be.

This PR lets operators provide the nsec via the MOSTRO_NSEC_PRIVKEY environment variable, which is the dominant pattern across Nostr daemons (Alby Hub, NWC, OpenClaw) and follows 12-factor conventions.

Changes

  • Add MOSTRO_NSEC_PRIVKEY env var support. Precedence: env var >
    <settings_dir>/.env (auto-loaded via dotenvy) > settings.toml.
  • Make nsec_privkey optional in settings.toml via #[serde(default)], so
    operators relying on the env var can omit the field entirely.
  • Update the setup wizard to ask where to store the generated key: either in
    ~/.mostro/.env (recommended, chmod 600) or inline in settings.toml.
  • Documentation updated with examples for shell, systemd, and Docker.
  • Unit tests for all precedence cases (env wins, empty env falls back,
    whitespace-only env ignored, no env keeps TOML, trimming, TOML without the
    field).

Backward compatibility

Fully backward-compatible. Existing nsec_privkey = 'nsec1...' in settings.toml keeps working exactly as before. The env var only takes effect when explicitly set.

Summary by CodeRabbit

  • New Features

    • Nostr private key can be provided via the MOSTRO_NSEC_PRIVKEY environment variable.
    • App auto-loads a local .env file and applies an environment-variable override with defined precedence.
    • Configuration wizard now offers choosing to store the private key in a .env file (recommended) or inline in settings.
  • Documentation

    • Updated guides with environment variable usage, trimming/blank-value behavior, and precedence order.

Add MOSTRO_NSEC_PRIVKEY environment variable to allow storing the Nostr private key separately from settings.toml. The variable can be set via real environment, ~/.mostro/.env (auto-loaded at startup), or systemd/Docker configuration.

Changes:
- Add dotenvy dependency for .env file loading
- Implement load_env_file() to auto-load <settings_dir>/.env at startup
- Implement apply_nsec_env_override() to read MOSTRO_NSEC_PRIVKEY and override
…ent variable

- Mark NostrSettings.nsec_privkey as optional with #[serde(default)]
- Remove "I'll configure MOSTRO_NSEC_PRIVKEY myself" option from setup wizard
- Export MOSTRO_NSEC_PRIVKEY to current process when saving to .env so daemon can use it immediately without restart
- Add test verifying settings.toml parses without nsec_privkey field when relying on environment variable
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 24, 2026

Warning

Rate limit exceeded

@AndreaDiazCorreia has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 44 minutes and 57 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 43cffc24-a6a5-4db5-8c80-61c4aae7a2c7

📥 Commits

Reviewing files that changed from the base of the PR and between 74db2f9 and 042b981.

📒 Files selected for processing (3)
  • src/config/constants.rs
  • src/config/util.rs
  • src/config/wizard.rs

Walkthrough

Adds optional nsec_privkey support via a MOSTRO_NSEC_PRIVKEY env var and optional <settings_dir>/.env file, loads .env on startup, makes nsec_privkey optional in config, updates the setup wizard to let users persist the key to .env or inline, and adds tests and the dotenvy dependency.

Changes

Nostr nsec env/.env support

Layer / File(s) Summary
Config shape and docs
src/config/types.rs, README.md, docs/STARTUP_AND_CONFIG.md
Make NostrSettings.nsec_privkey deserializable when missing via #[serde(default)]; update README and STARTUP docs to document MOSTRO_NSEC_PRIVKEY, .env loading, whitespace handling, and precedence (env > .env > settings.toml).
Process env & .env loading
src/config/util.rs
Add optional loader for <settings_dir>/.env via dotenvy::from_path (non-fatal on failure) and helper to apply MOSTRO_NSEC_PRIVKEY (trim, ignore empty/whitespace). Load .env before settings creation/parsing and apply override in both "settings missing" and "settings present" flows.
Validation & tests
src/config/util.rs (tests)
Introduce serialized-test mutex, an RAII EnvVarGuard, make_settings helper, and unit tests verifying precedence, trimming, blank-ignore behavior, and TOML parsing when nsec_privkey is omitted.
Interactive setup / persistence
src/config/wizard.rs
Change wizard to accept settings_dir; add prompt_nsec_storage(settings_dir, nsec) prompting to store nsec in <settings_dir>/.env or inline. If .env chosen, write MOSTRO_NSEC_PRIVKEY=<nsec> to file (with 0o600 perms), set process env var, and return empty string for inclusion in settings.toml. Add write_env_file helper that tightens Unix permissions.
Dependency
Cargo.toml
Add dotenvy = "0.15.7" to enable .env file loading.
sequenceDiagram
  participant Wizard as Wizard (setup)
  participant Process as Process Env
  participant FS as Filesystem (<settings_dir>/.env)
  participant Config as Config Loader (init_configuration_file)
  participant Settings as settings.toml

  Wizard->>FS: optionally write MOSTRO_NSEC_PRIVKEY=<nsec> to .env (write_env_file)
  Wizard->>Process: set MOSTRO_NSEC_PRIVKEY in-process
  Config->>FS: load optional .env (dotenvy::from_path)
  Config->>Process: read MOSTRO_NSEC_PRIVKEY (trim, ignore empty)
  alt env present
    Config->>Settings: override parsed/created nsec_privkey with env value
  else env absent
    Config->>Settings: use settings.toml nsec_privkey (may be empty)
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • MostroP2P/mostro#696: Related changes to the interactive setup wizard and setup menu flows that this PR builds upon.
  • MostroP2P/mostro#481: Prior refactor of init_configuration_file and config loading that this PR extends with .env and env-var precedence.

Suggested reviewers

  • arkanoider
  • grunch

Poem

🐰 I found a key beneath a log,
I tucked it safe in dotenv’s fog,
Env vars shout, .env hums low,
settings.toml waits for the show,
Now secrets hop where rabbits go.

🚥 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 PR title accurately summarizes the main change: adding support for the MOSTRO_NSEC_PRIVKEY environment variable for the Nostr private key, which is the core feature across all modified files.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/nsec-env-var

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: dd9e3e33be

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/config/wizard.rs
Comment on lines +251 to +255
std::fs::OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.mode(0o600)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Enforce 0600 when rewriting an existing .env file

On Unix, OpenOptionsExt::mode(0o600) only applies when the file is newly created; it does not tighten permissions on an already-existing file. If .env already exists with broader bits (for example 0644), this path truncates and rewrites it but leaves those broad permissions in place, exposing MOSTRO_NSEC_PRIVKEY to other local users. After opening/writing, explicitly set permissions to 0o600 (or recreate the file) so the secret is always protected.

Useful? React with 👍 / 👎.

Comment thread src/config/util.rs Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (5)
src/config/wizard.rs (2)

208-208: Reuse the env-var name and .env filename from shared constants.

Both "MOSTRO_NSEC_PRIVKEY" (line 225) and ".env" (line 208) are duplicated here after being introduced as NSEC_ENV_VAR / ENV_FILENAME in src/config/util.rs. Centralising them in src/config/constants.rs (and re-using from both modules) prevents drift if the name ever changes.

♻️ Suggested approach

Promote them in src/config/constants.rs:

pub const ENV_FILENAME: &str = ".env";
pub const NSEC_ENV_VAR: &str = "MOSTRO_NSEC_PRIVKEY";

Then update util.rs and wizard.rs to import from crate::config::constants::{ENV_FILENAME, NSEC_ENV_VAR} instead of defining/hardcoding them locally.

-    let env_file_path = settings_dir.join(".env");
+    let env_file_path = settings_dir.join(ENV_FILENAME);
@@
-        std::env::set_var("MOSTRO_NSEC_PRIVKEY", nsec);
+        std::env::set_var(NSEC_ENV_VAR, nsec);
@@
-    writeln!(file, "MOSTRO_NSEC_PRIVKEY={}", nsec)
+    writeln!(file, "{}={}", NSEC_ENV_VAR, nsec)

Also applies to: 225-225

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/config/wizard.rs` at line 208, Replace the hardcoded ".env" and
"MOSTRO_NSEC_PRIVKEY" occurrences with the shared constants ENV_FILENAME and
NSEC_ENV_VAR from crate::config::constants: update wizard.rs (where
env_file_path is built and where the NSEC env var is referenced) and util.rs to
import crate::config::constants::{ENV_FILENAME, NSEC_ENV_VAR} instead of
defining or hardcoding these values locally, and remove the duplicate local
definitions so both modules reuse the single source of truth.

247-270: write_env_file: permission + truncation edge cases.

Two small robustness gaps in the env-file writer:

  1. .mode(0o600) only applies when the file is created. If ~/.mostro/.env already exists with looser permissions, OpenOptions::open preserves them — no chmod takes effect. Calling std::fs::set_permissions after opening guarantees 0o600 regardless.
  2. .truncate(true) discards any unrelated entries the operator might have added to .env. In the current flow this is called only during initial wizard setup (when settings.toml is being created), so collisions are unlikely, but an append-or-replace strategy would be safer long-term.
🛡️ Proposed hardening for (1)
     let mut file = file.map_err(|e| MostroInternalErr(ServiceError::IOError(e.to_string())))?;
+    #[cfg(unix)]
+    {
+        use std::os::unix::fs::PermissionsExt;
+        std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o600))
+            .map_err(|e| MostroInternalErr(ServiceError::IOError(e.to_string())))?;
+    }
     writeln!(file, "MOSTRO_NSEC_PRIVKEY={}", nsec)
         .map_err(|e| MostroInternalErr(ServiceError::IOError(e.to_string())))?;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/config/wizard.rs` around lines 247 - 270, In write_env_file, ensure file
permissions are enforced after opening by calling std::fs::set_permissions(path,
Permissions::from_mode(0o600)) (or the cross-platform equivalent) so existing
files get 0o600 even when not created; and avoid blind truncate by implementing
an "append-or-replace" flow: if the file exists, read its contents, replace any
line starting with "MOSTRO_NSEC_PRIVKEY=" with the new value (or append the line
if missing), then write the updated content back (you may truncate when writing
the final assembled content). Keep these changes localized to the write_env_file
function so it still uses OpenOptions for creation but then enforces permissions
and safely updates the MOSTRO_NSEC_PRIVKEY entry.
src/config/util.rs (3)

21-26: Consider logging when .env load fails.

Silently discarding the dotenvy::from_path result will hide misconfigurations (e.g., malformed .env or permission errors on the file) from operators. A single tracing::warn! keeps behavior backward-compatible but makes troubleshooting easier.

🔎 Proposed fix
 fn load_env_file(settings_dir: &std::path::Path) {
     let env_file = settings_dir.join(ENV_FILENAME);
     if env_file.exists() {
-        let _ = dotenvy::from_path(&env_file);
+        if let Err(err) = dotenvy::from_path(&env_file) {
+            tracing::warn!(
+                path = %env_file.display(),
+                error = %err,
+                "Failed to load .env file"
+            );
+        }
     }
 }

As per coding guidelines: "Prefer tracing spans over ad-hoc logging in Rust code".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/config/util.rs` around lines 21 - 26, The function load_env_file
currently discards the result of dotenvy::from_path, hiding failures; update
load_env_file to inspect the Result from dotenvy::from_path(&env_file) and, on
Err, emit a tracing::warn! (including the error and the env_file path/metadata)
so operators see malformed or permission errors while keeping behavior otherwise
the same; reference the existing load_env_file function and the call to
dotenvy::from_path to locate where to add the Result handling and tracing::warn!
(prefer tracing spans if you already have one in scope, otherwise a simple
tracing::warn! with the error and file path is fine).

138-261: Test suite LGTM; one optional hardening note.

Tests comprehensively cover precedence (env > toml), blank/whitespace fallback, trimming, and missing-field TOML parsing. One small resilience nit: ENV_LOCK.lock().unwrap() will poison the mutex if any test in the critical section panics, cascading failures into subsequent tests. Using lock().unwrap_or_else(|e| e.into_inner()) makes the suite more robust to a single failure.

As per coding guidelines: "Co-locate tests in their modules under mod tests blocks with descriptive names" — the descriptive test names here comply.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/config/util.rs` around lines 138 - 261, Tests currently call
ENV_LOCK.lock().unwrap(), which will poison the mutex and cause cascading
failures if any test panics; change those calls to use lock().unwrap_or_else(|e|
e.into_inner()) so the guard is recovered instead of panicking. Update the five
test functions that create the lock (_lock in env_var_overrides_toml_nsec,
empty_env_var_falls_back_to_toml, no_env_var_keeps_toml,
whitespace_only_env_is_ignored, env_var_value_is_trimmed) to acquire the mutex
with ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner()).

40-60: Validate non-empty nsec_privkey after the env override.

With nsec_privkey now #[serde(default)] and the env override applied here, it's possible for settings.nostr.nsec_privkey to be an empty string (no TOML value AND no/whitespace-only env var) when init_mostro_settings runs. The failure currently surfaces later in src/util.rs::get_keys as a generic "Failed to parse nostr private key" message. A startup-time check gives operators a clearer, more actionable error and fails fast.

🛡️ Proposed fix
 /// Validates Mostro settings on startup
 fn validate_mostro_settings(settings: &Settings) -> Result<(), MostroError> {
     let dev_fee = settings.mostro.dev_fee_percentage;
@@
+    if settings.nostr.nsec_privkey.trim().is_empty() {
+        return Err(MostroInternalErr(ServiceError::IOError(format!(
+            "Nostr private key is missing. Set `{}` in the environment (or `{}/.env`), or set `nsec_privkey` in settings.toml.",
+            NSEC_ENV_VAR, ENV_FILENAME
+        ))));
+    }
+
     Ok(())
 }

Also applies to: 105-106, 122-125

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/config/util.rs` around lines 40 - 60, Add a startup validation in
validate_mostro_settings to ensure settings.nostr.nsec_privkey is not empty or
whitespace after env overrides: check
settings.nostr.nsec_privkey.trim().is_empty() and return an immediate
MostroInternalErr/ServiceError with a clear message like "nostr private key
(nsec_privkey) is empty after env override" so init_mostro_settings fails fast
(this prevents the later generic error in get_keys).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@README.md`:
- Around line 523-526: The fenced code block in README.md showing the
MOSTRO_NSEC_PRIVKEY example is missing a language specifier and triggers
markdownlint MD040; update the triple-backtick that opens the block to include a
language (e.g., "ini") so the block becomes ```ini and thus documents syntax and
satisfies MD040—look for the snippet containing MOSTRO_NSEC_PRIVKEY and change
the opening fence accordingly.

In `@src/config/wizard.rs`:
- Around line 200-206: The formatting mismatch comes from the first println!
splitting a single string literal across multiple lines; update that println!
(the one starting with "Storing your nsec as an environment variable (instead of
settings.toml) keeps") so it uses a single contiguous string literal like the
subsequent println! calls (i.e., combine the wrapped lines into one println!
call with the full message) so rustfmt/cargo fmt will no longer reflow it and CI
will pass.

---

Nitpick comments:
In `@src/config/util.rs`:
- Around line 21-26: The function load_env_file currently discards the result of
dotenvy::from_path, hiding failures; update load_env_file to inspect the Result
from dotenvy::from_path(&env_file) and, on Err, emit a tracing::warn! (including
the error and the env_file path/metadata) so operators see malformed or
permission errors while keeping behavior otherwise the same; reference the
existing load_env_file function and the call to dotenvy::from_path to locate
where to add the Result handling and tracing::warn! (prefer tracing spans if you
already have one in scope, otherwise a simple tracing::warn! with the error and
file path is fine).
- Around line 138-261: Tests currently call ENV_LOCK.lock().unwrap(), which will
poison the mutex and cause cascading failures if any test panics; change those
calls to use lock().unwrap_or_else(|e| e.into_inner()) so the guard is recovered
instead of panicking. Update the five test functions that create the lock (_lock
in env_var_overrides_toml_nsec, empty_env_var_falls_back_to_toml,
no_env_var_keeps_toml, whitespace_only_env_is_ignored, env_var_value_is_trimmed)
to acquire the mutex with ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner()).
- Around line 40-60: Add a startup validation in validate_mostro_settings to
ensure settings.nostr.nsec_privkey is not empty or whitespace after env
overrides: check settings.nostr.nsec_privkey.trim().is_empty() and return an
immediate MostroInternalErr/ServiceError with a clear message like "nostr
private key (nsec_privkey) is empty after env override" so init_mostro_settings
fails fast (this prevents the later generic error in get_keys).

In `@src/config/wizard.rs`:
- Line 208: Replace the hardcoded ".env" and "MOSTRO_NSEC_PRIVKEY" occurrences
with the shared constants ENV_FILENAME and NSEC_ENV_VAR from
crate::config::constants: update wizard.rs (where env_file_path is built and
where the NSEC env var is referenced) and util.rs to import
crate::config::constants::{ENV_FILENAME, NSEC_ENV_VAR} instead of defining or
hardcoding these values locally, and remove the duplicate local definitions so
both modules reuse the single source of truth.
- Around line 247-270: In write_env_file, ensure file permissions are enforced
after opening by calling std::fs::set_permissions(path,
Permissions::from_mode(0o600)) (or the cross-platform equivalent) so existing
files get 0o600 even when not created; and avoid blind truncate by implementing
an "append-or-replace" flow: if the file exists, read its contents, replace any
line starting with "MOSTRO_NSEC_PRIVKEY=" with the new value (or append the line
if missing), then write the updated content back (you may truncate when writing
the final assembled content). Keep these changes localized to the write_env_file
function so it still uses OpenOptions for creation but then enforces permissions
and safely updates the MOSTRO_NSEC_PRIVKEY entry.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b7d2c87c-cdb5-431e-9b65-65c80b70aad1

📥 Commits

Reviewing files that changed from the base of the PR and between 673340d and dd9e3e3.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (6)
  • Cargo.toml
  • README.md
  • docs/STARTUP_AND_CONFIG.md
  • src/config/types.rs
  • src/config/util.rs
  • src/config/wizard.rs

Comment thread README.md
Comment thread src/config/wizard.rs Outdated
Copy link
Copy Markdown
Contributor

@mostronatorcoder mostronatorcoder Bot left a comment

Choose a reason for hiding this comment

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

I did a strict pass and I can't approve this PR yet.

The overall direction makes sense and I agree with merging env-based support first as the backward-compatible transition step. CI is green, the scope is coherent, and the precedence behavior is well covered by tests.

However, I see two issues that are worth fixing before approval:

  1. write_env_file does not actually guarantee 0600 on an existing .env file.

On Unix, OpenOptionsExt::mode(0o600) only applies when the file is created. If ~/.mostro/.env already exists with broader permissions, this code truncates and rewrites it but keeps the old mode bits. That defeats the security claim printed by the wizard.

At minimum, the function should explicitly set_permissions(..., 0o600) after opening/writing on Unix.

  1. .env loading failures are silently ignored.

load_env_file currently discards the result of dotenvy::from_path. If the operator relies on <settings_dir>/.env and that file is malformed or unreadable, startup quietly falls back to TOML or to an empty key, and the eventual failure becomes much harder to diagnose.

This should at least emit a warning with the file path and the parsing/loading error.

These are not conceptual blockers to the feature itself, but they are real robustness/security gaps in the current implementation. Once they are addressed, I think this PR is in good shape for merge as the compatibility-preserving first step before the later file-based secret migration/deprecation work.

@gotcha
Copy link
Copy Markdown

gotcha commented May 3, 2026

I do understand how using an environment variable improves security wise compared to nsec inline in config.

However, I am uneasy with the .env file support and its inclusion in the setup wizard. Once there is .env support that includes secrets, we would be back at that place where there is a risk to leak for instance when versioning production setup.

@AndreaDiazCorreia
Copy link
Copy Markdown
Member Author

Hi @gotcha , fair point.

I pushed a small change to the wizard so the two storage options are presented neutrally, without the "recommended" / "legacy" labels. The preamble also mentions that setting MOSTRO_NSEC_PRIVKEY via systemd, Docker or a secrets manager is a valid path, so nobody is nudged into a .env file if they don't want one.

The intent here is purely additive. We're not deprecating inline nsec_privkey or asking anyone to migrate. Operators who want stronger isolation can opt in, the rest keep working unchanged.

The file-path option you described fits naturally on top of this and can land in a follow-up PR whenever there's appetite.

@AndreaDiazCorreia
Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 13, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@mostronatorcoder
Copy link
Copy Markdown
Contributor

Strict review pass after the latest update.

I would not approve this yet.

The two issues from my previous review that blocked approval are fixed, nice catch there:

  • .env loading failures are now surfaced with a warning instead of being silently ignored.
  • write_env_file() now tightens permissions on pre-existing files instead of only relying on OpenOptionsExt::mode(0o600).

However, I still see one thing worth cleaning up before approval:

src/config/wizard.rs:206-223

The wizard still hardcodes both ".env" and "MOSTRO_NSEC_PRIVKEY" even though the same identifiers already exist as shared constants in src/config/util.rs.

That is not a runtime bug today, but it is a maintainability problem in exactly the place where secret-handling behavior is being introduced. If the variable name or env filename ever changes, this code is now vulnerable to drift between the startup path and the wizard path.

I would fix that by moving both constants to a shared config constants module and reusing them from both util.rs and wizard.rs.

Once that duplication is removed, I would be comfortable approving this PR.

Move ENV_FILENAME and NSEC_ENV_VAR from config/util.rs to config/constants.rs to share them between the wizard and loader modules, eliminating duplicate string literals.
Copy link
Copy Markdown
Contributor

@mostronatorcoder mostronatorcoder Bot left a comment

Choose a reason for hiding this comment

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

I did another strict pass after the latest changes.

The remaining maintainability concern is resolved: ENV_FILENAME and NSEC_ENV_VAR are now centralized in src/config/constants.rs and reused from both the loader and the wizard, so the env-loading path and the setup path can no longer drift.

The earlier blockers are also fixed:

  • .env load failures are surfaced with a warning instead of being silently ignored.
  • write_env_file() now tightens permissions on pre-existing files instead of only relying on OpenOptionsExt::mode(0o600).

At this point I am comfortable approving this as a backward-compatible first step for env-based secret handling. The scope is coherent, existing inline settings.toml setups keep working, and the precedence behavior is covered by tests.

Approved.

Copy link
Copy Markdown
Member

@grunch grunch left a comment

Choose a reason for hiding this comment

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

LGTM

@grunch grunch merged commit 416aaf5 into main May 13, 2026
8 checks passed
@grunch grunch deleted the feat/nsec-env-var branch May 13, 2026 21:04
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