Skip to content

Collapse the four server-side .env key read/upsert implementations onto one helper #5833

Description

@atomantic

Problem

PortOS reads and rewrites a single key in a .env file in four places, each with its own parser and its own insert policy:

Where Read Write Insert position
scripts/lib/envFile.js parseEnvFile upsertEnvKey prepend
server/services/localLlm.js readEnv (private) writeBackend (private) prepend
server/lib/vaultCrypto.js inline inline
server/lib/vllmQwenProvision.js parseEnvContents mergeEnvFileContents / upsertEnvLine append

They already disagree in ways that matter. scripts/lib/envFile.js uses a replacer function so a value containing $& / $` / $' is written literally rather than expanded by String.replace — a bug it hit on a password with a $ in it. That fix was independently re-lost and re-found in vllmQwenProvision.js#upsertEnvLine. Only two of the four skip # comments. localLlm.js also lets .env win over process.env while vllmQwenProject.js#resolveVllmProjectDir deliberately inverts that; both document their reasoning, but two "record a marker in .env" features with opposite precedence is a coin-flip for the next one.

Three of the four also anchor the path differently: PATHS.root (localLlm.js, ecosystem.config.cjs) versus PATHS.installRoot (vllmQwenProject.js#PORTOS_ENV_PATH). Only the second is correct for a server booted from a CoS agent worktree, where the checkout has no .env and the install does (#1947).

Decision (already made — do not re-litigate)

One helper, in server/lib/, and every server-side caller collapses onto it. scripts/lib/envFile.js stays as it is — its header states it must have zero dependencies and must not import from server/lib, because it runs before/around npm install. Two implementations across that boundary is the correct number; four is not.

  1. Add server/lib/portosEnv.js: PORTOS_ENV_PATH (anchored to PATHS.installRoot, with the Boot migrations resolve data root from executing-file location — crash when run from a CoS agent worktree #1947 reasoning in the docstring), readPortosEnvValue(key, envPath?), upsertPortosEnvLine(key, value, envPath?) (async, over atomicWrite, replacer-function). Barrel + README row per the Module Organization rule.
  2. Move upsertEnvLine / parseEnvContents reuse out of vllmQwenProvision.js into it, leaving mergeEnvFileContents (additive-by-contract, for the project's .env) where it is.
  3. Collapse vllmQwenProject.js's readRecordedVllmProjectDir / recordVllmProjectDir and localLlm.js's readEnv / writeBackend / ENV_PATH onto it.
  4. Settle the precedence question once, in that module's docstring: an exported process.env value is this run's decision and wins; the .env record is durable memory and loses. Adjust localLlm.js#getBackend to match, and keep its "validate each source before falling through" guard (a stale/invalid marker must not mask a valid override) — that part is orthogonal and correct.
  5. server/lib/vaultCrypto.js's inline pair goes too, unless its bootstrapping order makes an import impossible — check before assuming, and say so in the PR if it does.

Acceptance

  • One server-side .env key reader and one writer; grep -n "readFileSync.*\.env" server/ finds only that module.
  • localLlm.js's existing suite passes unchanged (it mocks PATHS through ../lib/fileUtils.js, so the new module must resolve its default path lazily or accept the path as a parameter — the envPath parameter already used by vllmQwenProject.js is the pattern).
  • A value containing $& round-trips literally, with a test naming that.
  • Precedence is stated once and both callers follow it.

Context

Follow-up from the vLLM Windows placement change; raised by both the reuse and altitude passes on that PR. vllmQwenProject.js already reuses parseEnvContents and the new upsertEnvLine rather than adding a fifth copy — this issue is about the three that predate it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    effort:mediumEffort: mediummodel:mediumModel size: mediumplanTracked by /do:replanplanner:opus-5Plan authored by the opus-5 model

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions