├── LICENSE.md ├── README.md ├── RELEASE.md ├── cli ├── Valet │ ├── Brew.php │ ├── Cloudflared.php │ ├── CommandLine.php │ ├── Composer.php │ ├── Configuration.php │ ├── Diagnose.php │ ├── DnsMasq.php │ ├── Drivers │ │ ├── BasicValetDriver.php │ │ ├── BasicWithPublicValetDriver.php │ │ ├── LaravelValetDriver.php │ │ ├── Specific │ │ │ ├── BedrockValetDriver.php │ │ │ ├── CakeValetDriver.php │ │ │ ├── Concrete5ValetDriver.php │ │ │ ├── ContaoValetDriver.php │ │ │ ├── CraftValetDriver.php │ │ │ ├── DrupalValetDriver.php │ │ │ ├── JigsawValetDriver.php │ │ │ ├── JoomlaValetDriver.php │ │ │ ├── KatanaValetDriver.php │ │ │ ├── KirbyValetDriver.php │ │ │ ├── Magento2ValetDriver.php │ │ │ ├── NeosValetDriver.php │ │ │ ├── NetteValetDriver.php │ │ │ ├── RadicleValetDriver.php │ │ │ ├── SculpinValetDriver.php │ │ │ ├── StatamicV1ValetDriver.php │ │ │ ├── StatamicV2ValetDriver.php │ │ │ ├── StatamicValetDriver.php │ │ │ ├── SymfonyValetDriver.php │ │ │ ├── Typo3ValetDriver.php │ │ │ └── WordPressValetDriver.php │ │ └── ValetDriver.php │ ├── Expose.php │ ├── Filesystem.php │ ├── Nginx.php │ ├── Ngrok.php │ ├── PhpFpm.php │ ├── Server.php │ ├── Site.php │ ├── Status.php │ ├── Upgrader.php │ └── Valet.php ├── app.php ├── includes │ ├── compatibility.php │ ├── facades.php │ ├── helpers.php │ └── require-drivers.php ├── stubs │ ├── SampleValetDriver.php │ ├── Valet3SampleValetDriver.php │ ├── etc-dnsmasq-valet.conf │ ├── etc-phpfpm-error_log.ini │ ├── etc-phpfpm-valet.conf │ ├── fastcgi_params │ ├── loopback.plist │ ├── nginx.conf │ ├── openssl.conf │ ├── php-memory-limits.ini │ ├── proxy.valet.conf │ ├── secure.proxy.valet-legacy.conf │ ├── secure.proxy.valet.conf │ ├── secure.valet-legacy.conf │ ├── secure.valet.conf │ ├── site.valet.conf │ └── valet.conf ├── templates │ └── 404.html └── valet.php ├── composer.json ├── find-usable-php.php ├── server.php └── valet /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | Build Status 5 | Total Downloads 6 | Latest Stable Version 7 | License 8 |

