mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-06 13:10:37 -04:00
fix(installer): keep resource data ownership on reinstall and upgrade
install.sh ran chown -R 9999:root and chmod -R 700 on all of /data/coolify on every run. This changed bind-mounted data of services, applications, and databases, and reset the ownership that a non-root SSH user needs. Set owner and mode on /data/coolify and the resource directories only when the installer creates them. Change only source, ssh, and images recursively, because the coolify container uses them. Also wait until the Docker daemon accepts connections after a restart, because OpenRC returns before the daemon is ready. Fixes #11945
This commit is contained in:
@@ -29,12 +29,24 @@ if [ $EUID != 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p /data/coolify/{source,ssh,applications,databases,backups,services,proxy,sentinel}
|
||||
mkdir -p /data/coolify/ssh/{keys,mux}
|
||||
mkdir -p /data/coolify/proxy/dynamic
|
||||
# Resource directories hold data whose owner and mode the resource images or a non-root SSH user need.
|
||||
# Set their owner and mode only when they are created, never on an upgrade (#11945).
|
||||
for COOLIFY_DIRECTORY in /data/coolify /data/coolify/{applications,databases,backups,services,proxy,proxy/dynamic,sentinel}; do
|
||||
if [ ! -d "$COOLIFY_DIRECTORY" ]; then
|
||||
mkdir -p "$COOLIFY_DIRECTORY"
|
||||
chown 9999:root "$COOLIFY_DIRECTORY"
|
||||
chmod 700 "$COOLIFY_DIRECTORY"
|
||||
fi
|
||||
done
|
||||
mkdir -p /data/coolify/source /data/coolify/ssh/{keys,mux}
|
||||
|
||||
chown -R 9999:root /data/coolify
|
||||
chmod -R 700 /data/coolify
|
||||
# Coolify's own directories, used by the coolify container (UID 9999)
|
||||
set_coolify_directory_permissions() {
|
||||
chown -R 9999:root /data/coolify/{source,ssh}
|
||||
chmod -R 700 /data/coolify/{source,ssh}
|
||||
}
|
||||
|
||||
set_coolify_directory_permissions
|
||||
|
||||
INSTALLATION_LOG_WITH_DATE="/data/coolify/source/installation-${DATE}.log"
|
||||
|
||||
@@ -1071,8 +1083,7 @@ if [ "$IS_COOLIFY_VOLUME_EXISTS" -eq 0 ]; then
|
||||
rm -f /data/coolify/ssh/keys/id.$CURRENT_USER@host.docker.internal.pub
|
||||
fi
|
||||
|
||||
chown -R 9999:root /data/coolify
|
||||
chmod -R 700 /data/coolify
|
||||
set_coolify_directory_permissions
|
||||
log "SSH key check completed"
|
||||
step_done "$SSH_KEY_DETAIL"
|
||||
|
||||
|
||||
+33
-13
@@ -29,13 +29,24 @@ if [ $EUID != 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p /data/coolify/{source,ssh,applications,databases,backups,services,proxy,sentinel}
|
||||
mkdir -p /data/coolify/images
|
||||
mkdir -p /data/coolify/ssh/{keys,mux}
|
||||
mkdir -p /data/coolify/proxy/dynamic
|
||||
# Resource directories hold data whose owner and mode the resource images or a non-root SSH user need.
|
||||
# Set their owner and mode only when they are created, never on an upgrade (#11945).
|
||||
for COOLIFY_DIRECTORY in /data/coolify /data/coolify/{applications,databases,backups,services,proxy,proxy/dynamic,sentinel}; do
|
||||
if [ ! -d "$COOLIFY_DIRECTORY" ]; then
|
||||
mkdir -p "$COOLIFY_DIRECTORY"
|
||||
chown 9999:root "$COOLIFY_DIRECTORY"
|
||||
chmod 700 "$COOLIFY_DIRECTORY"
|
||||
fi
|
||||
done
|
||||
mkdir -p /data/coolify/source /data/coolify/images /data/coolify/ssh/{keys,mux}
|
||||
|
||||
chown -R 9999:root /data/coolify
|
||||
chmod -R 700 /data/coolify
|
||||
# Coolify's own directories, used by the coolify container (UID 9999)
|
||||
set_coolify_directory_permissions() {
|
||||
chown -R 9999:root /data/coolify/{source,ssh,images}
|
||||
chmod -R 700 /data/coolify/{source,ssh,images}
|
||||
}
|
||||
|
||||
set_coolify_directory_permissions
|
||||
|
||||
INSTALLATION_LOG_WITH_DATE="/data/coolify/source/installation-${DATE}.log"
|
||||
|
||||
@@ -278,11 +289,21 @@ if [ -n "${DOCKER_ADDRESS_POOL_SIZE+x}" ]; then
|
||||
DOCKER_POOL_SIZE_PROVIDED=true
|
||||
fi
|
||||
|
||||
# OpenRC (Alpine) returns from "service docker start" before the daemon accepts connections
|
||||
wait_for_docker_daemon() {
|
||||
for _ in $(seq 1 30); do
|
||||
if docker info >/dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
restart_docker_service() {
|
||||
# Check if systemctl is available
|
||||
if command -v systemctl >/dev/null 2>&1; then
|
||||
systemctl restart docker
|
||||
if [ $? -eq 0 ]; then
|
||||
if systemctl restart docker && wait_for_docker_daemon; then
|
||||
echo " - Docker daemon restarted successfully"
|
||||
else
|
||||
echo " - Failed to restart Docker daemon"
|
||||
@@ -290,8 +311,7 @@ restart_docker_service() {
|
||||
fi
|
||||
# Check if service command is available
|
||||
elif command -v service >/dev/null 2>&1; then
|
||||
service docker restart
|
||||
if [ $? -eq 0 ]; then
|
||||
if service docker restart && wait_for_docker_daemon; then
|
||||
echo " - Docker daemon restarted successfully"
|
||||
else
|
||||
echo " - Failed to restart Docker daemon"
|
||||
@@ -792,7 +812,8 @@ fi
|
||||
|
||||
# Verify minimum Docker version
|
||||
MIN_DOCKER_VERSION=24
|
||||
INSTALLED_DOCKER_VERSION=$(docker version --format '{{.Server.Version}}' 2>/dev/null | cut -d. -f1)
|
||||
wait_for_docker_daemon || true
|
||||
INSTALLED_DOCKER_VERSION=$(docker version --format '{{.Server.Version}}' 2>/dev/null | cut -d. -f1 || true)
|
||||
if [ -z "$INSTALLED_DOCKER_VERSION" ]; then
|
||||
warn "Could not determine Docker version. Please ensure Docker $MIN_DOCKER_VERSION+ is installed."
|
||||
elif [ "$INSTALLED_DOCKER_VERSION" -lt "$MIN_DOCKER_VERSION" ]; then
|
||||
@@ -1072,8 +1093,7 @@ if [ "$IS_COOLIFY_VOLUME_EXISTS" -eq 0 ]; then
|
||||
rm -f /data/coolify/ssh/keys/id.$CURRENT_USER@host.docker.internal.pub
|
||||
fi
|
||||
|
||||
chown -R 9999:root /data/coolify
|
||||
chmod -R 700 /data/coolify
|
||||
set_coolify_directory_permissions
|
||||
log "SSH key check completed"
|
||||
step_done "$SSH_KEY_DETAIL"
|
||||
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Runs the /data/coolify directory setup of the install script against a temporary data root.
|
||||
* chown is replaced with a logger because changing the owner to UID 9999 needs root.
|
||||
*
|
||||
* @return list<string> the paths that chown received
|
||||
*/
|
||||
function runInstallScriptDirectorySetup(string $scriptPath, string $dataRoot): array
|
||||
{
|
||||
$script = file_get_contents(dirname(__DIR__, 2).'/'.$scriptPath);
|
||||
preg_match('/^# Resource directories.*?^set_coolify_directory_permissions$/ms', $script, $matches);
|
||||
expect($matches)->not->toBeEmpty();
|
||||
|
||||
$setup = str_replace('/data/coolify', $dataRoot, $matches[0]);
|
||||
$chownLog = "{$dataRoot}.chown";
|
||||
$process = proc_open(
|
||||
['bash', '-c', "set -e\nchown() { printf '%s\\n' \"\$@\" >> ".escapeshellarg($chownLog)."; }\n{$setup}"],
|
||||
[1 => ['pipe', 'w'], 2 => ['pipe', 'w']],
|
||||
$pipes
|
||||
);
|
||||
$output = stream_get_contents($pipes[1]).stream_get_contents($pipes[2]);
|
||||
fclose($pipes[1]);
|
||||
fclose($pipes[2]);
|
||||
|
||||
expect(proc_close($process))->toBe(0, $output);
|
||||
|
||||
$chownedPaths = array_filter(file($chownLog, FILE_IGNORE_NEW_LINES), fn (string $argument) => str_starts_with($argument, '/'));
|
||||
unlink($chownLog);
|
||||
|
||||
return array_values(array_map(fn (string $path) => str_replace($dataRoot, '/data/coolify', $path), $chownedPaths));
|
||||
}
|
||||
|
||||
function installScriptFileMode(string $path): string
|
||||
{
|
||||
clearstatcache(true, $path);
|
||||
|
||||
return substr(sprintf('%o', fileperms($path)), -4);
|
||||
}
|
||||
|
||||
$installScripts = [
|
||||
'stable install' => ['scripts/install.sh', ['source', 'ssh', 'images']],
|
||||
'nightly install' => ['other/nightly/install.sh', ['source', 'ssh']],
|
||||
];
|
||||
$resourceDirectories = ['applications', 'databases', 'backups', 'services', 'proxy', 'sentinel'];
|
||||
|
||||
it('creates the data directories as uid 9999 with mode 700 on a fresh install', function (string $scriptPath, array $coolifyDirectories) use ($resourceDirectories) {
|
||||
$dataRoot = sys_get_temp_dir().'/coolify-install-permissions-'.bin2hex(random_bytes(6));
|
||||
|
||||
try {
|
||||
$chownedPaths = runInstallScriptDirectorySetup($scriptPath, $dataRoot);
|
||||
|
||||
foreach (['', ...array_map(fn ($directory) => "/{$directory}", $resourceDirectories), '/proxy/dynamic'] as $directory) {
|
||||
expect($chownedPaths)->toContain("/data/coolify{$directory}")
|
||||
->and(installScriptFileMode("{$dataRoot}{$directory}"))->toBe('0700');
|
||||
}
|
||||
foreach ($coolifyDirectories as $directory) {
|
||||
expect($chownedPaths)->toContain("/data/coolify/{$directory}")
|
||||
->and(installScriptFileMode("{$dataRoot}/{$directory}"))->toBe('0700');
|
||||
}
|
||||
} finally {
|
||||
exec('rm -rf '.escapeshellarg($dataRoot));
|
||||
}
|
||||
})->with($installScripts);
|
||||
|
||||
it('keeps existing resource directories and their content unchanged on an upgrade', function (string $scriptPath, array $coolifyDirectories) use ($resourceDirectories) {
|
||||
$dataRoot = sys_get_temp_dir().'/coolify-install-permissions-'.bin2hex(random_bytes(6));
|
||||
|
||||
try {
|
||||
// A non-root server user setup: the SSH user owns the tree with mode 750.
|
||||
mkdir($dataRoot, 0750);
|
||||
chmod($dataRoot, 0750);
|
||||
foreach ([...$resourceDirectories, 'proxy/dynamic', ...$coolifyDirectories] as $directory) {
|
||||
mkdir("{$dataRoot}/{$directory}/nested", 0755, true);
|
||||
chmod("{$dataRoot}/{$directory}", 0750);
|
||||
touch("{$dataRoot}/{$directory}/nested/file");
|
||||
chmod("{$dataRoot}/{$directory}/nested/file", 0644);
|
||||
}
|
||||
|
||||
$chownedPaths = runInstallScriptDirectorySetup($scriptPath, $dataRoot);
|
||||
|
||||
expect($chownedPaths)->toBe(array_map(fn ($directory) => "/data/coolify/{$directory}", $coolifyDirectories))
|
||||
->and(installScriptFileMode($dataRoot))->toBe('0750');
|
||||
foreach ([...$resourceDirectories, 'proxy/dynamic'] as $directory) {
|
||||
expect(installScriptFileMode("{$dataRoot}/{$directory}"))->toBe('0750')
|
||||
->and(installScriptFileMode("{$dataRoot}/{$directory}/nested"))->toBe('0755')
|
||||
->and(installScriptFileMode("{$dataRoot}/{$directory}/nested/file"))->toBe('0644');
|
||||
}
|
||||
foreach ($coolifyDirectories as $directory) {
|
||||
expect(installScriptFileMode("{$dataRoot}/{$directory}/nested"))->toBe('0700')
|
||||
->and(installScriptFileMode("{$dataRoot}/{$directory}/nested/file"))->toBe('0700');
|
||||
}
|
||||
} finally {
|
||||
exec('rm -rf '.escapeshellarg($dataRoot));
|
||||
}
|
||||
})->with($installScripts);
|
||||
|
||||
it('does not change the whole data directory recursively', function (string $scriptPath) {
|
||||
$script = file_get_contents(dirname(__DIR__, 2).'/'.$scriptPath);
|
||||
|
||||
expect($script)
|
||||
->not->toContain("chown -R 9999:root /data/coolify\n")
|
||||
->not->toContain("chmod -R 700 /data/coolify\n")
|
||||
->and(substr_count($script, "\nset_coolify_directory_permissions\n"))->toBe(2);
|
||||
})->with(array_map(fn (array $script) => $script[0], $installScripts));
|
||||
Reference in New Issue
Block a user