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
+21 -18
View File
@@ -30,6 +30,25 @@ function ownershipCommand(string $path, Server $server): string
return "find $path -user root -exec chown $server->user:$server->user {} + && chmod o-rwx $path";
}
/**
* Adds the ownership step right after `sudo mkdir -p <path>`. When more commands follow on the line
* (`&& ...` or `; ...`), they stay unchanged after the ownership step. Other forms (redirects, `||`,
* pipes, several paths) are left as they are.
*/
function addOwnershipAfterMkdir(string $line, Server $server): string
{
if (! preg_match('/^(sudo mkdir -p ([^\s;&|<>]+))(\s*(?:(?:&&|;).*)?)$/s', $line, $matches)) {
return $line;
}
$path = $matches[2];
if (! shouldChangeOwnership($path)) {
return $line;
}
// No sudo here: the && rule adds it. `sudo sudo` fails where root is not in sudoers (Alpine).
return "{$matches[1]} && ".ownershipCommand($path, $server).($matches[3] ?? '');
}
/**
* A line that is one `sudo sh -c '...'` call already runs completely as root. Adding sudo inside the script
* fails where root is not in sudoers (Alpine), and wrapping it again in `bash -c` needs bash.
@@ -135,17 +154,7 @@ function parseCommandsByLineForSudo(Collection $commands, Server $server): array
});
$commands = $commands->map(function ($line) use ($server) {
if (Str::startsWith($line, 'sudo mkdir -p')) {
$path = trim(Str::after($line, 'sudo mkdir -p'));
if (shouldChangeOwnership($path)) {
// No sudo here: the && rule below adds it. `sudo sudo` fails where root is not in sudoers (Alpine).
return "$line && ".ownershipCommand($path, $server);
}
return $line;
}
return $line;
return addOwnershipAfterMkdir($line, $server);
});
$commands = $commands->map(function ($line) {
@@ -197,13 +206,7 @@ function parseLineForSudo(string $command, Server $server): string
if (! str($command)->startSwith('cd') && ! str($command)->startSwith('command')) {
$command = "sudo $command";
}
if (Str::startsWith($command, 'sudo mkdir -p')) {
$path = trim(Str::after($command, 'sudo mkdir -p'));
if (shouldChangeOwnership($path)) {
// No sudo here: the && rule below adds it.
$command = "$command && ".ownershipCommand($path, $server);
}
}
$command = addOwnershipAfterMkdir($command, $server);
if (isSingleSudoShellScript($command)) {
return $command;
}