9 | 10 | ## Introduction 11 | 12 | Valet is a Laravel development environment for Mac minimalists. No Vagrant, no `/etc/hosts` file. You can even share your sites publicly using local tunnels. _Yeah, we like it too._ 13 | 14 | Laravel Valet configures your Mac to always run Nginx in the background when your machine starts. Then, using [DnsMasq](https://en.wikipedia.org/wiki/Dnsmasq), Valet proxies all requests on the `*.test` domain to point to sites installed on your local machine. 15 | 16 | In other words, a blazing fast Laravel development environment that uses roughly 7mb of RAM. Valet isn't a complete replacement for Vagrant or Homestead, but provides a great alternative if you want flexible basics, prefer extreme speed, or are working on a machine with a limited amount of RAM. 17 | 18 | ## Official Documentation 19 | 20 | Documentation for Valet can be found on the [Laravel website](https://laravel.com/docs/valet). 21 | 22 | ## Contributing 23 | 24 | Thank you for considering contributing to Valet! You can read the contribution guide [here](.github/CONTRIBUTING.md). 25 | 26 | ## Code of Conduct 27 | 28 | In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct). 29 | 30 | ## Security Vulnerabilities 31 | 32 | Please review [our security policy](https://github.com/laravel/valet/security/policy) on how to report security vulnerabilities. 33 | 34 | ## License 35 | 36 | Laravel Valet is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT). 37 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # Release Instructions 2 | 3 | 1. Update the `$version` variable in [`app.php`](./cli/app.php) and commit it 4 | 2. [Create a new GitHub release](https://github.com/laravel/valet/releases/new) for this version with the release notes 5 | -------------------------------------------------------------------------------- /cli/Valet/Brew.php: -------------------------------------------------------------------------------- 1 | cli->runAsUser("brew info $formula --json=v2"); 50 | 51 | // should be a json response, but if not installed then "Error: No available formula ..." 52 | if (starts_with($result, 'Error: No')) { 53 | return false; 54 | } 55 | 56 | $details = json_decode($result, true); 57 | 58 | if (! empty($details['formulae'])) { 59 | return ! empty($details['formulae'][0]['installed']); 60 | } 61 | 62 | if (! empty($details['casks'])) { 63 | return ! is_null($details['casks'][0]['installed']); 64 | } 65 | 66 | return false; 67 | } 68 | 69 | /** 70 | * Determine if a compatible PHP version is Homebrewed. 71 | */ 72 | public function hasInstalledPhp(): bool 73 | { 74 | $installed = $this->installedPhpFormulae()->first(function ($formula) { 75 | return $this->supportedPhpVersions()->contains($formula); 76 | }); 77 | 78 | return ! empty($installed); 79 | } 80 | 81 | /** 82 | * Get a list of supported PHP versions. 83 | */ 84 | public function supportedPhpVersions(): Collection 85 | { 86 | return collect(static::SUPPORTED_PHP_VERSIONS); 87 | } 88 | 89 | /** 90 | * Get a list of disabled/limited PHP versions. 91 | */ 92 | public function limitedPhpVersions(): Collection 93 | { 94 | return collect(static::LIMITED_PHP_VERSIONS); 95 | } 96 | 97 | /** 98 | * Get a list of installed PHP formulae. 99 | */ 100 | public function installedPhpFormulae(): Collection 101 | { 102 | return collect( 103 | explode(PHP_EOL, $this->cli->runAsUser('brew list --formula | grep php')) 104 | ); 105 | } 106 | 107 | /** 108 | * Get the aliased formula version from Homebrew. 109 | */ 110 | public function determineAliasedVersion($formula): string 111 | { 112 | $details = json_decode($this->cli->runAsUser("brew info $formula --json")); 113 | 114 | if (! empty($details[0]->aliases[0])) { 115 | return $details[0]->aliases[0]; 116 | } 117 | 118 | return 'ERROR - NO BREW ALIAS FOUND'; 119 | } 120 | 121 | /** 122 | * Determine if a compatible nginx version is Homebrewed. 123 | */ 124 | public function hasInstalledNginx(): bool 125 | { 126 | return $this->installed('nginx') 127 | || $this->installed('nginx-full'); 128 | } 129 | 130 | /** 131 | * Return name of the nginx service installed via Homebrew. 132 | */ 133 | public function nginxServiceName(): string 134 | { 135 | return $this->installed('nginx-full') ? 'nginx-full' : 'nginx'; 136 | } 137 | 138 | /** 139 | * Ensure that the given formula is installed. 140 | */ 141 | public function ensureInstalled(string $formula, array $options = [], array $taps = []): void 142 | { 143 | if (! $this->installed($formula)) { 144 | $this->installOrFail($formula, $options, $taps); 145 | } 146 | } 147 | 148 | /** 149 | * Install the given formula and throw an exception on failure. 150 | */ 151 | public function installOrFail(string $formula, array $options = [], array $taps = []): void 152 | { 153 | info("Installing {$formula}..."); 154 | 155 | if (count($taps) > 0) { 156 | $this->tap($taps); 157 | } 158 | 159 | output('['.$formula.'] is not installed, installing it now via Brew... 🍻'); 160 | 161 | if ($this->limitedPhpVersions()->contains($formula)) { 162 | $formula = 'shivammathur/php/'.$formula; 163 | warning('Note: older PHP versions may take 10+ minutes to compile from source. Please wait ...'); 164 | } 165 | 166 | $this->cli->runAsUser(trim(static::BREW_DISABLE_AUTO_CLEANUP.' brew install '.$formula.' '.implode(' ', $options)), function ($exitCode, $errorOutput) use ($formula) { 167 | output($errorOutput); 168 | 169 | throw new DomainException('Brew was unable to install ['.$formula.'].'); 170 | }); 171 | } 172 | 173 | /** 174 | * Tap the given formulas. 175 | */ 176 | public function tap($formulas): void 177 | { 178 | $formulas = is_array($formulas) ? $formulas : func_get_args(); 179 | 180 | foreach ($formulas as $formula) { 181 | $this->cli->passthru(static::BREW_DISABLE_AUTO_CLEANUP.' sudo -u "'.user().'" brew tap '.$formula); 182 | } 183 | } 184 | 185 | /** 186 | * Restart the given Homebrew services. 187 | */ 188 | public function restartService($services): void 189 | { 190 | $services = is_array($services) ? $services : func_get_args(); 191 | 192 | foreach ($services as $service) { 193 | if ($this->installed($service)) { 194 | info("Restarting {$service}..."); 195 | 196 | // first we ensure that the service is not incorrectly running as non-root 197 | $this->cli->quietly('brew services stop '.$service); 198 | // stop the actual/correct sudo version 199 | $this->cli->quietly('sudo brew services stop '.$service); 200 | // start correctly as root 201 | $this->cli->quietly('sudo brew services start '.$service); 202 | } 203 | } 204 | } 205 | 206 | /** 207 | * Stop the given Homebrew services. 208 | */ 209 | public function stopService($services): void 210 | { 211 | $services = is_array($services) ? $services : func_get_args(); 212 | 213 | foreach ($services as $service) { 214 | if ($this->installed($service)) { 215 | info("Stopping {$service}..."); 216 | 217 | // first we ensure that the service is not incorrectly running as non-root 218 | $this->cli->quietly('brew services stop '.$service); 219 | 220 | // stop the sudo version 221 | $this->cli->quietly('sudo brew services stop '.$service); 222 | 223 | // restore folder permissions: for each brew formula, these directories are owned by root:admin 224 | $directories = [ 225 | BREW_PREFIX."/Cellar/$service", 226 | BREW_PREFIX."/opt/$service", 227 | BREW_PREFIX."/var/homebrew/linked/$service", 228 | ]; 229 | 230 | $whoami = get_current_user(); 231 | 232 | foreach ($directories as $directory) { 233 | $this->cli->quietly("sudo chown -R {$whoami}:admin '$directory'"); 234 | } 235 | } 236 | } 237 | } 238 | 239 | /** 240 | * Determine if php is currently linked. 241 | */ 242 | public function hasLinkedPhp(): bool 243 | { 244 | return $this->files->isLink(BREW_PREFIX.'/bin/php'); 245 | } 246 | 247 | /** 248 | * Get the linked php parsed. 249 | */ 250 | public function getParsedLinkedPhp(): array 251 | { 252 | if (! $this->hasLinkedPhp()) { 253 | throw new DomainException('Homebrew PHP appears not to be linked. Please run [valet use php@X.Y]'); 254 | } 255 | 256 | $resolvedPath = $this->files->readLink(BREW_PREFIX.'/bin/php'); 257 | 258 | return $this->parsePhpPath($resolvedPath); 259 | } 260 | 261 | /** 262 | * Gets the currently linked formula by identifying the symlink in the homebrew bin directory. 263 | * Different to ->linkedPhp() in that this will just get the linked directory name, 264 | * whether that is php, php74 or php@7.4. 265 | */ 266 | public function getLinkedPhpFormula(): string 267 | { 268 | $matches = $this->getParsedLinkedPhp(); 269 | 270 | return $matches[1].$matches[2]; 271 | } 272 | 273 | /** 274 | * Determine which version of PHP is linked in Homebrew. 275 | */ 276 | public function linkedPhp(): string 277 | { 278 | $matches = $this->getParsedLinkedPhp(); 279 | $resolvedPhpVersion = $matches[3] ?: $matches[2]; 280 | 281 | return $this->supportedPhpVersions()->first( 282 | function ($version) use ($resolvedPhpVersion) { 283 | return $this->arePhpVersionsEqual($resolvedPhpVersion, $version); 284 | }, function () use ($resolvedPhpVersion) { 285 | throw new DomainException("Unable to determine linked PHP when parsing '$resolvedPhpVersion'"); 286 | }); 287 | } 288 | 289 | /** 290 | * Extract PHP executable path from PHP Version. 291 | * 292 | * @param string|null $phpVersion For example, "php@8.1" 293 | */ 294 | public function getPhpExecutablePath(?string $phpVersion = null): string 295 | { 296 | if (! $phpVersion) { 297 | return BREW_PREFIX.'/bin/php'; 298 | } 299 | 300 | $phpVersion = PhpFpm::normalizePhpVersion($phpVersion); 301 | 302 | // Check the default `/opt/homebrew/opt/php@8.1/bin/php` location first 303 | if ($this->files->exists(BREW_PREFIX."/opt/{$phpVersion}/bin/php")) { 304 | return BREW_PREFIX."/opt/{$phpVersion}/bin/php"; 305 | } 306 | 307 | // Check the `/opt/homebrew/opt/php71/bin/php` location for older installations 308 | $phpVersion = str_replace(['@', '.'], '', $phpVersion); // php@8.1 to php81 309 | if ($this->files->exists(BREW_PREFIX."/opt/{$phpVersion}/bin/php")) { 310 | return BREW_PREFIX."/opt/{$phpVersion}/bin/php"; 311 | } 312 | 313 | // Check if the default PHP is the version we are looking for 314 | if ($this->files->isLink(BREW_PREFIX.'/opt/php')) { 315 | $resolvedPath = $this->files->readLink(BREW_PREFIX.'/opt/php'); 316 | $matches = $this->parsePhpPath($resolvedPath); 317 | $resolvedPhpVersion = $matches[3] ?: $matches[2]; 318 | 319 | if ($this->arePhpVersionsEqual($resolvedPhpVersion, $phpVersion)) { 320 | return BREW_PREFIX.'/opt/php/bin/php'; 321 | } 322 | } 323 | 324 | return BREW_PREFIX.'/bin/php'; 325 | } 326 | 327 | /** 328 | * Restart the linked PHP-FPM Homebrew service. 329 | */ 330 | public function restartLinkedPhp(): void 331 | { 332 | $this->restartService($this->getLinkedPhpFormula()); 333 | } 334 | 335 | /** 336 | * Create the "sudoers.d" entry for running Brew. 337 | */ 338 | public function createSudoersEntry(): void 339 | { 340 | $this->files->ensureDirExists('/etc/sudoers.d'); 341 | 342 | $this->files->put('/etc/sudoers.d/brew', 'Cmnd_Alias BREW = '.BREW_PREFIX.'/bin/brew * 343 | %admin ALL=(root) NOPASSWD:SETENV: BREW'.PHP_EOL); 344 | } 345 | 346 | /** 347 | * Remove the "sudoers.d" entry for running Brew. 348 | */ 349 | public function removeSudoersEntry(): void 350 | { 351 | $this->cli->quietly('rm /etc/sudoers.d/brew'); 352 | } 353 | 354 | /** 355 | * Link passed formula. 356 | */ 357 | public function link(string $formula, bool $force = false): string 358 | { 359 | return $this->cli->runAsUser( 360 | sprintf('brew link %s%s', $formula, $force ? ' --force' : ''), 361 | function ($exitCode, $errorOutput) use ($formula) { 362 | output($errorOutput); 363 | 364 | throw new DomainException('Brew was unable to link ['.$formula.'].'); 365 | } 366 | ); 367 | } 368 | 369 | /** 370 | * Unlink passed formula. 371 | */ 372 | public function unlink(string $formula): string 373 | { 374 | return $this->cli->runAsUser( 375 | sprintf('brew unlink %s', $formula), 376 | function ($exitCode, $errorOutput) use ($formula) { 377 | output($errorOutput); 378 | 379 | throw new DomainException('Brew was unable to unlink ['.$formula.'].'); 380 | } 381 | ); 382 | } 383 | 384 | /** 385 | * Get all the currently running brew services. 386 | */ 387 | public function getAllRunningServices(): Collection 388 | { 389 | return $this->getRunningServicesAsRoot() 390 | ->concat($this->getRunningServicesAsUser()) 391 | ->unique(); 392 | } 393 | 394 | /** 395 | * Get the currently running brew services as root. 396 | * i.e. /Library/LaunchDaemons (started at boot). 397 | */ 398 | public function getRunningServicesAsRoot(): Collection 399 | { 400 | return $this->getRunningServices(); 401 | } 402 | 403 | /** 404 | * Get the currently running brew services. 405 | * i.e. ~/Library/LaunchAgents (started at login). 406 | */ 407 | public function getRunningServicesAsUser(): Collection 408 | { 409 | return $this->getRunningServices(true); 410 | } 411 | 412 | /** 413 | * Get the currently running brew services. 414 | */ 415 | public function getRunningServices(bool $asUser = false): Collection 416 | { 417 | $command = 'brew services list | grep started | awk \'{ print $1; }\''; 418 | $onError = function ($exitCode, $errorOutput) { 419 | output($errorOutput); 420 | 421 | throw new DomainException('Brew was unable to check which services are running.'); 422 | }; 423 | 424 | return collect(array_filter(explode(PHP_EOL, $asUser 425 | ? $this->cli->runAsUser($command, $onError) 426 | : $this->cli->run('sudo '.$command, $onError) 427 | ))); 428 | } 429 | 430 | /** 431 | * Tell Homebrew to forcefully remove all PHP versions that Valet supports. 432 | */ 433 | public function uninstallAllPhpVersions(): string 434 | { 435 | $this->supportedPhpVersions()->each(function ($formula) { 436 | $this->uninstallFormula($formula); 437 | }); 438 | 439 | return 'PHP versions removed.'; 440 | } 441 | 442 | /** 443 | * Uninstall a Homebrew app by formula name. 444 | */ 445 | public function uninstallFormula(string $formula): void 446 | { 447 | $this->cli->runAsUser(static::BREW_DISABLE_AUTO_CLEANUP.' brew uninstall --force '.$formula); 448 | $this->cli->run('rm -rf '.BREW_PREFIX.'/Cellar/'.$formula); 449 | } 450 | 451 | /** 452 | * Run Homebrew's cleanup commands. 453 | */ 454 | public function cleanupBrew(): string 455 | { 456 | return $this->cli->runAsUser( 457 | 'brew cleanup && brew services cleanup', 458 | function ($exitCode, $errorOutput) { 459 | output($errorOutput); 460 | } 461 | ); 462 | } 463 | 464 | /** 465 | * Parse homebrew PHP Path. 466 | */ 467 | public function parsePhpPath(string $resolvedPath): array 468 | { 469 | /** 470 | * Typical homebrew path resolutions are like: 471 | * "../Cellar/php@7.4/7.4.13/bin/php" 472 | * or older styles: 473 | * "../Cellar/php/7.4.9_2/bin/php 474 | * "../Cellar/php55/bin/php. 475 | */ 476 | preg_match('~\w{3,}/(php)(@?\d\.?\d)?/(\d\.\d)?([_\d\.]*)?/?\w{3,}~', $resolvedPath, $matches); 477 | 478 | return $matches; 479 | } 480 | 481 | /** 482 | * Check if two PHP versions are equal. 483 | */ 484 | public function arePhpVersionsEqual(string $versionA, string $versionB): bool 485 | { 486 | $versionANormalized = preg_replace('/[^\d]/', '', $versionA); 487 | $versionBNormalized = preg_replace('/[^\d]/', '', $versionB); 488 | 489 | return $versionANormalized === $versionBNormalized; 490 | } 491 | } 492 | -------------------------------------------------------------------------------- /cli/Valet/Cloudflared.php: -------------------------------------------------------------------------------- 1 | cli->run('pgrep -fl cloudflared'))) as $process) { 18 | // Get the URL shared in this process 19 | preg_match('/(?\d+)\s.+--http-host-header\s(?[^\s]+).*/', $process, $pgrepMatches); 20 | 21 | if (! array_key_exists('domain', $pgrepMatches) || ! array_key_exists('pid', $pgrepMatches)) { 22 | continue; 23 | } 24 | 25 | if ($pgrepMatches['domain'] !== $domain) { 26 | continue; 27 | } 28 | 29 | // Get the localhost URL for the metrics server 30 | $lsof = $this->cli->run("lsof -iTCP -P -a -p {$pgrepMatches['pid']}"); 31 | preg_match('/TCP\s(?[^\s]+:\d+)\s\(LISTEN\)/', $lsof, $lsofMatches); 32 | 33 | if (! array_key_exists('server', $lsofMatches)) { 34 | continue; 35 | } 36 | 37 | try { 38 | // Get the cloudflared share URL from the metrics server output 39 | $body = (new Client)->get("http://{$lsofMatches['server']}/metrics")->getBody(); 40 | preg_match('/userHostname="(?.+)"/', $body->getContents(), $userHostnameMatches); 41 | } catch (\Exception $e) { 42 | return false; 43 | } 44 | 45 | if (array_key_exists('url', $userHostnameMatches)) { 46 | return $userHostnameMatches['url']; 47 | } 48 | } 49 | 50 | return false; 51 | } 52 | 53 | /** 54 | * Return whether cloudflared is installed. 55 | */ 56 | public function installed(): bool 57 | { 58 | return $this->brew->installed('cloudflared'); 59 | } 60 | 61 | /** 62 | * Make sure cloudflared is installed. 63 | */ 64 | public function ensureInstalled(): void 65 | { 66 | $this->brew->ensureInstalled('cloudflared'); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /cli/Valet/CommandLine.php: -------------------------------------------------------------------------------- 1 | runCommand($command.' > /dev/null 2>&1'); 15 | } 16 | 17 | /** 18 | * Simple global function to run commands. 19 | */ 20 | public function quietlyAsUser(string $command): void 21 | { 22 | $this->quietly('sudo -u "'.user().'" '.$command.' > /dev/null 2>&1'); 23 | } 24 | 25 | /** 26 | * Pass the command to the command line and display the output. 27 | */ 28 | public function passthru(string $command): void 29 | { 30 | passthru($command); 31 | } 32 | 33 | /** 34 | * Run the given command as the non-root user. 35 | */ 36 | public function run(string $command, ?callable $onError = null): string 37 | { 38 | return $this->runCommand($command, $onError); 39 | } 40 | 41 | /** 42 | * Run the given command. 43 | */ 44 | public function runAsUser(string $command, ?callable $onError = null): string 45 | { 46 | return $this->runCommand('sudo -u "'.user().'" '.$command, $onError); 47 | } 48 | 49 | /** 50 | * Run the given command. 51 | */ 52 | public function runCommand(string $command, ?callable $onError = null): string 53 | { 54 | $onError = $onError ?: function () {}; 55 | 56 | // Symfony's 4.x Process component has deprecated passing a command string 57 | // to the constructor, but older versions (which Valet's Composer 58 | // constraints allow) don't have the fromShellCommandLine method. 59 | // For more information, see: https://github.com/laravel/valet/pull/761 60 | if (method_exists(Process::class, 'fromShellCommandline')) { 61 | $process = Process::fromShellCommandline($command); 62 | } else { 63 | $process = new Process($command); 64 | } 65 | 66 | $processOutput = ''; 67 | $process->setTimeout(null)->run(function ($type, $line) use (&$processOutput) { 68 | $processOutput .= $line; 69 | }); 70 | 71 | if ($process->getExitCode() > 0) { 72 | $onError($process->getExitCode(), $processOutput); 73 | } 74 | 75 | return $processOutput; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /cli/Valet/Composer.php: -------------------------------------------------------------------------------- 1 | cli->runAsUser("composer global show --format json -- $namespacedPackage"); 14 | 15 | if (str_contains($result, 'InvalidArgumentException') && str_contains($result, 'not found')) { 16 | return false; 17 | } 18 | 19 | if (starts_with($result, 'Changed current')) { 20 | $result = strstr($result, '{'); 21 | } 22 | 23 | $details = json_decode($result, true); 24 | 25 | return ! empty($details); 26 | } 27 | 28 | public function installOrFail(string $namespacedPackage): void 29 | { 30 | info('['.$namespacedPackage.'] is not installed, installing it now via Composer... 🎼'); 31 | 32 | $this->cli->runAsUser(('composer global require '.$namespacedPackage), function ($exitCode, $errorOutput) use ($namespacedPackage) { 33 | output($errorOutput); 34 | 35 | throw new DomainException('Composer was unable to install ['.$namespacedPackage.'].'); 36 | }); 37 | } 38 | 39 | public function installedVersion(string $namespacedPackage): ?string 40 | { 41 | $result = $this->cli->runAsUser("composer global show --format json -- $namespacedPackage"); 42 | 43 | if (str_contains($result, 'InvalidArgumentException') && str_contains($result, 'not found')) { 44 | return null; 45 | } 46 | 47 | if (starts_with($result, 'Changed current')) { 48 | $result = strstr($result, '{'); 49 | } 50 | 51 | $details = json_decode($result, true); 52 | $versions = $details['versions']; 53 | 54 | return reset($versions); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /cli/Valet/Configuration.php: -------------------------------------------------------------------------------- 1 | createConfigurationDirectory(); 15 | $this->createDriversDirectory(); 16 | $this->createSitesDirectory(); 17 | $this->createLogDirectory(); 18 | $this->createCertificatesDirectory(); 19 | $this->ensureBaseConfiguration(); 20 | 21 | $this->files->chown($this->path(), user()); 22 | } 23 | 24 | /** 25 | * Forcefully delete the Valet home configuration directory and contents. 26 | */ 27 | public function uninstall(): void 28 | { 29 | $this->files->unlink(VALET_HOME_PATH); 30 | } 31 | 32 | /** 33 | * Create the Valet configuration directory. 34 | */ 35 | public function createConfigurationDirectory(): void 36 | { 37 | $this->files->ensureDirExists(preg_replace('~/valet$~', '', VALET_HOME_PATH), user()); 38 | $this->files->ensureDirExists(VALET_HOME_PATH, user()); 39 | } 40 | 41 | /** 42 | * Create the Valet drivers directory. 43 | */ 44 | public function createDriversDirectory(): void 45 | { 46 | if ($this->files->isDir($driversDirectory = VALET_HOME_PATH.'/Drivers')) { 47 | return; 48 | } 49 | 50 | $this->files->mkdirAsUser($driversDirectory); 51 | 52 | $this->files->putAsUser( 53 | $driversDirectory.'/SampleValetDriver.php', 54 | $this->files->getStub('SampleValetDriver.php') 55 | ); 56 | } 57 | 58 | /** 59 | * Create the Valet sites directory. 60 | */ 61 | public function createSitesDirectory(): void 62 | { 63 | $this->files->ensureDirExists(VALET_HOME_PATH.'/Sites', user()); 64 | } 65 | 66 | /** 67 | * Create the directory for Nginx logs. 68 | */ 69 | public function createLogDirectory(): void 70 | { 71 | $this->files->ensureDirExists(VALET_HOME_PATH.'/Log', user()); 72 | 73 | $this->files->touch(VALET_HOME_PATH.'/Log/nginx-error.log'); 74 | } 75 | 76 | /** 77 | * Create the directory for SSL certificates. 78 | */ 79 | public function createCertificatesDirectory(): void 80 | { 81 | $this->files->ensureDirExists(VALET_HOME_PATH.'/Certificates', user()); 82 | } 83 | 84 | /** 85 | * Ensure the base initial configuration has been installed. 86 | */ 87 | public function ensureBaseConfiguration(): void 88 | { 89 | $this->writeBaseConfiguration(); 90 | 91 | if (empty($this->read()['tld'])) { 92 | $this->updateKey('tld', 'test'); 93 | } 94 | 95 | if (empty($this->read()['loopback'])) { 96 | $this->updateKey('loopback', '127.0.0.1'); 97 | } 98 | } 99 | 100 | /** 101 | * Write the base initial configuration for Valet. 102 | */ 103 | public function writeBaseConfiguration(): void 104 | { 105 | if (! $this->files->exists($this->path())) { 106 | $this->write(['tld' => 'test', 'loopback' => VALET_LOOPBACK, 'paths' => []]); 107 | } 108 | } 109 | 110 | /** 111 | * Add the given path to the configuration. 112 | */ 113 | public function addPath(string $path, bool $prepend = false): void 114 | { 115 | $this->write(tap($this->read(), function (&$config) use ($path, $prepend) { 116 | $method = $prepend ? 'prepend' : 'push'; 117 | 118 | $config['paths'] = collect($config['paths'])->{$method}($path)->unique()->values()->all(); 119 | })); 120 | } 121 | 122 | /** 123 | * Prepend the given path to the configuration. 124 | */ 125 | public function prependPath(string $path): void 126 | { 127 | $this->addPath($path, true); 128 | } 129 | 130 | /** 131 | * Remove the given path from the configuration. 132 | */ 133 | public function removePath(string $path): void 134 | { 135 | if ($path == VALET_HOME_PATH.'/Sites') { 136 | info('Cannot remove this directory because this is where Valet stores its site definitions.'); 137 | info('Run [valet paths] for a list of parked paths.'); 138 | exit(); 139 | } 140 | 141 | $this->write(tap($this->read(), function (&$config) use ($path) { 142 | $config['paths'] = collect($config['paths'])->reject(function ($value) use ($path) { 143 | return $value === $path; 144 | })->values()->all(); 145 | })); 146 | } 147 | 148 | /** 149 | * Prune all non-existent paths from the configuration. 150 | */ 151 | public function prune(): void 152 | { 153 | if (! $this->files->exists($this->path())) { 154 | return; 155 | } 156 | 157 | $this->write(tap($this->read(), function (&$config) { 158 | $config['paths'] = collect($config['paths'])->filter(function ($path) { 159 | return $this->files->isDir($path); 160 | })->values()->all(); 161 | })); 162 | } 163 | 164 | /** 165 | * Read the configuration file as JSON. 166 | */ 167 | public function read(): array 168 | { 169 | if (! $this->files->exists($this->path())) { 170 | return []; 171 | } 172 | 173 | return json_decode($this->files->get($this->path()), true, 512, JSON_THROW_ON_ERROR); 174 | } 175 | 176 | /** 177 | * Update a specific key in the configuration file. 178 | */ 179 | public function updateKey(string $key, mixed $value): array 180 | { 181 | return tap($this->read(), function (&$config) use ($key, $value) { 182 | $config[$key] = $value; 183 | 184 | $this->write($config); 185 | }); 186 | } 187 | 188 | /** 189 | * Write the given configuration to disk. 190 | */ 191 | public function write(array $config): void 192 | { 193 | $this->files->putAsUser($this->path(), json_encode( 194 | $config, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES 195 | ).PHP_EOL); 196 | } 197 | 198 | /** 199 | * Get the configuration file path. 200 | */ 201 | public function path(): string 202 | { 203 | return VALET_HOME_PATH.'/config.json'; 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /cli/Valet/Diagnose.php: -------------------------------------------------------------------------------- 1 | /dev/null 2>&1', 20 | 'brew config', 21 | 'brew services list', 22 | 'brew list --formula --versions | grep -E "(php|nginx|dnsmasq|mariadb|mysql|mailhog|openssl)(@\d\..*)?\s"', 23 | 'brew outdated', 24 | 'brew tap', 25 | 'php -v', 26 | 'which -a php', 27 | 'php --ini', 28 | 'nginx -v', 29 | 'curl --version', 30 | 'php --ri curl', 31 | BREW_PREFIX.'/bin/ngrok version', 32 | 'ls -al ~/.ngrok2', 33 | 'brew info nginx', 34 | 'brew info php', 35 | 'brew info openssl', 36 | 'openssl version -a', 37 | 'openssl ciphers', 38 | 'sudo nginx -t', 39 | 'which -a php-fpm', 40 | BREW_PREFIX.'/opt/php/sbin/php-fpm -v', 41 | 'sudo '.BREW_PREFIX.'/opt/php/sbin/php-fpm -y '.PHP_SYSCONFDIR.'/php-fpm.conf --test', 42 | 'ls -al ~/Library/LaunchAgents | grep homebrew', 43 | 'ls -al /Library/LaunchAgents | grep homebrew', 44 | 'ls -al /Library/LaunchDaemons | grep homebrew', 45 | 'ls -al /Library/LaunchDaemons | grep "com.laravel.valet."', 46 | 'ls -aln /etc/resolv.conf', 47 | 'cat /etc/resolv.conf', 48 | 'ifconfig lo0', 49 | 'sh -c \'echo "------\n'.BREW_PREFIX.'/etc/nginx/valet/valet.conf\n---\n"; cat '.BREW_PREFIX.'/etc/nginx/valet/valet.conf | grep -n "# valet loopback"; echo "\n------\n"\'', 50 | 'sh -c \'for file in ~/.config/valet/dnsmasq.d/*; do echo "------\n~/.config/valet/dnsmasq.d/$(basename $file)\n---\n"; cat $file; echo "\n------\n"; done\'', 51 | 'sh -c \'for file in ~/.config/valet/nginx/*; do echo "------\n~/.config/valet/nginx/$(basename $file)\n---\n"; cat $file | grep -n "# valet loopback"; echo "\n------\n"; done\'', 52 | ]; 53 | 54 | public $print; 55 | 56 | public $progressBar; 57 | 58 | public function __construct(public CommandLine $cli, public Filesystem $files) {} 59 | 60 | /** 61 | * Run diagnostics. 62 | */ 63 | public function run(bool $print, bool $plainText): void 64 | { 65 | $this->print = $print; 66 | 67 | $this->beforeRun(); 68 | 69 | $results = collect($this->commands)->map(function ($command) { 70 | $this->beforeCommand($command); 71 | 72 | $output = $this->runCommand($command); 73 | 74 | if ($this->ignoreOutput($command)) { 75 | return; 76 | } 77 | 78 | $this->afterCommand($command, $output); 79 | 80 | return compact('command', 'output'); 81 | })->filter()->values(); 82 | 83 | $output = $this->format($results, $plainText); 84 | 85 | $this->files->put('valet_diagnostics.txt', $output); 86 | 87 | $this->cli->run('pbcopy < valet_diagnostics.txt'); 88 | 89 | $this->files->unlink('valet_diagnostics.txt'); 90 | 91 | $this->afterRun(); 92 | } 93 | 94 | public function beforeRun(): void 95 | { 96 | if ($this->print) { 97 | return; 98 | } 99 | 100 | $this->progressBar = new ProgressBar(new ConsoleOutput, count($this->commands)); 101 | 102 | $this->progressBar->start(); 103 | } 104 | 105 | public function afterRun(): void 106 | { 107 | if ($this->progressBar) { 108 | $this->progressBar->finish(); 109 | } 110 | 111 | output(''); 112 | } 113 | 114 | public function runCommand(string $command): string 115 | { 116 | return strpos($command, 'sudo ') === 0 117 | ? $this->cli->run($command) 118 | : $this->cli->runAsUser($command); 119 | } 120 | 121 | public function beforeCommand(string $command): void 122 | { 123 | if ($this->print) { 124 | info(PHP_EOL."$ $command"); 125 | } 126 | } 127 | 128 | public function afterCommand(string $command, string $output): void 129 | { 130 | if ($this->print) { 131 | output(trim($output)); 132 | } else { 133 | $this->progressBar->advance(); 134 | } 135 | } 136 | 137 | public function ignoreOutput(string $command): bool 138 | { 139 | return strpos($command, '> /dev/null 2>&1') !== false; 140 | } 141 | 142 | public function format(Collection $results, bool $plainText): string 143 | { 144 | return $results->map(function ($result) use ($plainText) { 145 | $command = $result['command']; 146 | $output = trim($result['output']); 147 | 148 | if ($plainText) { 149 | return implode(PHP_EOL, ["$ {$command}", $output]); 150 | } 151 | 152 | return sprintf( 153 | '
%s%s%s
%s
%s
', 154 | PHP_EOL, $command, PHP_EOL, $output, PHP_EOL 155 | ); 156 | })->implode($plainText ? PHP_EOL.str_repeat('-', 20).PHP_EOL : PHP_EOL); 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /cli/Valet/DnsMasq.php: -------------------------------------------------------------------------------- 1 | brew->ensureInstalled('dnsmasq'); 21 | 22 | // For DnsMasq, we enable its feature of loading *.conf from /usr/local/etc/dnsmasq.d/ 23 | // and then we put a valet config file in there to point to the user's home .config/valet/dnsmasq.d 24 | // This allows Valet to make changes to our own files without needing to modify the core dnsmasq configs 25 | $this->ensureUsingDnsmasqDForConfigs(); 26 | 27 | $this->createDnsmasqTldConfigFile($tld); 28 | 29 | $this->createTldResolver($tld); 30 | 31 | $this->brew->restartService('dnsmasq'); 32 | 33 | info('Valet is configured to serve for TLD [.'.$tld.']'); 34 | } 35 | 36 | /** 37 | * Forcefully uninstall dnsmasq. 38 | */ 39 | public function uninstall(): void 40 | { 41 | $this->brew->stopService('dnsmasq'); 42 | $this->brew->uninstallFormula('dnsmasq'); 43 | $this->cli->run('rm -rf '.BREW_PREFIX.'/etc/dnsmasq.d/dnsmasq-valet.conf'); 44 | 45 | // As Laravel Herd uses the same DnsMasq resolver, we should only 46 | // delete it if Herd is not installed. 47 | if (! $this->files->exists('/Applications/Herd.app')) { 48 | $tld = $this->configuration->read()['tld']; 49 | $this->files->unlink($this->resolverPath.'/'.$tld); 50 | } 51 | } 52 | 53 | /** 54 | * Stop the dnsmasq service. 55 | */ 56 | public function stop(): void 57 | { 58 | $this->brew->stopService(['dnsmasq']); 59 | } 60 | 61 | /** 62 | * Tell Homebrew to restart dnsmasq. 63 | */ 64 | public function restart(): void 65 | { 66 | $this->brew->restartService('dnsmasq'); 67 | } 68 | 69 | /** 70 | * Ensure the DnsMasq configuration primary config is set to read custom configs. 71 | */ 72 | public function ensureUsingDnsmasqDForConfigs(): void 73 | { 74 | info('Updating Dnsmasq configuration...'); 75 | 76 | // set primary config to look for configs in /usr/local/etc/dnsmasq.d/*.conf 77 | $contents = $this->files->get($this->dnsmasqMasterConfigFile); 78 | // ensure the line we need to use is present, and uncomment it if needed 79 | if (strpos($contents, 'conf-dir='.BREW_PREFIX.'/etc/dnsmasq.d/,*.conf') === false) { 80 | $contents .= PHP_EOL.'conf-dir='.BREW_PREFIX.'/etc/dnsmasq.d/,*.conf'.PHP_EOL; 81 | } 82 | $contents = str_replace('#conf-dir='.BREW_PREFIX.'/etc/dnsmasq.d/,*.conf', 'conf-dir='.BREW_PREFIX.'/etc/dnsmasq.d/,*.conf', $contents); 83 | 84 | // remove entries used by older Valet versions: 85 | $contents = preg_replace('/^conf-file.*valet.*$/m', '', $contents); 86 | 87 | // save the updated config file 88 | $this->files->put($this->dnsmasqMasterConfigFile, $contents); 89 | 90 | // remove old ~/.config/valet/dnsmasq.conf file because things are moved to the ~/.config/valet/dnsmasq.d/ folder now 91 | if (file_exists($file = dirname($this->dnsmasqUserConfigDir()).'/dnsmasq.conf')) { 92 | unlink($file); 93 | } 94 | 95 | // add a valet-specific config file to point to user's home directory valet config 96 | $contents = $this->files->getStub('etc-dnsmasq-valet.conf'); 97 | $contents = str_replace('VALET_HOME_PATH', VALET_HOME_PATH, $contents); 98 | $this->files->ensureDirExists($this->dnsmasqSystemConfDir, user()); 99 | $this->files->putAsUser($this->dnsmasqSystemConfDir.'/dnsmasq-valet.conf', $contents); 100 | 101 | $this->files->ensureDirExists(VALET_HOME_PATH.'/dnsmasq.d', user()); 102 | } 103 | 104 | /** 105 | * Create the TLD-specific dnsmasq config file. 106 | */ 107 | public function createDnsmasqTldConfigFile(string $tld): void 108 | { 109 | $tldConfigFile = $this->dnsmasqUserConfigDir().'tld-'.$tld.'.conf'; 110 | $loopback = $this->configuration->read()['loopback']; 111 | 112 | $this->files->putAsUser($tldConfigFile, 'address=/.'.$tld.'/'.$loopback.PHP_EOL.'listen-address='.$loopback.PHP_EOL); 113 | } 114 | 115 | /** 116 | * Create the resolver file to point the configured TLD to configured loopback address. 117 | */ 118 | public function createTldResolver(string $tld): void 119 | { 120 | $this->files->ensureDirExists($this->resolverPath); 121 | $loopback = $this->configuration->read()['loopback']; 122 | 123 | $this->files->put($this->resolverPath.'/'.$tld, 'nameserver '.$loopback.PHP_EOL); 124 | } 125 | 126 | /** 127 | * Update the TLD/domain resolved by DnsMasq. 128 | */ 129 | public function updateTld(string $oldTld, string $newTld): void 130 | { 131 | $this->files->unlink($this->resolverPath.'/'.$oldTld); 132 | $this->files->unlink($this->dnsmasqUserConfigDir().'tld-'.$oldTld.'.conf'); 133 | 134 | $this->install($newTld); 135 | } 136 | 137 | /** 138 | * Refresh the DnsMasq configuration. 139 | */ 140 | public function refreshConfiguration(): void 141 | { 142 | $tld = $this->configuration->read()['tld']; 143 | 144 | $this->updateTld($tld, $tld); 145 | } 146 | 147 | /** 148 | * Get the custom configuration path. 149 | */ 150 | public function dnsmasqUserConfigDir(): string 151 | { 152 | return $_SERVER['HOME'].'/.config/valet/dnsmasq.d/'; 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /cli/Valet/Drivers/BasicValetDriver.php: -------------------------------------------------------------------------------- 1 | isActualFile($staticFilePath = $sitePath.$uri)) { 33 | return $staticFilePath; 34 | } 35 | 36 | return false; 37 | } 38 | 39 | /** 40 | * Get the fully resolved path to the application's front controller. 41 | */ 42 | public function frontControllerPath(string $sitePath, string $siteName, string $uri): ?string 43 | { 44 | $uri = rtrim($uri, '/'); 45 | 46 | $candidates = [ 47 | $sitePath.$uri, 48 | $sitePath.$uri.'/index.php', 49 | $sitePath.'/index.php', 50 | $sitePath.'/index.html', 51 | ]; 52 | 53 | foreach ($candidates as $candidate) { 54 | if ($this->isActualFile($candidate)) { 55 | $_SERVER['SCRIPT_FILENAME'] = $candidate; 56 | $_SERVER['SCRIPT_NAME'] = str_replace($sitePath, '', $candidate); 57 | $_SERVER['DOCUMENT_ROOT'] = $sitePath; 58 | 59 | return $candidate; 60 | } 61 | } 62 | 63 | return null; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /cli/Valet/Drivers/BasicWithPublicValetDriver.php: -------------------------------------------------------------------------------- 1 | isActualFile($publicPath)) { 23 | return $publicPath; 24 | } elseif (file_exists($publicPath.'/index.html')) { 25 | return $publicPath.'/index.html'; 26 | } 27 | 28 | return false; 29 | } 30 | 31 | /** 32 | * Get the fully resolved path to the application's front controller. 33 | */ 34 | public function frontControllerPath(string $sitePath, string $siteName, string $uri): ?string 35 | { 36 | $_SERVER['PHP_SELF'] = $uri; 37 | $_SERVER['SERVER_ADDR'] = '127.0.0.1'; 38 | $_SERVER['SERVER_NAME'] = $_SERVER['HTTP_HOST']; 39 | 40 | $docRoot = $sitePath.'/public'; 41 | $uri = rtrim($uri, '/'); 42 | 43 | $candidates = [ 44 | $docRoot.$uri, 45 | $docRoot.$uri.'/index.php', 46 | $docRoot.'/index.php', 47 | $docRoot.'/index.html', 48 | ]; 49 | 50 | foreach ($candidates as $candidate) { 51 | if ($this->isActualFile($candidate)) { 52 | $_SERVER['SCRIPT_FILENAME'] = $candidate; 53 | $_SERVER['SCRIPT_NAME'] = str_replace($sitePath.'/public', '', $candidate); 54 | $_SERVER['DOCUMENT_ROOT'] = $sitePath.'/public'; 55 | 56 | return $candidate; 57 | } 58 | } 59 | 60 | return null; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /cli/Valet/Drivers/LaravelValetDriver.php: -------------------------------------------------------------------------------- 1 | isActualFile($storagePath = $sitePath.'/storage/app/public'.$storageUri)) { 44 | return $storagePath; 45 | } 46 | 47 | return false; 48 | } 49 | 50 | /** 51 | * Get the fully resolved path to the application's front controller. 52 | */ 53 | public function frontControllerPath(string $sitePath, string $siteName, string $uri): ?string 54 | { 55 | if (file_exists($staticFilePath = $sitePath.'/public'.$uri) 56 | && $this->isActualFile($staticFilePath)) { 57 | return $staticFilePath; 58 | } 59 | 60 | return $sitePath.'/public/index.php'; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /cli/Valet/Drivers/Specific/BedrockValetDriver.php: -------------------------------------------------------------------------------- 1 | composerRequires($sitePath, 'roots/bedrock-autoloader') || 15 | file_exists($sitePath.'/web/app/mu-plugins/bedrock-autoloader.php') || 16 | (is_dir($sitePath.'/web/app/') && 17 | file_exists($sitePath.'/web/wp-config.php') && 18 | file_exists($sitePath.'/config/application.php')); 19 | } 20 | 21 | /** 22 | * Determine if the incoming request is for a static file. 23 | */ 24 | public function isStaticFile(string $sitePath, string $siteName, string $uri) 25 | { 26 | $staticFilePath = $sitePath.'/web'.$uri; 27 | 28 | if ($this->isActualFile($staticFilePath)) { 29 | return $staticFilePath; 30 | } 31 | 32 | return false; 33 | } 34 | 35 | /** 36 | * Get the fully resolved path to the application's front controller. 37 | */ 38 | public function frontControllerPath(string $sitePath, string $siteName, string $uri): ?string 39 | { 40 | return parent::frontControllerPath( 41 | $sitePath.'/web', 42 | $siteName, 43 | $this->forceTrailingSlash($uri) 44 | ); 45 | } 46 | 47 | /** 48 | * Redirect to uri with trailing slash. 49 | */ 50 | private function forceTrailingSlash($uri) 51 | { 52 | if (substr($uri, -1 * strlen('/wp/wp-admin')) == '/wp/wp-admin') { 53 | header('Location: '.$uri.'/'); 54 | exit; 55 | } 56 | 57 | return $uri; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /cli/Valet/Drivers/Specific/CakeValetDriver.php: -------------------------------------------------------------------------------- 1 | isActualFile($staticFilePath = $sitePath.'/webroot/'.$uri)) { 23 | return $staticFilePath; 24 | } 25 | 26 | return false; 27 | } 28 | 29 | /** 30 | * Get the fully resolved path to the application's front controller. 31 | */ 32 | public function frontControllerPath(string $sitePath, string $siteName, string $uri): ?string 33 | { 34 | $_SERVER['DOCUMENT_ROOT'] = $sitePath.'/webroot'; 35 | $_SERVER['SCRIPT_FILENAME'] = $sitePath.'/webroot/index.php'; 36 | $_SERVER['SCRIPT_NAME'] = '/index.php'; 37 | $_SERVER['PHP_SELF'] = '/index.php'; 38 | 39 | return $sitePath.'/webroot/index.php'; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /cli/Valet/Drivers/Specific/Concrete5ValetDriver.php: -------------------------------------------------------------------------------- 1 | isActualFile($staticFilePath = $sitePath.'/web'.$uri)) { 23 | return $staticFilePath; 24 | } 25 | 26 | return false; 27 | } 28 | 29 | /** 30 | * Get the fully resolved path to the application's front controller. 31 | */ 32 | public function frontControllerPath(string $sitePath, string $siteName, string $uri): ?string 33 | { 34 | if ($uri === '/install.php') { 35 | return $sitePath.'/web/install.php'; 36 | } 37 | 38 | if (strncmp($uri, '/app_dev.php', 12) === 0) { 39 | $_SERVER['SCRIPT_NAME'] = '/app_dev.php'; 40 | $_SERVER['SCRIPT_FILENAME'] = $sitePath.'/app_dev.php'; 41 | 42 | return $sitePath.'/web/app_dev.php'; 43 | } 44 | 45 | return $sitePath.'/web/app.php'; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /cli/Valet/Drivers/Specific/CraftValetDriver.php: -------------------------------------------------------------------------------- 1 | frontControllerDirectory($sitePath); 40 | 41 | if ($this->isActualFile($staticFilePath = $sitePath.'/'.$frontControllerDirectory.$uri)) { 42 | return $staticFilePath; 43 | } 44 | 45 | return false; 46 | } 47 | 48 | /** 49 | * Get the fully resolved path to the application's front controller. 50 | */ 51 | public function frontControllerPath(string $sitePath, string $siteName, string $uri): ?string 52 | { 53 | $frontControllerDirectory = $this->frontControllerDirectory($sitePath); 54 | 55 | // Default index path 56 | $indexPath = $sitePath.'/'.$frontControllerDirectory.'/index.php'; 57 | $scriptName = '/index.php'; 58 | 59 | // Check if the first URL segment matches any of the defined locales 60 | $locales = [ 61 | 'ar', 62 | 'ar_sa', 63 | 'bg', 64 | 'bg_bg', 65 | 'ca_es', 66 | 'cs', 67 | 'cy_gb', 68 | 'da', 69 | 'da_dk', 70 | 'de', 71 | 'de_at', 72 | 'de_ch', 73 | 'de_de', 74 | 'el', 75 | 'el_gr', 76 | 'en', 77 | 'en_as', 78 | 'en_au', 79 | 'en_bb', 80 | 'en_be', 81 | 'en_bm', 82 | 'en_bw', 83 | 'en_bz', 84 | 'en_ca', 85 | 'en_dsrt', 86 | 'en_dsrt_us', 87 | 'en_gb', 88 | 'en_gu', 89 | 'en_gy', 90 | 'en_hk', 91 | 'en_ie', 92 | 'en_in', 93 | 'en_jm', 94 | 'en_mh', 95 | 'en_mp', 96 | 'en_mt', 97 | 'en_mu', 98 | 'en_na', 99 | 'en_nz', 100 | 'en_ph', 101 | 'en_pk', 102 | 'en_sg', 103 | 'en_shaw', 104 | 'en_tt', 105 | 'en_um', 106 | 'en_us', 107 | 'en_us_posix', 108 | 'en_vi', 109 | 'en_za', 110 | 'en_zw', 111 | 'en_zz', 112 | 'es', 113 | 'es_cl', 114 | 'es_es', 115 | 'es_mx', 116 | 'es_us', 117 | 'es_ve', 118 | 'et', 119 | 'fi', 120 | 'fi_fi', 121 | 'fil', 122 | 'fr', 123 | 'fr_be', 124 | 'fr_ca', 125 | 'fr_ch', 126 | 'fr_fr', 127 | 'fr_ma', 128 | 'he', 129 | 'hr', 130 | 'hr_hr', 131 | 'hu', 132 | 'hu_hu', 133 | 'id', 134 | 'id_id', 135 | 'it', 136 | 'it_ch', 137 | 'it_it', 138 | 'ja', 139 | 'ja_jp', 140 | 'ko', 141 | 'ko_kr', 142 | 'lt', 143 | 'lv', 144 | 'ms', 145 | 'ms_my', 146 | 'nb', 147 | 'nb_no', 148 | 'nl', 149 | 'nl_be', 150 | 'nl_nl', 151 | 'nn', 152 | 'nn_no', 153 | 'no', 154 | 'pl', 155 | 'pl_pl', 156 | 'pt', 157 | 'pt_br', 158 | 'pt_pt', 159 | 'ro', 160 | 'ro_ro', 161 | 'ru', 162 | 'ru_ru', 163 | 'sk', 164 | 'sl', 165 | 'sr', 166 | 'sv', 167 | 'sv_se', 168 | 'th', 169 | 'th_th', 170 | 'tr', 171 | 'tr_tr', 172 | 'uk', 173 | 'vi', 174 | 'zh', 175 | 'zh_cn', 176 | 'zh_tw', 177 | ]; 178 | $parts = explode('/', $uri); 179 | 180 | if (count($parts) > 1 && in_array($parts[1], $locales)) { 181 | $indexLocalizedPath = $sitePath.'/'.$frontControllerDirectory.'/'.$parts[1].'/index.php'; 182 | 183 | // Check if index.php exists in the localized folder, this is optional in Craft 3 184 | if (file_exists($indexLocalizedPath)) { 185 | $indexPath = $indexLocalizedPath; 186 | $scriptName = '/'.$parts[1].'/index.php'; 187 | } 188 | } 189 | 190 | $_SERVER['SCRIPT_FILENAME'] = $indexPath; 191 | $_SERVER['SERVER_NAME'] = $_SERVER['HTTP_HOST']; 192 | $_SERVER['SCRIPT_NAME'] = $scriptName; 193 | $_SERVER['PHP_SELF'] = $scriptName; 194 | $_SERVER['DOCUMENT_ROOT'] = $sitePath.'/'.$frontControllerDirectory; 195 | 196 | if (isset($_SERVER['argv'])) { 197 | unset($_SERVER['argv']); 198 | } 199 | 200 | return $indexPath; 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /cli/Valet/Drivers/Specific/DrupalValetDriver.php: -------------------------------------------------------------------------------- 1 | addSubdirectory($sitePath); 15 | 16 | /** 17 | * /misc/drupal.js = Drupal 7 18 | * /core/lib/Drupal.php = Drupal 8. 19 | */ 20 | if (file_exists($sitePath.'/misc/drupal.js') || 21 | file_exists($sitePath.'/core/lib/Drupal.php')) { 22 | return true; 23 | } 24 | 25 | return false; 26 | } 27 | 28 | /** 29 | * Determine if the incoming request is for a static file. 30 | */ 31 | public function isStaticFile(string $sitePath, string $siteName, string $uri)/* : string|false */ 32 | { 33 | $sitePath = $this->addSubdirectory($sitePath); 34 | 35 | if (file_exists($sitePath.$uri) && 36 | ! is_dir($sitePath.$uri) && 37 | pathinfo($sitePath.$uri)['extension'] != 'php') { 38 | return $sitePath.$uri; 39 | } 40 | 41 | return false; 42 | } 43 | 44 | /** 45 | * Get the fully resolved path to the application's front controller. 46 | */ 47 | public function frontControllerPath(string $sitePath, string $siteName, string $uri): ?string 48 | { 49 | $sitePath = $this->addSubdirectory($sitePath); 50 | 51 | if (! isset($_GET['Q']) && ! empty($uri) && $uri !== '/' && strpos($uri, '/jsonapi/') === false) { 52 | $_GET['Q'] = $uri; 53 | } 54 | 55 | $matches = []; 56 | if (preg_match('/^\/(.*?)\.php/', $uri, $matches)) { 57 | $filename = $matches[0]; 58 | if (file_exists($sitePath.$filename) && ! is_dir($sitePath.$filename)) { 59 | $_SERVER['SCRIPT_FILENAME'] = $sitePath.$filename; 60 | $_SERVER['SCRIPT_NAME'] = $filename; 61 | 62 | return $sitePath.$filename; 63 | } 64 | } 65 | 66 | // Fallback 67 | $_SERVER['SCRIPT_FILENAME'] = $sitePath.'/index.php'; 68 | $_SERVER['SCRIPT_NAME'] = '/index.php'; 69 | 70 | return $sitePath.'/index.php'; 71 | } 72 | 73 | /** 74 | * Add any matching subdirectory to the site path. 75 | */ 76 | public function addSubdirectory($sitePath): string 77 | { 78 | $paths = array_map(function ($subDir) use ($sitePath) { 79 | return "$sitePath/$subDir"; 80 | }, $this->possibleSubdirectories()); 81 | 82 | $foundPaths = array_filter($paths, function ($path) { 83 | return file_exists($path); 84 | }); 85 | 86 | // If paths are found, return the first one. 87 | if (! empty($foundPaths)) { 88 | return array_shift($foundPaths); 89 | } 90 | 91 | // If there are no matches, return the original path. 92 | return $sitePath; 93 | } 94 | 95 | /** 96 | * Return an array of possible subdirectories. 97 | */ 98 | private function possibleSubdirectories(): array 99 | { 100 | return ['docroot', 'public', 'web']; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /cli/Valet/Drivers/Specific/JigsawValetDriver.php: -------------------------------------------------------------------------------- 1 | isActualFile($staticFilePath = $sitePath.$uri)) { 23 | return $staticFilePath; 24 | } elseif ($this->isActualFile($staticFilePath = $sitePath.'/public'.$uri)) { 25 | return $staticFilePath; 26 | } 27 | 28 | return false; 29 | } 30 | 31 | /** 32 | * Get the fully resolved path to the application's front controller. 33 | */ 34 | public function frontControllerPath(string $sitePath, string $siteName, string $uri): ?string 35 | { 36 | $scriptName = '/index.php'; 37 | 38 | if ($this->isActualFile($sitePath.'/index.php')) { 39 | $indexPath = $sitePath.'/index.php'; 40 | } 41 | 42 | if ($isAboveWebroot = $this->isActualFile($sitePath.'/public/index.php')) { 43 | $indexPath = $sitePath.'/public/index.php'; 44 | } 45 | 46 | if (preg_match('/^\/panel/', $uri) && $this->isActualFile($sitePath.'/panel/index.php')) { 47 | $scriptName = '/panel/index.php'; 48 | $indexPath = $sitePath.'/panel/index.php'; 49 | } 50 | 51 | // add this block 52 | if (preg_match('/^\/(?!(kirby|site|content)\/).+\.php$/', $uri)) { 53 | if ( 54 | $this->isActualFile($sitePath.$uri) || 55 | $isAboveWebroot && $this->isActualFile($sitePath.'/public'.$uri) 56 | ) { 57 | $scriptName = $uri; 58 | $indexPath = $sitePath.$scriptName; 59 | } 60 | } 61 | 62 | $sitePathPrefix = ($isAboveWebroot) ? $sitePath.'/public' : $sitePath; 63 | 64 | $_SERVER['SERVER_NAME'] = $_SERVER['HTTP_HOST']; 65 | $_SERVER['SCRIPT_NAME'] = $scriptName; 66 | $_SERVER['SCRIPT_FILENAME'] = $sitePathPrefix.$scriptName; 67 | 68 | return $indexPath; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /cli/Valet/Drivers/Specific/Magento2ValetDriver.php: -------------------------------------------------------------------------------- 1 | isActualFile($staticFilePath = $sitePath.'/Web'.$uri)) { 34 | return $staticFilePath; 35 | } 36 | 37 | return false; 38 | } 39 | 40 | /** 41 | * Get the fully resolved path to the application's front controller. 42 | */ 43 | public function frontControllerPath(string $sitePath, string $siteName, string $uri): ?string 44 | { 45 | return $sitePath.'/Web/index.php'; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /cli/Valet/Drivers/Specific/NetteValetDriver.php: -------------------------------------------------------------------------------- 1 | isActualFile($staticFilePath = $sitePath.'/www/'.$uri)) { 26 | return $staticFilePath; 27 | } 28 | 29 | return false; 30 | } 31 | 32 | /** 33 | * Get the fully resolved path to the application's front controller. 34 | */ 35 | public function frontControllerPath(string $sitePath, string $siteName, string $uri): ?string 36 | { 37 | $_SERVER['DOCUMENT_ROOT'] = $sitePath.'/www'; 38 | $_SERVER['SCRIPT_FILENAME'] = $sitePath.'/www/index.php'; 39 | $_SERVER['SCRIPT_NAME'] = '/index.php'; 40 | $_SERVER['PHP_SELF'] = '/index.php'; 41 | 42 | return $sitePath.'/www/index.php'; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /cli/Valet/Drivers/Specific/RadicleValetDriver.php: -------------------------------------------------------------------------------- 1 | isActualFile($staticFilePath)) { 28 | return $staticFilePath; 29 | } 30 | 31 | return false; 32 | } 33 | 34 | /** 35 | * Get the fully resolved path to the application's front controller. 36 | */ 37 | public function frontControllerPath(string $sitePath, string $siteName, string $uri): string 38 | { 39 | $_SERVER['PHP_SELF'] = $uri; 40 | if (strpos($uri, '/wp/') === 0) { 41 | return is_dir($sitePath.'/public'.$uri) 42 | ? $sitePath.'/public'.$this->forceTrailingSlash($uri).'/index.php' 43 | : $sitePath.'/public'.$uri; 44 | } 45 | 46 | return $sitePath.'/public/index.php'; 47 | } 48 | 49 | /** 50 | * Redirect to uri with trailing slash. 51 | * 52 | * @return string 53 | */ 54 | private function forceTrailingSlash(string $uri) 55 | { 56 | if (substr($uri, -1 * strlen('/wp/wp-admin')) == '/wp/wp-admin') { 57 | header('Location: '.$uri.'/'); 58 | exit; 59 | } 60 | 61 | return $uri; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /cli/Valet/Drivers/Specific/SculpinValetDriver.php: -------------------------------------------------------------------------------- 1 | isModernSculpinProject($sitePath) || 15 | $this->isLegacySculpinProject($sitePath); 16 | } 17 | 18 | private function isModernSculpinProject(string $sitePath): bool 19 | { 20 | return is_dir($sitePath.'/source') && 21 | is_dir($sitePath.'/output_dev') && 22 | $this->composerRequires($sitePath, 'sculpin/sculpin'); 23 | } 24 | 25 | private function isLegacySculpinProject(string $sitePath): bool 26 | { 27 | return is_dir($sitePath.'/.sculpin'); 28 | } 29 | 30 | /** 31 | * Mutate the incoming URI. 32 | */ 33 | public function mutateUri(string $uri): string 34 | { 35 | return rtrim('/output_dev'.$uri, '/'); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /cli/Valet/Drivers/Specific/StatamicV1ValetDriver.php: -------------------------------------------------------------------------------- 1 | isActualFile($staticFilePath = $sitePath.$uri)) { 30 | return $staticFilePath; 31 | } 32 | 33 | return false; 34 | } 35 | 36 | /** 37 | * Get the fully resolved path to the application's front controller. 38 | */ 39 | public function frontControllerPath(string $sitePath, string $siteName, string $uri): ?string 40 | { 41 | if (strpos($uri, '/admin.php') === 0) { 42 | $_SERVER['SCRIPT_NAME'] = '/admin.php'; 43 | 44 | return $sitePath.'/admin.php'; 45 | } 46 | 47 | if ($uri === '/admin') { 48 | $_SERVER['SCRIPT_NAME'] = '/admin/index.php'; 49 | 50 | return $sitePath.'/admin/index.php'; 51 | } 52 | 53 | $_SERVER['SCRIPT_NAME'] = '/index.php'; 54 | 55 | return $sitePath.'/index.php'; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /cli/Valet/Drivers/Specific/StatamicV2ValetDriver.php: -------------------------------------------------------------------------------- 1 | isActualFile($staticFilePath = $sitePath.$uri)) { 27 | return $staticFilePath; 28 | } elseif ($this->isActualFile($staticFilePath = $sitePath.'/public'.$uri)) { 29 | return $staticFilePath; 30 | } 31 | 32 | return false; 33 | } 34 | 35 | /** 36 | * Get the fully resolved path to the application's front controller. 37 | */ 38 | public function frontControllerPath(string $sitePath, string $siteName, string $uri): ?string 39 | { 40 | if ($_SERVER['REQUEST_METHOD'] === 'GET' && $this->isActualFile($staticPath = $this->getStaticPath($sitePath))) { 41 | return $staticPath; 42 | } 43 | 44 | if ($uri === '/installer.php') { 45 | return $sitePath.'/installer.php'; 46 | } 47 | 48 | $scriptName = '/index.php'; 49 | 50 | if ($this->isActualFile($sitePath.'/index.php')) { 51 | $indexPath = $sitePath.'/index.php'; 52 | } 53 | 54 | if ($isAboveWebroot = $this->isActualFile($sitePath.'/public/index.php')) { 55 | $indexPath = $sitePath.'/public/index.php'; 56 | } 57 | 58 | $sitePathPrefix = ($isAboveWebroot) ? $sitePath.'/public' : $sitePath; 59 | 60 | if ($locale = $this->getUriLocale($uri)) { 61 | if ($this->isActualFile($localeIndexPath = $sitePathPrefix.'/'.$locale.'/index.php')) { 62 | // Force trailing slashes on locale roots. 63 | if ($uri === '/'.$locale) { 64 | header('Location: '.$uri.'/'); 65 | exit; 66 | } 67 | 68 | $indexPath = $localeIndexPath; 69 | $scriptName = '/'.$locale.'/index.php'; 70 | } 71 | } 72 | 73 | $_SERVER['SCRIPT_NAME'] = $scriptName; 74 | $_SERVER['SCRIPT_FILENAME'] = $sitePathPrefix.$scriptName; 75 | 76 | return $indexPath; 77 | } 78 | 79 | /** 80 | * Get the locale from this URI. 81 | */ 82 | public function getUriLocale(string $uri): ?string 83 | { 84 | $parts = explode('/', $uri); 85 | $locale = $parts[1]; 86 | 87 | if (count($parts) < 2 || ! in_array($locale, $this->getLocales())) { 88 | return null; 89 | } 90 | 91 | return $locale; 92 | } 93 | 94 | /** 95 | * Get the list of possible locales used in the first segment of a URI. 96 | */ 97 | public function getLocales(): array 98 | { 99 | return [ 100 | 'af', 'ax', 'al', 'dz', 'as', 'ad', 'ao', 'ai', 'aq', 'ag', 'ar', 'am', 'aw', 'au', 'at', 'az', 'bs', 'bh', 101 | 'bd', 'bb', 'by', 'be', 'bz', 'bj', 'bm', 'bt', 'bo', 'bq', 'ba', 'bw', 'bv', 'br', 'io', 'bn', 'bg', 'bf', 102 | 'bi', 'cv', 'kh', 'cm', 'ca', 'ky', 'cf', 'td', 'cl', 'cn', 'cx', 'cc', 'co', 'km', 'cg', 'cd', 'ck', 'cr', 103 | 'ci', 'hr', 'cu', 'cw', 'cy', 'cz', 'dk', 'dj', 'dm', 'do', 'ec', 'eg', 'sv', 'gq', 'er', 'ee', 'et', 'fk', 104 | 'fo', 'fj', 'fi', 'fr', 'gf', 'pf', 'tf', 'ga', 'gm', 'ge', 'de', 'gh', 'gi', 'gr', 'gl', 'gd', 'gp', 'gu', 105 | 'gt', 'gg', 'gn', 'gw', 'gy', 'ht', 'hm', 'va', 'hn', 'hk', 'hu', 'is', 'in', 'id', 'ir', 'iq', 'ie', 'im', 106 | 'il', 'it', 'jm', 'jp', 'je', 'jo', 'kz', 'ke', 'ki', 'kp', 'kr', 'kw', 'kg', 'la', 'lv', 'lb', 'ls', 'lr', 107 | 'ly', 'li', 'lt', 'lu', 'mo', 'mk', 'mg', 'mw', 'my', 'mv', 'ml', 'mt', 'mh', 'mq', 'mr', 'mu', 'yt', 'mx', 108 | 'fm', 'md', 'mc', 'mn', 'me', 'ms', 'ma', 'mz', 'mm', 'na', 'nr', 'np', 'nl', 'nc', 'nz', 'ni', 'ne', 'ng', 109 | 'nu', 'nf', 'mp', 'no', 'om', 'pk', 'pw', 'ps', 'pa', 'pg', 'py', 'pe', 'ph', 'pn', 'pl', 'pt', 'pr', 'qa', 110 | 're', 'ro', 'ru', 'rw', 'bl', 'sh', 'kn', 'lc', 'mf', 'pm', 'vc', 'ws', 'sm', 'st', 'sa', 'sn', 'rs', 'sc', 111 | 'sl', 'sg', 'sx', 'sk', 'si', 'sb', 'so', 'za', 'gs', 'ss', 'es', 'lk', 'sd', 'sr', 'sj', 'sz', 'se', 'ch', 112 | 'sy', 'tw', 'tj', 'tz', 'th', 'tl', 'tg', 'tk', 'to', 'tt', 'tn', 'tr', 'tm', 'tc', 'tv', 'ug', 'ua', 'ae', 113 | 'gb', 'us', 'um', 'uy', 'uz', 'vu', 've', 'vn', 'vg', 'vi', 'wf', 'eh', 'ye', 'zm', 'zw', 'en', 'zh', 114 | ]; 115 | } 116 | 117 | /** 118 | * Get the path to a statically cached page. 119 | */ 120 | protected function getStaticPath(string $sitePath): string 121 | { 122 | $parts = parse_url($_SERVER['REQUEST_URI']); 123 | $query = isset($parts['query']) ? $parts['query'] : ''; 124 | 125 | return $sitePath.'/static'.$parts['path'].'_'.$query.'.html'; 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /cli/Valet/Drivers/Specific/StatamicValetDriver.php: -------------------------------------------------------------------------------- 1 | getStaticPath($sitePath); 24 | 25 | if ($staticPath && $this->isActualFile($staticPath)) { 26 | return $staticPath; 27 | } 28 | 29 | return parent::frontControllerPath($sitePath, $siteName, $uri); 30 | } 31 | 32 | /** 33 | * Get the path to the static file. 34 | */ 35 | private function getStaticPath(string $sitePath) 36 | { 37 | if (! $uri = $_SERVER['REQUEST_URI'] ?? null) { 38 | return; 39 | } 40 | 41 | $parts = parse_url($uri); 42 | $query = $parts['query'] ?? ''; 43 | 44 | return $sitePath.'/public/static'.$parts['path'].'_'.$query.'.html'; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /cli/Valet/Drivers/Specific/SymfonyValetDriver.php: -------------------------------------------------------------------------------- 1 | isActualFile($staticFilePath = $sitePath.'/web/'.$uri)) { 25 | return $staticFilePath; 26 | } elseif ($this->isActualFile($staticFilePath = $sitePath.'/public/'.$uri)) { 27 | return $staticFilePath; 28 | } 29 | 30 | return false; 31 | } 32 | 33 | /** 34 | * Get the fully resolved path to the application's front controller. 35 | */ 36 | public function frontControllerPath(string $sitePath, string $siteName, string $uri): ?string 37 | { 38 | $frontControllerPath = null; 39 | 40 | if (file_exists($path = $sitePath.'/web/app_dev.php')) { 41 | $frontControllerPath = $path; 42 | } elseif (file_exists($path = $sitePath.'/web/app.php')) { 43 | $frontControllerPath = $path; 44 | } elseif (file_exists($path = $sitePath.'/public/index.php')) { 45 | $frontControllerPath = $path; 46 | } 47 | 48 | $_SERVER['SCRIPT_FILENAME'] = $frontControllerPath; 49 | 50 | return $frontControllerPath; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /cli/Valet/Drivers/Specific/Typo3ValetDriver.php: -------------------------------------------------------------------------------- 1 | handleRedirectBackendShorthandUris($uri); 51 | 52 | $_SERVER['SERVER_NAME'] = $siteName.'.dev'; 53 | $_SERVER['DOCUMENT_URI'] = $uri; 54 | $_SERVER['SCRIPT_NAME'] = $uri; 55 | $_SERVER['PHP_SELF'] = $uri; 56 | } 57 | 58 | /** 59 | * Determine if the driver serves the request. For TYPO3, this is the 60 | * case, if a folder called "typo3" is present in the document root. 61 | */ 62 | public function serves(string $sitePath, string $siteName, string $uri): bool 63 | { 64 | $typo3Dir = $sitePath.$this->documentRoot.'/typo3'; 65 | 66 | return file_exists($typo3Dir) && is_dir($typo3Dir); 67 | } 68 | 69 | /** 70 | * Determine if the incoming request is for a static file. That is, it is 71 | * no PHP script file and the URI points to a valid file (no folder) on 72 | * the disk. Access to those static files will be authorized. 73 | */ 74 | public function isStaticFile(string $sitePath, string $siteName, string $uri)/* : string|false */ 75 | { 76 | // May the file contains a cache busting version string like filename.12345678.css 77 | // If that is the case, the file cannot be found on disk, so remove the version 78 | // identifier before retrying below. 79 | if (! $this->isActualFile($filePath = $sitePath.$this->documentRoot.$uri)) { 80 | $uri = preg_replace("@^(.+)\.(\d+)\.(js|css|png|jpg|gif|gzip)$@", '$1.$3', $uri); 81 | } 82 | 83 | // Now that any possible version string is cleared from the filename, the resulting 84 | // URI should be a valid file on disc. So assemble the absolut file name with the 85 | // same schema as above and if it exists, authorize access and return its path. 86 | if ($this->isActualFile($filePath = $sitePath.$this->documentRoot.$uri)) { 87 | return $this->isAccessAuthorized($uri) ? $filePath : false; 88 | } 89 | 90 | // This file cannot be found in the current project and thus cannot be served. 91 | return false; 92 | } 93 | 94 | /** 95 | * Determines if the given URI is blacklisted so that access is prevented. 96 | */ 97 | private function isAccessAuthorized(string $uri): bool 98 | { 99 | foreach ($this->forbiddenUriPatterns as $forbiddenUriPattern) { 100 | if (preg_match("@$forbiddenUriPattern@", $uri)) { 101 | return false; 102 | } 103 | } 104 | 105 | return true; 106 | } 107 | 108 | /** 109 | * Get the fully resolved path to the application's front controller. 110 | * This can be the currently requested PHP script, a folder that 111 | * contains an index.php or the global index.php otherwise. 112 | */ 113 | public function frontControllerPath(string $sitePath, string $siteName, string $uri): ?string 114 | { 115 | // from now on, remove trailing / for convenience for all the following join operations 116 | $uri = rtrim($uri, '/'); 117 | 118 | // try to find the responsible script file for the requested folder / script URI 119 | if (file_exists($absoluteFilePath = $sitePath.$this->documentRoot.$uri)) { 120 | if (is_dir($absoluteFilePath)) { 121 | if (file_exists($absoluteFilePath.'/index.php')) { 122 | // this folder can be served by index.php 123 | return $this->serveScript($sitePath, $uri.'/index.php'); 124 | } 125 | 126 | if (file_exists($absoluteFilePath.'/index.html')) { 127 | // this folder can be served by index.html 128 | return $absoluteFilePath.'/index.html'; 129 | } 130 | } elseif (pathinfo($absoluteFilePath, PATHINFO_EXTENSION) === 'php') { 131 | // this file can be served directly 132 | return $this->serveScript($sitePath, $uri); 133 | } 134 | } 135 | 136 | // the global index.php will handle all other cases 137 | return $this->serveScript($sitePath, '/index.php'); 138 | } 139 | 140 | /** 141 | * Direct access to installtool via domain.dev/typo3/install/ will be redirected to 142 | * sysext install script. domain.dev/typo3 will be redirected to /typo3/, because 143 | * the generated JavaScript URIs on the login screen would be broken on /typo3. 144 | */ 145 | private function handleRedirectBackendShorthandUris(string $uri): void 146 | { 147 | if (rtrim($uri, '/') === '/typo3/install') { 148 | header('Location: /typo3/sysext/install/Start/Install.php'); 149 | exit(); 150 | } 151 | 152 | if ($uri === '/typo3') { 153 | header('Location: /typo3/'); 154 | exit(); 155 | } 156 | } 157 | 158 | /** 159 | * Configures the $_SERVER globals for serving the script at 160 | * the specified URI and returns it absolute file path. 161 | */ 162 | private function serveScript(string $sitePath, string $uri): string 163 | { 164 | $docroot = $sitePath.$this->documentRoot; 165 | $abspath = $docroot.$uri; 166 | 167 | $_SERVER['DOCUMENT_ROOT'] = $docroot; 168 | $_SERVER['SCRIPT_FILENAME'] = $abspath; 169 | 170 | return $abspath; 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /cli/Valet/Drivers/Specific/WordPressValetDriver.php: -------------------------------------------------------------------------------- 1 | forceTrailingSlash($uri) 26 | ); 27 | } 28 | 29 | /** 30 | * Redirect to uri with trailing slash. 31 | */ 32 | private function forceTrailingSlash($uri): ?string 33 | { 34 | if (substr($uri, -1 * strlen('/wp-admin')) == '/wp-admin') { 35 | header('Location: '.$uri.'/'); 36 | exit; 37 | } 38 | 39 | return $uri; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /cli/Valet/Drivers/ValetDriver.php: -------------------------------------------------------------------------------- 1 | serves($sitePath, $siteName, $driver->mutateUri($uri))) { 63 | return $driver; 64 | } 65 | } 66 | } 67 | 68 | /** 69 | * Get the custom driver class from the site path, if one exists. 70 | */ 71 | public static function customSiteDriver(string $sitePath): ?string 72 | { 73 | if (! file_exists($sitePath.'/LocalValetDriver.php')) { 74 | return null; 75 | } 76 | 77 | require_once $sitePath.'/LocalValetDriver.php'; 78 | 79 | return 'LocalValetDriver'; 80 | } 81 | 82 | /** 83 | * Get all of the driver classes in a given path. 84 | */ 85 | public static function driversIn(string $path): array 86 | { 87 | if (! is_dir($path)) { 88 | return []; 89 | } 90 | 91 | $drivers = []; 92 | 93 | $dir = new RecursiveDirectoryIterator($path); 94 | $iterator = new RecursiveIteratorIterator($dir); 95 | $regex = new RegexIterator($iterator, '/^.+ValetDriver\.php$/i', RecursiveRegexIterator::GET_MATCH); 96 | 97 | foreach ($regex as $file) { 98 | require_once $file[0]; 99 | 100 | $drivers[] = basename($file[0], '.php'); 101 | } 102 | 103 | return $drivers; 104 | } 105 | 106 | /** 107 | * Get all of the specific drivers shipped with Valet. 108 | */ 109 | public static function specificDrivers(): array 110 | { 111 | return array_map(function ($item) { 112 | return 'Specific\\'.$item; 113 | }, static::driversIn(__DIR__.'/Specific')); 114 | } 115 | 116 | /** 117 | * Get all of the custom drivers defined by the user locally. 118 | */ 119 | public static function customDrivers(): array 120 | { 121 | return array_map(function ($item) { 122 | return 'Custom\\'.$item; 123 | }, static::driversIn(VALET_HOME_PATH.'/Drivers')); 124 | } 125 | 126 | /** 127 | * Take any steps necessary before loading the front controller for this driver. 128 | */ 129 | public function beforeLoading(string $sitePath, string $siteName, string $uri): void 130 | { 131 | // Do nothing 132 | } 133 | 134 | /** 135 | * Mutate the incoming URI. 136 | */ 137 | public function mutateUri(string $uri): string 138 | { 139 | return $uri; 140 | } 141 | 142 | /** 143 | * Serve the static file at the given path. 144 | */ 145 | public function serveStaticFile(string $staticFilePath, string $sitePath, string $siteName, string $uri): void 146 | { 147 | /** 148 | * Back story... 149 | * 150 | * PHP docs *claim* you can set default_mimetype = "" to disable the default 151 | * Content-Type header. This works in PHP 7+, but in PHP 5.* it sends an 152 | * *empty* Content-Type header, which is significantly different than 153 | * sending *no* Content-Type header. 154 | * 155 | * However, if you explicitly set a Content-Type header, then explicitly 156 | * remove that Content-Type header, PHP seems to not re-add the default. 157 | * 158 | * I have a hard time believing this is by design and not coincidence. 159 | * 160 | * Burn. it. all. 161 | */ 162 | header('Content-Type: text/html'); 163 | header_remove('Content-Type'); 164 | 165 | header('X-Accel-Redirect: /'.VALET_STATIC_PREFIX.$staticFilePath); 166 | } 167 | 168 | /** 169 | * Determine if the path is a file and not a directory. 170 | */ 171 | protected function isActualFile(string $path): bool 172 | { 173 | return ! is_dir($path) && file_exists($path); 174 | } 175 | 176 | /** 177 | * Load server environment variables if available. 178 | * Processes any '*' entries first, and then adds site-specific entries. 179 | */ 180 | public function loadServerEnvironmentVariables(string $sitePath, string $siteName): void 181 | { 182 | $varFilePath = $sitePath.'/.valet-env.php'; 183 | if (! file_exists($varFilePath)) { 184 | $varFilePath = VALET_HOME_PATH.'/.valet-env.php'; 185 | } 186 | if (! file_exists($varFilePath)) { 187 | return; 188 | } 189 | 190 | $variables = include $varFilePath; 191 | 192 | $variablesToSet = isset($variables['*']) ? $variables['*'] : []; 193 | 194 | if (isset($variables[$siteName])) { 195 | $variablesToSet = array_merge($variablesToSet, $variables[$siteName]); 196 | } 197 | 198 | foreach ($variablesToSet as $key => $value) { 199 | if (! is_string($key)) { 200 | continue; 201 | } 202 | $_SERVER[$key] = $value; 203 | $_ENV[$key] = $value; 204 | putenv($key.'='.$value); 205 | } 206 | } 207 | 208 | public function composerRequires(string $sitePath, string $namespacedPackage): bool 209 | { 210 | if (! file_exists($sitePath.'/composer.json')) { 211 | return false; 212 | } 213 | 214 | $composer_json_source = file_get_contents($sitePath.'/composer.json'); 215 | $composer_json = json_decode($composer_json_source, true); 216 | 217 | if (json_last_error() !== JSON_ERROR_NONE) { 218 | return false; 219 | } 220 | 221 | return isset($composer_json['require'][$namespacedPackage]); 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /cli/Valet/Expose.php: -------------------------------------------------------------------------------- 1 | get($endpoint)->getBody()); 19 | 20 | if (isset($body->tunnels) && count($body->tunnels) > 0) { 21 | if ($tunnelUrl = $this->findHttpTunnelUrl($body->tunnels, $domain)) { 22 | return $tunnelUrl; 23 | } 24 | } 25 | }, 250); 26 | 27 | if (! empty($response)) { 28 | return $response; 29 | } 30 | 31 | return warning("The project $domain cannot be found as an Expose share.\nEither it is not currently shared, or you may be on a free plan."); 32 | } catch (ConnectException $e) { 33 | return warning('There is no Expose instance running.'); 34 | } 35 | } 36 | 37 | /** 38 | * Find the HTTP tunnel URL from the list of tunnels. 39 | */ 40 | public function findHttpTunnelUrl(array $tunnels, string $domain): ?string 41 | { 42 | foreach ($tunnels as $tunnel) { 43 | if (strpos($tunnel, strtolower($domain))) { 44 | return $tunnel; 45 | } 46 | } 47 | 48 | return null; 49 | } 50 | 51 | /** 52 | * Return whether Expose is installed. 53 | */ 54 | public function installed(): bool 55 | { 56 | return $this->composer->installed('beyondcode/expose'); 57 | } 58 | 59 | /** 60 | * Return which version of Expose is installed. 61 | */ 62 | public function installedVersion(): ?string 63 | { 64 | return $this->composer->installedVersion('beyondcode/expose'); 65 | } 66 | 67 | /** 68 | * Make sure Expose is installed. 69 | */ 70 | public function ensureInstalled(): void 71 | { 72 | if (! $this->installed()) { 73 | $this->composer->installOrFail('beyondcode/expose'); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /cli/Valet/Filesystem.php: -------------------------------------------------------------------------------- 1 | chown($path, $owner); 28 | } 29 | } 30 | 31 | /** 32 | * Ensure that the given directory exists. 33 | */ 34 | public function ensureDirExists(string $path, ?string $owner = null, int $mode = 0755): void 35 | { 36 | if (! $this->isDir($path)) { 37 | $this->mkdir($path, $owner, $mode); 38 | } 39 | } 40 | 41 | /** 42 | * Create a directory as the non-root user. 43 | */ 44 | public function mkdirAsUser(string $path, int $mode = 0755): void 45 | { 46 | $this->mkdir($path, user(), $mode); 47 | } 48 | 49 | /** 50 | * Touch the given path. 51 | */ 52 | public function touch(string $path, ?string $owner = null): string 53 | { 54 | touch($path); 55 | 56 | if ($owner) { 57 | $this->chown($path, $owner); 58 | } 59 | 60 | return $path; 61 | } 62 | 63 | /** 64 | * Touch the given path as the non-root user. 65 | */ 66 | public function touchAsUser(string $path): string 67 | { 68 | return $this->touch($path, user()); 69 | } 70 | 71 | /** 72 | * Determine if the given file exists. 73 | */ 74 | public function exists(string $path): bool 75 | { 76 | return file_exists($path); 77 | } 78 | 79 | /** 80 | * Read the contents of the given file. 81 | */ 82 | public function get(string $path): string 83 | { 84 | return file_get_contents($path); 85 | } 86 | 87 | /** 88 | * Write to the given file. 89 | */ 90 | public function put(string $path, string $contents, ?string $owner = null): void 91 | { 92 | file_put_contents($path, $contents); 93 | 94 | if ($owner) { 95 | $this->chown($path, $owner); 96 | } 97 | } 98 | 99 | /** 100 | * Write to the given file as the non-root user. 101 | */ 102 | public function putAsUser(string $path, ?string $contents): void 103 | { 104 | $this->put($path, $contents, user()); 105 | } 106 | 107 | /** 108 | * Append the contents to the given file. 109 | */ 110 | public function append(string $path, string $contents, ?string $owner = null): void 111 | { 112 | file_put_contents($path, $contents, FILE_APPEND); 113 | 114 | if ($owner) { 115 | $this->chown($path, $owner); 116 | } 117 | } 118 | 119 | /** 120 | * Append the contents to the given file as the non-root user. 121 | */ 122 | public function appendAsUser(string $path, string $contents): void 123 | { 124 | $this->append($path, $contents, user()); 125 | } 126 | 127 | /** 128 | * Copy the given file to a new location. 129 | */ 130 | public function copy(string $from, string $to): void 131 | { 132 | copy($from, $to); 133 | } 134 | 135 | /** 136 | * Copy the given file to a new location for the non-root user. 137 | */ 138 | public function copyAsUser(string $from, string $to): void 139 | { 140 | copy($from, $to); 141 | 142 | $this->chown($to, user()); 143 | } 144 | 145 | /** 146 | * Create a symlink to the given target. 147 | */ 148 | public function symlink(string $target, string $link): void 149 | { 150 | if ($this->exists($link)) { 151 | $this->unlink($link); 152 | } 153 | 154 | symlink($target, $link); 155 | } 156 | 157 | /** 158 | * Create a symlink to the given target for the non-root user. 159 | * 160 | * This uses the command line as PHP can't change symlink permissions. 161 | */ 162 | public function symlinkAsUser(string $target, string $link): void 163 | { 164 | if ($this->exists($link)) { 165 | $this->unlink($link); 166 | } 167 | 168 | CommandLineFacade::runAsUser('ln -s '.escapeshellarg($target).' '.escapeshellarg($link)); 169 | } 170 | 171 | /** 172 | * Delete the file at the given path. 173 | */ 174 | public function unlink(string $path): void 175 | { 176 | if (file_exists($path) || is_link($path)) { 177 | @unlink($path); 178 | } 179 | } 180 | 181 | /** 182 | * Recursively delete a directory and its contents. 183 | */ 184 | public function rmDirAndContents(string $path): void 185 | { 186 | $dir = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS); 187 | $files = new RecursiveIteratorIterator($dir, RecursiveIteratorIterator::CHILD_FIRST); 188 | 189 | foreach ($files as $file) { 190 | if ($file->isLink()) { 191 | unlink($file->getPathname()); 192 | } else { 193 | $file->isDir() ? rmdir($file->getRealPath()) : unlink($file->getRealPath()); 194 | } 195 | } 196 | 197 | rmdir($path); 198 | } 199 | 200 | /** 201 | * Change the owner of the given path. 202 | */ 203 | public function chown(string $path, string $user): void 204 | { 205 | chown($path, $user); 206 | } 207 | 208 | /** 209 | * Change the group of the given path. 210 | */ 211 | public function chgrp(string $path, string $group): void 212 | { 213 | chgrp($path, $group); 214 | } 215 | 216 | /** 217 | * Resolve the given path. 218 | */ 219 | public function realpath(string $path): string 220 | { 221 | return realpath($path); 222 | } 223 | 224 | /** 225 | * Determine if the given path is a symbolic link. 226 | */ 227 | public function isLink(string $path): bool 228 | { 229 | return is_link($path); 230 | } 231 | 232 | /** 233 | * Resolve the given symbolic link. 234 | */ 235 | public function readLink(string $path): string 236 | { 237 | return readlink($path); 238 | } 239 | 240 | /** 241 | * Remove all of the broken symbolic links at the given path. 242 | */ 243 | public function removeBrokenLinksAt(string $path): void 244 | { 245 | collect($this->scandir($path)) 246 | ->filter(function ($file) use ($path) { 247 | return $this->isBrokenLink($path.'/'.$file); 248 | }) 249 | ->each(function ($file) use ($path) { 250 | $this->unlink($path.'/'.$file); 251 | }); 252 | } 253 | 254 | /** 255 | * Determine if the given path is a broken symbolic link. 256 | */ 257 | public function isBrokenLink(string $path): bool 258 | { 259 | return is_link($path) && ! file_exists($path); 260 | } 261 | 262 | /** 263 | * Scan the given directory path. 264 | */ 265 | public function scandir(string $path): array 266 | { 267 | return collect(scandir($path)) 268 | ->reject(function ($file) { 269 | return in_array($file, ['.', '..']); 270 | })->values()->all(); 271 | } 272 | 273 | /** 274 | * Get custom stub file if exists. 275 | */ 276 | public function getStub(string $filename): string 277 | { 278 | $default = __DIR__.'/../stubs/'.$filename; 279 | 280 | $custom = VALET_HOME_PATH.'/stubs/'.$filename; 281 | 282 | $path = file_exists($custom) ? $custom : $default; 283 | 284 | return $this->get($path); 285 | } 286 | } 287 | -------------------------------------------------------------------------------- /cli/Valet/Nginx.php: -------------------------------------------------------------------------------- 1 | brew->hasInstalledNginx()) { 21 | $this->brew->installOrFail('nginx', []); 22 | } 23 | 24 | $this->installConfiguration(); 25 | $this->installServer(); 26 | $this->installNginxDirectory(); 27 | } 28 | 29 | /** 30 | * Install the Nginx configuration file. 31 | */ 32 | public function installConfiguration(): void 33 | { 34 | info('Installing nginx configuration...'); 35 | 36 | $contents = $this->files->getStub('nginx.conf'); 37 | 38 | $this->files->putAsUser( 39 | static::NGINX_CONF, 40 | str_replace(['VALET_USER', 'VALET_HOME_PATH'], [user(), VALET_HOME_PATH], $contents) 41 | ); 42 | } 43 | 44 | /** 45 | * Install the Valet Nginx server configuration file. 46 | */ 47 | public function installServer(): void 48 | { 49 | $this->files->ensureDirExists(BREW_PREFIX.'/etc/nginx/valet'); 50 | 51 | $this->files->putAsUser( 52 | BREW_PREFIX.'/etc/nginx/valet/valet.conf', 53 | str_replace( 54 | ['VALET_HOME_PATH', 'VALET_SERVER_PATH', 'VALET_STATIC_PREFIX'], 55 | [VALET_HOME_PATH, VALET_SERVER_PATH, VALET_STATIC_PREFIX], 56 | $this->site->replaceLoopback($this->files->getStub('valet.conf')) 57 | ) 58 | ); 59 | 60 | $this->files->putAsUser( 61 | BREW_PREFIX.'/etc/nginx/fastcgi_params', 62 | $this->files->getStub('fastcgi_params') 63 | ); 64 | } 65 | 66 | /** 67 | * Install the Nginx configuration directory to the ~/.config/valet directory. 68 | * 69 | * This directory contains all site-specific Nginx servers. 70 | */ 71 | public function installNginxDirectory(): void 72 | { 73 | info('Installing nginx directory...'); 74 | 75 | if (! $this->files->isDir($nginxDirectory = VALET_HOME_PATH.'/Nginx')) { 76 | $this->files->mkdirAsUser($nginxDirectory); 77 | } 78 | 79 | $this->files->putAsUser($nginxDirectory.'/.keep', PHP_EOL); 80 | 81 | $this->rewriteSecureNginxFiles(); 82 | } 83 | 84 | /** 85 | * Check nginx.conf for errors. 86 | */ 87 | private function lint(): void 88 | { 89 | $this->cli->run( 90 | 'sudo nginx -c '.static::NGINX_CONF.' -t', 91 | function ($exitCode, $outputMessage) { 92 | throw new DomainException("Nginx cannot start; please check your nginx.conf [$exitCode: $outputMessage]."); 93 | } 94 | ); 95 | } 96 | 97 | /** 98 | * Generate fresh Nginx servers for existing secure sites. 99 | */ 100 | public function rewriteSecureNginxFiles(): void 101 | { 102 | $tld = $this->configuration->read()['tld']; 103 | $loopback = $this->configuration->read()['loopback']; 104 | 105 | if ($loopback !== VALET_LOOPBACK) { 106 | $this->site->aliasLoopback(VALET_LOOPBACK, $loopback); 107 | } 108 | 109 | $config = compact('tld', 'loopback'); 110 | 111 | $this->site->resecureForNewConfiguration($config, $config); 112 | } 113 | 114 | /** 115 | * Restart the Nginx service. 116 | */ 117 | public function restart(): void 118 | { 119 | $this->lint(); 120 | 121 | $this->brew->restartService($this->brew->nginxServiceName()); 122 | } 123 | 124 | /** 125 | * Stop the Nginx service. 126 | */ 127 | public function stop(): void 128 | { 129 | $this->brew->stopService(['nginx']); 130 | } 131 | 132 | /** 133 | * Forcefully uninstall Nginx. 134 | */ 135 | public function uninstall(): void 136 | { 137 | $this->brew->stopService(['nginx', 'nginx-full']); 138 | $this->brew->uninstallFormula('nginx nginx-full'); 139 | $this->cli->quietly('rm -rf '.BREW_PREFIX.'/etc/nginx '.BREW_PREFIX.'/var/log/nginx'); 140 | } 141 | 142 | /** 143 | * Return a list of all sites with explicit Nginx configurations. 144 | */ 145 | public function configuredSites(): Collection 146 | { 147 | return collect($this->files->scandir(VALET_HOME_PATH.'/Nginx')) 148 | ->reject(function ($file) { 149 | return starts_with($file, '.'); 150 | }); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /cli/Valet/Ngrok.php: -------------------------------------------------------------------------------- 1 | tunnelsEndpoints as $endpoint) { 27 | try { 28 | $response = retry(20, function () use ($endpoint, $domain) { 29 | $body = json_decode((new Client)->get($endpoint)->getBody()); 30 | 31 | if (isset($body->tunnels) && count($body->tunnels) > 0) { 32 | if ($tunnelUrl = $this->findHttpTunnelUrl($body->tunnels, $domain)) { 33 | return $tunnelUrl; 34 | } 35 | } 36 | 37 | throw new DomainException('Failed to retrieve tunnel URL.'); 38 | }, 250); 39 | 40 | if (! empty($response)) { 41 | return $response; 42 | } 43 | } catch (Exception $e) { 44 | // Do nothing, suppress the exception to check the other port 45 | } 46 | } 47 | 48 | throw new DomainException('There is no Ngrok tunnel established for '.$domain.'.'); 49 | } 50 | 51 | /** 52 | * Find the HTTP/HTTPS tunnel URL from the list of tunnels. 53 | */ 54 | public function findHttpTunnelUrl(array $tunnels, string $domain): ?string 55 | { 56 | $httpTunnel = null; 57 | $httpsTunnel = null; 58 | 59 | // If there are active tunnels on the Ngrok instance we will spin through them and 60 | // find the one responding on HTTP. Each tunnel has an HTTP and a HTTPS address 61 | // if no HTTP tunnel is found we will return the HTTPS tunnel as a fallback. 62 | 63 | // Iterate through tunnels to find both HTTP and HTTPS tunnels 64 | foreach ($tunnels as $tunnel) { 65 | if (stripos($tunnel->config->addr, $domain)) { 66 | if ($tunnel->proto === 'http') { 67 | $httpTunnel = $tunnel->public_url; 68 | } elseif ($tunnel->proto === 'https') { 69 | $httpsTunnel = $tunnel->public_url; 70 | } 71 | } 72 | } 73 | 74 | // Return HTTP tunnel if available; HTTPS tunnel if not; null if neither 75 | return $httpTunnel ?? $httpsTunnel; 76 | } 77 | 78 | /** 79 | * Set the Ngrok auth token. 80 | */ 81 | public function setToken($token): string 82 | { 83 | return $this->cli->runAsUser(BREW_PREFIX.'/bin/ngrok authtoken '.$token); 84 | } 85 | 86 | /** 87 | * Return whether ngrok is installed. 88 | */ 89 | public function installed(): bool 90 | { 91 | return $this->brew->installed('ngrok'); 92 | } 93 | 94 | /** 95 | * Make sure ngrok is installed. 96 | */ 97 | public function ensureInstalled(): void 98 | { 99 | $this->brew->ensureInstalled('ngrok'); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /cli/Valet/PhpFpm.php: -------------------------------------------------------------------------------- 1 | brew->hasInstalledPhp()) { 25 | $this->brew->ensureInstalled('php', [], $this->taps); 26 | } 27 | 28 | $this->files->ensureDirExists(VALET_HOME_PATH.'/Log', user()); 29 | 30 | foreach ($this->utilizedPhpVersions() as $phpVersion) { 31 | $this->createConfigurationFiles($phpVersion); 32 | } 33 | 34 | // Remove old valet.sock 35 | $this->files->unlink(VALET_HOME_PATH.'/valet.sock'); 36 | $this->cli->quietly('sudo rm '.VALET_HOME_PATH.'/valet.sock'); 37 | 38 | $this->restart(); 39 | 40 | $linkedPhpVersion = $this->brew->linkedPhp(); 41 | $this->symlinkPrimaryValetSock($linkedPhpVersion); 42 | } 43 | 44 | /** 45 | * Forcefully uninstall all of Valet's supported PHP versions and configurations. 46 | */ 47 | public function uninstall(): void 48 | { 49 | $this->brew->uninstallAllPhpVersions(); 50 | rename(BREW_PREFIX.'/etc/php', BREW_PREFIX.'/etc/php-valet-bak'.time()); 51 | $this->cli->run('rm -rf '.BREW_PREFIX.'/var/log/php-fpm.log'); 52 | } 53 | 54 | /** 55 | * Create (or re-create) the PHP FPM configuration files. 56 | * 57 | * Writes FPM config file, pointing to the correct .sock file, and log and ini files. 58 | */ 59 | public function createConfigurationFiles(string $phpVersion): void 60 | { 61 | info("Updating PHP configuration for {$phpVersion}..."); 62 | 63 | $fpmConfigFile = $this->fpmConfigPath($phpVersion); 64 | 65 | $this->files->ensureDirExists(dirname($fpmConfigFile), user()); 66 | 67 | // rename (to disable) old FPM Pool configuration, regardless of whether it's a default config or one customized by an older Valet version 68 | $oldFile = dirname($fpmConfigFile).'/www.conf'; 69 | if (file_exists($oldFile)) { 70 | rename($oldFile, $oldFile.'-backup'); 71 | } 72 | 73 | // Create FPM Config File from stub 74 | $contents = str_replace( 75 | ['VALET_USER', 'VALET_HOME_PATH', 'valet.sock'], 76 | [user(), VALET_HOME_PATH, self::fpmSockName($phpVersion)], 77 | $this->files->getStub('etc-phpfpm-valet.conf') 78 | ); 79 | $this->files->put($fpmConfigFile, $contents); 80 | 81 | // Create other config files from stubs 82 | $destDir = dirname(dirname($fpmConfigFile)).'/conf.d'; 83 | $this->files->ensureDirExists($destDir, user()); 84 | 85 | $this->files->putAsUser( 86 | $destDir.'/php-memory-limits.ini', 87 | $this->files->getStub('php-memory-limits.ini') 88 | ); 89 | 90 | $contents = str_replace( 91 | ['VALET_USER', 'VALET_HOME_PATH'], 92 | [user(), VALET_HOME_PATH], 93 | $this->files->getStub('etc-phpfpm-error_log.ini') 94 | ); 95 | $this->files->putAsUser($destDir.'/error_log.ini', $contents); 96 | 97 | // Create log directory and file 98 | $this->files->ensureDirExists(VALET_HOME_PATH.'/Log', user()); 99 | $this->files->touch(VALET_HOME_PATH.'/Log/php-fpm.log', user()); 100 | } 101 | 102 | /** 103 | * Restart the PHP FPM process (if one specified) or processes (if none specified). 104 | */ 105 | public function restart(?string $phpVersion = null): void 106 | { 107 | $this->brew->restartService($phpVersion ?: $this->utilizedPhpVersions()); 108 | } 109 | 110 | /** 111 | * Stop the PHP FPM process. 112 | */ 113 | public function stop(): void 114 | { 115 | info('Stopping phpfpm...'); 116 | call_user_func_array( 117 | [$this->brew, 'stopService'], 118 | Brew::SUPPORTED_PHP_VERSIONS 119 | ); 120 | } 121 | 122 | /** 123 | * Get the path to the FPM configuration file for the current PHP version. 124 | */ 125 | public function fpmConfigPath(?string $phpVersion = null): string 126 | { 127 | if (! $phpVersion) { 128 | $phpVersion = $this->brew->linkedPhp(); 129 | } 130 | 131 | $versionNormalized = $this->normalizePhpVersion($phpVersion === 'php' ? Brew::LATEST_PHP_VERSION : $phpVersion); 132 | $versionNormalized = preg_replace('~[^\d\.]~', '', $versionNormalized); 133 | 134 | return BREW_PREFIX."/etc/php/{$versionNormalized}/php-fpm.d/valet-fpm.conf"; 135 | } 136 | 137 | /** 138 | * Stop only the running php services. 139 | */ 140 | public function stopRunning(): void 141 | { 142 | info('Stopping phpfpm...'); 143 | $this->brew->stopService( 144 | $this->brew->getAllRunningServices() 145 | ->filter(function ($service) { 146 | return substr($service, 0, 3) === 'php'; 147 | }) 148 | ->all() 149 | ); 150 | } 151 | 152 | /** 153 | * Stop a given PHP version, if that specific version isn't being used globally or by any sites. 154 | */ 155 | public function stopIfUnused(?string $phpVersion = null): void 156 | { 157 | if (! $phpVersion) { 158 | return; 159 | } 160 | 161 | $phpVersion = $this->normalizePhpVersion($phpVersion); 162 | 163 | if (! in_array($phpVersion, $this->utilizedPhpVersions())) { 164 | $this->brew->stopService($phpVersion); 165 | } 166 | } 167 | 168 | /** 169 | * Isolate a given directory to use a specific version of PHP. 170 | */ 171 | public function isolateDirectory(string $directory, string $version): void 172 | { 173 | $site = $this->site->getSiteUrl($directory); 174 | 175 | $version = $this->validateRequestedVersion($version); 176 | 177 | $this->brew->ensureInstalled($version, [], $this->taps); 178 | 179 | $oldCustomPhpVersion = $this->site->customPhpVersion($site); // Example output: "74" 180 | $this->createConfigurationFiles($version); 181 | 182 | $this->site->isolate($site, $version); 183 | 184 | $this->stopIfUnused($oldCustomPhpVersion); 185 | $this->restart($version); 186 | $this->nginx->restart(); 187 | 188 | info(sprintf('The site [%s] is now using %s.', $site, $version)); 189 | } 190 | 191 | /** 192 | * Remove PHP version isolation for a given directory. 193 | */ 194 | public function unIsolateDirectory(string $directory): void 195 | { 196 | $site = $this->site->getSiteUrl($directory); 197 | 198 | $oldCustomPhpVersion = $this->site->customPhpVersion($site); // Example output: "74" 199 | 200 | $this->site->removeIsolation($site); 201 | $this->stopIfUnused($oldCustomPhpVersion); 202 | $this->nginx->restart(); 203 | 204 | info(sprintf('The site [%s] is now using the default PHP version.', $site)); 205 | } 206 | 207 | /** 208 | * List all directories with PHP isolation configured. 209 | */ 210 | public function isolatedDirectories(): Collection 211 | { 212 | return $this->nginx->configuredSites()->filter(function ($item) { 213 | return strpos($this->files->get(VALET_HOME_PATH.'/Nginx/'.$item), ISOLATED_PHP_VERSION) !== false; 214 | })->map(function ($item) { 215 | return ['url' => $item, 'version' => $this->normalizePhpVersion($this->site->customPhpVersion($item))]; 216 | }); 217 | } 218 | 219 | /** 220 | * Use a specific version of PHP globally. 221 | */ 222 | public function useVersion(string $version, bool $force = false): ?string 223 | { 224 | $version = $this->validateRequestedVersion($version); 225 | 226 | try { 227 | if ($version === $this->brew->linkedPhp() && ! $force) { 228 | output(sprintf('Valet is already using version: %s. To re-link and re-configure use the --force parameter.'.PHP_EOL, 229 | $version)); 230 | exit(); 231 | } 232 | } catch (DomainException $e) { /* ignore thrown exception when no linked php is found */ 233 | } 234 | 235 | $this->brew->ensureInstalled($version, [], $this->taps); 236 | 237 | // Unlink the current global PHP if there is one installed 238 | if ($this->brew->hasLinkedPhp()) { 239 | $currentVersion = $this->brew->getLinkedPhpFormula(); 240 | info(sprintf('Unlinking current version: %s', $currentVersion)); 241 | $this->brew->unlink($currentVersion); 242 | } 243 | 244 | info(sprintf('Linking new version: %s', $version)); 245 | $this->brew->link($version, true); 246 | 247 | $this->stopRunning(); 248 | 249 | $this->install(); 250 | 251 | $newVersion = $version === 'php' ? $this->brew->determineAliasedVersion($version) : $version; 252 | 253 | $this->nginx->restart(); 254 | 255 | info(sprintf('Valet is now using %s.', $newVersion).PHP_EOL); 256 | info('Note that you might need to run composer global update if your PHP version change affects the dependencies of global packages required by Composer.'); 257 | 258 | return $newVersion; 259 | } 260 | 261 | /** 262 | * Symlink (Capistrano-style) a given Valet.sock file to be the primary valet.sock. 263 | */ 264 | public function symlinkPrimaryValetSock(string $phpVersion): void 265 | { 266 | $this->files->symlinkAsUser(VALET_HOME_PATH.'/'.$this->fpmSockName($phpVersion), VALET_HOME_PATH.'/valet.sock'); 267 | } 268 | 269 | /** 270 | * If passed php7.4, or php74, 7.4, or 74 formats, normalize to php@7.4 format. 271 | */ 272 | public function normalizePhpVersion(?string $version): string 273 | { 274 | return preg_replace('/(?:php@?)?([0-9+])(?:.)?([0-9+])/i', 'php@$1.$2', (string) $version); 275 | } 276 | 277 | /** 278 | * Validate the requested version to be sure we can support it. 279 | */ 280 | public function validateRequestedVersion(string $version): string 281 | { 282 | if (is_null($version)) { 283 | throw new DomainException("Please specify a PHP version (try something like 'php@8.1')"); 284 | } 285 | 286 | $version = $this->normalizePhpVersion($version); 287 | 288 | if (! $this->brew->supportedPhpVersions()->contains($version)) { 289 | throw new DomainException("Valet doesn't support PHP version: {$version} (try something like 'php@8.1' instead)"); 290 | } 291 | 292 | if (strpos($aliasedVersion = $this->brew->determineAliasedVersion($version), '@')) { 293 | return $aliasedVersion; 294 | } 295 | 296 | if ($version === 'php') { 297 | if ($this->brew->hasInstalledPhp()) { 298 | throw new DomainException('Brew is already using PHP '.PHP_VERSION.' as \'php\' in Homebrew. To use another version, please specify. eg: php@8.1'); 299 | } 300 | } 301 | 302 | return $version; 303 | } 304 | 305 | /** 306 | * Get FPM sock file name for a given PHP version. 307 | */ 308 | public static function fpmSockName(?string $phpVersion = null): string 309 | { 310 | $versionInteger = preg_replace('~[^\d]~', '', $phpVersion); 311 | 312 | return "valet{$versionInteger}.sock"; 313 | } 314 | 315 | /** 316 | * Get a list including the global PHP version and allPHP versions currently serving "isolated sites" (sites with 317 | * custom Nginx configs pointing them to a specific PHP version). 318 | */ 319 | public function utilizedPhpVersions(): array 320 | { 321 | $fpmSockFiles = $this->brew->supportedPhpVersions()->map(function ($version) { 322 | return self::fpmSockName($this->normalizePhpVersion($version)); 323 | })->unique(); 324 | 325 | return $this->nginx->configuredSites()->map(function ($file) use ($fpmSockFiles) { 326 | $content = $this->files->get(VALET_HOME_PATH.'/Nginx/'.$file); 327 | 328 | // Get the normalized PHP version for this config file, if it's defined 329 | foreach ($fpmSockFiles as $sock) { 330 | if (strpos($content, $sock) !== false) { 331 | // Extract the PHP version number from a custom .sock path and normalize it to, e.g., "php@7.4" 332 | return $this->normalizePhpVersion(str_replace(['valet', '.sock'], '', $sock)); 333 | } 334 | } 335 | })->merge([$this->brew->linkedPhp()])->filter()->unique()->values()->toArray(); 336 | } 337 | } 338 | -------------------------------------------------------------------------------- /cli/Valet/Server.php: -------------------------------------------------------------------------------- 1 | config = $config; 13 | } 14 | 15 | /** 16 | * Extract $uri from $SERVER['REQUEST_URI'] variable. 17 | */ 18 | public static function uriFromRequestUri(string $requestUri): string 19 | { 20 | return rawurldecode( 21 | explode('?', $requestUri)[0] 22 | ); 23 | } 24 | 25 | /** 26 | * Extract the domain from the site name. 27 | */ 28 | public static function domainFromSiteName(string $siteName): string 29 | { 30 | return array_slice(explode('.', $siteName), -1)[0]; 31 | } 32 | 33 | /** 34 | * Show the Valet 404 "Not Found" page. 35 | */ 36 | public static function show404() 37 | { 38 | http_response_code(404); 39 | require __DIR__.'/../../cli/templates/404.html'; 40 | exit; 41 | } 42 | 43 | /** 44 | * Show directory listing or 404 if directory doesn't exist. 45 | */ 46 | public static function showDirectoryListing(string $valetSitePath, string $uri) 47 | { 48 | $is_root = ($uri == '/'); 49 | $directory = ($is_root) ? $valetSitePath : $valetSitePath.$uri; 50 | 51 | if (! file_exists($directory)) { 52 | static::show404(); 53 | } 54 | 55 | // Sort directories at the top 56 | $paths = glob("$directory/*"); 57 | usort($paths, function ($a, $b) { 58 | return (is_dir($a) == is_dir($b)) ? strnatcasecmp($a, $b) : (is_dir($a) ? -1 : 1); 59 | }); 60 | 61 | // Output the HTML for the directory listing 62 | echo "

