Skip to content

fix(volumes): resolve env fallback paths for convert-to-file#9364

Open
andrasbacsai wants to merge 5 commits intonextfrom
8854-investigate-bug
Open

fix(volumes): resolve env fallback paths for convert-to-file#9364
andrasbacsai wants to merge 5 commits intonextfrom
8854-investigate-bug

Conversation

@andrasbacsai
Copy link
Copy Markdown
Member

Summary

  • Fixes [Bug]: "Convert to file" in Persistent Storage UI fails for volume paths using variables with fallback values (${VAR:-./path}) #8854 by resolving Docker Compose env-var volume sources like ${VAR:-./path} before storage-path validation and file operations.
  • Introduces resolveEnvVarDefault() and applies it in volume parsing (applicationParser, serviceParser) and LocalFileVolume path handling (save/load/delete).
  • Adds LocalFileVolume::reResolveVolumePaths() and triggers it after env var updates so file volume paths stay in sync when env values change.
  • Merges preview env vars during application parsing so PR deployments use preview overrides when resolving volume paths.
  • Improves the Persistent Storage UI by showing when a source path was resolved from an environment variable.
  • Adds focused unit tests for env-var default resolution, safety validation behavior, and LocalFileVolume::resolvedFsPath().
  • Includes minor cleanup/refactors (import normalization and exception class usage) in environment variable Livewire components.

Fixes #8854

Normalize `${VAR}` and `${VAR:-default}` volume sources via `resolveEnvVarDefault()`,
use preview env vars during application parsing, and re-resolve persisted file-volume
paths after environment-variable updates. Also surface env-var source info in the
file storage UI and add unit coverage for env-var/default resolution and path safety.
@andrasbacsai
Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 1, 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.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 1, 2026

Walkthrough

