From 5132f183625ec9477c4db44d859b0d166ed5bf2b Mon Sep 17 00:00:00 2001 From: James Titcumb Date: Wed, 17 Jun 2026 10:19:00 +0100 Subject: [PATCH 1/3] 579: resolve symlinks for fullPathToSelf, and prevent Brew installs from using self-update --- src/Command/SelfUpdateCommand.php | 13 ++- src/SelfManage/Update/IsBrewInstallation.php | 19 ++++ .../Update/IsBrewInstallationTest.php | 104 ++++++++++++++++++ 3 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 src/SelfManage/Update/IsBrewInstallation.php create mode 100644 test/unit/SelfManage/Update/IsBrewInstallationTest.php diff --git a/src/Command/SelfUpdateCommand.php b/src/Command/SelfUpdateCommand.php index 31ff5798..bc7897ca 100644 --- a/src/Command/SelfUpdateCommand.php +++ b/src/Command/SelfUpdateCommand.php @@ -14,6 +14,7 @@ use Php\Pie\Platform; use Php\Pie\SelfManage\Update\Channel; use Php\Pie\SelfManage\Update\FetchPieReleaseFromGitHub; +use Php\Pie\SelfManage\Update\IsBrewInstallation; use Php\Pie\SelfManage\Update\ReleaseIsNewer; use Php\Pie\SelfManage\Update\ReleaseMetadata; use Php\Pie\SelfManage\Verify\FailedToVerifyRelease; @@ -30,6 +31,8 @@ use Throwable; use function file_get_contents; +use function is_link; +use function realpath; use function sprintf; use function unlink; @@ -87,6 +90,15 @@ public function execute(InputInterface $input, OutputInterface $output): int return Command::FAILURE; } + $originalPathToSelf = ($this->fullPathToSelf)(); + $fullPathToSelf = is_link($originalPathToSelf) ? (realpath($originalPathToSelf) ?: $originalPathToSelf) : $originalPathToSelf; + + if ((new IsBrewInstallation())($fullPathToSelf, $originalPathToSelf)) { + $this->io->writeError('Aborting! PIE was installed with Brew, you should upgrade with `brew upgrade pie`.'); + + return Command::FAILURE; + } + $settings = new Settings(Platform::getPieBaseWorkingDirectory()); $updateChannel = $settings->updateChannel(); @@ -197,7 +209,6 @@ public function execute(InputInterface $input, OutputInterface $output): int return Command::FAILURE; } - $fullPathToSelf = ($this->fullPathToSelf)(); $this->io->write( sprintf('Writing new version to %s', $fullPathToSelf), verbosity: IOInterface::VERBOSE, diff --git a/src/SelfManage/Update/IsBrewInstallation.php b/src/SelfManage/Update/IsBrewInstallation.php new file mode 100644 index 00000000..ab951423 --- /dev/null +++ b/src/SelfManage/Update/IsBrewInstallation.php @@ -0,0 +1,19 @@ + */ + public static function pathProvider(): array + { + return [ + 'regular-path' => ['/home/user/.local/bin/pie.phar', '/home/user/.local/bin/pie.phar', false], + 'both-regular-usr-local' => ['/usr/local/bin/pie', '/usr/local/bin/pie', false], + 'intel-mac-cellar-direct' => ['/usr/local/Cellar/pie/1.0/bin/pie', '/usr/local/Cellar/pie/1.0/bin/pie', true], + 'apple-silicon-cellar-direct' => ['/opt/homebrew/Cellar/pie/1.0/bin/pie', '/opt/homebrew/Cellar/pie/1.0/bin/pie', true], + 'resolved-is-cellar' => ['/opt/homebrew/Cellar/pie/1.0/bin/pie', '/usr/local/bin/pie', true], + 'original-is-cellar' => ['/usr/local/bin/pie', '/opt/homebrew/Cellar/pie/1.0/bin/pie', true], + ]; + } + + #[DataProvider('pathProvider')] + public function testIsBrewInstallationWithPaths( + string $resolvedPath, + string $originalPath, + bool $expected, + ): void { + self::assertSame($expected, (new IsBrewInstallation())($resolvedPath, $originalPath)); + } + + public function testSymlinkAtRegularPathPointingIntoBrewCellarIsDetected(): void + { + $tmpDir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid('pie_brew_test_', true); + $cellarFile = $tmpDir . '/opt/homebrew/Cellar/pie/1.0/pie.phar'; + $symlinkPath = $tmpDir . '/pie'; + + mkdir($tmpDir . '/opt/homebrew/Cellar/pie/1.0', 0777, true); + touch($cellarFile); + symlink($cellarFile, $symlinkPath); + + try { + $resolvedPath = realpath($symlinkPath); + self::assertIsString($resolvedPath); + self::assertTrue((new IsBrewInstallation())($resolvedPath, $symlinkPath)); + } finally { + (new Filesystem())->remove($tmpDir); + } + } + + public function testSymlinkAtRegularPathPointingToNonBrewPathIsNotDetected(): void + { + $tmpDir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid('pie_brew_test_', true); + $regularFile = $tmpDir . '/regular/pie.phar'; + $symlinkPath = $tmpDir . '/pie'; + + mkdir($tmpDir . '/regular', 0777, true); + touch($regularFile); + symlink($regularFile, $symlinkPath); + + try { + $resolvedPath = realpath($symlinkPath); + self::assertIsString($resolvedPath); + self::assertFalse((new IsBrewInstallation())($resolvedPath, $symlinkPath)); + } finally { + (new Filesystem())->remove($tmpDir); + } + } + + public function testSymlinkInCellarPointingToRegularPathIsDetectedViaOriginalPath(): void + { + $tmpDir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid('pie_brew_test_', true); + $regularFile = $tmpDir . '/regular/pie.phar'; + $symlinkPath = $tmpDir . '/opt/homebrew/Cellar/pie/1.0/pie'; + + mkdir($tmpDir . '/regular', 0777, true); + mkdir($tmpDir . '/opt/homebrew/Cellar/pie/1.0', 0777, true); + touch($regularFile); + symlink($regularFile, $symlinkPath); + + try { + $resolvedPath = realpath($symlinkPath); + self::assertIsString($resolvedPath); + self::assertTrue((new IsBrewInstallation())($resolvedPath, $symlinkPath)); + } finally { + (new Filesystem())->remove($tmpDir); + } + } +} From 8dbd9927d2abbc5def74d954bfda6289e05e6180 Mon Sep 17 00:00:00 2001 From: James Titcumb Date: Wed, 17 Jun 2026 10:55:48 +0100 Subject: [PATCH 2/3] 579: alpine uses php8.5 now --- test/end-to-end/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/end-to-end/Dockerfile b/test/end-to-end/Dockerfile index a827bb98..375978f6 100644 --- a/test/end-to-end/Dockerfile +++ b/test/end-to-end/Dockerfile @@ -50,7 +50,7 @@ COPY --from=build_pie_phar /app/pie.phar /usr/local/bin/pie RUN pie install -v --auto-install-system-dependencies php/sodium FROM alpine AS test_pie_installs_system_deps_on_alpine -RUN apk add php php-phar php-mbstring php-iconv php-openssl bzip2-dev libbz2 build-base autoconf bison re2c libtool php84-dev +RUN apk add php php-phar php-mbstring php-iconv php-openssl bzip2-dev libbz2 build-base autoconf bison re2c libtool php85-dev COPY --from=build_pie_phar /app/pie.phar /usr/local/bin/pie RUN pie install -v --auto-install-system-dependencies php/sodium From 854c9c4d683d9766fe0a5da2c6dc94170b7419d6 Mon Sep 17 00:00:00 2001 From: James Titcumb Date: Wed, 17 Jun 2026 11:11:22 +0100 Subject: [PATCH 3/3] 579: only run path checking brew tests on Linux --- test/unit/SelfManage/Update/IsBrewInstallationTest.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/unit/SelfManage/Update/IsBrewInstallationTest.php b/test/unit/SelfManage/Update/IsBrewInstallationTest.php index c6f6bde1..7c640ae7 100644 --- a/test/unit/SelfManage/Update/IsBrewInstallationTest.php +++ b/test/unit/SelfManage/Update/IsBrewInstallationTest.php @@ -8,6 +8,7 @@ use Php\Pie\SelfManage\Update\IsBrewInstallation; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\RequiresOperatingSystemFamily; use PHPUnit\Framework\TestCase; use function mkdir; @@ -44,6 +45,7 @@ public function testIsBrewInstallationWithPaths( self::assertSame($expected, (new IsBrewInstallation())($resolvedPath, $originalPath)); } + #[RequiresOperatingSystemFamily('Linux')] public function testSymlinkAtRegularPathPointingIntoBrewCellarIsDetected(): void { $tmpDir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid('pie_brew_test_', true); @@ -63,6 +65,7 @@ public function testSymlinkAtRegularPathPointingIntoBrewCellarIsDetected(): void } } + #[RequiresOperatingSystemFamily('Linux')] public function testSymlinkAtRegularPathPointingToNonBrewPathIsNotDetected(): void { $tmpDir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid('pie_brew_test_', true); @@ -82,6 +85,7 @@ public function testSymlinkAtRegularPathPointingToNonBrewPathIsNotDetected(): vo } } + #[RequiresOperatingSystemFamily('Linux')] public function testSymlinkInCellarPointingToRegularPathIsDetectedViaOriginalPath(): void { $tmpDir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid('pie_brew_test_', true);