Index of $uri

"; 63 | echo '
'; 64 | echo implode('
'.PHP_EOL, array_map(function ($path) use ($uri, $is_root) { 65 | $file = basename($path); 66 | 67 | return ($is_root) ? "/$file" : "$uri/$file/"; 68 | }, $paths)); 69 | 70 | exit; 71 | } 72 | 73 | /** 74 | * Return whether a given host (from $_SERVER['HTTP_HOST']) is an IP address. 75 | */ 76 | public static function hostIsIpAddress(string $host): bool 77 | { 78 | return preg_match('/^([0-9]+\.){3}[0-9]+$/', $host); 79 | } 80 | 81 | /** 82 | * Return the root level Valet site if given the request URI ($_SERVER['REQUEST_URI']) 83 | * of an address using IP address local access. 84 | * 85 | * E.g. URL is 192.168.1.100/onramp.tes/auth/login, passes $uri as onramp.test/auth/login and 86 | * $tld as 'test', and this method returns onramp.test 87 | * 88 | * For use when accessing Valet sites across a local network. 89 | */ 90 | public static function valetSiteFromIpAddressUri(string $uri, string $tld): ?string 91 | { 92 | if (preg_match('/^[-.0-9a-zA-Z]+\.'.$tld.'/', $uri, $matches)) { 93 | return $matches[0]; 94 | } 95 | 96 | return null; 97 | } 98 | 99 | /** 100 | * Extract site name from HTTP host, stripping www. and supporting wildcard DNS. 101 | */ 102 | public function siteNameFromHttpHost(string $httpHost): string 103 | { 104 | $siteName = basename( 105 | // Filter host to support wildcard dns feature 106 | $this->allowWildcardDnsDomains($httpHost), 107 | '.'.$this->config['tld'] 108 | ); 109 | 110 | if (strpos($siteName, 'www.') === 0) { 111 | $siteName = substr($siteName, 4); 112 | } 113 | 114 | return $siteName; 115 | } 116 | 117 | /** 118 | * You may use wildcard DNS provider nip.io as a tool for testing your site via an IP address. 119 | * First, determine the IP address of your local computer (like 192.168.0.10). 120 | * Then, visit http://project.your-ip.nip.io - e.g.: http://laravel.192.168.0.10.nip.io. 121 | */ 122 | public function allowWildcardDnsDomains(string $domain): string 123 | { 124 | $services = [ 125 | '.*.*.*.*.nip.io', 126 | '-*-*-*-*.nip.io', 127 | ]; 128 | 129 | if (isset($this->config['tunnel_services'])) { 130 | $services = array_merge($services, (array) $this->config['tunnel_services']); 131 | } 132 | 133 | $patterns = []; 134 | foreach ($services as $service) { 135 | $pattern = preg_quote($service, '#'); 136 | $pattern = str_replace('\*', '.*', $pattern); 137 | $patterns[] = '(.*)'.$pattern; 138 | } 139 | 140 | $pattern = implode('|', $patterns); 141 | 142 | if (preg_match('#(?:'.$pattern.')\z#u', $domain, $matches)) { 143 | $domain = array_pop($matches); 144 | } 145 | 146 | if (strpos($domain, ':') !== false) { 147 | $domain = explode(':', $domain)[0]; 148 | } 149 | 150 | return $domain; 151 | } 152 | 153 | /** 154 | * Determine the fully qualified path to the site. 155 | * Inspects registered path directories, case-sensitive. 156 | */ 157 | public function sitePath(string $siteName): ?string 158 | { 159 | $valetSitePath = null; 160 | $domain = static::domainFromSiteName($siteName); 161 | 162 | foreach ($this->config['paths'] as $path) { 163 | if (! is_dir($path)) { 164 | continue; 165 | } 166 | 167 | $handle = opendir($path); 168 | 169 | if ($handle === false) { 170 | continue; 171 | } 172 | 173 | $dirs = []; 174 | 175 | while (($file = readdir($handle)) !== false) { 176 | if (is_dir($path.'/'.$file) && ! in_array($file, ['.', '..'])) { 177 | $dirs[] = $file; 178 | } 179 | } 180 | 181 | closedir($handle); 182 | 183 | // Note: strtolower used below because Nginx only tells us lowercase names 184 | foreach ($dirs as $dir) { 185 | if ($siteName === strtolower($dir)) { 186 | // early return when exact match for linked subdomain 187 | return $path.'/'.$dir; 188 | } 189 | 190 | if ($domain === strtolower($dir)) { 191 | // no early return here because the foreach may still have some subdomains to process with higher priority 192 | $valetSitePath = $path.'/'.$dir; 193 | } 194 | } 195 | 196 | if ($valetSitePath) { 197 | return $valetSitePath; 198 | } 199 | } 200 | 201 | return null; 202 | } 203 | 204 | /** 205 | * Return the default site path for uncaught URLs, if it's set. 206 | **/ 207 | public function defaultSitePath(): ?string 208 | { 209 | if (isset($this->config['default']) && is_string($this->config['default']) && is_dir($this->config['default'])) { 210 | return $this->config['default']; 211 | } 212 | 213 | return null; 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /cli/Valet/Status.php: -------------------------------------------------------------------------------- 1 | checks())->map(function (array $check) use (&$isValid) { 24 | if (! $thisIsValid = $check['check']()) { 25 | $this->debugInstructions[] = $check['debug']; 26 | $isValid = false; 27 | } 28 | 29 | return ['description' => $check['description'], 'success' => $thisIsValid ? 'Yes' : 'No']; 30 | }); 31 | 32 | return [ 33 | 'success' => $isValid, 34 | 'output' => $output->all(), 35 | 'debug' => $this->debugInstructions(), 36 | ]; 37 | } 38 | 39 | /** 40 | * Define a list of checks to test the health of the Valet ecosystem of tools and configs. 41 | */ 42 | public function checks(): array 43 | { 44 | $linkedPhp = $this->brew->getLinkedPhpFormula(); 45 | 46 | return [ 47 | [ 48 | 'description' => 'Is Valet fully installed?', 49 | 'check' => function () { 50 | return $this->valetInstalled(); 51 | }, 52 | 'debug' => 'Run `composer require laravel/valet` and `valet install`.', 53 | ], 54 | [ 55 | 'description' => 'Is Valet config valid?', 56 | 'check' => function () { 57 | try { 58 | $config = $this->config->read(); 59 | 60 | foreach (['tld', 'loopback', 'paths'] as $key) { 61 | if (! array_key_exists($key, $config)) { 62 | $this->debugInstructions[] = 'Your Valet config is missing the "'.$key.'" key. Re-add this manually, or delete your config file and re-install.'; 63 | 64 | return false; 65 | } 66 | } 67 | 68 | return true; 69 | } catch (\JsonException $e) { 70 | return false; 71 | } 72 | }, 73 | 'debug' => 'Run `valet install` to update your configuration.', 74 | ], 75 | [ 76 | 'description' => 'Is Homebrew installed?', 77 | 'check' => function () { 78 | return $this->cli->run('which brew') !== ''; 79 | }, 80 | 'debug' => 'Visit https://brew.sh/ for instructions on installing Homebrew.', 81 | ], 82 | [ 83 | 'description' => 'Is DnsMasq installed?', 84 | 'check' => function () { 85 | return $this->brew->installed('dnsmasq'); 86 | }, 87 | 'debug' => 'Run `valet install`.', 88 | ], 89 | [ 90 | 'description' => 'Is Dnsmasq running?', 91 | 'check' => function () { 92 | return $this->isBrewServiceRunning('dnsmasq'); 93 | }, 94 | 'debug' => 'Run `valet restart`.', 95 | ], 96 | [ 97 | 'description' => 'Is Dnsmasq running as root?', 98 | 'check' => function () { 99 | return $this->isBrewServiceRunningAsRoot('dnsmasq'); 100 | }, 101 | 'debug' => 'Uninstall Dnsmasq with Brew and run `valet install`.', 102 | ], 103 | [ 104 | 'description' => 'Is Nginx installed?', 105 | 'check' => function () { 106 | return $this->brew->installed('nginx') || $this->brew->installed('nginx-full'); 107 | }, 108 | 'debug' => 'Run `valet install`.', 109 | ], 110 | [ 111 | 'description' => 'Is Nginx running?', 112 | 'check' => function () { 113 | return $this->isBrewServiceRunning('nginx'); 114 | }, 115 | 'debug' => 'Run `valet restart`.', 116 | ], 117 | [ 118 | 'description' => 'Is Nginx running as root?', 119 | 'check' => function () { 120 | return $this->isBrewServiceRunningAsRoot('nginx'); 121 | }, 122 | 'debug' => 'Uninstall nginx with Brew and run `valet install`.', 123 | ], 124 | [ 125 | 'description' => 'Is PHP installed?', 126 | 'check' => function () { 127 | return $this->brew->hasInstalledPhp(); 128 | }, 129 | 'debug' => 'Run `valet install`.', 130 | ], 131 | [ 132 | 'description' => 'Is linked PHP ('.$linkedPhp.') running?', 133 | 'check' => function () use ($linkedPhp) { 134 | return $this->isBrewServiceRunning($linkedPhp); 135 | }, 136 | 'debug' => 'Run `valet restart`.', 137 | ], 138 | [ 139 | 'description' => 'Is linked PHP ('.$linkedPhp.') running as root?', 140 | 'check' => function () use ($linkedPhp) { 141 | return $this->isBrewServiceRunningAsRoot($linkedPhp); 142 | }, 143 | 'debug' => 'Uninstall PHP with Brew and run `valet use php@8.2`', 144 | ], 145 | [ 146 | 'description' => 'Is valet.sock present?', 147 | 'check' => function () { 148 | return $this->files->exists(VALET_HOME_PATH.'/valet.sock'); 149 | }, 150 | 'debug' => 'Run `valet install`.', 151 | ], 152 | ]; 153 | } 154 | 155 | public function isBrewServiceRunning(string $name, bool $exactMatch = true): bool 156 | { 157 | return $this->isBrewServiceRunningAsUser($name, $exactMatch) 158 | || $this->isBrewServiceRunningAsRoot($name, $exactMatch); 159 | } 160 | 161 | public function isBrewServiceRunningAsRoot(string $name, bool $exactMatch = true): bool 162 | { 163 | if (! $this->brewServicesRootOutput) { 164 | $this->brewServicesRootOutput = $this->jsonFromCli('brew services info --all --json', true); 165 | } 166 | 167 | return $this->isBrewServiceRunningGivenServiceList($this->brewServicesRootOutput, $name, $exactMatch); 168 | } 169 | 170 | public function isBrewServiceRunningAsUser(string $name, bool $exactMatch = true): bool 171 | { 172 | if (! $this->brewServicesUserOutput) { 173 | $this->brewServicesUserOutput = $this->jsonFromCli('brew services info --all --json', false); 174 | } 175 | 176 | return $this->isBrewServiceRunningGivenServiceList($this->brewServicesUserOutput, $name, $exactMatch); 177 | } 178 | 179 | public function jsonFromCli(string $input, bool $sudo = false): array 180 | { 181 | $contents = $sudo ? $this->cli->run($input) : $this->cli->runAsUser($input); 182 | // Skip to the JSON, to avoid warnings; we're only getting arrays so start with [ 183 | $contents = substr($contents, strpos($contents, '[')); 184 | 185 | try { 186 | return json_decode($contents, false, 512, JSON_THROW_ON_ERROR); 187 | } catch (\Throwable $e) { 188 | $command = $sudo ? 'sudo '.$input : $input; 189 | throw new \Exception('Invalid JSON returned from command: '.$command); 190 | } 191 | } 192 | 193 | protected function isBrewServiceRunningGivenServiceList(array $serviceList, string $name, bool $exactMatch = true): bool 194 | { 195 | foreach ($serviceList as $service) { 196 | if ($service->running === true) { 197 | if ($exactMatch && $service->name == $name) { 198 | return true; 199 | } elseif (! $exactMatch && str_contains($service->name, $name)) { 200 | return true; 201 | } 202 | } 203 | } 204 | 205 | return false; 206 | } 207 | 208 | public function valetInstalled(): bool 209 | { 210 | return is_dir(VALET_HOME_PATH) 211 | && file_exists($this->config->path()) 212 | && is_dir(VALET_HOME_PATH.'/Drivers') 213 | && is_dir(VALET_HOME_PATH.'/Sites') 214 | && is_dir(VALET_HOME_PATH.'/Log') 215 | && is_dir(VALET_HOME_PATH.'/Certificates'); 216 | } 217 | 218 | public function debugInstructions(): string 219 | { 220 | return collect($this->debugInstructions)->unique()->join(PHP_EOL); 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /cli/Valet/Upgrader.php: -------------------------------------------------------------------------------- 1 | pruneMissingDirectories(); 18 | $this->pruneSymbolicLinks(); 19 | $this->fixOldSampleValetDriver(); 20 | $this->errorIfOldCustomDrivers(); 21 | } 22 | 23 | /** 24 | * Prune all non-existent paths from the configuration. 25 | */ 26 | public function pruneMissingDirectories(): void 27 | { 28 | try { 29 | Configuration::prune(); 30 | } catch (\JsonException $e) { 31 | warning('Invalid configuration file at '.Configuration::path().'.'); 32 | exit; 33 | } 34 | } 35 | 36 | /** 37 | * Remove all broken symbolic links in the Valet config Sites diretory. 38 | */ 39 | public function pruneSymbolicLinks(): void 40 | { 41 | Site::pruneLinks(); 42 | } 43 | 44 | /** 45 | * If the user has the old `SampleValetDriver` without the Valet namespace, 46 | * replace it with the new `SampleValetDriver` that uses the namespace. 47 | */ 48 | public function fixOldSampleValetDriver(): void 49 | { 50 | $samplePath = VALET_HOME_PATH.'/Drivers/SampleValetDriver.php'; 51 | 52 | if ($this->files->exists($samplePath)) { 53 | $contents = $this->files->get($samplePath); 54 | 55 | if (! str_contains($contents, 'namespace')) { 56 | if ($contents !== $this->files->get(__DIR__.'/../stubs/Valet3SampleValetDriver.php')) { 57 | warning('Existing SampleValetDriver.php has been customized.'); 58 | warning('Backing up at '.$samplePath.'.bak'); 59 | 60 | $this->files->putAsUser( 61 | VALET_HOME_PATH.'/Drivers/SampleValetDriver.php.bak', 62 | $contents 63 | ); 64 | } 65 | 66 | $this->files->putAsUser( 67 | VALET_HOME_PATH.'/Drivers/SampleValetDriver.php', 68 | $this->files->getStub('SampleValetDriver.php') 69 | ); 70 | } 71 | } 72 | } 73 | 74 | /** 75 | * Throw an exception if the user has old (non-namespaced) custom drivers. 76 | */ 77 | public function errorIfOldCustomDrivers(): void 78 | { 79 | $driversPath = VALET_HOME_PATH.'/Drivers'; 80 | 81 | if (! $this->files->isDir($driversPath)) { 82 | return; 83 | } 84 | 85 | foreach ($this->files->scanDir($driversPath) as $driver) { 86 | if (! ends_with($driver, 'ValetDriver.php')) { 87 | continue; 88 | } 89 | 90 | if (! str_contains($this->files->get($driversPath.'/'.$driver), 'namespace')) { 91 | warning('Please make sure all custom drivers have been upgraded for Valet 4.'); 92 | warning('See the upgrade guide for more info:'); 93 | warning('https://github.com/laravel/valet/blob/master/UPGRADE.md'); 94 | exit; 95 | } 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /cli/Valet/Valet.php: -------------------------------------------------------------------------------- 1 | unlinkFromUsersBin(); 19 | 20 | $this->cli->runAsUser('ln -s "'.realpath(__DIR__.'/../../valet').'" '.$this->valetBin); 21 | } 22 | 23 | /** 24 | * Remove the symlink from the user's local bin. 25 | */ 26 | public function unlinkFromUsersBin(): void 27 | { 28 | $this->cli->quietlyAsUser('rm '.$this->valetBin); 29 | } 30 | 31 | /** 32 | * Determine if this is the latest version of Valet. 33 | * 34 | * @throws \GuzzleHttp\Exception\GuzzleException 35 | */ 36 | public function onLatestVersion(string $currentVersion): bool 37 | { 38 | $url = 'https://api.github.com/repos/laravel/valet/releases/latest'; 39 | $response = json_decode((new Client)->get($url)->getBody()); 40 | 41 | return version_compare($currentVersion, trim($response->tag_name, 'v'), '>='); 42 | } 43 | 44 | /** 45 | * Create the "sudoers.d" entry for running Valet. 46 | */ 47 | public function createSudoersEntry(): void 48 | { 49 | $this->files->ensureDirExists('/etc/sudoers.d'); 50 | 51 | $this->files->put('/etc/sudoers.d/valet', 'Cmnd_Alias VALET = '.BREW_PREFIX.'/bin/valet * 52 | %admin ALL=(root) NOPASSWD:SETENV: VALET'.PHP_EOL); 53 | } 54 | 55 | /** 56 | * Remove the "sudoers.d" entry for running Valet. 57 | */ 58 | public function removeSudoersEntry(): void 59 | { 60 | $this->cli->quietly('rm /etc/sudoers.d/valet'); 61 | } 62 | 63 | /** 64 | * Run composer global diagnose. 65 | */ 66 | public function composerGlobalDiagnose(): void 67 | { 68 | $this->cli->runAsUser('composer global diagnose'); 69 | } 70 | 71 | /** 72 | * Run composer global update. 73 | */ 74 | public function composerGlobalUpdate(): void 75 | { 76 | $this->cli->runAsUser('composer global update'); 77 | } 78 | 79 | public function forceUninstallText(): string 80 | { 81 | return 'NOTE: 82 | Valet has attempted to uninstall itself, but there are some steps you need to do manually: 83 | 84 | 1. Run php -v, and also which php, to see what PHP version you are now really using. 85 | 2. Run composer global update to update your globally-installed Composer packages to work with your default PHP. 86 | NOTE: Composer may have other dependencies for other global apps you have installed, and those may not be compatible with your default PHP. 87 | 3. Finish removing any Composer fragments of Valet: 88 | Run composer global remove laravel/valet 89 | and then rm '.BREW_PREFIX.'/bin/valet to remove the Valet bin link if it still exists. 90 | 91 | Optional: 92 | - brew list --formula will show any other Homebrew services installed, in case you want to make changes to those as well. 93 | - brew doctor can indicate if there might be any broken things left behind. 94 | - brew cleanup can purge old cached Homebrew downloads. 95 | 96 | If you had customized your Mac DNS settings in System Preferences->Network, you will need to remove 127.0.0.1 from that list. 97 | 98 | You may also want to open Keychain Access and search for valet to remove any leftover trust certificates.'; 99 | } 100 | 101 | public function uninstallText(): string 102 | { 103 | return ' 104 | You did not pass the --force parameter, so this will only return instructions on how to uninstall, not ACTUALLY uninstall anything. 105 | A --force removal WILL delete your custom configuration information, so be sure to make backups first. 106 | 107 | IF YOU WANT TO UNINSTALL VALET MANUALLY, DO THE FOLLOWING... 108 | 109 | 1. Valet Keychain Certificates 110 | Before removing Valet configuration files, we recommend that you run valet unsecure --all to clean up the certificates that Valet inserted into your Keychain. 111 | Alternatively you can do a search for @laravel.valet in Keychain Access and delete those certificates there manually. 112 | 113 | 2. Valet Configuration Files 114 | You may remove your user-specific Valet config files by running: rm -rf ~/.config/valet 115 | 116 | 3. Remove Valet package 117 | You can run composer global remove laravel/valet to uninstall the Valet package. 118 | 119 | 4. Homebrew Services 120 | You may remove the core services (php, nginx, dnsmasq) by running: brew uninstall --force php nginx dnsmasq 121 | You can then remove selected leftover configurations for these services manually in both '.BREW_PREFIX.'/etc/ and '.BREW_PREFIX.'/logs/. 122 | (If you have other PHP versions installed, run brew list --formula | grep php to see which versions you should also uninstall manually.) 123 | 124 | BEWARE: Uninstalling PHP via Homebrew will leave your Mac with its original PHP version, which may not be compatible with other Composer dependencies you have installed. As a result, you may get unexpected errors. 125 | 126 | If you have customized your Mac DNS settings in System Preferences->Network, you may need to add or remove 127.0.0.1 from the top of that list. 127 | 128 | 5. GENERAL TROUBLESHOOTING 129 | If your reasons for considering an uninstall are more for troubleshooting purposes, consider running brew doctor and/or brew cleanup to see if any problems exist there. 130 | Also consider running sudo nginx -t to test your nginx configs in case there are failures/errors there preventing nginx from running. 131 | Most of the nginx configs used by Valet are in your ~/.config/valet/Nginx directory. 132 | 133 | You might also want to investigate your global Composer configs. Helpful commands include: 134 | composer global update to apply updates to packages 135 | composer global outdated to identify outdated packages 136 | composer global diagnose to run diagnostics 137 | '; 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /cli/includes/compatibility.php: -------------------------------------------------------------------------------- 1 | make(static::containerKey()); 21 | 22 | return call_user_func_array([$resolvedInstance, $method], $parameters); 23 | } 24 | } 25 | 26 | class Brew extends Facade {} 27 | class Nginx extends Facade {} 28 | class CommandLine extends Facade {} 29 | class Composer extends Facade {} 30 | class Configuration extends Facade {} 31 | class Diagnose extends Facade {} 32 | class DnsMasq extends Facade {} 33 | class Expose extends Facade {} 34 | class Filesystem extends Facade {} 35 | class Ngrok extends Facade {} 36 | class PhpFpm extends Facade {} 37 | class Site extends Facade {} 38 | class Status extends Facade {} 39 | class Upgrader extends Facade {} 40 | class Valet extends Facade {} 41 | class Cloudflared extends Facade {} 42 | -------------------------------------------------------------------------------- /cli/includes/helpers.php: -------------------------------------------------------------------------------- 1 | runAsUser('printf $(brew --prefix)')); 29 | 30 | define('ISOLATED_PHP_VERSION', 'ISOLATED_PHP_VERSION'); 31 | 32 | /** 33 | * Set or get a global console writer. 34 | */ 35 | function writer(?OutputInterface $writer = null): OutputInterface|\NullWriter|null 36 | { 37 | $container = Container::getInstance(); 38 | 39 | if (! $writer) { 40 | if (! $container->bound('writer')) { 41 | $container->instance('writer', new ConsoleOutput); 42 | } 43 | 44 | return $container->make('writer'); 45 | } 46 | 47 | $container->instance('writer', $writer); 48 | 49 | return null; 50 | } 51 | 52 | /** 53 | * Output the given text to the console. 54 | */ 55 | function info($output): void 56 | { 57 | output(''.$output.''); 58 | } 59 | 60 | /** 61 | * Output the given text to the console. 62 | */ 63 | function warning(string $output): void 64 | { 65 | output(''.$output.''); 66 | } 67 | 68 | /** 69 | * Output a table to the console. 70 | */ 71 | function table(array $headers = [], array $rows = []): void 72 | { 73 | $table = new Table(writer()); 74 | 75 | $table->setHeaders($headers)->setRows($rows); 76 | 77 | $table->render(); 78 | } 79 | 80 | /** 81 | * Return whether the app is in the testing environment. 82 | */ 83 | function testing(): bool 84 | { 85 | return strpos($_SERVER['SCRIPT_NAME'], 'phpunit') !== false; 86 | } 87 | 88 | /** 89 | * Output the given text to the console. 90 | */ 91 | function output(?string $output = ''): void 92 | { 93 | writer()->writeln($output); 94 | } 95 | 96 | /** 97 | * Resolve the given class from the container. 98 | */ 99 | function resolve(string $class): mixed 100 | { 101 | return Container::getInstance()->make($class); 102 | } 103 | 104 | /** 105 | * Swap the given class implementation in the container. 106 | */ 107 | function swap(string $class, mixed $instance): void 108 | { 109 | Container::getInstance()->instance($class, $instance); 110 | } 111 | 112 | /** 113 | * Retry the given function N times. 114 | */ 115 | function retry(int $retries, callable $fn, int $sleep = 0): mixed 116 | { 117 | beginning: 118 | try { 119 | return $fn(); 120 | } catch (Exception $e) { 121 | if (! $retries) { 122 | throw $e; 123 | } 124 | 125 | $retries--; 126 | 127 | if ($sleep > 0) { 128 | usleep($sleep * 1000); 129 | } 130 | 131 | goto beginning; 132 | } 133 | } 134 | 135 | /** 136 | * Verify that the script is currently running as "sudo". 137 | */ 138 | function should_be_sudo(): void 139 | { 140 | if (! isset($_SERVER['SUDO_USER'])) { 141 | throw new Exception('This command must be run with sudo.'); 142 | } 143 | } 144 | 145 | /** 146 | * Tap the given value. 147 | */ 148 | function tap(mixed $value, callable $callback): mixed 149 | { 150 | $callback($value); 151 | 152 | return $value; 153 | } 154 | 155 | /** 156 | * Determine if a given string ends with a given substring. 157 | */ 158 | function ends_with(string $haystack, array|string $needles): bool 159 | { 160 | foreach ((array) $needles as $needle) { 161 | if (substr($haystack, -strlen($needle)) === (string) $needle) { 162 | return true; 163 | } 164 | } 165 | 166 | return false; 167 | } 168 | 169 | /** 170 | * Determine if a given string starts with a given substring. 171 | */ 172 | function starts_with(string $haystack, array|string $needles): bool 173 | { 174 | foreach ((array) $needles as $needle) { 175 | if ((string) $needle !== '' && strncmp($haystack, $needle, strlen($needle)) === 0) { 176 | return true; 177 | } 178 | } 179 | 180 | return false; 181 | } 182 | 183 | /** 184 | * Get the user. 185 | */ 186 | function user(): string 187 | { 188 | if (! isset($_SERVER['SUDO_USER'])) { 189 | return $_SERVER['USER']; 190 | } 191 | 192 | return $_SERVER['SUDO_USER']; 193 | } 194 | -------------------------------------------------------------------------------- /cli/includes/require-drivers.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Label 6 | com.laravel.valet.loopback 7 | RunAtLoad 8 | 9 | ProgramArguments 10 | 11 | /sbin/ifconfig 12 | lo0 13 | alias 14 | VALET_LOOPBACK 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /cli/stubs/nginx.conf: -------------------------------------------------------------------------------- 1 | user "VALET_USER" staff; 2 | worker_processes auto; 3 | 4 | events { 5 | worker_connections 1024; 6 | } 7 | 8 | http { 9 | include mime.types; 10 | default_type application/octet-stream; 11 | 12 | sendfile on; 13 | keepalive_timeout 65; 14 | types_hash_max_size 2048; 15 | 16 | client_max_body_size 512M; 17 | 18 | server_names_hash_bucket_size 128; 19 | 20 | ssi on; 21 | 22 | gzip on; 23 | gzip_comp_level 5; 24 | gzip_min_length 256; 25 | gzip_proxied any; 26 | gzip_vary on; 27 | gzip_types 28 | application/atom+xml 29 | application/javascript 30 | application/json 31 | application/rss+xml 32 | application/vnd.ms-fontobject 33 | application/x-font-ttf 34 | application/x-web-app-manifest+json 35 | application/xhtml+xml 36 | application/xml 37 | font/opentype 38 | image/svg+xml 39 | image/x-icon 40 | text/css 41 | text/plain 42 | text/x-component; 43 | 44 | include "VALET_HOME_PATH/Nginx/*"; 45 | include servers/*; 46 | include valet/valet.conf; 47 | } 48 | -------------------------------------------------------------------------------- /cli/stubs/openssl.conf: -------------------------------------------------------------------------------- 1 | [req] 2 | distinguished_name = req_distinguished_name 3 | req_extensions = v3_req 4 | 5 | [req_distinguished_name] 6 | countryName = Country Name (2 letter code) 7 | countryName_default = US 8 | stateOrProvinceName = State or Province Name (full name) 9 | stateOrProvinceName_default = MN 10 | localityName = Locality Name (eg, city) 11 | localityName_default = Minneapolis 12 | organizationalUnitName = Organizational Unit Name (eg, section) 13 | organizationalUnitName_default = Domain Control Validated 14 | commonName = Internet Widgits Ltd 15 | commonName_max = 64 16 | 17 | [ v3_req ] 18 | # Extensions to add to a certificate request 19 | basicConstraints = CA:FALSE 20 | keyUsage = nonRepudiation, digitalSignature, keyEncipherment 21 | subjectAltName = @alt_names 22 | 23 | [alt_names] 24 | DNS.1 = VALET_DOMAIN 25 | DNS.2 = *.VALET_DOMAIN 26 | -------------------------------------------------------------------------------- /cli/stubs/php-memory-limits.ini: -------------------------------------------------------------------------------- 1 | ; Max memory per instance 2 | memory_limit = 512M 3 | 4 | ;The maximum size of an uploaded file. 5 | upload_max_filesize = 512M 6 | 7 | ; Sets max size of post data allowed. 8 | ; Changes to this will also need to be reflected in Nginx with client_max_body_size 9 | ; This setting also affects file upload. To upload large files, this value must be larger than upload_max_filesize 10 | post_max_size = 512M 11 | -------------------------------------------------------------------------------- /cli/stubs/proxy.valet.conf: -------------------------------------------------------------------------------- 1 | # valet stub: proxy.valet.conf 2 | 3 | server { 4 | listen 127.0.0.1:80; 5 | #listen VALET_LOOPBACK:80; # valet loopback 6 | server_name VALET_SITE www.VALET_SITE *.VALET_SITE; 7 | root /; 8 | charset utf-8; 9 | client_max_body_size 128M; 10 | 11 | location /VALET_STATIC_PREFIX/ { 12 | internal; 13 | alias /; 14 | try_files $uri $uri/; 15 | } 16 | 17 | access_log off; 18 | error_log "VALET_HOME_PATH/Log/VALET_SITE-error.log"; 19 | 20 | error_page 404 "VALET_SERVER_PATH"; 21 | 22 | location / { 23 | proxy_pass VALET_PROXY_HOST; 24 | proxy_set_header Host $host; 25 | proxy_set_header X-Real-IP $remote_addr; 26 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 27 | proxy_set_header X-Forwarded-Proto $scheme; 28 | proxy_set_header X-Client-Verify SUCCESS; 29 | proxy_set_header X-Client-DN $ssl_client_s_dn; 30 | proxy_set_header X-SSL-Subject $ssl_client_s_dn; 31 | proxy_set_header X-SSL-Issuer $ssl_client_i_dn; 32 | proxy_set_header X-NginX-Proxy true; 33 | proxy_set_header Upgrade $http_upgrade; 34 | proxy_set_header Connection "upgrade"; 35 | proxy_http_version 1.1; 36 | proxy_read_timeout 1800; 37 | proxy_connect_timeout 1800; 38 | chunked_transfer_encoding on; 39 | proxy_redirect off; 40 | proxy_buffering off; 41 | } 42 | 43 | location ~ /\.ht { 44 | deny all; 45 | } 46 | } 47 | 48 | server { 49 | listen 127.0.0.1:60; 50 | #listen VALET_LOOPBACK:60; # valet loopback 51 | server_name VALET_SITE www.VALET_SITE *.VALET_SITE; 52 | root /; 53 | charset utf-8; 54 | client_max_body_size 128M; 55 | 56 | add_header X-Robots-Tag 'noindex, nofollow, nosnippet, noarchive'; 57 | 58 | location /VALET_STATIC_PREFIX/ { 59 | internal; 60 | alias /; 61 | try_files $uri $uri/; 62 | } 63 | 64 | access_log off; 65 | error_log "VALET_HOME_PATH/Log/VALET_SITE-error.log"; 66 | 67 | error_page 404 "VALET_SERVER_PATH"; 68 | 69 | location / { 70 | proxy_pass VALET_PROXY_HOST; 71 | proxy_set_header Host $host; 72 | proxy_set_header X-Real-IP $remote_addr; 73 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 74 | proxy_set_header X-Forwarded-Proto $scheme; 75 | } 76 | 77 | location ~ /\.ht { 78 | deny all; 79 | } 80 | } 81 | 82 | -------------------------------------------------------------------------------- /cli/stubs/secure.proxy.valet-legacy.conf: -------------------------------------------------------------------------------- 1 | # valet stub: secure.proxy.valet.conf 2 | 3 | server { 4 | listen 127.0.0.1:80; 5 | #listen VALET_LOOPBACK:80; # valet loopback 6 | server_name VALET_SITE www.VALET_SITE *.VALET_SITE; 7 | return 301 https://$host$request_uri; 8 | } 9 | 10 | server { 11 | listen 127.0.0.1:443 ssl http2; 12 | #listen VALET_LOOPBACK:443 ssl http2; # valet loopback 13 | server_name VALET_SITE www.VALET_SITE *.VALET_SITE; 14 | root /; 15 | charset utf-8; 16 | client_max_body_size 128M; 17 | http2_push_preload on; 18 | 19 | location /VALET_STATIC_PREFIX/ { 20 | internal; 21 | alias /; 22 | try_files $uri $uri/; 23 | } 24 | 25 | ssl_certificate "VALET_CERT"; 26 | ssl_certificate_key "VALET_KEY"; 27 | 28 | access_log off; 29 | error_log "VALET_HOME_PATH/Log/VALET_SITE-error.log"; 30 | 31 | error_page 404 "VALET_SERVER_PATH"; 32 | 33 | location / { 34 | proxy_pass VALET_PROXY_HOST; 35 | proxy_set_header Host $host; 36 | proxy_set_header X-Real-IP $remote_addr; 37 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 38 | proxy_set_header X-Forwarded-Proto $scheme; 39 | proxy_set_header X-Client-Verify SUCCESS; 40 | proxy_set_header X-Client-DN $ssl_client_s_dn; 41 | proxy_set_header X-SSL-Subject $ssl_client_s_dn; 42 | proxy_set_header X-SSL-Issuer $ssl_client_i_dn; 43 | proxy_set_header X-NginX-Proxy true; 44 | proxy_set_header Upgrade $http_upgrade; 45 | proxy_set_header Connection "upgrade"; 46 | proxy_http_version 1.1; 47 | proxy_read_timeout 1800; 48 | proxy_connect_timeout 1800; 49 | chunked_transfer_encoding on; 50 | proxy_redirect off; 51 | proxy_buffering off; 52 | } 53 | 54 | location ~ /\.ht { 55 | deny all; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /cli/stubs/secure.proxy.valet.conf: -------------------------------------------------------------------------------- 1 | # valet stub: secure.proxy.valet.conf 2 | 3 | server { 4 | listen 127.0.0.1:80; 5 | #listen VALET_LOOPBACK:80; # valet loopback 6 | server_name VALET_SITE www.VALET_SITE *.VALET_SITE; 7 | return 301 https://$host$request_uri; 8 | } 9 | 10 | server { 11 | listen 127.0.0.1:443 ssl; 12 | #listen VALET_LOOPBACK:443 ssl; # valet loopback 13 | server_name VALET_SITE www.VALET_SITE *.VALET_SITE; 14 | root /; 15 | charset utf-8; 16 | client_max_body_size 128M; 17 | http2 on; 18 | 19 | location /VALET_STATIC_PREFIX/ { 20 | internal; 21 | alias /; 22 | try_files $uri $uri/; 23 | } 24 | 25 | ssl_certificate "VALET_CERT"; 26 | ssl_certificate_key "VALET_KEY"; 27 | 28 | access_log off; 29 | error_log "VALET_HOME_PATH/Log/VALET_SITE-error.log"; 30 | 31 | error_page 404 "VALET_SERVER_PATH"; 32 | 33 | location / { 34 | proxy_pass VALET_PROXY_HOST; 35 | proxy_set_header Host $host; 36 | proxy_set_header X-Real-IP $remote_addr; 37 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 38 | proxy_set_header X-Forwarded-Proto $scheme; 39 | proxy_set_header X-Client-Verify SUCCESS; 40 | proxy_set_header X-Client-DN $ssl_client_s_dn; 41 | proxy_set_header X-SSL-Subject $ssl_client_s_dn; 42 | proxy_set_header X-SSL-Issuer $ssl_client_i_dn; 43 | proxy_set_header X-NginX-Proxy true; 44 | proxy_set_header Upgrade $http_upgrade; 45 | proxy_set_header Connection "upgrade"; 46 | proxy_http_version 1.1; 47 | proxy_read_timeout 1800; 48 | proxy_connect_timeout 1800; 49 | chunked_transfer_encoding on; 50 | proxy_redirect off; 51 | proxy_buffering off; 52 | } 53 | 54 | location ~ /\.ht { 55 | deny all; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /cli/stubs/secure.valet-legacy.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 127.0.0.1:80; 3 | #listen VALET_LOOPBACK:80; # valet loopback 4 | server_name VALET_SITE www.VALET_SITE *.VALET_SITE; 5 | return 301 https://$host$request_uri; 6 | } 7 | 8 | server { 9 | listen 127.0.0.1:443 ssl http2; 10 | #listen VALET_LOOPBACK:443 ssl http2; # valet loopback 11 | server_name VALET_SITE www.VALET_SITE *.VALET_SITE; 12 | root /; 13 | charset utf-8; 14 | client_max_body_size 512M; 15 | http2_push_preload on; 16 | 17 | location /VALET_STATIC_PREFIX/ { 18 | internal; 19 | alias /; 20 | try_files $uri $uri/; 21 | } 22 | 23 | ssl_certificate "VALET_CERT"; 24 | ssl_certificate_key "VALET_KEY"; 25 | 26 | location / { 27 | rewrite ^ "VALET_SERVER_PATH" last; 28 | } 29 | 30 | location = /favicon.ico { access_log off; log_not_found off; } 31 | location = /robots.txt { access_log off; log_not_found off; } 32 | 33 | access_log off; 34 | error_log "VALET_HOME_PATH/Log/nginx-error.log"; 35 | 36 | error_page 404 "VALET_SERVER_PATH"; 37 | 38 | location ~ [^/]\.php(/|$) { 39 | fastcgi_split_path_info ^(.+\.php)(/.+)$; 40 | fastcgi_pass "unix:VALET_HOME_PATH/valet.sock"; 41 | fastcgi_index "VALET_SERVER_PATH"; 42 | include fastcgi_params; 43 | fastcgi_param SCRIPT_FILENAME "VALET_SERVER_PATH"; 44 | fastcgi_param PATH_INFO $fastcgi_path_info; 45 | } 46 | 47 | location ~ /\.ht { 48 | deny all; 49 | } 50 | } 51 | 52 | server { 53 | listen 127.0.0.1:60; 54 | #listen VALET_LOOPBACK:60; # valet loopback 55 | server_name VALET_SITE www.VALET_SITE *.VALET_SITE; 56 | root /; 57 | charset utf-8; 58 | client_max_body_size 128M; 59 | 60 | add_header X-Robots-Tag 'noindex, nofollow, nosnippet, noarchive'; 61 | 62 | location /VALET_STATIC_PREFIX/ { 63 | internal; 64 | alias /; 65 | try_files $uri $uri/; 66 | } 67 | 68 | location / { 69 | rewrite ^ "VALET_SERVER_PATH" last; 70 | } 71 | 72 | location = /favicon.ico { access_log off; log_not_found off; } 73 | location = /robots.txt { access_log off; log_not_found off; } 74 | 75 | access_log off; 76 | error_log "VALET_HOME_PATH/Log/nginx-error.log"; 77 | 78 | error_page 404 "VALET_SERVER_PATH"; 79 | 80 | location ~ [^/]\.php(/|$) { 81 | fastcgi_split_path_info ^(.+\.php)(/.+)$; 82 | fastcgi_pass "unix:VALET_HOME_PATH/valet.sock"; 83 | fastcgi_index "VALET_SERVER_PATH"; 84 | include fastcgi_params; 85 | fastcgi_param SCRIPT_FILENAME "VALET_SERVER_PATH"; 86 | fastcgi_param PATH_INFO $fastcgi_path_info; 87 | } 88 | 89 | location ~ /\.ht { 90 | deny all; 91 | } 92 | } 93 | 94 | -------------------------------------------------------------------------------- /cli/stubs/secure.valet.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 127.0.0.1:80; 3 | #listen VALET_LOOPBACK:80; # valet loopback 4 | server_name VALET_SITE www.VALET_SITE *.VALET_SITE; 5 | return 301 https://$host$request_uri; 6 | } 7 | 8 | server { 9 | listen 127.0.0.1:443 ssl; 10 | #listen VALET_LOOPBACK:443 ssl; # valet loopback 11 | server_name VALET_SITE www.VALET_SITE *.VALET_SITE; 12 | root /; 13 | charset utf-8; 14 | client_max_body_size 512M; 15 | http2 on; 16 | 17 | location /VALET_STATIC_PREFIX/ { 18 | internal; 19 | alias /; 20 | try_files $uri $uri/; 21 | } 22 | 23 | ssl_certificate "VALET_CERT"; 24 | ssl_certificate_key "VALET_KEY"; 25 | 26 | location / { 27 | rewrite ^ "VALET_SERVER_PATH" last; 28 | } 29 | 30 | location = /favicon.ico { access_log off; log_not_found off; } 31 | location = /robots.txt { access_log off; log_not_found off; } 32 | 33 | access_log off; 34 | error_log "VALET_HOME_PATH/Log/nginx-error.log"; 35 | 36 | error_page 404 "VALET_SERVER_PATH"; 37 | 38 | location ~ [^/]\.php(/|$) { 39 | fastcgi_split_path_info ^(.+\.php)(/.+)$; 40 | fastcgi_pass "unix:VALET_HOME_PATH/valet.sock"; 41 | fastcgi_index "VALET_SERVER_PATH"; 42 | include fastcgi_params; 43 | fastcgi_param SCRIPT_FILENAME "VALET_SERVER_PATH"; 44 | fastcgi_param PATH_INFO $fastcgi_path_info; 45 | } 46 | 47 | location ~ /\.ht { 48 | deny all; 49 | } 50 | } 51 | 52 | server { 53 | listen 127.0.0.1:60; 54 | #listen VALET_LOOPBACK:60; # valet loopback 55 | server_name VALET_SITE www.VALET_SITE *.VALET_SITE; 56 | root /; 57 | charset utf-8; 58 | client_max_body_size 128M; 59 | 60 | add_header X-Robots-Tag 'noindex, nofollow, nosnippet, noarchive'; 61 | 62 | location /VALET_STATIC_PREFIX/ { 63 | internal; 64 | alias /; 65 | try_files $uri $uri/; 66 | } 67 | 68 | location / { 69 | rewrite ^ "VALET_SERVER_PATH" last; 70 | } 71 | 72 | location = /favicon.ico { access_log off; log_not_found off; } 73 | location = /robots.txt { access_log off; log_not_found off; } 74 | 75 | access_log off; 76 | error_log "VALET_HOME_PATH/Log/nginx-error.log"; 77 | 78 | error_page 404 "VALET_SERVER_PATH"; 79 | 80 | location ~ [^/]\.php(/|$) { 81 | fastcgi_split_path_info ^(.+\.php)(/.+)$; 82 | fastcgi_pass "unix:VALET_HOME_PATH/valet.sock"; 83 | fastcgi_index "VALET_SERVER_PATH"; 84 | include fastcgi_params; 85 | fastcgi_param SCRIPT_FILENAME "VALET_SERVER_PATH"; 86 | fastcgi_param PATH_INFO $fastcgi_path_info; 87 | } 88 | 89 | location ~ /\.ht { 90 | deny all; 91 | } 92 | } 93 | 94 | -------------------------------------------------------------------------------- /cli/stubs/site.valet.conf: -------------------------------------------------------------------------------- 1 | # ISOLATED_PHP_VERSION=VALET_ISOLATED_PHP_VERSION 2 | server { 3 | listen 127.0.0.1:80; 4 | server_name VALET_SITE www.VALET_SITE *.VALET_SITE; 5 | #listen VALET_LOOPBACK:80; # valet loopback 6 | root /; 7 | charset utf-8; 8 | client_max_body_size 128M; 9 | 10 | location /VALET_STATIC_PREFIX/ { 11 | internal; 12 | alias /; 13 | try_files $uri $uri/; 14 | } 15 | 16 | location / { 17 | rewrite ^ "VALET_SERVER_PATH" last; 18 | } 19 | 20 | location = /favicon.ico { access_log off; log_not_found off; } 21 | location = /robots.txt { access_log off; log_not_found off; } 22 | 23 | access_log off; 24 | error_log "VALET_HOME_PATH/Log/nginx-error.log"; 25 | 26 | error_page 404 "VALET_SERVER_PATH"; 27 | 28 | location ~ [^/]\.php(/|$) { 29 | fastcgi_split_path_info ^(.+\.php)(/.+)$; 30 | fastcgi_pass "unix:VALET_HOME_PATH/VALET_PHP_FPM_SOCKET"; 31 | fastcgi_index "VALET_SERVER_PATH"; 32 | include fastcgi_params; 33 | fastcgi_param SCRIPT_FILENAME "VALET_SERVER_PATH"; 34 | fastcgi_param PATH_INFO $fastcgi_path_info; 35 | } 36 | 37 | location ~ /\.ht { 38 | deny all; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /cli/stubs/valet.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 127.0.0.1:80 default_server; 3 | #listen VALET_LOOPBACK:80; # valet loopback 4 | server_name "!valet!" ""; 5 | root /; 6 | charset utf-8; 7 | client_max_body_size 128M; 8 | 9 | location /VALET_STATIC_PREFIX/ { 10 | internal; 11 | alias /; 12 | try_files $uri $uri/; 13 | } 14 | 15 | location / { 16 | rewrite ^ "VALET_SERVER_PATH" last; 17 | } 18 | 19 | location = /favicon.ico { access_log off; log_not_found off; } 20 | location = /robots.txt { access_log off; log_not_found off; } 21 | 22 | access_log off; 23 | error_log "VALET_HOME_PATH/Log/nginx-error.log"; 24 | 25 | error_page 404 "VALET_SERVER_PATH"; 26 | 27 | location ~ [^/]\.php(/|$) { 28 | fastcgi_split_path_info ^(.+\.php)(/.+)$; 29 | fastcgi_pass "unix:VALET_HOME_PATH/valet.sock"; 30 | fastcgi_index "VALET_SERVER_PATH"; 31 | include fastcgi_params; 32 | fastcgi_param SCRIPT_FILENAME "VALET_SERVER_PATH"; 33 | fastcgi_param PATH_INFO $fastcgi_path_info; 34 | } 35 | 36 | location ~ /\.ht { 37 | deny all; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /cli/templates/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Valet - Not Found 4 | 5 | 10 | 11 | 12 | 404 - Not Found 13 | 14 | 15 | -------------------------------------------------------------------------------- /cli/valet.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | run(); 10 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "laravel/valet", 3 | "description": "A more enjoyable local development experience for Mac.", 4 | "keywords": ["laravel", "zonda", "wwdhhd"], 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Taylor Otwell", 9 | "email": "taylor@laravel.com" 10 | }, 11 | { 12 | "name": "Adam Wathan", 13 | "email": "adam.wathan@gmail.com" 14 | }, 15 | { 16 | "name": "Matt Stauffer", 17 | "email": "matt@tighten.co" 18 | } 19 | ], 20 | "autoload": { 21 | "files": [ 22 | "cli/includes/compatibility.php", 23 | "cli/includes/facades.php", 24 | "cli/includes/helpers.php" 25 | ], 26 | "psr-4": { 27 | "Valet\\": "cli/Valet/" 28 | } 29 | }, 30 | "autoload-dev": { 31 | "files": [ 32 | "tests/UsesNullWriter.php", 33 | "tests/BaseApplicationTestCase.php", 34 | "tests/Drivers/BaseDriverTestCase.php" 35 | ] 36 | }, 37 | "conflict": { 38 | "mnapoli/silly": ">=1.8.1 <1.8.3" 39 | }, 40 | "require": { 41 | "php": "^7.1|^8.0", 42 | "illuminate/collections": "^8.0|^9.0|^10.0|^11.0|^12.0", 43 | "illuminate/container": "~5.1|^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", 44 | "mnapoli/silly": "^1.5", 45 | "symfony/console": "^3.0|^4.0|^5.0|^6.0|^7.0", 46 | "symfony/process": "^3.0|^4.0|^5.0|^6.0|^7.0", 47 | "guzzlehttp/guzzle": "^6.0|^7.4", 48 | "symfony/event-dispatcher": "^3.0|^4.0|^5.0|^6.0|^7.0" 49 | }, 50 | "require-dev": { 51 | "mockery/mockery": "^1.2.3", 52 | "yoast/phpunit-polyfills": "^0.2.0|^4.0", 53 | "laravel/pint": "^1.4" 54 | }, 55 | "bin": [ 56 | "valet" 57 | ], 58 | "extra": { 59 | "branch-alias": { 60 | "dev-master": "4.x-dev" 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /find-usable-php.php: -------------------------------------------------------------------------------- 1 | = 0) { 10 | echo exec('which php'); 11 | 12 | return; 13 | } 14 | 15 | // If not, let's find it whether we have a version of PHP installed that's 8+; 16 | // all users that run through this code path will see Valet run more slowly 17 | $phps = explode(PHP_EOL, trim(shell_exec('brew list --formula | grep php'))); 18 | 19 | // Normalize version numbers 20 | $phps = array_reduce($phps, function ($carry, $php) { 21 | $carry[$php] = presumePhpVersionFromBrewFormulaName($php); 22 | 23 | return $carry; 24 | }, []); 25 | 26 | // Filter out older versions of PHP 27 | $modernPhps = array_filter($phps, function ($php) use ($minimumPhpVersion) { 28 | return version_compare($php, $minimumPhpVersion) >= 0; 29 | }); 30 | 31 | // If we don't have any modern versions of PHP, throw an error 32 | if (empty($modernPhps)) { 33 | throw new Exception('Sorry, but you do not have a version of PHP installed that is compatible with Valet (8+).'); 34 | } 35 | 36 | // Sort newest version to oldest 37 | sort($modernPhps); 38 | $modernPhps = array_reverse($modernPhps); 39 | 40 | // Grab the highest, set as $foundVersion, and output its path 41 | $foundVersion = reset($modernPhps); 42 | echo getPhpExecutablePath(array_search($foundVersion, $phps)); 43 | 44 | /** 45 | * Function definitions. 46 | */ 47 | 48 | /** 49 | * Extract PHP executable path from PHP Version. 50 | * Copied from Brew.php and modified. 51 | * 52 | * @param string|null $phpFormulaName For example, "php@8.1" 53 | * @return string 54 | */ 55 | function getPhpExecutablePath(?string $phpFormulaName = null) 56 | { 57 | $brewPrefix = exec('printf $(brew --prefix)'); 58 | 59 | // Check the default `/opt/homebrew/opt/php@8.1/bin/php` location first 60 | if (file_exists($brewPrefix."/opt/{$phpFormulaName}/bin/php")) { 61 | return $brewPrefix."/opt/{$phpFormulaName}/bin/php"; 62 | } 63 | 64 | // Check the `/opt/homebrew/opt/php71/bin/php` location for older installations 65 | $oldPhpFormulaName = str_replace(['@', '.'], '', $phpFormulaName); // php@7.1 to php71 66 | if (file_exists($brewPrefix."/opt/{$oldPhpFormulaName}/bin/php")) { 67 | return $brewPrefix."/opt/{$oldPhpFormulaName}/bin/php"; 68 | } 69 | 70 | throw new Exception('Cannot find an executable path for provided PHP version: '.$phpFormulaName); 71 | } 72 | 73 | function presumePhpVersionFromBrewFormulaName(string $formulaName) 74 | { 75 | if ($formulaName === 'php') { 76 | // Figure out its link 77 | $details = json_decode(shell_exec("brew info $formulaName --json")); 78 | 79 | if (! empty($details[0]->aliases[0])) { 80 | $formulaName = $details[0]->aliases[0]; 81 | } else { 82 | return null; 83 | } 84 | } 85 | 86 | if (strpos($formulaName, 'php@') === false) { 87 | return null; 88 | } 89 | 90 | return substr($formulaName, strpos($formulaName, '@') + 1); 91 | } 92 | -------------------------------------------------------------------------------- /server.php: -------------------------------------------------------------------------------- 1 | siteNameFromHttpHost($_SERVER['HTTP_HOST']); 43 | $valetSitePath = $server->sitePath($siteName); 44 | 45 | if (is_null($valetSitePath) && is_null($valetSitePath = $server->defaultSitePath())) { 46 | Server::show404(); 47 | } 48 | 49 | $valetSitePath = realpath($valetSitePath); 50 | 51 | /** 52 | * Find the appropriate Valet driver for the request. 53 | */ 54 | $valetDriver = ValetDriver::assign($valetSitePath, $siteName, $uri); 55 | 56 | if (! $valetDriver) { 57 | Server::show404(); 58 | } 59 | 60 | /** 61 | * ngrok uses the X-Original-Host to store the forwarded hostname. 62 | */ 63 | if (isset($_SERVER['HTTP_X_ORIGINAL_HOST']) && ! isset($_SERVER['HTTP_X_FORWARDED_HOST'])) { 64 | $_SERVER['HTTP_X_FORWARDED_HOST'] = $_SERVER['HTTP_X_ORIGINAL_HOST']; 65 | } 66 | 67 | /** 68 | * Attempt to load server environment variables. 69 | */ 70 | $valetDriver->loadServerEnvironmentVariables( 71 | $valetSitePath, $siteName 72 | ); 73 | 74 | /** 75 | * Allow driver to mutate incoming URL. 76 | */ 77 | $uri = $valetDriver->mutateUri($uri); 78 | 79 | /** 80 | * Determine if the incoming request is for a static file. 81 | */ 82 | $isPhpFile = pathinfo($uri, PATHINFO_EXTENSION) === 'php'; 83 | 84 | if ($uri !== '/' && ! $isPhpFile && $staticFilePath = $valetDriver->isStaticFile($valetSitePath, $siteName, $uri)) { 85 | return $valetDriver->serveStaticFile($staticFilePath, $valetSitePath, $siteName, $uri); 86 | } 87 | 88 | /** 89 | * Allow for drivers to take pre-loading actions (e.g. setting server variables). 90 | */ 91 | $valetDriver->beforeLoading($valetSitePath, $siteName, $uri); 92 | 93 | /** 94 | * Attempt to dispatch to a front controller. 95 | */ 96 | $frontControllerPath = $valetDriver->frontControllerPath( 97 | $valetSitePath, $siteName, $uri 98 | ); 99 | 100 | if (! $frontControllerPath) { 101 | if (isset($valetConfig['directory-listing']) && $valetConfig['directory-listing'] == 'on') { 102 | Server::showDirectoryListing($valetSitePath, $uri); 103 | } 104 | 105 | Server::show404(); 106 | } 107 | 108 | chdir(dirname($frontControllerPath)); 109 | 110 | require $frontControllerPath; 111 | -------------------------------------------------------------------------------- /valet: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | SOURCE="${BASH_SOURCE[0]}" 4 | 5 | # If the current source is a symbolic link, we need to resolve it to an 6 | # actual directory name. We'll use PHP to do this easier than we can 7 | # do it in pure Bash. So, we'll call into PHP CLI here to resolve. 8 | if [[ -L "$SOURCE" ]] 9 | then 10 | DIR=$(php -r "echo dirname(realpath('$SOURCE'));") 11 | else 12 | DIR="$( cd "$( dirname "$SOURCE" )" && pwd )" 13 | fi 14 | 15 | # If we are in the global Composer "bin" directory, we need to bump our 16 | # current directory up two, so that we will correctly proxy into the 17 | # Valet CLI script which is written in PHP. Will use PHP to do it. 18 | if [ ! -f "$DIR/cli/valet.php" ] 19 | then 20 | DIR=$(php -r "echo realpath('$DIR/../laravel/valet');") 21 | fi 22 | 23 | # Get a command-line executable we can use for php that's 8+; if this 24 | # is the inside loop (Valet runs itself 2x in some settings), skip 25 | # checking and pulling again by reading the exported env var 26 | if [[ "$PHP_EXECUTABLE" = "" ]] 27 | then 28 | PHP="$(php $DIR/find-usable-php.php)" 29 | 30 | # Validate output before running it on the CLI 31 | if [[ ! -f "$PHP" ]]; then 32 | echo "Error finding executable PHP. Quitting for safety." 33 | echo "Provided output from find-usable-php.php:" 34 | echo $PHP 35 | exit 36 | fi 37 | 38 | export PHP_EXECUTABLE="$PHP" 39 | else 40 | PHP="$PHP_EXECUTABLE" 41 | fi 42 | 43 | # If the command is the "share" command we will need to resolve out any 44 | # symbolic links for the site. Before starting the share tool, we will fire a 45 | # process to retrieve the live the share tool tunnel URL in the background. 46 | if [[ "$1" = "share" ]] 47 | then 48 | SHARETOOL="$("$PHP" "$DIR/cli/valet.php" share-tool)" 49 | 50 | # Check for parameters to pass through to share tool (these will start with '-' or '--') 51 | PARAMS=(${@:2}) 52 | 53 | for PARAM in ${PARAMS[@]} 54 | do 55 | if [[ ${PARAM:0:1} != '-' ]]; then 56 | PARAMS=("${PARAMS[@]/$PARAM}") # Quotes when working with strings 57 | fi 58 | done 59 | 60 | PARAMS=${PARAMS[@]} 61 | 62 | HOST="${PWD##*/}" 63 | 64 | # Find the first linked site for the current dir, if one exists 65 | for linkname in ~/.config/valet/Sites/*; do 66 | if [[ "$(readlink $linkname)" = "$PWD" ]] 67 | then 68 | HOST="${linkname##*/}" 69 | break 70 | fi 71 | done 72 | 73 | # Lowercase the host to match how the rest of our domains are looked up 74 | HOST=$(echo "$HOST" | tr '[:upper:]' '[:lower:]') 75 | TLD=$("$PHP" "$DIR/cli/valet.php" tld) 76 | $(grep --quiet --no-messages 443 ~/.config/valet/Nginx/$HOST*) 77 | SECURED=$? 78 | 79 | if [[ $SHARETOOL = "ngrok" ]] 80 | then 81 | # ngrok 82 | # Check to make sure ngrok is configured correctly 83 | BREW_PREFIX=$(brew --prefix) 84 | $($BREW_PREFIX/bin/ngrok config check >/dev/null 2>&1) 85 | 86 | if [[ $? -ne 0 ]]; then 87 | echo "Please sign up for a free ngrok account and then run valet set-ngrok-token {yourTokenHere}." 88 | exit 89 | fi 90 | 91 | # Decide the correct PORT: uses 60 for secure, else 80 92 | if [[ $SECURED -eq 0 ]]; then 93 | PORT=60 94 | else 95 | PORT=80 96 | fi 97 | 98 | sudo -u "$USER" "$BREW_PREFIX/bin/ngrok" http "$HOST.$TLD:$PORT" --host-header=rewrite $PARAMS 99 | 100 | exit 101 | 102 | elif [[ $SHARETOOL = "expose" ]] 103 | then 104 | 105 | # expose 106 | # Decide the correct PORT: uses 443 for secure, else 80 107 | if [[ $SECURED -eq 0 ]]; then 108 | PORT=443 109 | else 110 | PORT=80 111 | fi 112 | 113 | sudo -u "$USER" expose share "$HOST.$TLD:$PORT" $PARAMS 114 | 115 | exit 116 | 117 | elif [[ $SHARETOOL = "cloudflared" ]] 118 | then 119 | # cloudflared 120 | if [[ $SECURED -eq 0 ]]; then 121 | SCHEME="https" 122 | else 123 | SCHEME="http" 124 | fi 125 | 126 | sudo -u "$USER" cloudflared tunnel --no-tls-verify --url "$SCHEME://localhost" \ 127 | --http-host-header "$HOST.$TLD" $PARAMS 128 | 129 | exit 130 | 131 | else 132 | echo '' 133 | echo "Please use 'valet share-tool cloudflared', 'valet share-tool expose' or 'valet share-tool ngrok'" 134 | echo "to set your preferred share tool." 135 | exit 136 | fi 137 | 138 | # Proxy PHP commands to the "php" executable on the isolated site 139 | elif [[ "$1" = "php" ]] 140 | then 141 | if [[ $2 == *"--site="* ]]; then 142 | SITE=${2#*=} 143 | $("$PHP" "$DIR/cli/valet.php" which-php $SITE) "${@:3}" 144 | else 145 | $("$PHP" "$DIR/cli/valet.php" which-php) "${@:2}" 146 | fi 147 | 148 | exit 149 | 150 | # Proxy Composer commands with the "php" executable on the isolated site 151 | elif [[ "$1" = "composer" ]] 152 | then 153 | if [[ $2 == *"--site="* ]]; then 154 | SITE=${2#*=} 155 | $("$PHP" "$DIR/cli/valet.php" which-php $SITE) $(which composer) "${@:3}" 156 | else 157 | $("$PHP" "$DIR/cli/valet.php" which-php) $(which composer) "${@:2}" 158 | fi 159 | 160 | exit 161 | 162 | # Finally, for every other command we will just proxy into the PHP tool 163 | # and let it handle the request. These are commands which can be run 164 | # without sudo and don't require taking over terminals like Ngrok. 165 | else 166 | if [[ "$EUID" -ne 0 ]] 167 | then 168 | sudo USER="$USER" --preserve-env "$SOURCE" "$@" 169 | exit 170 | fi 171 | 172 | "$PHP" "$DIR/cli/valet.php" "$@" 173 | fi 174 | --------------------------------------------------------------------------------