This pull request enhances Docker volume path resolution by detecting and resolving environment variable references in file storage configurations. The changes introduce a new public property resolvedFromEnvVar in the FileStorage component to track detected environment variables, add utilities in LocalFileVolume for resolving paths with env-var defaults (via ${VAR} or ${VAR:-default} syntax), and create a shared helper function resolveEnvVarDefault() for consistent resolution logic. The code parses docker-compose YAML to identify volume sources containing environment variable references and resolves them against available environment variables or fallback defaults. Parser logic is updated to handle these patterns during volume source processing, and comprehensive unit tests validate both the resolution behavior and security considerations.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 8854-investigate-bug

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
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: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/Livewire/Project/Service/FileStorage.php`:
- Around line 93-145: The loop variable $serviceVolumes in the
detectEnvVarSource() method is misleading because it holds the entire service
definition, not just volumes; rename it to $service (or $serviceDefinition) and
update any references inside the foreach to improve clarity (search for the
foreach over $compose['services'] and the subsequent data_get($serviceVolumes,
'volumes', []) usage), leaving the rest of the logic unchanged and keeping the
try/catch and Yaml::parse usage as-is.

In `@app/Models/LocalFileVolume.php`:
- Around line 52-56: resolvedFsPath() (and the other locations noted) currently
resolves legacy ${VAR} syntax without the resource's env collection and can
return the bare '.' fallback which later becomes $workdir in shell ops; change
resolvedFsPath() to first resolve against the owning resource’s env vars (use
the resource/env collection associated with this LocalFileVolume) and if
resolution yields the bare current-directory fallback ('.' or empty) throw a
hard error rather than returning it; likewise update callers
updateFileVolumePaths(), deleteStorageOnServer(), and saveStorageOnServer() to
rely on the env-aware resolved path and to fail early if the path is the bare
dot to avoid building any shell commands that could target $workdir.
- Around line 146-149: The code in LocalFileVolume::reResolveVolumePaths (the
$mainDirectory assignment) hardcodes '/applications/' which mismatches
serviceParser()'s v4+ storage root and causes volume paths to be repointed
incorrectly; change $mainDirectory to use the same service storage root as
serviceParser() (e.g. build the path with
base_configuration_dir().'/services/'.$service->uuid or call the shared helper
used by serviceParser()) so both lookup and write paths are consistent.

In `@bootstrap/helpers/parsers.php`:
- Around line 292-304: The code currently replaces "${VAR:-default}" with the
default value early (in the preg_match block operating on $source), which
prevents applicationParser()/serviceParser() and $allEnvironments from resolving
real or preview env values; change the logic so that when preg_match finds a
pattern with ':-' you do NOT overwrite $source with the default string — instead
preserve the full original short-syntax token (e.g. keep '${VAR:-default}') so
later env-aware resolution in applicationParser() / serviceParser() can apply
overrides from $allEnvironments; only convert to a plain '${VAR}' when the
default part is explicitly empty, and add a small regression test that supplies
an overriding env value to ensure the preserved short syntax picks the
env-provided value rather than the static default.
- Around line 708-709: resolveEnvVarDefault() can return a bare "." which
sourceIsLocal() doesn't treat as local, causing bind sources to be misclassified
as named volumes; after each call to resolveEnvVarDefault (e.g., where $source
is set) normalize a bare "." to "./" (e.g., if ($source === '.') $source = './')
so the later sourceIsLocal() / LocalFileVolume logic picks the bind-mount
branch; alternatively, update sourceIsLocal() to return true for "." — apply
this normalization in the same spots where resolveEnvVarDefault() is used (the
occurrences you noted) so the current-directory fallback becomes a
LocalFileVolume.

In `@bootstrap/helpers/shared.php`:
- Around line 1237-1270: The code in resolveEnvVarDefault uses
strpos($varContent, ':-') !== false to detect the default separator; replace
that check with PHP 8's str_contains($varContent, ':-') for consistency with the
codebase style, leaving the subsequent explode(':-', $varContent, 2) and
variable assignment logic ($varName, $defaultValue) intact and preserving
behavior when $environmentVariables is null or when defaults are empty.
🪄 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: ASSERTIVE

Plan: Pro

Run ID: cbb9037a-e832-4b3b-b707-2979a9c501b6

📥 Commits

Reviewing files that changed from the base of the PR and between 9685085 and 2cbc22a.

📒 Files selected for processing (8)
  • app/Livewire/Project/Service/FileStorage.php
  • app/Livewire/Project/Shared/EnvironmentVariable/All.php
  • app/Livewire/Project/Shared/EnvironmentVariable/Show.php
  • app/Models/LocalFileVolume.php
  • bootstrap/helpers/parsers.php
  • bootstrap/helpers/shared.php
  • resources/views/livewire/project/service/file-storage.blade.php
  • tests/Unit/VolumeArrayFormatSecurityTest.php

Comment on lines +52 to +56
public function resolvedFsPath(): string
{
$resolved = resolveEnvVarDefault(str($this->fs_path));

return $resolved->value();
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.

⚠️ Potential issue | 🔴 Critical

Do not turn an unresolved env var into $workdir.

resolvedFsPath() resolves legacy ${VAR...} without the owning env collection, so it can only produce the fallback or . — never the actual resource value. updateFileVolumePaths() also skips that bare-dot result, and the downstream file ops expand it to $workdir; deleteStorageOnServer() can rm -rf the whole workdir, and saveStorageOnServer() can replace it with a file. That is less “convert to file” and more “hasta la vista, app data.” Please resolve against the related resource’s env vars first, and hard-fail on the bare current-directory fallback before any shell command is built.

Also applies to: 201-203, 223-227, 258-279, 296-325, 337-345

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

In `@app/Models/LocalFileVolume.php` around lines 52 - 56, resolvedFsPath() (and
the other locations noted) currently resolves legacy ${VAR} syntax without the
resource's env collection and can return the bare '.' fallback which later
becomes $workdir in shell ops; change resolvedFsPath() to first resolve against
the owning resource’s env vars (use the resource/env collection associated with
this LocalFileVolume) and if resolution yields the bare current-directory
fallback ('.' or empty) throw a hard error rather than returning it; likewise
update callers updateFileVolumePaths(), deleteStorageOnServer(), and
saveStorageOnServer() to rely on the env-aware resolved path and to fail early
if the path is the bare dot to avoid building any shell commands that could
target $workdir.

Comment on lines +708 to +709
// Resolve env vars in source (e.g., ${VAR} -> value from Coolify env vars)
$source = resolveEnvVarDefault($source, $allEnvironments);
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.

⚠️ Potential issue | 🟠 Major

Treat bare . as a local bind source.

resolveEnvVarDefault() returns . for ${VAR} / ${VAR:-} when nothing is set, but sourceIsLocal() does not recognize bare dot. These paths fall into the named-volume branch instead of the bind-mount branch, so the current-directory fallback never becomes a LocalFileVolume. Tiny dot, giant self-hosted headache. Normalizing . to ./ here, or teaching sourceIsLocal() about bare dot, would keep the fallback on the correct path.

Also applies to: 734-737, 2101-2102, 2127-2130

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

In `@bootstrap/helpers/parsers.php` around lines 708 - 709, resolveEnvVarDefault()
can return a bare "." which sourceIsLocal() doesn't treat as local, causing bind
sources to be misclassified as named volumes; after each call to
resolveEnvVarDefault (e.g., where $source is set) normalize a bare "." to "./"
(e.g., if ($source === '.') $source = './') so the later sourceIsLocal() /
LocalFileVolume logic picks the bind-mount branch; alternatively, update
sourceIsLocal() to return true for "." — apply this normalization in the same
spots where resolveEnvVarDefault() is used (the occurrences you noted) so the
current-directory fallback becomes a LocalFileVolume.

Keep `${VAR:-default}` intact in volume parsing so env overrides can be
resolved later, then re-resolve local file volume paths after env var updates
or deletions.

Also treat `.` as a local source path, update file-storage UI copy/actions for
read-only mounts, and add regression/unit tests for parser, source locality,
and service volume path handling.
Switch `wireNavigate()` to return `wire:navigate` when enabled,
including the exception fallback path. Add a unit test to assert the
helper no longer contains `wire:navigate.hover`.
Return unresolved ${VAR} references unchanged in resolveEnvVarDefault
instead of coercing them to ".", so sourceIsLocal() does not classify
missing env vars as local bind mounts.

Also hard-fail LocalFileVolume::resolvedFsPath() when unresolved placeholders
remain in the resolved value, and update unit tests to cover the regression
and expected local-default behavior.
Switch default parser version to v6 for Application and Service, and only
preserve/resolve `${VAR:-default}` sources in v6 parsing flows.

Also update file storage path re-resolution to run for v6 resources and
reclassify volumes between LocalFileVolume and LocalPersistentVolume when
env var resolution changes.
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.

1 participant