Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 44 additions & 5 deletions src/QuickInstall/Sandbox/DockerComposeWriter.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public function write(string $name, array $config): array
$this->writeFile($installConfig, $this->installConfig($name, $config));
$this->writeFile($compose, $this->compose($name, $config));
$this->writeFile($dockerfile, $this->dockerfile($config));
$this->writeFile($entrypoint, $this->entrypoint());
$this->writeFile($entrypoint, $this->entrypoint($config));
if ((PHP_OS_FAMILY !== 'Windows') && !chmod($entrypoint, 0755))
{
throw new RuntimeException("Unable to make entrypoint executable: $entrypoint");
Expand Down Expand Up @@ -253,9 +253,11 @@ private function aptSourceSetup(string $phpVersion): string
. " && ";
}

private function entrypoint(): string
private function entrypoint(array $config): string
{
return <<<'SH'
$ownershipCommand = $this->ownershipCommand($config);

$entrypoint = <<<'SH'
set -eu

if [ ! -f /var/www/html/common.php ]; then
Expand All @@ -265,7 +267,7 @@ private function entrypoint(): string
fi

cp -R /opt/phpbb-source/. /var/www/html/
chown -R www-data:www-data /var/www/html
__QUICKINSTALL_OWNERSHIP_COMMAND__
fi

if [ -f /var/www/html/config.php ] && [ ! -s /var/www/html/config.php ]; then
Expand All @@ -281,11 +283,48 @@ private function entrypoint(): string
echo "Host timezone '$QUICKINSTALL_BOARD_TIMEZONE' is unsupported by this PHP runtime; using UTC."
php /var/www/html/bin/phpbbcli.php config:set board_timezone UTC
fi
chown -R www-data:www-data /var/www/html
__QUICKINSTALL_OWNERSHIP_COMMAND__
fi

apache2-foreground
SH;

return str_replace('__QUICKINSTALL_OWNERSHIP_COMMAND__', $ownershipCommand, $entrypoint);
}

/** Builds an ownership command that does not enter host bind mounts. */
private function ownershipCommand(array $config): string
{
$mountTargets = [];
foreach (($config['extensions'] ?? []) as $name => $extension)
{
if (($extension['mode'] ?? '') === 'bind' && ($extension['source'] ?? '') !== '')
{
$mountTargets[] = '/var/www/html/ext/' . $name;
}
}
foreach (($config['styles'] ?? []) as $name => $style)
{
if (($style['mode'] ?? '') === 'bind' && ($style['source'] ?? '') !== '')
{
$mountTargets[] = '/var/www/html/styles/' . $name;
}
}

$command = 'find /var/www/html';
foreach ($mountTargets as $target)
{
$command .= " \\\n\t\t-path " . $this->shellString($target) . ' -prune -o';
}
$command .= " \\\n\t\t-exec chown www-data:www-data {} +";

return $command;
}

/** Quotes one string for the generated POSIX shell script. */
private function shellString(string $value): string
{
return "'" . str_replace("'", "'\"'\"'", $value) . "'";
}

private function databaseService(string $db, string $name, string $phpVersion): string
Expand Down
2 changes: 1 addition & 1 deletion src/QuickInstall/Sandbox/Project.php
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ public function appendBoard(array $board): void
$boards = $this->readJson('boards.json', []);
foreach (array_keys($boards) as $registeredName)
{
if ($registeredName !== $name && $this->namesEqual((string) $registeredName, $name))
if ((string) $registeredName !== $name && $this->namesEqual((string) $registeredName, $name))
{
throw new InvalidArgumentException("Board already exists: $registeredName. Board names are case-insensitive.");
}
Expand Down
39 changes: 39 additions & 0 deletions tests/Unit/DockerComposeWriterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,45 @@ public function testQuotesYamlSignificantInstallerValues(): void
self::assertStringContainsString(' name: "demo"', $installConfig);
}

public function testEntrypointExcludesBindMountsFromOwnershipChanges(): void
{
[, $paths] = $this->writeBoard('demo', [
'extensions' => [
'acme/bound' => ['mode' => 'bind', 'source' => '/tmp/acme-bound'],
'acme/copied' => ['mode' => 'copy', 'source' => '/tmp/acme-copied'],
'acme/missing' => ['mode' => 'bind', 'source' => ''],
],
'styles' => [
'bound style' => ['mode' => 'bind', 'source' => '/tmp/bound-style'],
'copied' => ['mode' => 'copy', 'source' => '/tmp/copied-style'],
],
]);

$entrypoint = file_get_contents($paths['entrypoint']);

self::assertSame(2, substr_count($entrypoint, 'find /var/www/html'));
self::assertSame(2, substr_count($entrypoint, "-path '/var/www/html/ext/acme/bound' -prune -o"));
self::assertSame(2, substr_count($entrypoint, "-path '/var/www/html/styles/bound style' -prune -o"));
self::assertStringNotContainsString('/var/www/html/ext/acme/copied', $entrypoint);
self::assertStringNotContainsString('/var/www/html/ext/acme/missing', $entrypoint);
self::assertStringNotContainsString('/var/www/html/styles/copied', $entrypoint);
self::assertSame(2, substr_count($entrypoint, '-exec chown www-data:www-data {} +'));
}

public function testEntrypointQuotesBindMountTargetsForPosixShell(): void
{
[, $paths] = $this->writeBoard('demo', [
'styles' => [
"designer's style" => ['mode' => 'bind', 'source' => '/tmp/designer-style'],
],
]);

self::assertStringContainsString(
"-path '/var/www/html/styles/designer'\"'\"'s style' -prune -o",
file_get_contents($paths['entrypoint'])
);
}

private function config(array $overrides = []): array
{
return $overrides + [
Expand Down
19 changes: 19 additions & 0 deletions tests/Unit/ProjectTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,25 @@ public function testBoardRegistryRejectsNamesThatDifferOnlyByCase(): void
$project->appendBoard(['name' => 'Demo']);
}

public function testBoardRegistryUpdatesNumericName(): void
{
$project = new Project($this->createTempProjectRoot());
$project->init();
$project->appendBoard(['name' => '322', 'extensions' => []]);

$project->appendBoard([
'name' => '322',
'extensions' => [
'vse/abbc3' => ['mode' => 'bind', 'source' => '/tmp/abbc3'],
],
]);

self::assertSame(
['mode' => 'bind', 'source' => '/tmp/abbc3'],
$project->board('322')['extensions']['vse/abbc3']
);
}

public function testDeleteTreeRefusesPathOutsideWorkspace(): void
{
$root = $this->createTempProjectRoot();
Expand Down