fix(server): keep commands after mkdir -p for non-root users

The sudo parser read everything after `mkdir -p` as the path, so the
ownership step ended up after the last command of the line. For
`mkdir -p /data/coolify/proxy/dynamic && ls ... | head -n 1000`, `head`
got the `find` options and the proxy dynamic configurations failed on
every server with a non-root user.

- Add the ownership step right after the mkdir path; commands after
  `&&` or `;` stay unchanged
- Lines with other forms (redirects, `||`, pipes) get no ownership step
This commit is contained in:
Andras Bacsai
2026-10-05 22:37:06 +02:00
parent 9a11407e2f
commit 41dbcbbab3
2 changed files with 57 additions and 18 deletions
@@ -355,6 +355,42 @@ test('ownership changes keep container-owned files and file modes', function (st
(new Process(['rm', '-rf', $directory, $path]))->run();
})->with(['command list', 'deployment line']);
test('ownership changes apply only to the mkdir path when more commands follow', function (string $parser, string $separator) {
$directory = sys_get_temp_dir().'/coolify-sudo-'.bin2hex(random_bytes(4));
mkdir($directory);
file_put_contents("{$directory}/sudo", "#!/bin/sh\nexec \"\$@\"\n");
foreach (['chown', 'chmod'] as $tool) {
file_put_contents("{$directory}/{$tool}", "#!/bin/sh\necho \"{$tool} \$*\" >> \"\$LOG\"\n");
}
array_map(fn (string $tool) => chmod("{$directory}/{$tool}", 0755), ['sudo', 'chown', 'chmod']);
$path = '/tmp/coolify/sudo-test-'.bin2hex(random_bytes(4));
$line = "mkdir -p {$path}/dynamic{$separator} ls -1 {$path}/dynamic | head -n 1000";
$command = $parser === 'command list'
? parseCommandsByLineForSudo(collect([$line]), $this->server)[0]
: parseLineForSudo($line, $this->server);
$process = new Process(['bash', '-c', $command], env: ['PATH' => "{$directory}:".getenv('PATH'), 'LOG' => "{$directory}/log"]);
$process->run();
expect($command)->toContain("find {$path}/dynamic -user root -exec chown ubuntu:ubuntu {} + && ")
->and($command)->toContain("chmod o-rwx {$path}/dynamic{$separator} ")
->and($command)->toEndWith('head -n 1000'.($parser === 'command list' ? "'" : ''))
->and($process->getErrorOutput())->toBe('')
->and($process->isSuccessful())->toBeTrue()
->and(is_dir("{$path}/dynamic"))->toBeTrue()
->and(file_get_contents("{$directory}/log"))->toEndWith("chmod o-rwx {$path}/dynamic\n");
(new Process(['rm', '-rf', $directory, $path]))->run();
})->with(['command list', 'deployment line'])->with(['and' => ' &&', 'semicolon' => ';']);
test('does not add ownership changes when mkdir is followed by other operators', function () {
$result = parseCommandsByLineForSudo(collect([
'mkdir -p /data/coolify/proxy > /dev/null 2>&1 || true',
]), $this->server);
expect($result[0])->toBe('sudo mkdir -p /data/coolify/proxy > /dev/null 2>&1 || sudo true');
});
test('does not add ownership changes for system paths', function () {
$commands = collect([
'mkdir -p /var/log',