├── stubs ├── config.json ├── .env └── docker-compose.yaml ├── valet ├── cli │ ├── stubs │ │ ├── etc-dnsmasq-valet.conf │ │ ├── etc-phpfpm-error_log.ini │ │ ├── php-memory-limits.ini │ │ ├── etc-phpfpm-valet.conf │ │ ├── fastcgi_params │ │ ├── openssl.conf │ │ ├── valet.conf │ │ ├── nginx.conf │ │ ├── SampleValetDriver.php │ │ ├── secure.valet.conf │ │ └── proxy.valet.conf │ ├── scripts │ │ └── fetch-share-url.sh │ ├── templates │ │ └── 404.html │ ├── includes │ │ ├── compatibility.php │ │ ├── facades.php │ │ └── helpers.php │ ├── drivers │ │ ├── KatanaValetDriver.php │ │ ├── JigsawValetDriver.php │ │ ├── JoomlaValetDriver.php │ │ ├── require.php │ │ ├── Concrete5ValetDriver.php │ │ ├── WordPressValetDriver.php │ │ ├── NeosValetDriver.php │ │ ├── CakeValetDriver.php │ │ ├── SculpinValetDriver.php │ │ ├── ContaoValetDriver.php │ │ ├── LaravelValetDriver.php │ │ ├── SymfonyValetDriver.php │ │ ├── StatamicV1ValetDriver.php │ │ ├── KirbyValetDriver.php │ │ ├── BedrockValetDriver.php │ │ ├── DrupalValetDriver.php │ │ ├── Magento2ValetDriver.php │ │ ├── BasicValetDriver.php │ │ ├── StatamicValetDriver.php │ │ ├── CraftValetDriver.php │ │ ├── ValetDriver.php │ │ └── Typo3ValetDriver.php │ ├── Valet │ │ ├── Ngrok.php │ │ ├── CommandLine.php │ │ ├── Valet.php │ │ ├── DnsMasq.php │ │ ├── Diagnose.php │ │ ├── Nginx.php │ │ ├── Configuration.php │ │ ├── Filesystem.php │ │ ├── PhpFpm.php │ │ └── Brew.php │ └── valet.php ├── valet ├── composer.json └── server.php ├── templates ├── dnsmasq │ └── dnsmasq-valet.conf.template ├── dnsmasq-internal │ └── dnsmasq-valet.conf.template └── nginx │ ├── valet.conf.template │ └── nginx.conf.template ├── .gitignore ├── docker ├── dnsmasq │ ├── dnsmasq.conf │ ├── Dockerfile │ └── docker-entrypoint.sh └── openresty │ ├── Dockerfile │ └── entrypoint │ ├── docker-entrypoint.sh │ └── 10-envsubst-on-templates.sh ├── LICENSE ├── install.sh ├── bin └── butler └── README.md /stubs/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "tld": "test", 3 | "paths": [ 4 | "/var/www/default" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /valet/cli/stubs/etc-dnsmasq-valet.conf: -------------------------------------------------------------------------------- 1 | # Valet 2 | # Include all *.conf files in user's valet directory 3 | conf-dir=VALET_HOME_PATH/dnsmasq.d/,*.conf 4 | -------------------------------------------------------------------------------- /templates/dnsmasq/dnsmasq-valet.conf.template: -------------------------------------------------------------------------------- 1 | # Valet 2 | # Include all *.conf files in user's valet directory 3 | conf-dir=${VALET_HOME_PATH}/dnsmasq.d/,*.conf -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.env 2 | /docker-compose.yaml 3 | /valet-ori 4 | /valet/vendor 5 | /valet/composer.lock 6 | /.valet-home 7 | /www 8 | /files 9 | !/www/* 10 | .DS_Store -------------------------------------------------------------------------------- /valet/cli/stubs/etc-phpfpm-error_log.ini: -------------------------------------------------------------------------------- 1 | ; php-fpm error logging directives 2 | 3 | error_log="VALET_HOME_PATH/Log/php-fpm.log" 4 | log_errors=on 5 | log_level=debug 6 | -------------------------------------------------------------------------------- /templates/dnsmasq-internal/dnsmasq-valet.conf.template: -------------------------------------------------------------------------------- 1 | # Valet 2 | # Include all *.conf files in user's valet directory 3 | conf-dir=${VALET_HOME_PATH}/dnsmasq-internal.d/,*.conf -------------------------------------------------------------------------------- /valet/cli/scripts/fetch-share-url.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 4 | 5 | php $DIR/../valet.php fetch-share-url $1 | pbcopy 6 | -------------------------------------------------------------------------------- /valet/cli/templates/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Valet - Not Found 4 | 5 | 10 | 11 | 12 | 404 - Not Found 13 | 14 | 15 | -------------------------------------------------------------------------------- /valet/cli/includes/compatibility.php: -------------------------------------------------------------------------------- 1 | /etc/default/dnsmasq \ 12 | && apk --no-cache add dnsmasq \ 13 | && apk add --update $RUNTIME_DEPS \ 14 | && apk add --virtual build_deps $BUILD_DEPS \ 15 | && cp /usr/bin/envsubst /usr/local/bin/envsubst \ 16 | && apk del build_deps 17 | 18 | COPY ./dnsmasq.conf /etc/dnsmasq.conf 19 | COPY ./docker-entrypoint.sh /docker-entrypoint.sh 20 | 21 | # ENTRYPOINT ["dnsmasq", "-k"] 22 | ENTRYPOINT ["/docker-entrypoint.sh"] 23 | 24 | CMD [ "dnsmasq", "-k" ] -------------------------------------------------------------------------------- /valet/cli/stubs/etc-phpfpm-valet.conf: -------------------------------------------------------------------------------- 1 | ; FPM pool configuration for Valet 2 | 3 | [valet] 4 | user = VALET_USER 5 | group = staff 6 | listen = VALET_HOME_PATH/valet.sock 7 | listen.owner = VALET_USER 8 | listen.group = staff 9 | listen.mode = 0777 10 | 11 | ;; When uncommented, the following values will take precedence over settings declared elsewhere 12 | ;php_admin_value[memory_limit] = 512M 13 | ;php_admin_value[upload_max_filesize] = 128M 14 | ;php_admin_value[post_max_size] = 128M 15 | 16 | ;php_admin_value[error_log] = VALET_HOME_PATH/Log/php-fpm.log 17 | ;php_admin_flag[log_errors] = on 18 | 19 | 20 | ;; Note: increasing these values will increase the demand on your CPU and RAM resources 21 | pm = dynamic 22 | pm.max_children = 5 23 | pm.start_servers = 2 24 | pm.min_spare_servers = 1 25 | pm.max_spare_servers = 3 26 | 27 | -------------------------------------------------------------------------------- /docker/dnsmasq/docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # vim:sw=4:ts=4:et 3 | 4 | set -e 5 | 6 | echo $1 7 | exec 3>&1 8 | 9 | template_dir="/etc/dnsmasq-templates" 10 | output_dir="/etc/dnsmasq.d" 11 | suffix=".template" 12 | 13 | defined_envs=$(printf '${%s} ' $(env | cut -d= -f1)) 14 | 15 | find "$template_dir" -follow -type f -name "*$suffix" -print | while read -r template; do 16 | relative_path="${template#$template_dir/}" 17 | output_path="$output_dir/${relative_path%$suffix}" 18 | subdir=$(dirname "$relative_path") 19 | # create a subdirectory where the template file exists 20 | mkdir -p "$output_dir/$subdir" 21 | echo >&2 "$ME: Running envsubst on $template to $output_path" 22 | envsubst "$defined_envs" < "$template" > "$output_path" 23 | done 24 | 25 | echo >&2 "$0: Configuration complete; ready for start up" 26 | 27 | exec "$@" -------------------------------------------------------------------------------- /valet/cli/drivers/JoomlaValetDriver.php: -------------------------------------------------------------------------------- 1 | make(static::containerKey()); 27 | 28 | return call_user_func_array([$resolvedInstance, $method], $parameters); 29 | } 30 | } 31 | 32 | class Brew extends Facade {} 33 | class Nginx extends Facade {} 34 | class CommandLine extends Facade {} 35 | class Configuration extends Facade {} 36 | class Diagnose extends Facade {} 37 | class DnsMasq extends Facade {} 38 | class Filesystem extends Facade {} 39 | class Ngrok extends Facade {} 40 | class PhpFpm extends Facade {} 41 | class Site extends Facade {} 42 | class Valet extends Facade {} 43 | -------------------------------------------------------------------------------- /valet/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 | if [[ "$EUID" -ne 0 ]] 24 | then 25 | sudo USER="$USER" --preserve-env "$SOURCE" "$@" 26 | exit 27 | fi 28 | 29 | # If the command is the "share" command we will need to resolve out any 30 | # symbolic links for the site. Before starting Ngrok, we will fire a 31 | # process to retrieve the live Ngrok tunnel URL in the background. 32 | if [[ "$1" = "share" ]] 33 | then 34 | # This should be a code to share using ngrok. 35 | echo "valet (Butler) share isn't supported yet in this version" 36 | else 37 | php "$DIR/cli/valet.php" "$@" 38 | fi 39 | -------------------------------------------------------------------------------- /valet/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 | "autoload": { 17 | "files": [ 18 | "cli/includes/compatibility.php", 19 | "cli/includes/facades.php", 20 | "cli/includes/helpers.php" 21 | ], 22 | "psr-4": { 23 | "Valet\\": "cli/Valet/" 24 | } 25 | }, 26 | "require": { 27 | "php": ">=5.6", 28 | "illuminate/container": "~5.1|^6.0|^7.0|^8.0", 29 | "mnapoli/silly": "~1.0", 30 | "symfony/process": "~3.0|~4.0|~5.0", 31 | "nategood/httpful": "~0.2", 32 | "tightenco/collect": "^5.3|^6.0|^7.0|^8.0" 33 | }, 34 | "require-dev": { 35 | "mockery/mockery": "^1.2.3", 36 | "yoast/phpunit-polyfills": "^0.2.0" 37 | }, 38 | "bin": [ 39 | "valet" 40 | ], 41 | "extra": { 42 | "branch-alias": { 43 | "dev-master": "2.x-dev" 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /templates/nginx/nginx.conf.template: -------------------------------------------------------------------------------- 1 | user root root; 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 | client_body_temp_path /var/run/openresty/nginx-client-body; 13 | proxy_temp_path /var/run/openresty/nginx-proxy; 14 | fastcgi_temp_path /var/run/openresty/nginx-fastcgi; 15 | uwsgi_temp_path /var/run/openresty/nginx-uwsgi; 16 | scgi_temp_path /var/run/openresty/nginx-scgi; 17 | 18 | sendfile on; 19 | keepalive_timeout 65; 20 | types_hash_max_size 2048; 21 | 22 | client_max_body_size 512M; 23 | 24 | server_names_hash_bucket_size 128; 25 | 26 | ssi on; 27 | 28 | gzip on; 29 | gzip_comp_level 5; 30 | gzip_min_length 256; 31 | gzip_proxied any; 32 | gzip_vary on; 33 | gzip_types 34 | application/atom+xml 35 | application/javascript 36 | application/json 37 | application/rss+xml 38 | application/vnd.ms-fontobject 39 | application/x-font-ttf 40 | application/x-web-app-manifest+json 41 | application/xhtml+xml 42 | application/xml 43 | font/opentype 44 | image/svg+xml 45 | image/x-icon 46 | text/css 47 | text/plain 48 | text/x-component; 49 | 50 | include "${VALET_HOME_PATH}/Nginx/*"; 51 | include servers/*; 52 | include /etc/nginx/conf.d/valet.conf; 53 | } 54 | -------------------------------------------------------------------------------- /docker/openresty/entrypoint/docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # vim:sw=4:ts=4:et 3 | 4 | set -e 5 | 6 | echo $1 7 | 8 | if [ -z "${NGINX_ENTRYPOINT_QUIET_LOGS:-}" ]; then 9 | exec 3>&1 10 | else 11 | exec 3>/dev/null 12 | fi 13 | 14 | if [ "$1" = "nginx" -o "$1" = "nginx-debug" -o "$1" = "/usr/local/openresty/bin/openresty" ]; then 15 | if /usr/bin/find "/docker-entrypoint.d/" -mindepth 1 -maxdepth 1 -type f -print -quit 2>/dev/null | read v; then 16 | echo >&2 "$0: /docker-entrypoint.d/ is not empty, will attempt to perform configuration" 17 | 18 | echo >&2 "$0: Looking for shell scripts in /docker-entrypoint.d/" 19 | find "/docker-entrypoint.d/" -follow -type f -print | sort -V | while read -r f; do 20 | case "$f" in 21 | *.sh) 22 | if [ -x "$f" ]; then 23 | echo >&2 "$0: Launching $f"; 24 | "$f" 25 | else 26 | # warn on shell scripts without exec bit 27 | echo >&2 "$0: Ignoring $f, not executable"; 28 | fi 29 | ;; 30 | *) echo >&2 "$0: Ignoring $f";; 31 | esac 32 | done 33 | 34 | echo >&2 "$0: Configuration complete; ready for start up" 35 | else 36 | echo >&2 "$0: No files found in /docker-entrypoint.d/, skipping configuration" 37 | fi 38 | fi 39 | 40 | exec "$@" -------------------------------------------------------------------------------- /valet/cli/stubs/SampleValetDriver.php: -------------------------------------------------------------------------------- 1 | &2 "$ME: ERROR: $template_dir exists, but $output_dir is not writable" 18 | return 0 19 | fi 20 | find "$template_dir" -follow -type f -name "*$suffix" -print | while read -r template; do 21 | relative_path="${template#$template_dir/}" 22 | output_path="$output_dir/${relative_path%$suffix}" 23 | subdir=$(dirname "$relative_path") 24 | # create a subdirectory where the template file exists 25 | mkdir -p "$output_dir/$subdir" 26 | if [ $template == "/etc/nginx/templates/nginx.conf.template" ]; then 27 | echo >&2 "$ME: Running envsubst on $template to $NGINXCONFPATH" 28 | envsubst "$defined_envs" < "$template" > "$NGINXCONFPATH" 29 | else 30 | echo >&2 "$ME: Running envsubst on $template to $output_path" 31 | envsubst "$defined_envs" < "$template" > "$output_path" 32 | fi 33 | done 34 | } 35 | 36 | auto_envsubst 37 | 38 | 39 | exit 0 -------------------------------------------------------------------------------- /valet/cli/drivers/WordPressValetDriver.php: -------------------------------------------------------------------------------- 1 | forceTrailingSlash($uri) 34 | ); 35 | } 36 | 37 | /** 38 | * Redirect to uri with trailing slash. 39 | * 40 | * @param string $uri 41 | * @return string 42 | */ 43 | private function forceTrailingSlash($uri) 44 | { 45 | if (substr($uri, -1 * strlen('/wp-admin')) == '/wp-admin') { 46 | header('Location: '.$uri.'/'); die; 47 | } 48 | 49 | return $uri; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /valet/cli/drivers/NeosValetDriver.php: -------------------------------------------------------------------------------- 1 | isActualFile($staticFilePath = $sitePath.'/Web'.$uri)) { 29 | return $staticFilePath; 30 | } 31 | return false; 32 | } 33 | 34 | /** 35 | * Get the fully resolved path to the application's front controller. 36 | * 37 | * @param string $sitePath 38 | * @param string $siteName 39 | * @param string $uri 40 | * @return string 41 | */ 42 | public function frontControllerPath($sitePath, $siteName, $uri) 43 | { 44 | putenv('FLOW_CONTEXT=Development'); 45 | putenv('FLOW_REWRITEURLS=1'); 46 | $_SERVER['SCRIPT_FILENAME'] = $sitePath.'/Web/index.php'; 47 | $_SERVER['SCRIPT_NAME'] = '/index.php'; 48 | return $sitePath.'/Web/index.php'; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /valet/cli/drivers/CakeValetDriver.php: -------------------------------------------------------------------------------- 1 | isActualFile($staticFilePath = $sitePath.'/webroot/'.$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 | * @param string $sitePath 39 | * @param string $siteName 40 | * @param string $uri 41 | * @return string 42 | */ 43 | public function frontControllerPath($sitePath, $siteName, $uri) 44 | { 45 | $_SERVER['DOCUMENT_ROOT'] = $sitePath.'/webroot'; 46 | $_SERVER['SCRIPT_FILENAME'] = $sitePath.'/webroot/index.php'; 47 | $_SERVER['SCRIPT_NAME'] = '/index.php'; 48 | $_SERVER['PHP_SELF'] = '/index.php'; 49 | 50 | return $sitePath.'/webroot/index.php'; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /valet/cli/drivers/SculpinValetDriver.php: -------------------------------------------------------------------------------- 1 | isModernSculpinProject($sitePath) || 16 | $this->isLegacySculpinProject($sitePath); 17 | } 18 | 19 | private function isModernSculpinProject($sitePath) 20 | { 21 | return is_dir($sitePath.'/source') && 22 | is_dir($sitePath.'/output_dev') && 23 | $this->composerRequiresSculpin($sitePath); 24 | } 25 | 26 | private function isLegacySculpinProject($sitePath) 27 | { 28 | return is_dir($sitePath.'/.sculpin'); 29 | } 30 | 31 | private function composerRequiresSculpin($sitePath) 32 | { 33 | if (! file_exists($sitePath.'/composer.json')) { 34 | return false; 35 | } 36 | 37 | $composer_json_source = file_get_contents($sitePath.'/composer.json'); 38 | $composer_json = json_decode($composer_json_source, true); 39 | 40 | if (json_last_error() !== JSON_ERROR_NONE) { 41 | return false; 42 | } 43 | 44 | return isset($composer_json['require']['sculpin/sculpin']); 45 | } 46 | 47 | /** 48 | * Mutate the incoming URI. 49 | * 50 | * @param string $uri 51 | * @return string 52 | */ 53 | public function mutateUri($uri) 54 | { 55 | return rtrim('/output_dev'.$uri, '/'); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /valet/cli/drivers/ContaoValetDriver.php: -------------------------------------------------------------------------------- 1 | isActualFile($staticFilePath = $sitePath.'/web'.$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 | * @param string $sitePath 39 | * @param string $siteName 40 | * @param string $uri 41 | * @return string 42 | */ 43 | public function frontControllerPath($sitePath, $siteName, $uri) 44 | { 45 | if ($uri === '/install.php') { 46 | return $sitePath.'/web/install.php'; 47 | } 48 | 49 | if (0 === strncmp($uri, '/app_dev.php', 12)) { 50 | $_SERVER['SCRIPT_NAME'] = '/app_dev.php'; 51 | $_SERVER['SCRIPT_FILENAME'] = $sitePath.'/app_dev.php'; 52 | 53 | return $sitePath.'/web/app_dev.php'; 54 | } 55 | 56 | return $sitePath.'/web/app.php'; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /valet/cli/Valet/Ngrok.php: -------------------------------------------------------------------------------- 1 | tunnelsEndpoints as $endpoint) { 26 | $response = retry(20, function () use ($endpoint, $domain) { 27 | $body = Request::get($endpoint)->send()->body; 28 | 29 | if (isset($body->tunnels) && count($body->tunnels) > 0) { 30 | return $this->findHttpTunnelUrl($body->tunnels, $domain); 31 | } 32 | }, 250); 33 | 34 | if (!empty($response)) { 35 | return $response; 36 | } 37 | } 38 | 39 | throw new DomainException("Tunnel not established."); 40 | } 41 | 42 | /** 43 | * Find the HTTP tunnel URL from the list of tunnels. 44 | * 45 | * @param array $tunnels 46 | * @return string|null 47 | */ 48 | function findHttpTunnelUrl($tunnels, $domain) 49 | { 50 | // If there are active tunnels on the Ngrok instance we will spin through them and 51 | // find the one responding on HTTP. Each tunnel has an HTTP and a HTTPS address 52 | // but for local dev purposes we just desire the plain HTTP URL endpoint. 53 | foreach ($tunnels as $tunnel) { 54 | if ($tunnel->proto === 'http' && strpos($tunnel->config->addr, $domain) ) { 55 | return $tunnel->public_url; 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /valet/cli/drivers/LaravelValetDriver.php: -------------------------------------------------------------------------------- 1 | isActualFile($storagePath = $sitePath.'/storage/app/public'.$storageUri)) { 41 | return $storagePath; 42 | } 43 | 44 | return false; 45 | } 46 | 47 | /** 48 | * Get the fully resolved path to the application's front controller. 49 | * 50 | * @param string $sitePath 51 | * @param string $siteName 52 | * @param string $uri 53 | * @return string 54 | */ 55 | public function frontControllerPath($sitePath, $siteName, $uri) 56 | { 57 | // Shortcut for getting the "local" hostname as the HTTP_HOST 58 | if (isset($_SERVER['HTTP_X_ORIGINAL_HOST'], $_SERVER['HTTP_X_FORWARDED_HOST'])) { 59 | $_SERVER['HTTP_HOST'] = $_SERVER['HTTP_X_FORWARDED_HOST']; 60 | } 61 | 62 | return $sitePath.'/public/index.php'; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /valet/cli/drivers/SymfonyValetDriver.php: -------------------------------------------------------------------------------- 1 | isActualFile($staticFilePath = $sitePath.'/web/'.$uri)) { 32 | return $staticFilePath; 33 | } elseif ($this->isActualFile($staticFilePath = $sitePath.'/public/'.$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 | * @param string $sitePath 44 | * @param string $siteName 45 | * @param string $uri 46 | * @return string 47 | */ 48 | public function frontControllerPath($sitePath, $siteName, $uri) 49 | { 50 | if (file_exists($frontControllerPath = $sitePath.'/web/app_dev.php')) { 51 | return $frontControllerPath; 52 | } elseif (file_exists($frontControllerPath = $sitePath.'/web/app.php')) { 53 | return $frontControllerPath; 54 | } elseif (file_exists($frontControllerPath = $sitePath.'/public/index.php')) { 55 | return $frontControllerPath; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /valet/cli/drivers/StatamicV1ValetDriver.php: -------------------------------------------------------------------------------- 1 | isActualFile($staticFilePath = $sitePath.$uri)) { 36 | return $staticFilePath; 37 | } 38 | 39 | return false; 40 | } 41 | 42 | /** 43 | * Get the fully resolved path to the application's front controller. 44 | * 45 | * @param string $sitePath 46 | * @param string $siteName 47 | * @param string $uri 48 | * @return string 49 | */ 50 | public function frontControllerPath($sitePath, $siteName, $uri) 51 | { 52 | if (strpos($uri, '/admin.php') === 0) { 53 | $_SERVER['SCRIPT_NAME'] = '/admin.php'; 54 | 55 | return $sitePath.'/admin.php'; 56 | } 57 | 58 | if ($uri === '/admin') { 59 | $_SERVER['SCRIPT_NAME'] = '/admin/index.php'; 60 | 61 | return $sitePath.'/admin/index.php'; 62 | } 63 | 64 | $_SERVER['SCRIPT_NAME'] = '/index.php'; 65 | 66 | return $sitePath.'/index.php'; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | VALET_HOME="./.valet-home" 4 | WHITE='\033[1;37m' 5 | NC='\033[0m' 6 | 7 | echo -e "${WHITE} 8 | Butler (Laravel Valet for Docker) 9 | ================================= 10 | 11 | This will install Butler (Laravel Valet for Docker) 12 | 13 | Although it is a replacement for the original Laravel Valet, it is not a drop-in replacement. 14 | It means you need to disable current running Laravel Valet either by uninstall it or by running valet stop. 15 | 16 | If you are not running Laravel Valet, please make sure port 80, 443, 53 inside your MacOS is not in use. 17 | ${NC}" 18 | 19 | 20 | 21 | while true; do 22 | read -p "Do you wish to install continue this installation? [Y/n]" yn 23 | case $yn in 24 | [Yy]* ) break;; 25 | [Nn]* ) exit;; 26 | * ) echo "Please answer yes or no.";; 27 | esac 28 | done 29 | 30 | # Ensure that Docker is running... 31 | if ! docker info > /dev/null 2>&1; then 32 | echo -e "${WHITE}Docker is not running.${NC}" >&2 33 | 34 | exit 1 35 | fi 36 | 37 | 38 | mkdir -p www 39 | mkdir -p $VALET_HOME/{CA,Certificates,Drivers,Extensions,Log,Nginx,Sites,dnsmasq.d,dnsmasq-internal.d} 40 | touch $VALET_HOME/Log/nginx-error.log 41 | cp ./valet/cli/stubs/SampleValetDriver.php $VALET_HOME/Drivers/SampleValetDriver.php 42 | 43 | # copy config file if not exists 44 | if [ ! -f $VALET_HOME/config.json ]; then 45 | cp ./stubs/config.json $VALET_HOME/config.json 46 | fi 47 | 48 | if [ ! -f .env ]; then 49 | cp ./stubs/.env .env 50 | sed -i '' "s|REPLACEME|$PWD/www|g" ./.env 51 | fi 52 | 53 | if [ ! -f docker-compose.yaml ]; then 54 | cp ./stubs/docker-compose.yaml ./docker-compose.yaml 55 | fi 56 | 57 | 58 | sed "s|REPLACEME|$PWD|g" ./bin/butler > ./butler 59 | chmod +x ./butler 60 | mv ./butler /usr/local/bin/butler 61 | echo "Waiting for Butler services to start..." 62 | butler start 63 | docker exec -i -w /valet/master butler_php_1 composer install -vvv 64 | butler install 65 | 66 | echo -e "${WHITE} 67 | 68 | 69 | Please make sure to set 127.0.0.1 inside your DNS setting for custom domain to work 70 | 71 | 72 | ${NC}" -------------------------------------------------------------------------------- /valet/cli/drivers/KirbyValetDriver.php: -------------------------------------------------------------------------------- 1 | isActualFile($staticFilePath = $sitePath.$uri)) { 29 | return $staticFilePath; 30 | } elseif ($this->isActualFile($staticFilePath = $sitePath.'/public'.$uri)) { 31 | return $staticFilePath; 32 | } 33 | 34 | return false; 35 | } 36 | 37 | /** 38 | * Get the fully resolved path to the application's front controller. 39 | * 40 | * @param string $sitePath 41 | * @param string $siteName 42 | * @param string $uri 43 | * @return string 44 | */ 45 | public function frontControllerPath($sitePath, $siteName, $uri) 46 | { 47 | $scriptName = '/index.php'; 48 | 49 | if ($this->isActualFile($sitePath.'/index.php')) { 50 | $indexPath = $sitePath.'/index.php'; 51 | } 52 | 53 | if ($isAboveWebroot = $this->isActualFile($sitePath.'/public/index.php')) { 54 | $indexPath = $sitePath.'/public/index.php'; 55 | } 56 | 57 | if (preg_match('/^\/panel/', $uri) && $this->isActualFile($sitePath.'/panel/index.php')) { 58 | $scriptName = '/panel/index.php'; 59 | $indexPath = $sitePath.'/panel/index.php'; 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 | -------------------------------------------------------------------------------- /valet/cli/drivers/BedrockValetDriver.php: -------------------------------------------------------------------------------- 1 | isActualFile($staticFilePath)) { 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 | * @param string $sitePath 44 | * @param string $siteName 45 | * @param string $uri 46 | * @return string 47 | */ 48 | public function frontControllerPath($sitePath, $siteName, $uri) 49 | { 50 | $_SERVER['PHP_SELF'] = $uri; 51 | $_SERVER['SERVER_NAME'] = $_SERVER['HTTP_HOST']; 52 | 53 | if (strpos($uri, '/wp/') === 0) { 54 | return is_dir($sitePath.'/web'.$uri) 55 | ? $sitePath.'/web'.$this->forceTrailingSlash($uri).'/index.php' 56 | : $sitePath.'/web'.$uri; 57 | } 58 | 59 | return $sitePath.'/web/index.php'; 60 | } 61 | 62 | /** 63 | * Redirect to uri with trailing slash. 64 | * 65 | * @param string $uri 66 | * @return string 67 | */ 68 | private function forceTrailingSlash($uri) 69 | { 70 | if (substr($uri, -1 * strlen('/wp/wp-admin')) == '/wp/wp-admin') { 71 | header('Location: '.$uri.'/'); die; 72 | } 73 | 74 | return $uri; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /stubs/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3.9' 2 | x-enviroment: &commonEnvironment 3 | - VALET_HOME_PATH: ${VALET_HOME_PATH} 4 | - VALET_STATIC_PREFIX: ${VALET_STATIC_PREFIX} 5 | - VALET_SERVER_PATH: ${VALET_SERVER_PATH} 6 | - PATH: /valet/master:$PATH 7 | 8 | services: 9 | webserver: 10 | build: 11 | context: docker/openresty 12 | dockerfile: Dockerfile 13 | ports: 14 | - 80:80 15 | - 443:443 16 | working_dir: ${WORK_DIR_PATH} 17 | volumes_from: 18 | - php 19 | volumes: 20 | - ./templates/nginx:/etc/nginx/templates 21 | environment: 22 | <<: *commonEnvironment 23 | depends_on: 24 | - php 25 | networks: 26 | vpc: 27 | ipv4_address: ${BUTLER_WEBSERVER_IP:-10.172.0.10} 28 | 29 | php: 30 | image: jtreminio/php:${BUTLER_PHP_VERSION:-8.0} 31 | dns: 32 | - ${BUTLER_DNS_INTERNAL_IP:-10.172.0.101} 33 | extra_hosts: 34 | - "host.docker.internal:${BUTLER_GATEWAY:-10.172.0.1}" 35 | environment: 36 | <<: *commonEnvironment 37 | PHP_INI_SCAN_DIR: ${BUTLER_PHP_MODULES} 38 | BUTLER_WEBSERVER_IP: ${BUTLER_WEBSERVER_IP:-10.172.0.10} 39 | working_dir: ${WORK_DIR_PATH} 40 | volumes: 41 | - composer:/./composer 42 | - ./valet:${VALET_PATH} 43 | - ./.valet-home:${VALET_HOME_PATH} 44 | - ${DEFAULT_WWW_PATH}:${WORK_DIR_PATH} 45 | networks: 46 | vpc: 47 | ipv4_address: ${BUTLER_PHP_IP:-10.172.0.11} 48 | 49 | dns: 50 | build: 51 | context: docker/dnsmasq 52 | dockerfile: Dockerfile 53 | ports: 54 | - 53:53/udp 55 | environment: 56 | <<: *commonEnvironment 57 | volumes: 58 | - ./templates/dnsmasq:/etc/dnsmasq-templates 59 | volumes_from: 60 | - php 61 | networks: 62 | vpc: 63 | ipv4_address: ${BUTLER_DNS_IP:-10.172.0.100} 64 | 65 | dns-internal: 66 | image: butler_dns 67 | ports: 68 | - 53/udp 69 | environment: 70 | <<: *commonEnvironment 71 | volumes: 72 | - ./templates/dnsmasq-internal:/etc/dnsmasq-templates 73 | volumes_from: 74 | - php 75 | networks: 76 | vpc: 77 | ipv4_address: ${BUTLER_DNS_INTERNAL_IP:-10.172.0.101} 78 | 79 | volumes: 80 | composer: 81 | 82 | networks: 83 | vpc: 84 | driver: bridge 85 | ipam: 86 | config: 87 | - subnet: ${BUTLER_SUBNET:-10.172.0.0/16} 88 | gateway: ${BUTLER_GATEWAY:-10.172.0.1} 89 | -------------------------------------------------------------------------------- /valet/cli/stubs/secure.valet.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 0.0.0.0:80; 3 | server_name VALET_SITE www.VALET_SITE *.VALET_SITE; 4 | return 301 https://$host$request_uri; 5 | } 6 | 7 | server { 8 | listen 0.0.0.0:443 ssl http2; 9 | server_name VALET_SITE www.VALET_SITE *.VALET_SITE; 10 | root /; 11 | charset utf-8; 12 | client_max_body_size 512M; 13 | http2_push_preload on; 14 | 15 | location /VALET_STATIC_PREFIX/ { 16 | internal; 17 | alias /; 18 | try_files $uri $uri/; 19 | } 20 | 21 | ssl_certificate "VALET_CERT"; 22 | ssl_certificate_key "VALET_KEY"; 23 | 24 | location / { 25 | rewrite ^ "VALET_SERVER_PATH" last; 26 | } 27 | 28 | location = /favicon.ico { access_log off; log_not_found off; } 29 | location = /robots.txt { access_log off; log_not_found off; } 30 | 31 | access_log off; 32 | error_log "VALET_HOME_PATH/Log/nginx-error.log"; 33 | 34 | error_page 404 "VALET_SERVER_PATH"; 35 | 36 | location ~ [^/]\.php(/|$) { 37 | fastcgi_split_path_info ^(.+\.php)(/.+)$; 38 | fastcgi_pass "php:9000"; 39 | fastcgi_index "VALET_SERVER_PATH"; 40 | include fastcgi_params; 41 | fastcgi_param SCRIPT_FILENAME "VALET_SERVER_PATH"; 42 | fastcgi_param PATH_INFO $fastcgi_path_info; 43 | } 44 | 45 | location ~ /\.ht { 46 | deny all; 47 | } 48 | } 49 | 50 | server { 51 | listen 0.0.0.0:60; 52 | server_name VALET_SITE www.VALET_SITE *.VALET_SITE; 53 | root /; 54 | charset utf-8; 55 | client_max_body_size 128M; 56 | 57 | add_header X-Robots-Tag 'noindex, nofollow, nosnippet, noarchive'; 58 | 59 | location /VALET_STATIC_PREFIX/ { 60 | internal; 61 | alias /; 62 | try_files $uri $uri/; 63 | } 64 | 65 | location / { 66 | rewrite ^ "VALET_SERVER_PATH" last; 67 | } 68 | 69 | location = /favicon.ico { access_log off; log_not_found off; } 70 | location = /robots.txt { access_log off; log_not_found off; } 71 | 72 | access_log off; 73 | error_log "VALET_HOME_PATH/Log/nginx-error.log"; 74 | 75 | error_page 404 "VALET_SERVER_PATH"; 76 | 77 | location ~ [^/]\.php(/|$) { 78 | fastcgi_split_path_info ^(.+\.php)(/.+)$; 79 | fastcgi_pass "unix:VALET_HOME_PATH/valet.sock"; 80 | fastcgi_index "VALET_SERVER_PATH"; 81 | include fastcgi_params; 82 | fastcgi_param SCRIPT_FILENAME "VALET_SERVER_PATH"; 83 | fastcgi_param PATH_INFO $fastcgi_path_info; 84 | } 85 | 86 | location ~ /\.ht { 87 | deny all; 88 | } 89 | } 90 | 91 | -------------------------------------------------------------------------------- /valet/cli/stubs/proxy.valet.conf: -------------------------------------------------------------------------------- 1 | # valet stub: proxy.valet.conf 2 | 3 | server { 4 | listen 0.0.0.0:80; 5 | server_name VALET_SITE www.VALET_SITE *.VALET_SITE; 6 | return 301 https://$host$request_uri; 7 | } 8 | 9 | server { 10 | listen 0.0.0.0:443 ssl http2; 11 | server_name VALET_SITE www.VALET_SITE *.VALET_SITE; 12 | root /; 13 | charset utf-8; 14 | client_max_body_size 128M; 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 | access_log off; 27 | error_log "VALET_HOME_PATH/Log/VALET_SITE-error.log"; 28 | 29 | error_page 404 "VALET_SERVER_PATH"; 30 | 31 | location / { 32 | proxy_pass VALET_PROXY_HOST; 33 | proxy_set_header Host $host; 34 | proxy_set_header X-Real-IP $remote_addr; 35 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 36 | proxy_set_header X-Forwarded-Proto $scheme; 37 | proxy_set_header X-Client-Verify SUCCESS; 38 | proxy_set_header X-Client-DN $ssl_client_s_dn; 39 | proxy_set_header X-SSL-Subject $ssl_client_s_dn; 40 | proxy_set_header X-SSL-Issuer $ssl_client_i_dn; 41 | proxy_set_header X-NginX-Proxy true; 42 | proxy_set_header Upgrade $http_upgrade; 43 | proxy_set_header Connection "upgrade"; 44 | proxy_http_version 1.1; 45 | proxy_read_timeout 1800; 46 | proxy_connect_timeout 1800; 47 | chunked_transfer_encoding on; 48 | proxy_redirect off; 49 | proxy_buffering off; 50 | } 51 | 52 | location ~ /\.ht { 53 | deny all; 54 | } 55 | } 56 | 57 | server { 58 | listen 0.0.0.0:60; 59 | server_name VALET_SITE www.VALET_SITE *.VALET_SITE; 60 | root /; 61 | charset utf-8; 62 | client_max_body_size 128M; 63 | 64 | add_header X-Robots-Tag 'noindex, nofollow, nosnippet, noarchive'; 65 | 66 | location /VALET_STATIC_PREFIX/ { 67 | internal; 68 | alias /; 69 | try_files $uri $uri/; 70 | } 71 | 72 | access_log off; 73 | error_log "VALET_HOME_PATH/Log/VALET_SITE-error.log"; 74 | 75 | error_page 404 "VALET_SERVER_PATH"; 76 | 77 | location / { 78 | proxy_pass VALET_PROXY_HOST; 79 | proxy_set_header Host $host; 80 | proxy_set_header X-Real-IP $remote_addr; 81 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 82 | proxy_set_header X-Forwarded-Proto $scheme; 83 | } 84 | 85 | location ~ /\.ht { 86 | deny all; 87 | } 88 | } 89 | 90 | -------------------------------------------------------------------------------- /valet/cli/Valet/CommandLine.php: -------------------------------------------------------------------------------- 1 | runCommand($command . ' > /dev/null 2>&1'); 18 | } 19 | 20 | /** 21 | * Simple global function to run commands. 22 | * 23 | * @param string $command 24 | * @return void 25 | */ 26 | public function quietlyAsUser($command) 27 | { 28 | $this->quietly('sudo -u "' . user() . '" ' . $command . ' > /dev/null 2>&1'); 29 | } 30 | 31 | /** 32 | * Pass the command to the command line and display the output. 33 | * 34 | * @param string $command 35 | * @return void 36 | */ 37 | public function passthru($command) 38 | { 39 | passthru($command); 40 | } 41 | 42 | /** 43 | * Run the given command as the non-root user. 44 | * 45 | * @param string $command 46 | * @param callable $onError 47 | * @return string 48 | */ 49 | public function run($command, callable $onError = null) 50 | { 51 | return $this->runCommand($command, $onError); 52 | } 53 | 54 | /** 55 | * Run the given command. 56 | * 57 | * @param string $command 58 | * @param callable $onError 59 | * @return string 60 | */ 61 | public function runAsUser($command, callable $onError = null) 62 | { 63 | return $this->runCommand('sudo -u "' . user() . '" ' . $command, $onError); 64 | } 65 | 66 | /** 67 | * Run the given command. 68 | * 69 | * @param string $command 70 | * @param callable $onError 71 | * @return string 72 | */ 73 | public function runCommand($command, callable $onError = null) 74 | { 75 | $onError = $onError ?: function () {}; 76 | 77 | // Symfony's 4.x Process component has deprecated passing a command string 78 | // to the constructor, but older versions (which Valet's Composer 79 | // constraints allow) don't have the fromShellCommandLine method. 80 | // For more information, see: https://github.com/laravel/valet/pull/761 81 | if (method_exists(Process::class, 'fromShellCommandline')) { 82 | $process = Process::fromShellCommandline($command); 83 | } else { 84 | $process = new Process($command); 85 | } 86 | 87 | $processOutput = ''; 88 | $process->setTimeout(null)->run(function ($type, $line) use (&$processOutput) { 89 | $processOutput .= $line; 90 | }); 91 | 92 | if ($process->getExitCode() > 0) { 93 | $onError($process->getExitCode(), $processOutput); 94 | } 95 | 96 | return $processOutput; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /valet/cli/Valet/Valet.php: -------------------------------------------------------------------------------- 1 | cli = $cli; 22 | $this->files = $files; 23 | } 24 | 25 | /** 26 | * Symlink the Valet Bash script into the user's local bin. 27 | * 28 | * @return void 29 | */ 30 | public function symlinkToUsersBin() 31 | { 32 | $this->unlinkFromUsersBin(); 33 | 34 | $this->cli->runAsUser('ln -s "' . realpath(__DIR__ . '/../../valet') . '" ' . $this->valetBin); 35 | } 36 | 37 | /** 38 | * Remove the symlink from the user's local bin. 39 | * 40 | * @return void 41 | */ 42 | public function unlinkFromUsersBin() 43 | { 44 | $this->cli->quietlyAsUser('rm ' . $this->valetBin); 45 | } 46 | 47 | /** 48 | * Get the paths to all of the Valet extensions. 49 | * 50 | * @return array 51 | */ 52 | public function extensions() 53 | { 54 | if (!$this->files->isDir(VALET_HOME_PATH . '/Extensions')) { 55 | return []; 56 | } 57 | 58 | return collect($this->files->scandir(VALET_HOME_PATH . '/Extensions')) 59 | ->reject(function ($file) { 60 | return is_dir($file); 61 | }) 62 | ->map(function ($file) { 63 | return VALET_HOME_PATH . '/Extensions/' . $file; 64 | }) 65 | ->values()->all(); 66 | } 67 | 68 | /** 69 | * Determine if this is the latest version of Valet. 70 | * 71 | * @param string $currentVersion 72 | * @return bool 73 | * @throws \Httpful\Exception\ConnectionErrorException 74 | */ 75 | public function onLatestVersion($currentVersion) 76 | { 77 | $response = Request::get('https://api.github.com/repos/RunCloudIO/butler/releases/latest')->send(); 78 | 79 | return version_compare($currentVersion, trim($response->body->tag_name, 'v'), '>='); 80 | } 81 | 82 | /** 83 | * Create the "sudoers.d" entry for running Valet. 84 | * 85 | * @return void 86 | */ 87 | public function createSudoersEntry() 88 | { 89 | $this->files->ensureDirExists('/etc/sudoers.d'); 90 | 91 | $this->files->put('/etc/sudoers.d/valet', 'Cmnd_Alias VALET = ' . BREW_PREFIX . '/bin/valet * 92 | %admin ALL=(root) NOPASSWD:SETENV: VALET' . PHP_EOL); 93 | } 94 | 95 | /** 96 | * Remove the "sudoers.d" entry for running Valet. 97 | * 98 | * @return void 99 | */ 100 | public function removeSudoersEntry() 101 | { 102 | $this->cli->quietly('rm /etc/sudoers.d/valet'); 103 | } 104 | 105 | /** 106 | * Run composer global diagnose 107 | */ 108 | public function composerGlobalDiagnose() 109 | { 110 | $this->cli->runAsUser('composer global diagnose'); 111 | } 112 | 113 | /** 114 | * Run composer global update 115 | */ 116 | public function composerGlobalUpdate() 117 | { 118 | $this->cli->runAsUser('composer global update'); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /valet/cli/drivers/DrupalValetDriver.php: -------------------------------------------------------------------------------- 1 | addSubdirectory($sitePath); 16 | 17 | /** 18 | * /misc/drupal.js = Drupal 7 19 | * /core/lib/Drupal.php = Drupal 8 20 | */ 21 | if (file_exists($sitePath.'/misc/drupal.js') || 22 | file_exists($sitePath.'/core/lib/Drupal.php')) { 23 | return true; 24 | } 25 | } 26 | 27 | /** 28 | * Determine if the incoming request is for a static file. 29 | * 30 | * @param string $sitePath 31 | * @param string $siteName 32 | * @param string $uri 33 | * @return string|false 34 | */ 35 | public function isStaticFile($sitePath, $siteName, $uri) 36 | { 37 | $sitePath = $this->addSubdirectory($sitePath); 38 | 39 | if (file_exists($sitePath.$uri) && 40 | ! is_dir($sitePath.$uri) && 41 | pathinfo($sitePath.$uri)['extension'] != 'php') { 42 | return $sitePath.$uri; 43 | } 44 | 45 | return false; 46 | } 47 | 48 | /** 49 | * Get the fully resolved path to the application's front controller. 50 | * 51 | * @param string $sitePath 52 | * @param string $siteName 53 | * @param string $uri 54 | * @return string 55 | */ 56 | public function frontControllerPath($sitePath, $siteName, $uri) 57 | { 58 | $sitePath = $this->addSubdirectory($sitePath); 59 | 60 | if (!isset($_GET['q']) && !empty($uri) && $uri !== '/' && strpos($uri, '/jsonapi/') === false) { 61 | $_GET['q'] = $uri; 62 | } 63 | 64 | $matches = []; 65 | if (preg_match('/^\/(.*?)\.php/', $uri, $matches)) { 66 | $filename = $matches[0]; 67 | if (file_exists($sitePath.$filename) && ! is_dir($sitePath.$filename)) { 68 | $_SERVER['SCRIPT_FILENAME'] = $sitePath.$filename; 69 | $_SERVER['SCRIPT_NAME'] = $filename; 70 | return $sitePath.$filename; 71 | } 72 | } 73 | 74 | // Fallback 75 | $_SERVER['SCRIPT_FILENAME'] = $sitePath.'/index.php'; 76 | $_SERVER['SCRIPT_NAME'] = '/index.php'; 77 | return $sitePath.'/index.php'; 78 | } 79 | 80 | /** 81 | * Add any matching subdirectory to the site path. 82 | */ 83 | public function addSubdirectory($sitePath) 84 | { 85 | $paths = array_map(function ($subDir) use ($sitePath) { 86 | return "$sitePath/$subDir"; 87 | }, $this->possibleSubdirectories()); 88 | 89 | $foundPaths = array_filter($paths, function ($path) { 90 | return file_exists($path); 91 | }); 92 | 93 | // If paths are found, return the first one. 94 | if (!empty($foundPaths)) { 95 | return array_shift($foundPaths); 96 | } 97 | 98 | // If there are no matches, return the original path. 99 | return $sitePath; 100 | } 101 | 102 | /** 103 | * Return an array of possible subdirectories. 104 | * 105 | * @return array 106 | */ 107 | private function possibleSubdirectories() 108 | { 109 | return ['docroot', 'public', 'web']; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /valet/cli/Valet/DnsMasq.php: -------------------------------------------------------------------------------- 1 | cli = $cli; 18 | $this->brew = $brew; 19 | $this->files = $files; 20 | $this->configuration = $configuration; 21 | } 22 | 23 | /** 24 | * Install and configure DnsMasq. 25 | * 26 | * @return void 27 | */ 28 | public function install($tld = 'test') 29 | { 30 | // For DnsMasq, we enable its feature of loading *.conf from /usr/local/etc/dnsmasq.d/ 31 | // and then we put a valet config file in there to point to the user's home .config/valet/dnsmasq.d 32 | // This allows Valet to make changes to our own files without needing to modify the core dnsmasq configs 33 | $this->ensureUsingDnsmasqDForConfigs(); 34 | 35 | $this->createDnsmasqTldConfigFile($tld); 36 | 37 | info('Valet is configured to serve for TLD [.' . $tld . ']'); 38 | } 39 | 40 | /** 41 | * Forcefully uninstall dnsmasq. 42 | * 43 | * @return void 44 | */ 45 | public function uninstall() 46 | { 47 | $this->brew->stopService('dnsmasq'); 48 | $this->brew->uninstallFormula('dnsmasq'); 49 | $this->cli->run('rm -rf ' . BREW_PREFIX . '/etc/dnsmasq.d/dnsmasq-valet.conf'); 50 | $tld = $this->configuration->read()['tld']; 51 | $this->files->unlink($this->resolverPath . '/' . $tld); 52 | } 53 | 54 | /** 55 | * Tell Homebrew to restart dnsmasq 56 | * 57 | * @return void 58 | */ 59 | public function restart() 60 | { 61 | $this->brew->restartService('dnsmasq'); 62 | } 63 | 64 | /** 65 | * Ensure the DnsMasq configuration primary config is set to read custom configs 66 | * 67 | * @return void 68 | */ 69 | public function ensureUsingDnsmasqDForConfigs() 70 | { 71 | info('Updating Dnsmasq configuration...'); 72 | 73 | $this->files->ensureDirExists(VALET_HOME_PATH . '/dnsmasq.d', user()); 74 | $this->files->ensureDirExists(VALET_HOME_PATH . '/dnsmasq-internal.d', user()); 75 | } 76 | 77 | /** 78 | * Create the TLD-specific dnsmasq config file 79 | * @param string $tld 80 | * @return void 81 | */ 82 | public function createDnsmasqTldConfigFile($tld) 83 | { 84 | $tldConfigFile = $this->dnsmasqUserConfigDir() . 'tld-' . $tld . '.conf'; 85 | $tldInternalConfigFile = $this->dnsmasqInternalConfigDir() . 'tld-' . $tld . '.conf'; 86 | 87 | $this->files->putAsUser($tldConfigFile, 'address=/.' . $tld . '/127.0.0.1' . PHP_EOL); 88 | $this->files->putAsUser($tldInternalConfigFile, 'address=/.' . $tld . '/' . BUTLER_WEBSERVER_IP . PHP_EOL); 89 | } 90 | 91 | /** 92 | * Update the TLD/domain resolved by DnsMasq. 93 | * 94 | * @param string $oldTld 95 | * @param string $newTld 96 | * @return void 97 | */ 98 | public function updateTld($oldTld, $newTld) 99 | { 100 | $this->files->unlink($this->resolverPath . '/' . $oldTld); 101 | $this->files->unlink($this->dnsmasqUserConfigDir() . 'tld-' . $oldTld . '.conf'); 102 | $this->files->unlink($this->dnsmasqInternalConfigDir() . 'tld-' . $oldTld . '.conf'); 103 | 104 | $this->install($newTld); 105 | } 106 | 107 | /** 108 | * Get the custom configuration path. 109 | * 110 | * @return string 111 | */ 112 | public function dnsmasqUserConfigDir() 113 | { 114 | return VALET_HOME_PATH . '/dnsmasq.d/'; 115 | } 116 | 117 | public function dnsmasqInternalConfigDir() 118 | { 119 | return VALET_HOME_PATH . '/dnsmasq-internal.d/'; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /valet/cli/Valet/Diagnose.php: -------------------------------------------------------------------------------- 1 | /dev/null 2>&1', 19 | 'brew config', 20 | 'brew services list', 21 | 'brew list --formula --versions | grep -E "(php|nginx|dnsmasq|mariadb|mysql|mailhog|openssl)(@\d\..*)?\s"', 22 | 'brew outdated', 23 | 'brew tap', 24 | 'php -v', 25 | 'which -a php', 26 | 'php --ini', 27 | 'nginx -v', 28 | 'curl --version', 29 | 'php --ri curl', 30 | '~/.composer/vendor/laravel/valet/bin/ngrok version', 31 | 'ls -al ~/.ngrok2', 32 | 'brew info nginx', 33 | 'brew info php', 34 | 'brew info openssl', 35 | 'openssl version -a', 36 | 'openssl ciphers', 37 | 'sudo nginx -t', 38 | 'which -a php-fpm', 39 | BREW_PREFIX.'/opt/php/sbin/php-fpm -v', 40 | 'sudo '.BREW_PREFIX.'/opt/php/sbin/php-fpm -y '.PHP_SYSCONFDIR.'/php-fpm.conf --test', 41 | 'ls -al ~/Library/LaunchAgents | grep homebrew', 42 | 'ls -al /Library/LaunchAgents | grep homebrew', 43 | 'ls -al /Library/LaunchDaemons | grep homebrew', 44 | 'ls -aln /etc/resolv.conf', 45 | 'cat /etc/resolv.conf', 46 | ]; 47 | 48 | var $cli, $files, $print, $progressBar; 49 | 50 | /** 51 | * Create a new Diagnose instance. 52 | * 53 | * @param CommandLine $cli 54 | * @param Filesystem $files 55 | * @return void 56 | */ 57 | function __construct(CommandLine $cli, Filesystem $files) 58 | { 59 | $this->cli = $cli; 60 | $this->files = $files; 61 | } 62 | 63 | /** 64 | * Run diagnostics. 65 | */ 66 | function run($print, $plainText) 67 | { 68 | $this->print = $print; 69 | 70 | $this->beforeRun(); 71 | 72 | $results = collect($this->commands)->map(function ($command) { 73 | $this->beforeCommand($command); 74 | 75 | $output = $this->runCommand($command); 76 | 77 | if ($this->ignoreOutput($command)) return; 78 | 79 | $this->afterCommand($command, $output); 80 | 81 | return compact('command', 'output'); 82 | })->filter()->values(); 83 | 84 | $output = $this->format($results, $plainText); 85 | 86 | $this->files->put('valet_diagnostics.txt', $output); 87 | 88 | $this->cli->run('pbcopy < valet_diagnostics.txt'); 89 | 90 | $this->files->unlink('valet_diagnostics.txt'); 91 | 92 | $this->afterRun(); 93 | } 94 | 95 | function beforeRun() 96 | { 97 | if ($this->print) { 98 | return; 99 | } 100 | 101 | $this->progressBar = new ProgressBar(new ConsoleOutput, count($this->commands)); 102 | 103 | $this->progressBar->start(); 104 | } 105 | 106 | function afterRun() 107 | { 108 | if ($this->progressBar) { 109 | $this->progressBar->finish(); 110 | } 111 | 112 | output(''); 113 | } 114 | 115 | function runCommand($command) 116 | { 117 | return strpos($command, 'sudo ') === 0 118 | ? $this->cli->run($command) 119 | : $this->cli->runAsUser($command); 120 | } 121 | 122 | function beforeCommand($command) 123 | { 124 | if ($this->print) { 125 | info(PHP_EOL."$ $command"); 126 | } 127 | } 128 | 129 | function afterCommand($command, $output) 130 | { 131 | if ($this->print) { 132 | output(trim($output)); 133 | } else { 134 | $this->progressBar->advance(); 135 | } 136 | } 137 | 138 | function ignoreOutput($command) 139 | { 140 | return strpos($command, '> /dev/null 2>&1') !== false; 141 | } 142 | 143 | function format($results, $plainText) 144 | { 145 | return $results->map(function ($result) use ($plainText) { 146 | $command = $result['command']; 147 | $output = trim($result['output']); 148 | 149 | if ($plainText) { 150 | return implode(PHP_EOL, ["$ {$command}", $output]); 151 | } 152 | 153 | return sprintf( 154 | '
%s%s%s
%s
%s
', 155 | PHP_EOL, $command, PHP_EOL, $output, PHP_EOL 156 | ); 157 | })->implode($plainText ? PHP_EOL.str_repeat('-', 20).PHP_EOL : PHP_EOL); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /valet/cli/drivers/Magento2ValetDriver.php: -------------------------------------------------------------------------------- 1 | checkMageMode($sitePath); 37 | 38 | $uri = $this->handleForVersions($uri); 39 | $route = parse_url(substr($uri, 1))['path']; 40 | 41 | $pub = ''; 42 | if ('developer' === $this->mageMode) { 43 | $pub = 'pub/'; 44 | } 45 | 46 | if (!$this->isPubDirectory($sitePath, $route, $pub)) { 47 | return false; 48 | } 49 | 50 | $magentoPackagePubDir = $sitePath; 51 | if ('developer' !== $this->mageMode) { 52 | $magentoPackagePubDir .= '/pub'; 53 | } 54 | 55 | $file = $magentoPackagePubDir . '/' . $route; 56 | 57 | if (file_exists($file)) { 58 | return $magentoPackagePubDir . $uri; 59 | } 60 | 61 | if (strpos($route, $pub . 'static/') === 0) { 62 | $route = preg_replace('#' . $pub . 'static/#', '', $route, 1); 63 | $_GET['resource'] = $route; 64 | include $magentoPackagePubDir . '/' . $pub . 'static.php'; 65 | exit; 66 | } 67 | 68 | if (strpos($route, $pub . 'media/') === 0) { 69 | include $magentoPackagePubDir . '/' . $pub . 'get.php'; 70 | exit; 71 | } 72 | 73 | return false; 74 | } 75 | 76 | /** 77 | * Rewrite URLs that look like "versions12345/" to remove 78 | * the versions12345/ part 79 | * 80 | * @param string $route 81 | */ 82 | private function handleForVersions($route) 83 | { 84 | return preg_replace('/version\d*\//', '', $route); 85 | } 86 | 87 | /** 88 | * Determine the current MAGE_MODE 89 | * 90 | * @param string $sitePath 91 | */ 92 | private function checkMageMode($sitePath) 93 | { 94 | if (null !== $this->mageMode) { 95 | // We have already figure out mode, no need to check it again 96 | return; 97 | } 98 | if (!file_exists($sitePath . '/index.php')) { 99 | $this->mageMode = 'production'; // Can't use developer mode without index.php in project root 100 | return; 101 | } 102 | $mageConfig = []; 103 | if (file_exists($sitePath . '/app/etc/env.php')) { 104 | $mageConfig = require $sitePath . '/app/etc/env.php'; 105 | } 106 | if (array_key_exists('MAGE_MODE', $mageConfig)) { 107 | $this->mageMode = $mageConfig['MAGE_MODE']; 108 | } 109 | } 110 | 111 | /** 112 | * Checks to see if route is referencing any directory inside pub. This is a dynamic check so that if any new 113 | * directories are added to pub this driver will not need to be updated. 114 | * 115 | * @param string $sitePath 116 | * @param string $route 117 | * @param string $pub 118 | * @return bool 119 | */ 120 | private function isPubDirectory($sitePath, $route, $pub = '') 121 | { 122 | $sitePath .= '/pub/'; 123 | $dirs = glob($sitePath . '*', GLOB_ONLYDIR); 124 | 125 | $dirs = str_replace($sitePath, '', $dirs); 126 | foreach ($dirs as $dir) { 127 | if (strpos($route, $pub . $dir . '/') === 0) { 128 | return true; 129 | } 130 | } 131 | return false; 132 | } 133 | 134 | /** 135 | * Get the fully resolved path to the application's front controller. 136 | * 137 | * @param string $sitePath 138 | * @param string $siteName 139 | * @param string $uri 140 | * @return string 141 | */ 142 | public function frontControllerPath($sitePath, $siteName, $uri) 143 | { 144 | $this->checkMageMode($sitePath); 145 | 146 | if ('developer' === $this->mageMode) { 147 | $_SERVER['DOCUMENT_ROOT'] = $sitePath; 148 | return $sitePath . '/index.php'; 149 | } 150 | $_SERVER['DOCUMENT_ROOT'] = $sitePath . '/pub'; 151 | return $sitePath . '/pub/index.php'; 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /valet/cli/Valet/Nginx.php: -------------------------------------------------------------------------------- 1 | cli = $cli; 30 | $this->brew = $brew; 31 | $this->site = $site; 32 | $this->files = $files; 33 | $this->configuration = $configuration; 34 | } 35 | 36 | /** 37 | * Install the configuration files for Nginx. 38 | * 39 | * @return void 40 | */ 41 | function install() 42 | { 43 | if (!$this->brew->hasInstalledNginx()) { 44 | $this->brew->installOrFail('nginx', []); 45 | } 46 | 47 | $this->installConfiguration(); 48 | $this->installServer(); 49 | $this->installNginxDirectory(); 50 | } 51 | 52 | /** 53 | * Install the Nginx configuration file. 54 | * 55 | * @return void 56 | */ 57 | function installConfiguration() 58 | { 59 | info('Installing nginx configuration...'); 60 | 61 | $contents = $this->files->get(__DIR__.'/../stubs/nginx.conf'); 62 | 63 | $this->files->putAsUser( 64 | static::NGINX_CONF, 65 | str_replace(['VALET_USER', 'VALET_HOME_PATH'], [user(), VALET_HOME_PATH], $contents) 66 | ); 67 | } 68 | 69 | /** 70 | * Install the Valet Nginx server configuration file. 71 | * 72 | * @return void 73 | */ 74 | function installServer() 75 | { 76 | $this->files->ensureDirExists(BREW_PREFIX.'/etc/nginx/valet'); 77 | 78 | $this->files->putAsUser( 79 | BREW_PREFIX.'/etc/nginx/valet/valet.conf', 80 | str_replace( 81 | ['VALET_HOME_PATH', 'VALET_SERVER_PATH', 'VALET_STATIC_PREFIX'], 82 | [VALET_HOME_PATH, VALET_SERVER_PATH, VALET_STATIC_PREFIX], 83 | $this->files->get(__DIR__.'/../stubs/valet.conf') 84 | ) 85 | ); 86 | 87 | $this->files->putAsUser( 88 | BREW_PREFIX.'/etc/nginx/fastcgi_params', 89 | $this->files->get(__DIR__.'/../stubs/fastcgi_params') 90 | ); 91 | } 92 | 93 | /** 94 | * Install the Nginx configuration directory to the ~/.config/valet directory. 95 | * 96 | * This directory contains all site-specific Nginx servers. 97 | * 98 | * @return void 99 | */ 100 | function installNginxDirectory() 101 | { 102 | info('Installing nginx directory...'); 103 | 104 | if (! $this->files->isDir($nginxDirectory = VALET_HOME_PATH.'/Nginx')) { 105 | $this->files->mkdirAsUser($nginxDirectory); 106 | } 107 | 108 | $this->files->putAsUser($nginxDirectory.'/.keep', "\n"); 109 | 110 | $this->rewriteSecureNginxFiles(); 111 | } 112 | 113 | /** 114 | * Check nginx.conf for errors. 115 | */ 116 | private function lint() 117 | { 118 | $this->cli->run( 119 | 'sudo nginx -c '.static::NGINX_CONF.' -t', 120 | function ($exitCode, $outputMessage) { 121 | throw new DomainException("Nginx cannot start; please check your nginx.conf [$exitCode: $outputMessage]."); 122 | } 123 | ); 124 | } 125 | 126 | /** 127 | * Generate fresh Nginx servers for existing secure sites. 128 | * 129 | * @return void 130 | */ 131 | function rewriteSecureNginxFiles() 132 | { 133 | $tld = $this->configuration->read()['tld']; 134 | 135 | $this->site->resecureForNewTld($tld, $tld); 136 | } 137 | 138 | /** 139 | * Restart the Nginx service. 140 | * 141 | * @return void 142 | */ 143 | function restart() 144 | { 145 | $this->lint(); 146 | 147 | $this->brew->restartService($this->brew->nginxServiceName()); 148 | } 149 | 150 | /** 151 | * Stop the Nginx service. 152 | * 153 | * @return void 154 | */ 155 | function stop() 156 | { 157 | info('Stopping nginx...'); 158 | 159 | $this->cli->quietly('sudo brew services stop '. $this->brew->nginxServiceName()); 160 | } 161 | 162 | /** 163 | * Forcefully uninstall Nginx. 164 | * 165 | * @return void 166 | */ 167 | function uninstall() 168 | { 169 | $this->brew->stopService(['nginx', 'nginx-full']); 170 | $this->brew->uninstallFormula('nginx nginx-full'); 171 | $this->cli->quietly('rm -rf '.BREW_PREFIX.'/etc/nginx '.BREW_PREFIX.'/var/log/nginx'); 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /valet/cli/drivers/BasicValetDriver.php: -------------------------------------------------------------------------------- 1 | isActualFile($staticFilePath = $sitePath.$uri)) { 35 | return $staticFilePath; 36 | } 37 | 38 | return false; 39 | } 40 | 41 | /** 42 | * Get the fully resolved path to the application's front controller. 43 | * 44 | * @param string $sitePath 45 | * @param string $siteName 46 | * @param string $uri 47 | * @return string 48 | */ 49 | public function frontControllerPath($sitePath, $siteName, $uri) 50 | { 51 | $_SERVER['PHP_SELF'] = $uri; 52 | $_SERVER['SERVER_ADDR'] = '127.0.0.1'; 53 | $_SERVER['SERVER_NAME'] = $_SERVER['HTTP_HOST']; 54 | 55 | $dynamicCandidates = [ 56 | $this->asActualFile($sitePath, $uri), 57 | $this->asPhpIndexFileInDirectory($sitePath, $uri), 58 | $this->asHtmlIndexFileInDirectory($sitePath, $uri), 59 | ]; 60 | 61 | foreach ($dynamicCandidates as $candidate) { 62 | if ($this->isActualFile($candidate)) { 63 | $_SERVER['SCRIPT_FILENAME'] = $candidate; 64 | $_SERVER['SCRIPT_NAME'] = str_replace($sitePath, '', $candidate); 65 | $_SERVER['DOCUMENT_ROOT'] = $sitePath; 66 | return $candidate; 67 | } 68 | } 69 | 70 | $fixedCandidatesAndDocroots = [ 71 | $this->asRootPhpIndexFile($sitePath) => $sitePath, 72 | $this->asPublicPhpIndexFile($sitePath) => $sitePath . '/public', 73 | $this->asPublicHtmlIndexFile($sitePath) => $sitePath . '/public', 74 | ]; 75 | 76 | foreach ($fixedCandidatesAndDocroots as $candidate => $docroot) { 77 | if ($this->isActualFile($candidate)) { 78 | $_SERVER['SCRIPT_FILENAME'] = $candidate; 79 | $_SERVER['SCRIPT_NAME'] = '/index.php'; 80 | $_SERVER['DOCUMENT_ROOT'] = $docroot; 81 | return $candidate; 82 | } 83 | } 84 | } 85 | 86 | /** 87 | * Concatenate the site path and URI as a single file name. 88 | * 89 | * @param string $sitePath 90 | * @param string $uri 91 | * @return string 92 | */ 93 | protected function asActualFile($sitePath, $uri) 94 | { 95 | return $sitePath.$uri; 96 | } 97 | 98 | /** 99 | * Format the site path and URI with a trailing "index.php". 100 | * 101 | * @param string $sitePath 102 | * @param string $uri 103 | * @return string 104 | */ 105 | protected function asPhpIndexFileInDirectory($sitePath, $uri) 106 | { 107 | return $sitePath.rtrim($uri, '/').'/index.php'; 108 | } 109 | 110 | /** 111 | * Format the site path and URI with a trailing "index.html". 112 | * 113 | * @param string $sitePath 114 | * @param string $uri 115 | * @return string 116 | */ 117 | protected function asHtmlIndexFileInDirectory($sitePath, $uri) 118 | { 119 | return $sitePath.rtrim($uri, '/').'/index.html'; 120 | } 121 | 122 | /** 123 | * Format the incoming site path as root "index.php" file path. 124 | * 125 | * @param string $sitePath 126 | * @return string 127 | */ 128 | protected function asRootPhpIndexFile($sitePath) 129 | { 130 | return $sitePath.'/index.php'; 131 | } 132 | 133 | /** 134 | * Format the incoming site path as a "public/index.php" file path. 135 | * 136 | * @param string $sitePath 137 | * @return string 138 | */ 139 | protected function asPublicPhpIndexFile($sitePath) 140 | { 141 | return $sitePath.'/public/index.php'; 142 | } 143 | 144 | /** 145 | * Format the incoming site path as a "public/index.php" file path. 146 | * 147 | * @param string $sitePath 148 | * @return string 149 | */ 150 | protected function asPublicHtmlIndexFile($sitePath) 151 | { 152 | return $sitePath.'/public/index.html'; 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /valet/cli/includes/helpers.php: -------------------------------------------------------------------------------- 1 | runAsUser('printf $(brew --prefix)')); 21 | 22 | /** 23 | * Output the given text to the console. 24 | * 25 | * @param string $output 26 | * @return void 27 | */ 28 | function info($output) 29 | { 30 | output('' . $output . ''); 31 | } 32 | 33 | /** 34 | * Output the given text to the console. 35 | * 36 | * @param string $output 37 | * @return void 38 | */ 39 | function warning($output) 40 | { 41 | output('' . $output . ''); 42 | } 43 | 44 | /** 45 | * Output a table to the console. 46 | * 47 | * @param array $headers 48 | * @param array $rows 49 | * @return void 50 | */ 51 | function table(array $headers = [], array $rows = []) 52 | { 53 | $table = new Table(new ConsoleOutput); 54 | 55 | $table->setHeaders($headers)->setRows($rows); 56 | 57 | $table->render(); 58 | } 59 | 60 | /** 61 | * Output the given text to the console. 62 | * 63 | * @param string $output 64 | * @return void 65 | */ 66 | function output($output) 67 | { 68 | if (isset($_ENV['APP_ENV']) && $_ENV['APP_ENV'] === 'testing') { 69 | return; 70 | } 71 | 72 | (new ConsoleOutput)->writeln($output); 73 | } 74 | 75 | if (!function_exists('resolve')) { 76 | /** 77 | * Resolve the given class from the container. 78 | * 79 | * @param string $class 80 | * @return mixed 81 | */ 82 | function resolve($class) 83 | { 84 | return Container::getInstance()->make($class); 85 | } 86 | } 87 | 88 | /** 89 | * Swap the given class implementation in the container. 90 | * 91 | * @param string $class 92 | * @param mixed $instance 93 | * @return void 94 | */ 95 | function swap($class, $instance) 96 | { 97 | Container::getInstance()->instance($class, $instance); 98 | } 99 | 100 | if (!function_exists('retry')) { 101 | /** 102 | * Retry the given function N times. 103 | * 104 | * @param int $retries 105 | * @param callable $retries 106 | * @param int $sleep 107 | * @return mixed 108 | */ 109 | function retry($retries, $fn, $sleep = 0) 110 | { 111 | beginning: 112 | try { 113 | return $fn(); 114 | } catch (Exception $e) { 115 | if (!$retries) { 116 | throw $e; 117 | } 118 | 119 | $retries--; 120 | 121 | if ($sleep > 0) { 122 | usleep($sleep * 1000); 123 | } 124 | 125 | goto beginning; 126 | } 127 | } 128 | } 129 | 130 | /** 131 | * Verify that the script is currently running as "sudo". 132 | * 133 | * @return void 134 | */ 135 | function should_be_sudo() 136 | { 137 | if (!isset($_SERVER['SUDO_USER'])) { 138 | throw new Exception('This command must be run with sudo.'); 139 | } 140 | } 141 | 142 | if (!function_exists('tap')) { 143 | /** 144 | * Tap the given value. 145 | * 146 | * @param mixed $value 147 | * @param callable $callback 148 | * @return mixed 149 | */ 150 | function tap($value, callable $callback) 151 | { 152 | $callback($value); 153 | 154 | return $value; 155 | } 156 | } 157 | 158 | if (!function_exists('ends_with')) { 159 | /** 160 | * Determine if a given string ends with a given substring. 161 | * 162 | * @param string $haystack 163 | * @param string|array $needles 164 | * @return bool 165 | */ 166 | function ends_with($haystack, $needles) 167 | { 168 | foreach ((array) $needles as $needle) { 169 | if (substr($haystack, -strlen($needle)) === (string) $needle) { 170 | return true; 171 | } 172 | } 173 | return false; 174 | } 175 | } 176 | 177 | if (!function_exists('starts_with')) { 178 | /** 179 | * Determine if a given string starts with a given substring. 180 | * 181 | * @param string $haystack 182 | * @param string|string[] $needles 183 | * @return bool 184 | */ 185 | function starts_with($haystack, $needles) 186 | { 187 | foreach ((array) $needles as $needle) { 188 | if ((string) $needle !== '' && strncmp($haystack, $needle, strlen($needle)) === 0) { 189 | return true; 190 | } 191 | } 192 | 193 | return false; 194 | } 195 | } 196 | 197 | /** 198 | * Get the user 199 | */ 200 | function user() 201 | { 202 | if (!isset($_SERVER['SUDO_USER'])) { 203 | return $_SERVER['USER']; 204 | } 205 | 206 | return $_SERVER['SUDO_USER']; 207 | } 208 | -------------------------------------------------------------------------------- /valet/cli/drivers/StatamicValetDriver.php: -------------------------------------------------------------------------------- 1 | isActualFile($staticFilePath = $sitePath.$uri)) { 33 | return $staticFilePath; 34 | } elseif ($this->isActualFile($staticFilePath = $sitePath.'/public'.$uri)) { 35 | return $staticFilePath; 36 | } 37 | 38 | return false; 39 | } 40 | 41 | /** 42 | * Get the fully resolved path to the application's front controller. 43 | * 44 | * @param string $sitePath 45 | * @param string $siteName 46 | * @param string $uri 47 | * @return string 48 | */ 49 | public function frontControllerPath($sitePath, $siteName, $uri) 50 | { 51 | if ($_SERVER['REQUEST_METHOD'] === 'GET' && $this->isActualFile($staticPath = $this->getStaticPath($sitePath))) { 52 | return $staticPath; 53 | } 54 | 55 | if ($uri === '/installer.php') { 56 | return $sitePath.'/installer.php'; 57 | } 58 | 59 | $scriptName = '/index.php'; 60 | 61 | if ($this->isActualFile($sitePath.'/index.php')) { 62 | $indexPath = $sitePath.'/index.php'; 63 | } 64 | 65 | if ($isAboveWebroot = $this->isActualFile($sitePath.'/public/index.php')) { 66 | $indexPath = $sitePath.'/public/index.php'; 67 | } 68 | 69 | $sitePathPrefix = ($isAboveWebroot) ? $sitePath.'/public' : $sitePath; 70 | 71 | if ($locale = $this->getUriLocale($uri)) { 72 | if ($this->isActualFile($localeIndexPath = $sitePathPrefix . '/' . $locale . '/index.php')) { 73 | // Force trailing slashes on locale roots. 74 | if ($uri === '/' . $locale) { 75 | header('Location: ' . $uri . '/'); 76 | die; 77 | } 78 | 79 | $indexPath = $localeIndexPath; 80 | $scriptName = '/' . $locale . '/index.php'; 81 | } 82 | } 83 | 84 | $_SERVER['SCRIPT_NAME'] = $scriptName; 85 | $_SERVER['SCRIPT_FILENAME'] = $sitePathPrefix . $scriptName; 86 | 87 | return $indexPath; 88 | } 89 | 90 | /** 91 | * Get the locale from this URI 92 | * 93 | * @param string $uri 94 | * @return string|null 95 | */ 96 | public function getUriLocale($uri) 97 | { 98 | $parts = explode('/', $uri); 99 | $locale = $parts[1]; 100 | 101 | if (count($parts) < 2 || ! in_array($locale, $this->getLocales())) { 102 | return; 103 | } 104 | 105 | return $locale; 106 | } 107 | 108 | /** 109 | * Get the list of possible locales used in the first segment of a URI 110 | * 111 | * @return array 112 | */ 113 | public function getLocales() 114 | { 115 | return [ 116 | 'af', 'ax', 'al', 'dz', 'as', 'ad', 'ao', 'ai', 'aq', 'ag', 'ar', 'am', 'aw', 'au', 'at', 'az', 'bs', 'bh', 117 | 'bd', 'bb', 'by', 'be', 'bz', 'bj', 'bm', 'bt', 'bo', 'bq', 'ba', 'bw', 'bv', 'br', 'io', 'bn', 'bg', 'bf', 118 | 'bi', 'cv', 'kh', 'cm', 'ca', 'ky', 'cf', 'td', 'cl', 'cn', 'cx', 'cc', 'co', 'km', 'cg', 'cd', 'ck', 'cr', 119 | 'ci', 'hr', 'cu', 'cw', 'cy', 'cz', 'dk', 'dj', 'dm', 'do', 'ec', 'eg', 'sv', 'gq', 'er', 'ee', 'et', 'fk', 120 | 'fo', 'fj', 'fi', 'fr', 'gf', 'pf', 'tf', 'ga', 'gm', 'ge', 'de', 'gh', 'gi', 'gr', 'gl', 'gd', 'gp', 'gu', 121 | 'gt', 'gg', 'gn', 'gw', 'gy', 'ht', 'hm', 'va', 'hn', 'hk', 'hu', 'is', 'in', 'id', 'ir', 'iq', 'ie', 'im', 122 | 'il', 'it', 'jm', 'jp', 'je', 'jo', 'kz', 'ke', 'ki', 'kp', 'kr', 'kw', 'kg', 'la', 'lv', 'lb', 'ls', 'lr', 123 | 'ly', 'li', 'lt', 'lu', 'mo', 'mk', 'mg', 'mw', 'my', 'mv', 'ml', 'mt', 'mh', 'mq', 'mr', 'mu', 'yt', 'mx', 124 | 'fm', 'md', 'mc', 'mn', 'me', 'ms', 'ma', 'mz', 'mm', 'na', 'nr', 'np', 'nl', 'nc', 'nz', 'ni', 'ne', 'ng', 125 | 'nu', 'nf', 'mp', 'no', 'om', 'pk', 'pw', 'ps', 'pa', 'pg', 'py', 'pe', 'ph', 'pn', 'pl', 'pt', 'pr', 'qa', 126 | 're', 'ro', 'ru', 'rw', 'bl', 'sh', 'kn', 'lc', 'mf', 'pm', 'vc', 'ws', 'sm', 'st', 'sa', 'sn', 'rs', 'sc', 127 | 'sl', 'sg', 'sx', 'sk', 'si', 'sb', 'so', 'za', 'gs', 'ss', 'es', 'lk', 'sd', 'sr', 'sj', 'sz', 'se', 'ch', 128 | 'sy', 'tw', 'tj', 'tz', 'th', 'tl', 'tg', 'tk', 'to', 'tt', 'tn', 'tr', 'tm', 'tc', 'tv', 'ug', 'ua', 'ae', 129 | 'gb', 'us', 'um', 'uy', 'uz', 'vu', 've', 'vn', 'vg', 'vi', 'wf', 'eh', 'ye', 'zm', 'zw', 'en', 'zh', 130 | ]; 131 | } 132 | 133 | /** 134 | * Get the path to a statically cached page 135 | * 136 | * @param string $sitePath 137 | * @return string 138 | */ 139 | protected function getStaticPath($sitePath) 140 | { 141 | $parts = parse_url($_SERVER['REQUEST_URI']); 142 | $query = isset($parts['query']) ? $parts['query'] : ''; 143 | 144 | return $sitePath . '/static' . $parts['path'] . '_' . $query . '.html'; 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /valet/cli/drivers/CraftValetDriver.php: -------------------------------------------------------------------------------- 1 | frontControllerDirectory($sitePath); 48 | 49 | if ($this->isActualFile($staticFilePath = $sitePath.'/'.$frontControllerDirectory.$uri)) { 50 | return $staticFilePath; 51 | } 52 | 53 | return false; 54 | } 55 | 56 | /** 57 | * Get the fully resolved path to the application's front controller. 58 | * 59 | * @param string $sitePath 60 | * @param string $siteName 61 | * @param string $uri 62 | * @return string 63 | */ 64 | public function frontControllerPath($sitePath, $siteName, $uri) 65 | { 66 | $frontControllerDirectory = $this->frontControllerDirectory($sitePath); 67 | 68 | // Default index path 69 | $indexPath = $sitePath.'/'.$frontControllerDirectory.'/index.php'; 70 | $scriptName = '/index.php'; 71 | 72 | // Check if the first URL segment matches any of the defined locales 73 | $locales = [ 74 | 'ar', 75 | 'ar_sa', 76 | 'bg', 77 | 'bg_bg', 78 | 'ca_es', 79 | 'cs', 80 | 'cy_gb', 81 | 'da', 82 | 'da_dk', 83 | 'de', 84 | 'de_at', 85 | 'de_ch', 86 | 'de_de', 87 | 'el', 88 | 'el_gr', 89 | 'en', 90 | 'en_as', 91 | 'en_au', 92 | 'en_bb', 93 | 'en_be', 94 | 'en_bm', 95 | 'en_bw', 96 | 'en_bz', 97 | 'en_ca', 98 | 'en_dsrt', 99 | 'en_dsrt_us', 100 | 'en_gb', 101 | 'en_gu', 102 | 'en_gy', 103 | 'en_hk', 104 | 'en_ie', 105 | 'en_in', 106 | 'en_jm', 107 | 'en_mh', 108 | 'en_mp', 109 | 'en_mt', 110 | 'en_mu', 111 | 'en_na', 112 | 'en_nz', 113 | 'en_ph', 114 | 'en_pk', 115 | 'en_sg', 116 | 'en_shaw', 117 | 'en_tt', 118 | 'en_um', 119 | 'en_us', 120 | 'en_us_posix', 121 | 'en_vi', 122 | 'en_za', 123 | 'en_zw', 124 | 'en_zz', 125 | 'es', 126 | 'es_cl', 127 | 'es_es', 128 | 'es_mx', 129 | 'es_us', 130 | 'es_ve', 131 | 'et', 132 | 'fi', 133 | 'fi_fi', 134 | 'fil', 135 | 'fr', 136 | 'fr_be', 137 | 'fr_ca', 138 | 'fr_ch', 139 | 'fr_fr', 140 | 'fr_ma', 141 | 'he', 142 | 'hr', 143 | 'hr_hr', 144 | 'hu', 145 | 'hu_hu', 146 | 'id', 147 | 'id_id', 148 | 'it', 149 | 'it_ch', 150 | 'it_it', 151 | 'ja', 152 | 'ja_jp', 153 | 'ko', 154 | 'ko_kr', 155 | 'lt', 156 | 'lv', 157 | 'ms', 158 | 'ms_my', 159 | 'nb', 160 | 'nb_no', 161 | 'nl', 162 | 'nl_be', 163 | 'nl_nl', 164 | 'nn', 165 | 'nn_no', 166 | 'no', 167 | 'pl', 168 | 'pl_pl', 169 | 'pt', 170 | 'pt_br', 171 | 'pt_pt', 172 | 'ro', 173 | 'ro_ro', 174 | 'ru', 175 | 'ru_ru', 176 | 'sk', 177 | 'sl', 178 | 'sr', 179 | 'sv', 180 | 'sv_se', 181 | 'th', 182 | 'th_th', 183 | 'tr', 184 | 'tr_tr', 185 | 'uk', 186 | 'vi', 187 | 'zh', 188 | 'zh_cn', 189 | 'zh_tw', 190 | ]; 191 | $parts = explode('/', $uri); 192 | 193 | if (count($parts) > 1 && in_array($parts[1], $locales)) { 194 | $indexLocalizedPath = $sitePath.'/'.$frontControllerDirectory.'/'.$parts[1].'/index.php'; 195 | 196 | // Check if index.php exists in the localized folder, this is optional in Craft 3 197 | if (file_exists($indexLocalizedPath)) { 198 | $indexPath = $indexLocalizedPath; 199 | $scriptName = '/'.$parts[1].'/index.php'; 200 | } 201 | } 202 | 203 | $_SERVER['SCRIPT_FILENAME'] = $indexPath; 204 | $_SERVER['SERVER_NAME'] = $_SERVER['HTTP_HOST']; 205 | $_SERVER['SCRIPT_NAME'] = $scriptName; 206 | $_SERVER['PHP_SELF'] = $scriptName; 207 | $_SERVER['DOCUMENT_ROOT'] = $sitePath.'/'.$frontControllerDirectory; 208 | 209 | return $indexPath; 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /valet/server.php: -------------------------------------------------------------------------------- 1 | Index of $uri"; 40 | echo "
"; 41 | echo implode("
\n", array_map(function ($path) use ($uri, $is_root) { 42 | $file = basename($path); 43 | return ($is_root) ? "/$file" : "$uri/$file/"; 44 | }, $paths)); 45 | 46 | exit; 47 | } 48 | 49 | /** 50 | * You may use wildcard DNS providers xip.io or nip.io as a tool for testing your site via an IP address. 51 | * It's simple to use: First determine the IP address of your local computer (like 192.168.0.10). 52 | * Then simply use http://project.your-ip.xip.io - ie: http://laravel.192.168.0.10.xip.io 53 | */ 54 | function valet_support_wildcard_dns($domain, $config) 55 | { 56 | $services = [ 57 | '.*.*.*.*.xip.io', 58 | '.*.*.*.*.nip.io', 59 | '-*-*-*-*.nip.io', 60 | ]; 61 | 62 | if (isset($config['tunnel_services'])) { 63 | $services = array_merge($services, (array) $config['tunnel_services']); 64 | } 65 | 66 | $patterns = []; 67 | foreach ($services as $service) { 68 | $pattern = preg_quote($service, '#'); 69 | $pattern = str_replace('\*', '.*', $pattern); 70 | $patterns[] = '(.*)' . $pattern; 71 | } 72 | 73 | $pattern = implode('|', $patterns); 74 | 75 | if (preg_match('#(?:' . $pattern . ')\z#u', $domain, $matches)) { 76 | $domain = array_pop($matches); 77 | } 78 | 79 | if (strpos($domain, ':') !== false) { 80 | $domain = explode(':', $domain)[0]; 81 | } 82 | 83 | return $domain; 84 | } 85 | 86 | /** 87 | * @param array $config Valet configuration array 88 | * 89 | * @return string|null If set, default site path for uncaught urls 90 | * */ 91 | function valet_default_site_path($config) 92 | { 93 | if (isset($config['default']) && is_string($config['default']) && is_dir($config['default'])) { 94 | return $config['default']; 95 | } 96 | 97 | return null; 98 | } 99 | 100 | /** 101 | * Load the Valet configuration. 102 | */ 103 | $valetConfig = json_decode( 104 | file_get_contents(VALET_HOME_PATH . '/config.json'), true 105 | ); 106 | 107 | /** 108 | * Parse the URI and site / host for the incoming request. 109 | */ 110 | $uri = rawurldecode( 111 | explode("?", $_SERVER['REQUEST_URI'])[0] 112 | ); 113 | 114 | $siteName = basename( 115 | // Filter host to support wildcard dns feature 116 | valet_support_wildcard_dns($_SERVER['HTTP_HOST'], $valetConfig), 117 | '.' . $valetConfig['tld'] 118 | ); 119 | 120 | if (strpos($siteName, 'www.') === 0) { 121 | $siteName = substr($siteName, 4); 122 | } 123 | 124 | /** 125 | * Determine the fully qualified path to the site. 126 | * Inspects registered path directories, case-sensitive. 127 | */ 128 | $valetSitePath = null; 129 | $domain = array_slice(explode('.', $siteName), -1)[0]; 130 | 131 | foreach ($valetConfig['paths'] as $path) { 132 | if ($handle = opendir($path)) { 133 | while (false !== ($file = readdir($handle))) { 134 | if (!is_dir($path . '/' . $file)) { 135 | continue; 136 | } 137 | 138 | if (in_array($file, ['.', '..', '.DS_Store'])) { 139 | continue; 140 | } 141 | 142 | // match dir for lowercase, because Nginx only tells us lowercase names 143 | if (strtolower($file) === $siteName) { 144 | $valetSitePath = $path . '/' . $file; 145 | break; 146 | } 147 | if (strtolower($file) === $domain) { 148 | $valetSitePath = $path . '/' . $file; 149 | break; 150 | } 151 | } 152 | closedir($handle); 153 | 154 | if ($valetSitePath) { 155 | break; 156 | } 157 | } 158 | } 159 | 160 | if (is_null($valetSitePath) && is_null($valetSitePath = valet_default_site_path($valetConfig))) { 161 | show_valet_404(); 162 | } 163 | 164 | $valetSitePath = realpath($valetSitePath); 165 | 166 | /** 167 | * Find the appropriate Valet driver for the request. 168 | */ 169 | $valetDriver = null; 170 | 171 | require __DIR__ . '/cli/drivers/require.php'; 172 | 173 | $valetDriver = ValetDriver::assign($valetSitePath, $siteName, $uri); 174 | 175 | if (!$valetDriver) { 176 | show_valet_404(); 177 | } 178 | 179 | /** 180 | * ngrok uses the X-Original-Host to store the forwarded hostname. 181 | */ 182 | if (isset($_SERVER['HTTP_X_ORIGINAL_HOST']) && !isset($_SERVER['HTTP_X_FORWARDED_HOST'])) { 183 | $_SERVER['HTTP_X_FORWARDED_HOST'] = $_SERVER['HTTP_X_ORIGINAL_HOST']; 184 | } 185 | 186 | /** 187 | * Attempt to load server environment variables. 188 | */ 189 | $valetDriver->loadServerEnvironmentVariables( 190 | $valetSitePath, $siteName 191 | ); 192 | 193 | /** 194 | * Allow driver to mutate incoming URL. 195 | */ 196 | $uri = $valetDriver->mutateUri($uri); 197 | 198 | /** 199 | * Determine if the incoming request is for a static file. 200 | */ 201 | $isPhpFile = pathinfo($uri, PATHINFO_EXTENSION) === 'php'; 202 | 203 | if ($uri !== '/' && !$isPhpFile && $staticFilePath = $valetDriver->isStaticFile($valetSitePath, $siteName, $uri)) { 204 | return $valetDriver->serveStaticFile($staticFilePath, $valetSitePath, $siteName, $uri); 205 | } 206 | 207 | /** 208 | * Attempt to dispatch to a front controller. 209 | */ 210 | $frontControllerPath = $valetDriver->frontControllerPath( 211 | $valetSitePath, $siteName, $uri 212 | ); 213 | 214 | if (!$frontControllerPath) { 215 | if (isset($valetConfig['directory-listing']) && $valetConfig['directory-listing'] == 'on') { 216 | show_directory_listing($valetSitePath, $uri); 217 | } 218 | 219 | show_valet_404(); 220 | } 221 | 222 | chdir(dirname($frontControllerPath)); 223 | 224 | require $frontControllerPath; 225 | -------------------------------------------------------------------------------- /valet/cli/drivers/ValetDriver.php: -------------------------------------------------------------------------------- 1 | serves($sitePath, $siteName, $driver->mutateUri($uri))) { 80 | return $driver; 81 | } 82 | } 83 | } 84 | 85 | /** 86 | * Get the custom driver class from the site path, if one exists. 87 | * 88 | * @param string $sitePath 89 | * @return string 90 | */ 91 | public static function customSiteDriver($sitePath) 92 | { 93 | if (!file_exists($sitePath . '/LocalValetDriver.php')) { 94 | return; 95 | } 96 | 97 | require_once $sitePath . '/LocalValetDriver.php'; 98 | 99 | return 'LocalValetDriver'; 100 | } 101 | 102 | /** 103 | * Get all of the driver classes in a given path. 104 | * 105 | * @param string $path 106 | * @return array 107 | */ 108 | public static function driversIn($path) 109 | { 110 | if (!is_dir($path)) { 111 | return []; 112 | } 113 | 114 | $drivers = []; 115 | 116 | $dir = new RecursiveDirectoryIterator($path); 117 | $iterator = new RecursiveIteratorIterator($dir); 118 | $regex = new RegexIterator($iterator, '/^.+ValetDriver\.php$/i', RecursiveRegexIterator::GET_MATCH); 119 | 120 | foreach ($regex as $file) { 121 | require_once $file[0]; 122 | 123 | $drivers[] = basename($file[0], '.php'); 124 | } 125 | 126 | return $drivers; 127 | } 128 | 129 | /** 130 | * Mutate the incoming URI. 131 | * 132 | * @param string $uri 133 | * @return string 134 | */ 135 | public function mutateUri($uri) 136 | { 137 | return $uri; 138 | } 139 | 140 | /** 141 | * Serve the static file at the given path. 142 | * 143 | * @param string $staticFilePath 144 | * @param string $sitePath 145 | * @param string $siteName 146 | * @param string $uri 147 | * @return void 148 | */ 149 | public function serveStaticFile($staticFilePath, $sitePath, $siteName, $uri) 150 | { 151 | /** 152 | * Back story... 153 | * 154 | * PHP docs *claim* you can set default_mimetype = "" to disable the default 155 | * Content-Type header. This works in PHP 7+, but in PHP 5.* it sends an 156 | * *empty* Content-Type header, which is significantly different than 157 | * sending *no* Content-Type header. 158 | * 159 | * However, if you explicitly set a Content-Type header, then explicitly 160 | * remove that Content-Type header, PHP seems to not re-add the default. 161 | * 162 | * I have a hard time believing this is by design and not coincidence. 163 | * 164 | * Burn. it. all. 165 | */ 166 | header('Content-Type: text/html'); 167 | header_remove('Content-Type'); 168 | 169 | $staticFilePath = preg_replace('#/+#', '/', VALET_STATIC_PREFIX . '/' . $staticFilePath); 170 | 171 | header('X-Accel-Redirect: /' . $staticFilePath); 172 | } 173 | 174 | /** 175 | * Determine if the path is a file and not a directory. 176 | * 177 | * @param string $path 178 | * @return bool 179 | */ 180 | protected function isActualFile($path) 181 | { 182 | return !is_dir($path) && file_exists($path); 183 | } 184 | 185 | /** 186 | * Load server environment variables if available. 187 | * Processes any '*' entries first, and then adds site-specific entries 188 | * 189 | * @param string $sitePath 190 | * @param string $siteName 191 | * @return void 192 | */ 193 | public function loadServerEnvironmentVariables($sitePath, $siteName) 194 | { 195 | $varFilePath = $sitePath . '/.valet-env.php'; 196 | if (!file_exists($varFilePath)) { 197 | $varFilePath = VALET_HOME_PATH . '/.valet-env.php'; 198 | } 199 | if (!file_exists($varFilePath)) { 200 | return; 201 | } 202 | 203 | $variables = include $varFilePath; 204 | 205 | $variablesToSet = isset($variables['*']) ? $variables['*'] : []; 206 | 207 | if (isset($variables[$siteName])) { 208 | $variablesToSet = array_merge($variablesToSet, $variables[$siteName]); 209 | } 210 | 211 | foreach ($variablesToSet as $key => $value) { 212 | if (!is_string($key)) { 213 | continue; 214 | } 215 | 216 | $_SERVER[$key] = $value; 217 | $_ENV[$key] = $value; 218 | putenv($key . '=' . $value); 219 | } 220 | } 221 | 222 | } 223 | -------------------------------------------------------------------------------- /valet/cli/Valet/Configuration.php: -------------------------------------------------------------------------------- 1 | files = $files; 17 | } 18 | 19 | /** 20 | * Install the Valet configuration file. 21 | * 22 | * @return void 23 | */ 24 | function install() 25 | { 26 | $this->createConfigurationDirectory(); 27 | $this->createDriversDirectory(); 28 | $this->createSitesDirectory(); 29 | $this->createExtensionsDirectory(); 30 | $this->createLogDirectory(); 31 | $this->createCertificatesDirectory(); 32 | $this->writeBaseConfiguration(); 33 | 34 | $this->files->chown($this->path(), user()); 35 | } 36 | 37 | /** 38 | * Forcefully delete the Valet home configuration directory and contents. 39 | * 40 | * @return void 41 | */ 42 | function uninstall() 43 | { 44 | $this->files->unlink(VALET_HOME_PATH); 45 | } 46 | 47 | /** 48 | * Create the Valet configuration directory. 49 | * 50 | * @return void 51 | */ 52 | function createConfigurationDirectory() 53 | { 54 | $this->files->ensureDirExists(preg_replace('~/valet$~', '', VALET_HOME_PATH), user()); 55 | 56 | $oldPath = posix_getpwuid(fileowner(__FILE__))['dir'].'/.valet'; 57 | 58 | if ($this->files->isDir($oldPath)) { 59 | rename($oldPath, VALET_HOME_PATH); 60 | $this->prependPath(VALET_HOME_PATH.'/Sites'); 61 | } 62 | 63 | $this->files->ensureDirExists(VALET_HOME_PATH, user()); 64 | } 65 | 66 | /** 67 | * Create the Valet drivers directory. 68 | * 69 | * @return void 70 | */ 71 | function createDriversDirectory() 72 | { 73 | if ($this->files->isDir($driversDirectory = VALET_HOME_PATH.'/Drivers')) { 74 | return; 75 | } 76 | 77 | $this->files->mkdirAsUser($driversDirectory); 78 | 79 | $this->files->putAsUser( 80 | $driversDirectory.'/SampleValetDriver.php', 81 | $this->files->get(__DIR__.'/../stubs/SampleValetDriver.php') 82 | ); 83 | } 84 | 85 | /** 86 | * Create the Valet sites directory. 87 | * 88 | * @return void 89 | */ 90 | function createSitesDirectory() 91 | { 92 | $this->files->ensureDirExists(VALET_HOME_PATH.'/Sites', user()); 93 | } 94 | 95 | /** 96 | * Create the directory for the Valet extensions. 97 | * 98 | * @return void 99 | */ 100 | function createExtensionsDirectory() 101 | { 102 | $this->files->ensureDirExists(VALET_HOME_PATH.'/Extensions', user()); 103 | } 104 | 105 | /** 106 | * Create the directory for Nginx logs. 107 | * 108 | * @return void 109 | */ 110 | function createLogDirectory() 111 | { 112 | $this->files->ensureDirExists(VALET_HOME_PATH.'/Log', user()); 113 | 114 | $this->files->touch(VALET_HOME_PATH.'/Log/nginx-error.log'); 115 | } 116 | 117 | /** 118 | * Create the directory for SSL certificates. 119 | * 120 | * @return void 121 | */ 122 | function createCertificatesDirectory() 123 | { 124 | $this->files->ensureDirExists(VALET_HOME_PATH.'/Certificates', user()); 125 | } 126 | 127 | /** 128 | * Write the base, initial configuration for Valet. 129 | */ 130 | function writeBaseConfiguration() 131 | { 132 | if (! $this->files->exists($this->path())) { 133 | $this->write(['tld' => 'test', 'paths' => []]); 134 | } 135 | 136 | /** 137 | * Migrate old configurations from 'domain' to 'tld' 138 | */ 139 | $config = $this->read(); 140 | 141 | if (isset($config['tld'])) { 142 | return; 143 | } 144 | 145 | $this->updateKey('tld', !empty($config['domain']) ? $config['domain'] : 'test'); 146 | } 147 | 148 | /** 149 | * Add the given path to the configuration. 150 | * 151 | * @param string $path 152 | * @param bool $prepend 153 | * @return void 154 | */ 155 | function addPath($path, $prepend = false) 156 | { 157 | $this->write(tap($this->read(), function (&$config) use ($path, $prepend) { 158 | $method = $prepend ? 'prepend' : 'push'; 159 | 160 | $config['paths'] = collect($config['paths'])->{$method}($path)->unique()->all(); 161 | })); 162 | } 163 | 164 | /** 165 | * Prepend the given path to the configuration. 166 | * 167 | * @param string $path 168 | * @return void 169 | */ 170 | function prependPath($path) 171 | { 172 | $this->addPath($path, true); 173 | } 174 | 175 | /** 176 | * Remove the given path from the configuration. 177 | * 178 | * @param string $path 179 | * @return void 180 | */ 181 | function removePath($path) 182 | { 183 | if ($path == VALET_HOME_PATH.'/Sites') { 184 | info("Cannot remove this directory because this is where Valet stores its site definitions.\nRun [valet paths] for a list of parked paths."); 185 | die(); 186 | } 187 | 188 | $this->write(tap($this->read(), function (&$config) use ($path) { 189 | $config['paths'] = collect($config['paths'])->reject(function ($value) use ($path) { 190 | return $value === $path; 191 | })->values()->all(); 192 | })); 193 | } 194 | 195 | /** 196 | * Prune all non-existent paths from the configuration. 197 | * 198 | * @return void 199 | */ 200 | function prune() 201 | { 202 | if (! $this->files->exists($this->path())) { 203 | return; 204 | } 205 | 206 | $this->write(tap($this->read(), function (&$config) { 207 | $config['paths'] = collect($config['paths'])->filter(function ($path) { 208 | return $this->files->isDir($path); 209 | })->values()->all(); 210 | })); 211 | } 212 | 213 | /** 214 | * Read the configuration file as JSON. 215 | * 216 | * @return array 217 | */ 218 | function read() 219 | { 220 | return json_decode($this->files->get($this->path()), true); 221 | } 222 | 223 | /** 224 | * Update a specific key in the configuration file. 225 | * 226 | * @param string $key 227 | * @param mixed $value 228 | * @return array 229 | */ 230 | function updateKey($key, $value) 231 | { 232 | return tap($this->read(), function (&$config) use ($key, $value) { 233 | $config[$key] = $value; 234 | 235 | $this->write($config); 236 | }); 237 | } 238 | 239 | /** 240 | * Write the given configuration to disk. 241 | * 242 | * @param array $config 243 | * @return void 244 | */ 245 | function write($config) 246 | { 247 | $this->files->putAsUser($this->path(), json_encode( 248 | $config, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES 249 | ).PHP_EOL); 250 | } 251 | 252 | /** 253 | * Get the configuration file path. 254 | * 255 | * @return string 256 | */ 257 | function path() 258 | { 259 | return VALET_HOME_PATH.'/config.json'; 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /valet/cli/drivers/Typo3ValetDriver.php: -------------------------------------------------------------------------------- 1 | documentRoot . '/typo3'; 52 | return file_exists($typo3Dir) && is_dir($typo3Dir); 53 | } 54 | 55 | /** 56 | * Determine if the incoming request is for a static file. That is, it is 57 | * no PHP script file and the URI points to a valid file (no folder) on 58 | * the disk. Access to those static files will be authorized. 59 | * 60 | * @param string $sitePath 61 | * @param string $siteName 62 | * @param string $uri 63 | * @return string|false 64 | */ 65 | public function isStaticFile($sitePath, $siteName, $uri) 66 | { 67 | // May the file contains a cache busting version string like filename.12345678.css 68 | // If that is the case, the file cannot be found on disk, so remove the version 69 | // identifier before retrying below. 70 | if (!$this->isActualFile($filePath = $sitePath . $this->documentRoot . $uri)) 71 | { 72 | $uri = preg_replace("@^(.+)\.(\d+)\.(js|css|png|jpg|gif|gzip)$@", "$1.$3", $uri); 73 | } 74 | 75 | // Now that any possible version string is cleared from the filename, the resulting 76 | // URI should be a valid file on disc. So assemble the absolut file name with the 77 | // same schema as above and if it exists, authorize access and return its path. 78 | if ($this->isActualFile($filePath = $sitePath . $this->documentRoot . $uri)) 79 | { 80 | return $this->isAccessAuthorized($uri) ? $filePath : false; 81 | } 82 | 83 | // This file cannot be found in the current project and thus cannot be served. 84 | return false; 85 | } 86 | 87 | /** 88 | * Determines if the given URI is blacklisted so that access is prevented. 89 | * 90 | * @param string $uri 91 | * @return boolean 92 | */ 93 | private function isAccessAuthorized($uri) 94 | { 95 | foreach ($this->forbiddenUriPatterns as $forbiddenUriPattern) 96 | { 97 | if (preg_match("@$forbiddenUriPattern@", $uri)) 98 | { 99 | return false; 100 | } 101 | } 102 | return true; 103 | } 104 | 105 | /** 106 | * Get the fully resolved path to the application's front controller. 107 | * This can be the currently requested PHP script, a folder that 108 | * contains an index.php or the global index.php otherwise. 109 | * 110 | * @param string $sitePath 111 | * @param string $siteName 112 | * @param string $uri 113 | * @return string 114 | */ 115 | public function frontControllerPath($sitePath, $siteName, $uri) 116 | { 117 | // without modifying the URI, redirect if necessary 118 | $this->handleRedirectBackendShorthandUris($uri); 119 | 120 | // from now on, remove trailing / for convenience for all the following join operations 121 | $uri = rtrim($uri, '/'); 122 | 123 | // try to find the responsible script file for the requested folder / script URI 124 | if (file_exists($absoluteFilePath = $sitePath . $this->documentRoot . $uri)) 125 | { 126 | if (is_dir($absoluteFilePath)) 127 | { 128 | if (file_exists($absoluteFilePath . '/index.php')) 129 | { 130 | // this folder can be served by index.php 131 | return $this->serveScript($sitePath, $siteName, $uri . '/index.php'); 132 | } 133 | 134 | if (file_exists($absoluteFilePath . '/index.html')) 135 | { 136 | // this folder can be served by index.html 137 | return $absoluteFilePath . '/index.html'; 138 | } 139 | } 140 | else if (pathinfo($absoluteFilePath, PATHINFO_EXTENSION) === 'php') 141 | { 142 | // this file can be served directly 143 | return $this->serveScript($sitePath, $siteName, $uri); 144 | } 145 | } 146 | 147 | // the global index.php will handle all other cases 148 | return $this->serveScript($sitePath, $siteName, '/index.php'); 149 | } 150 | 151 | /** 152 | * Direct access to installtool via domain.dev/typo3/install/ will be redirected to 153 | * sysext install script. domain.dev/typo3 will be redirected to /typo3/, because 154 | * the generated JavaScript URIs on the login screen would be broken on /typo3. 155 | * 156 | * @param string $uri 157 | */ 158 | private function handleRedirectBackendShorthandUris($uri) 159 | { 160 | if (rtrim($uri, '/') === '/typo3/install') 161 | { 162 | header('Location: /typo3/sysext/install/Start/Install.php'); 163 | die(); 164 | } 165 | 166 | if ($uri === '/typo3') 167 | { 168 | header('Location: /typo3/'); 169 | die(); 170 | } 171 | } 172 | 173 | /** 174 | * Configures the $_SERVER globals for serving the script at 175 | * the specified URI and returns it absolute file path. 176 | * 177 | * @param string $sitePath 178 | * @param string $siteName 179 | * @param string $uri 180 | * @param string $script 181 | * @return string 182 | */ 183 | private function serveScript($sitePath, $siteName, $uri) 184 | { 185 | $docroot = $sitePath . $this->documentRoot; 186 | $abspath = $docroot . $uri; 187 | 188 | $_SERVER['SERVER_NAME'] = $siteName . '.dev'; 189 | $_SERVER['DOCUMENT_ROOT'] = $docroot; 190 | $_SERVER['DOCUMENT_URI'] = $uri; 191 | $_SERVER['SCRIPT_FILENAME'] = $abspath; 192 | $_SERVER['SCRIPT_NAME'] = $uri; 193 | $_SERVER['PHP_SELF'] = $uri; 194 | 195 | return $abspath; 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /valet/cli/Valet/Filesystem.php: -------------------------------------------------------------------------------- 1 | chown($path, $owner); 34 | } 35 | } 36 | 37 | /** 38 | * Ensure that the given directory exists. 39 | * 40 | * @param string $path 41 | * @param string|null $owner 42 | * @param int $mode 43 | * @return void 44 | */ 45 | function ensureDirExists($path, $owner = null, $mode = 0755) 46 | { 47 | if (! $this->isDir($path)) { 48 | $this->mkdir($path, $owner, $mode); 49 | } 50 | } 51 | 52 | /** 53 | * Create a directory as the non-root user. 54 | * 55 | * @param string $path 56 | * @param int $mode 57 | * @return void 58 | */ 59 | function mkdirAsUser($path, $mode = 0755) 60 | { 61 | $this->mkdir($path, user(), $mode); 62 | } 63 | 64 | /** 65 | * Touch the given path. 66 | * 67 | * @param string $path 68 | * @param string|null $owner 69 | * @return string 70 | */ 71 | function touch($path, $owner = null) 72 | { 73 | touch($path); 74 | 75 | if ($owner) { 76 | $this->chown($path, $owner); 77 | } 78 | 79 | return $path; 80 | } 81 | 82 | /** 83 | * Touch the given path as the non-root user. 84 | * 85 | * @param string $path 86 | * @return void 87 | */ 88 | function touchAsUser($path) 89 | { 90 | return $this->touch($path, user()); 91 | } 92 | 93 | /** 94 | * Determine if the given file exists. 95 | * 96 | * @param string $path 97 | * @return bool 98 | */ 99 | function exists($path) 100 | { 101 | return file_exists($path); 102 | } 103 | 104 | /** 105 | * Read the contents of the given file. 106 | * 107 | * @param string $path 108 | * @return string 109 | */ 110 | function get($path) 111 | { 112 | return file_get_contents($path); 113 | } 114 | 115 | /** 116 | * Write to the given file. 117 | * 118 | * @param string $path 119 | * @param string $contents 120 | * @param string|null $owner 121 | * @return void 122 | */ 123 | function put($path, $contents, $owner = null) 124 | { 125 | file_put_contents($path, $contents); 126 | 127 | if ($owner) { 128 | $this->chown($path, $owner); 129 | } 130 | } 131 | 132 | /** 133 | * Write to the given file as the non-root user. 134 | * 135 | * @param string $path 136 | * @param string $contents 137 | * @return void 138 | */ 139 | function putAsUser($path, $contents) 140 | { 141 | $this->put($path, $contents, user()); 142 | } 143 | 144 | /** 145 | * Append the contents to the given file. 146 | * 147 | * @param string $path 148 | * @param string $contents 149 | * @param string|null $owner 150 | * @return void 151 | */ 152 | function append($path, $contents, $owner = null) 153 | { 154 | file_put_contents($path, $contents, FILE_APPEND); 155 | 156 | if ($owner) { 157 | $this->chown($path, $owner); 158 | } 159 | } 160 | 161 | /** 162 | * Append the contents to the given file as the non-root user. 163 | * 164 | * @param string $path 165 | * @param string $contents 166 | * @return void 167 | */ 168 | function appendAsUser($path, $contents) 169 | { 170 | $this->append($path, $contents, user()); 171 | } 172 | 173 | /** 174 | * Copy the given file to a new location. 175 | * 176 | * @param string $from 177 | * @param string $to 178 | * @return void 179 | */ 180 | function copy($from, $to) 181 | { 182 | copy($from, $to); 183 | } 184 | 185 | /** 186 | * Copy the given file to a new location for the non-root user. 187 | * 188 | * @param string $from 189 | * @param string $to 190 | * @return void 191 | */ 192 | function copyAsUser($from, $to) 193 | { 194 | copy($from, $to); 195 | 196 | $this->chown($to, user()); 197 | } 198 | 199 | /** 200 | * Create a symlink to the given target. 201 | * 202 | * @param string $target 203 | * @param string $link 204 | * @return void 205 | */ 206 | function symlink($target, $link) 207 | { 208 | if ($this->exists($link)) { 209 | $this->unlink($link); 210 | } 211 | 212 | symlink($target, $link); 213 | } 214 | 215 | /** 216 | * Create a symlink to the given target for the non-root user. 217 | * 218 | * This uses the command line as PHP can't change symlink permissions. 219 | * 220 | * @param string $target 221 | * @param string $link 222 | * @return void 223 | */ 224 | function symlinkAsUser($target, $link) 225 | { 226 | if ($this->exists($link)) { 227 | $this->unlink($link); 228 | } 229 | 230 | CommandLineFacade::runAsUser('ln -s '.escapeshellarg($target).' '.escapeshellarg($link)); 231 | } 232 | 233 | /** 234 | * Delete the file at the given path. 235 | * 236 | * @param string $path 237 | * @return void 238 | */ 239 | function unlink($path) 240 | { 241 | if (file_exists($path) || is_link($path)) { 242 | @unlink($path); 243 | } 244 | } 245 | 246 | /** 247 | * Change the owner of the given path. 248 | * 249 | * @param string $path 250 | * @param string $user 251 | */ 252 | function chown($path, $user) 253 | { 254 | chown($path, $user); 255 | } 256 | 257 | /** 258 | * Change the group of the given path. 259 | * 260 | * @param string $path 261 | * @param string $group 262 | */ 263 | function chgrp($path, $group) 264 | { 265 | chgrp($path, $group); 266 | } 267 | 268 | /** 269 | * Resolve the given path. 270 | * 271 | * @param string $path 272 | * @return string 273 | */ 274 | function realpath($path) 275 | { 276 | return realpath($path); 277 | } 278 | 279 | /** 280 | * Determine if the given path is a symbolic link. 281 | * 282 | * @param string $path 283 | * @return bool 284 | */ 285 | function isLink($path) 286 | { 287 | return is_link($path); 288 | } 289 | 290 | /** 291 | * Resolve the given symbolic link. 292 | * 293 | * @param string $path 294 | * @return string 295 | */ 296 | function readLink($path) 297 | { 298 | return readlink($path); 299 | } 300 | 301 | /** 302 | * Remove all of the broken symbolic links at the given path. 303 | * 304 | * @param string $path 305 | * @return void 306 | */ 307 | function removeBrokenLinksAt($path) 308 | { 309 | collect($this->scandir($path)) 310 | ->filter(function ($file) use ($path) { 311 | return $this->isBrokenLink($path.'/'.$file); 312 | }) 313 | ->each(function ($file) use ($path) { 314 | $this->unlink($path.'/'.$file); 315 | }); 316 | } 317 | 318 | /** 319 | * Determine if the given path is a broken symbolic link. 320 | * 321 | * @param string $path 322 | * @return bool 323 | */ 324 | function isBrokenLink($path) 325 | { 326 | return is_link($path) && ! file_exists($path); 327 | } 328 | 329 | /** 330 | * Scan the given directory path. 331 | * 332 | * @param string $path 333 | * @return array 334 | */ 335 | function scandir($path) 336 | { 337 | return collect(scandir($path)) 338 | ->reject(function ($file) { 339 | return in_array($file, ['.', '..']); 340 | })->values()->all(); 341 | } 342 | } 343 | -------------------------------------------------------------------------------- /valet/cli/Valet/PhpFpm.php: -------------------------------------------------------------------------------- 1 | cli = $cli; 27 | $this->brew = $brew; 28 | $this->files = $files; 29 | } 30 | 31 | /** 32 | * Install and configure PhpFpm. 33 | * 34 | * @return void 35 | */ 36 | function install() 37 | { 38 | if (! $this->brew->hasInstalledPhp()) { 39 | $this->brew->ensureInstalled('php', [], $this->taps); 40 | } 41 | 42 | $this->files->ensureDirExists(VALET_HOME_PATH . '/Log', user()); 43 | 44 | $this->updateConfiguration(); 45 | 46 | $this->restart(); 47 | } 48 | 49 | /** 50 | * Forcefully uninstall all of Valet's supported PHP versions and configurations 51 | * 52 | * @return void 53 | */ 54 | function uninstall() 55 | { 56 | $this->brew->uninstallAllPhpVersions(); 57 | rename(BREW_PREFIX.'/etc/php', BREW_PREFIX.'/etc/php-valet-bak'.time()); 58 | $this->cli->run('rm -rf '.BREW_PREFIX.'/var/log/php-fpm.log'); 59 | } 60 | 61 | /** 62 | * Update the PHP FPM configuration. 63 | * 64 | * @return void 65 | */ 66 | function updateConfiguration() 67 | { 68 | info('Updating PHP configuration...'); 69 | 70 | $fpmConfigFile = $this->fpmConfigPath(); 71 | 72 | $this->files->ensureDirExists(dirname($fpmConfigFile), user()); 73 | 74 | // rename (to disable) old FPM Pool configuration, regardless of whether it's a default config or one customized by an older Valet version 75 | $oldFile = dirname($fpmConfigFile) . '/www.conf'; 76 | if (file_exists($oldFile)) { 77 | rename($oldFile, $oldFile . '-backup'); 78 | } 79 | 80 | if (false === strpos($fpmConfigFile, '5.6')) { 81 | // since PHP 7 we can simply drop in a valet-specific fpm pool config, and not touch the default config 82 | $contents = $this->files->get(__DIR__.'/../stubs/etc-phpfpm-valet.conf'); 83 | $contents = str_replace(['VALET_USER', 'VALET_HOME_PATH'], [user(), VALET_HOME_PATH], $contents); 84 | } else { 85 | // for PHP 5 we must do a direct edit of the fpm pool config to switch it to Valet's needs 86 | $contents = $this->files->get($fpmConfigFile); 87 | $contents = preg_replace('/^user = .+$/m', 'user = '.user(), $contents); 88 | $contents = preg_replace('/^group = .+$/m', 'group = staff', $contents); 89 | $contents = preg_replace('/^listen = .+$/m', 'listen = '.VALET_HOME_PATH.'/valet.sock', $contents); 90 | $contents = preg_replace('/^;?listen\.owner = .+$/m', 'listen.owner = '.user(), $contents); 91 | $contents = preg_replace('/^;?listen\.group = .+$/m', 'listen.group = staff', $contents); 92 | $contents = preg_replace('/^;?listen\.mode = .+$/m', 'listen.mode = 0777', $contents); 93 | } 94 | $this->files->put($fpmConfigFile, $contents); 95 | 96 | $contents = $this->files->get(__DIR__.'/../stubs/php-memory-limits.ini'); 97 | $destFile = dirname($fpmConfigFile); 98 | $destFile = str_replace('/php-fpm.d', '', $destFile); 99 | $destFile .= '/conf.d/php-memory-limits.ini'; 100 | $this->files->ensureDirExists(dirname($destFile), user()); 101 | $this->files->putAsUser($destFile, $contents); 102 | 103 | $contents = $this->files->get(__DIR__.'/../stubs/etc-phpfpm-error_log.ini'); 104 | $contents = str_replace(['VALET_USER', 'VALET_HOME_PATH'], [user(), VALET_HOME_PATH], $contents); 105 | $destFile = dirname($fpmConfigFile); 106 | $destFile = str_replace('/php-fpm.d', '', $destFile); 107 | $destFile .= '/conf.d/error_log.ini'; 108 | $this->files->ensureDirExists(dirname($destFile), user()); 109 | $this->files->putAsUser($destFile, $contents); 110 | $this->files->ensureDirExists(VALET_HOME_PATH . '/Log', user()); 111 | $this->files->touch(VALET_HOME_PATH . '/Log/php-fpm.log', user()); 112 | } 113 | 114 | /** 115 | * Restart the PHP FPM process. 116 | * 117 | * @return void 118 | */ 119 | function restart() 120 | { 121 | $this->brew->restartLinkedPhp(); 122 | } 123 | 124 | /** 125 | * Stop the PHP FPM process. 126 | * 127 | * @return void 128 | */ 129 | function stop() 130 | { 131 | call_user_func_array( 132 | [$this->brew, 'stopService'], 133 | Brew::SUPPORTED_PHP_VERSIONS 134 | ); 135 | } 136 | 137 | /** 138 | * Get the path to the FPM configuration file for the current PHP version. 139 | * 140 | * @return string 141 | */ 142 | function fpmConfigPath() 143 | { 144 | $version = $this->brew->linkedPhp(); 145 | 146 | $versionNormalized = $this->normalizePhpVersion($version === 'php' ? Brew::LATEST_PHP_VERSION : $version); 147 | $versionNormalized = preg_replace('~[^\d\.]~', '', $versionNormalized); 148 | 149 | return $versionNormalized === '5.6' 150 | ? BREW_PREFIX.'/etc/php/5.6/php-fpm.conf' 151 | : BREW_PREFIX."/etc/php/${versionNormalized}/php-fpm.d/valet-fpm.conf"; 152 | } 153 | 154 | /** 155 | * Only stop running php services 156 | */ 157 | function stopRunning() 158 | { 159 | $this->brew->stopService( 160 | $this->brew->getRunningServices() 161 | ->filter(function ($service) { 162 | return substr($service, 0, 3) === 'php'; 163 | }) 164 | ->all() 165 | ); 166 | } 167 | 168 | /** 169 | * Use a specific version of php 170 | * 171 | * @param $version 172 | * @param $force 173 | * @return string 174 | */ 175 | function useVersion($version, $force = false) 176 | { 177 | $version = $this->validateRequestedVersion($version); 178 | 179 | try { 180 | if ($this->brew->linkedPhp() === $version && !$force) { 181 | output(sprintf('Valet is already using version: %s. To re-link and re-configure use the --force parameter.' . PHP_EOL, 182 | $version)); 183 | exit(); 184 | } 185 | } catch (DomainException $e) 186 | { /* ignore thrown exception when no linked php is found */ } 187 | 188 | if (!$this->brew->installed($version)) { 189 | // Install the relevant formula if not already installed 190 | $this->brew->ensureInstalled($version, [], $this->taps); 191 | } 192 | 193 | // Unlink the current php if there is one 194 | if ($this->brew->hasLinkedPhp()) { 195 | $currentVersion = $this->brew->getLinkedPhpFormula(); 196 | info(sprintf('Unlinking current version: %s', $currentVersion)); 197 | $this->brew->unlink($currentVersion); 198 | } 199 | 200 | info(sprintf('Linking new version: %s', $version)); 201 | $this->brew->link($version, true); 202 | 203 | $this->stopRunning(); 204 | 205 | // remove any orphaned valet.sock files that PHP didn't clean up due to version conflicts 206 | $this->files->unlink(VALET_HOME_PATH.'/valet.sock'); 207 | $this->cli->quietly('sudo rm ' . VALET_HOME_PATH.'/valet.sock'); 208 | 209 | // ensure configuration is correct and start the linked version 210 | $this->install(); 211 | 212 | return $version === 'php' ? $this->brew->determineAliasedVersion($version) : $version; 213 | } 214 | 215 | 216 | /** 217 | * If passed php7.4 or php74 formats, normalize to php@7.4 format. 218 | */ 219 | function normalizePhpVersion($version) 220 | { 221 | return preg_replace('/(php)([0-9+])(?:.)?([0-9+])/i', '$1@$2.$3', $version); 222 | } 223 | 224 | /** 225 | * Validate the requested version to be sure we can support it. 226 | * 227 | * @param $version 228 | * @return string 229 | */ 230 | function validateRequestedVersion($version) 231 | { 232 | $version = $this->normalizePhpVersion($version); 233 | 234 | if (!$this->brew->supportedPhpVersions()->contains($version)) { 235 | throw new DomainException( 236 | sprintf( 237 | 'Valet doesn\'t support PHP version: %s (try something like \'php@7.3\' instead)', 238 | $version 239 | ) 240 | ); 241 | } 242 | 243 | if (strpos($aliasedVersion = $this->brew->determineAliasedVersion($version), '@')) { 244 | return $aliasedVersion; 245 | } 246 | 247 | if ($version === 'php') { 248 | if (strpos($aliasedVersion = $this->brew->determineAliasedVersion($version), '@')) { 249 | return $aliasedVersion; 250 | } 251 | 252 | if ($this->brew->hasInstalledPhp()) { 253 | throw new DomainException('Brew is already using PHP '.PHP_VERSION.' as \'php\' in Homebrew. To use another version, please specify. eg: php@7.3'); 254 | } 255 | } 256 | 257 | return $version; 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /bin/butler: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | BUTLERRUNPATH="REPLACEME" 4 | VALET_HOME=".valet-home" 5 | CURRENTDIR="$(pwd)" 6 | UNAMEOUT="$(uname -s)" 7 | 8 | WHITE='\033[1;37m' 9 | NC='\033[0m' 10 | 11 | if [[ $NOTTY == "true" ]];then 12 | TTY="" 13 | else 14 | TTY="t" 15 | fi 16 | 17 | # Function that outputs Butler is not running... 18 | function butler_is_not_running { 19 | echo -e "${WHITE}Butler is not running.${NC}" >&2 20 | echo "" >&2 21 | echo -e "${WHITE}You may Butler using the following command:${NC} 'butler up'" >&2 22 | 23 | exit 1 24 | } 25 | 26 | function is_file_exist { 27 | if test -f "$1"; then 28 | return 0 29 | else 30 | return 1 31 | fi 32 | } 33 | 34 | function generate_ca { 35 | CA_PATH="$BUTLERRUNPATH/$VALET_HOME/CA/LaravelValetCASelfSigned.pem" 36 | CA_KEY_PATH="$BUTLERRUNPATH/$VALET_HOME/CA/LaravelValetCASelfSigned.key" 37 | 38 | if is_file_exist $CA_PATH && is_file_exist $CA_KEY_PATH ; then 39 | return 40 | else 41 | # Need to change name to distinguish between real Valet certificate 42 | oName="Laravel Valet (Butler) CA Self Signed Organization"; 43 | cName="Laravel Valet (Butler) CA Self Signed CN"; 44 | 45 | # Remove existing CA and CA Key 46 | rm $CA_PATH $CA_KEY_PATH 2> /dev/null 47 | 48 | echo -e "${WHITE}Installing CA Cert. Enter password to install${NC}" >&2 49 | # remove the old cert 50 | sudo security delete-certificate -c $cName /Library/Keychains/System.keychain 51 | openssl req -new -newkey rsa:2048 -days 10000 -nodes -x509 -subj "/C=MY/ST=Negeri Sembilan/O=$oName/localityName=Mantin/commonName=$cName/organizationalUnitName=Engineering/emailAddress=rootcertificate@runcloud.io/" -keyout $CA_KEY_PATH -out $CA_PATH 52 | sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain $CA_PATH 53 | fi 54 | } 55 | 56 | function get_workdir { 57 | www_path=$(get_env_value DEFAULT_WWW_PATH) 58 | www_path=${www_path%/} 59 | 60 | if test "${CURRENTDIR##$www_path}" != "$CURRENTDIR"; then 61 | workdir=${CURRENTDIR//$www_path/} 62 | 63 | if [ "$workdir" == "" ]; then 64 | workdir="/" 65 | fi 66 | 67 | echo "/var/www$workdir" 68 | else 69 | echo "/var/www" 70 | fi 71 | } 72 | 73 | function get_tld { 74 | # We dont want to use jq. so... KISS 75 | cat $BUTLERRUNPATH/$VALET_HOME/config.json | tr { '\n' | tr , '\n' | tr } '\n' | grep "tld" | awk -F'"' '{print $4}' 76 | } 77 | 78 | function trust_certificate { 79 | tld=$(get_tld) 80 | 81 | CERT_PATH="$BUTLERRUNPATH/$VALET_HOME/Certificates/$1.$tld.crt" 82 | 83 | if is_file_exist $CERT_PATH; then 84 | untrust_certificate $1 85 | sudo security add-trusted-cert -d -r trustAsRoot -k /Library/Keychains/System.keychain $CERT_PATH 86 | else 87 | echo -e "${WHITE}Error:${NC} $CERT_PATH does not exists!" >&2 88 | fi 89 | } 90 | 91 | function untrust_certificate { 92 | tld=$(get_tld) 93 | 94 | sudo security delete-certificate -c "$1.$tld" /Library/Keychains/System.keychain 2> /dev/null 95 | sudo security delete-certificate -c "*.$1.$tld" /Library/Keychains/System.keychain 2> /dev/null 96 | } 97 | 98 | function untrust_old_certificates { 99 | if [ -d $BUTLERRUNPATH/$VALET_HOME/Certificates-old ]; then 100 | for filePath in $BUTLERRUNPATH/$VALET_HOME/Certificates-old/*.crt; do 101 | fileName=$(basename $filePath) 102 | domain="${fileName%.*}" 103 | domainWithoutTLD="${domain%.*}" 104 | untrust_certificate $domainWithoutTLD 105 | done 106 | fi 107 | 108 | rm -rf $BUTLERRUNPATH/$VALET_HOME/Certificates-old 2> /dev/null 109 | } 110 | 111 | function valet_tld { 112 | shift 1 113 | workdir=$(get_workdir) 114 | TLDCHANGE="no" 115 | # if the $1 is not empty, then we are requesting tld change 116 | if [ ! -z $1 ]; then 117 | TLDCHANGE="yes" 118 | 119 | # backup old certificates 120 | backup_old_certificates 121 | # untrust those cert 122 | untrust_old_certificates 123 | fi 124 | 125 | # pass to valet 126 | docker exec -i${TTY} \ 127 | -w $workdir \ 128 | butler_php_1 \ 129 | valet tld "$@" 130 | 131 | if [ $TLDCHANGE == "yes" ]; then 132 | trust_new_certificates 133 | reload_dnsmasq 134 | reload_webserver 135 | fi 136 | } 137 | 138 | function valet_secure { 139 | workdir=$(get_workdir) 140 | shift 1 141 | # We need to install CA cert to keychain. so we interfere valet secure and only after that we pass to valet command 142 | # Do the CA installation here 143 | generate_ca 144 | 145 | if [ -z $1 ]; then 146 | CN=$(basename $CURRENTDIR) 147 | else 148 | CN=$1 149 | fi 150 | 151 | docker exec -i${TTY} \ 152 | -w $workdir \ 153 | butler_php_1 \ 154 | valet secure $CN 155 | 156 | trust_certificate $CN 157 | reload_webserver 158 | } 159 | 160 | function valet_unsecure { 161 | workdir=$(get_workdir) 162 | shift 1 163 | if [ -z $1 ]; then 164 | CN=$(basename $CURRENTDIR) 165 | else 166 | CN=$1 167 | fi 168 | 169 | docker exec -i${TTY} \ 170 | -w $workdir \ 171 | butler_php_1 \ 172 | valet unsecure $CN 173 | 174 | untrust_certificate $CN 175 | reload_webserver 176 | } 177 | 178 | function valet_proxy { 179 | workdir=$(get_workdir) 180 | shift 1 181 | 182 | CN=$1 183 | 184 | docker exec -i${TTY} \ 185 | butler_php_1 \ 186 | valet proxy "$@" 187 | 188 | trust_certificate $CN 189 | reload_webserver 190 | } 191 | 192 | function valet_unproxy { 193 | workdir=$(get_workdir) 194 | shift 1 195 | 196 | CN=$1 197 | 198 | docker exec -i${TTY} \ 199 | -w $workdir \ 200 | butler_php_1 \ 201 | valet unproxy "$@" 202 | 203 | untrust_certificate $CN 204 | reload_webserver 205 | } 206 | 207 | function trust_new_certificates { 208 | if [ -d $BUTLERRUNPATH/$VALET_HOME/Certificates ]; then 209 | for filePath in $BUTLERRUNPATH/$VALET_HOME/Certificates/*.crt; do 210 | fileName=$(basename $filePath) 211 | domain="${fileName%.*}" 212 | domainWithoutTLD="${domain%.*}" 213 | trust_certificate $domainWithoutTLD 214 | done 215 | fi 216 | } 217 | 218 | function valet_trust { 219 | shift 1 220 | 221 | if [ "$1" == "--off" ]; then 222 | sudo rm /etc/sudoers.d/butler 2> /dev/null 223 | 224 | echo -e "${WHITE}Sudoers entries have been removed for Butler.${NC}" 225 | else 226 | echo -e "Cmnd_Alias BUTLER = /usr/local/bin/butler *\n%admin ALL=(root) NOPASSWD:SETENV: BUTLER" | sudo tee /etc/sudoers.d/butler > /dev/null 227 | 228 | echo -e "${WHITE}Sudoers entries have been added for Butler.${NC}" 229 | fi 230 | } 231 | 232 | function reload_webserver { 233 | docker restart butler_webserver_1 > /dev/null 2>&1 234 | } 235 | 236 | function reload_dnsmasq { 237 | docker restart butler_dns_1 butler_dns-internal_1 > /dev/null 2>&1 238 | sudo killall -HUP mDNSResponder 239 | } 240 | 241 | function backup_old_certificates { 242 | cp -r $BUTLERRUNPATH/$VALET_HOME/Certificates $BUTLERRUNPATH/$VALET_HOME/Certificates-old 243 | } 244 | 245 | function get_env_value { 246 | VALUE=$(cat $BUTLERRUNPATH/.env | grep -o "$1=.*" | cut -f2- -d =) 247 | echo $VALUE 248 | } 249 | 250 | # Verify operating system is supported... We add it here in case we wanted to support Linux and Windows too 251 | case "${UNAMEOUT}" in 252 | Darwin*) MACHINE=mac;; 253 | *) MACHINE="UNKNOWN" 254 | esac 255 | 256 | if [ "$MACHINE" == "UNKNOWN" ]; then 257 | echo -e "${WHITE}Unsupported operating system [$(uname -s)]. Butler only supports macOS${NC}" >&2 258 | 259 | exit 1 260 | fi 261 | 262 | # Ensure that Docker is running... 263 | if ! docker info > /dev/null 2>&1; then 264 | echo -e "${WHITE}Docker is not running.${NC}" >&2 265 | 266 | exit 1 267 | fi 268 | 269 | 270 | if [ ! -d $BUTLERRUNPATH ] 271 | then 272 | echo -e "${WHITE}Uh oh... Butler directory does not exist. Please reinstall Butler..${NC}" >&2 273 | 274 | exit 1 275 | fi 276 | 277 | cd $BUTLERRUNPATH 278 | 279 | # Need to make sure .env exist 280 | if ! is_file_exist $BUTLERRUNPATH/.env; then 281 | echo -e "${WHITE}Uh oh... .env file not exists. Please run install.sh to reinstall${NC}" >&2 282 | exit 1 283 | fi 284 | 285 | if [ "$1" == "start" ];then 286 | docker-compose -p butler up -d 287 | echo -e "${WHITE}Butler process started...${NC}" >&2 288 | exit 0 289 | elif [ "$1" == "reset" ];then 290 | echo -e "${WHITE}Remove all butler related processes...${NC}" >&2 291 | docker-compose -p butler down 292 | echo -e "${WHITE}Adding back butler processes...${NC}" >&2 293 | docker-compose -p butler up -d 294 | exit 0 295 | elif [ "$1" == "restart" ];then 296 | echo -e "${WHITE}Restart all butler related processes...${NC}" >&2 297 | docker-compose -p butler restart 298 | exit 0 299 | elif [ "$1" == "reload" ];then 300 | echo -e "${WHITE}Reload all butler related processes...${NC}" >&2 301 | docker exec -i${TTY} \ 302 | butler_php_1 \ 303 | valet install 304 | docker-compose -p butler up -d 305 | docker-compose -p butler restart 306 | exit 0 307 | elif [ "$1" == "stop" ];then 308 | echo -e "${WHITE}Stopping all butler related processes...${NC}" >&2 309 | docker-compose -p butler down 310 | exit 0 311 | fi 312 | 313 | 314 | PSRESULT="$(docker ps | grep butler | wc -l)" 315 | 316 | if [[ PSRESULT -lt 2 ]]; then 317 | echo -e "${WHITE}Shutting down old Butler processes...${NC}" >&2 318 | 319 | docker-compose -p butler down > /dev/null 2>&1 320 | 321 | butler_is_not_running 322 | 323 | exit 1 324 | fi 325 | 326 | 327 | # # Proxy PHP commands to the "php" binary on the application container... 328 | if [[ "$1" == "php" || "$1" == "valet" || $1 == "composer" || $1 == "artisan" ]]; then 329 | COMMAND=$1 330 | workdir=$(get_workdir) 331 | shift 1 332 | 333 | if [[ $COMMAND == "valet" && $1 == "secure" ]]; then 334 | valet_secure "$@" 335 | elif [[ $COMMAND == "valet" && $1 == "unsecure" ]]; then 336 | valet_unsecure "$@" 337 | elif [[ $COMMAND == "valet" && $1 == "proxy" ]]; then 338 | valet_proxy "$@" 339 | elif [[ $COMMAND == "valet" && $1 == "unproxy" ]]; then 340 | valet_unsecure "$@" 341 | elif [[ $COMMAND == "valet" && $1 == "tld" ]]; then 342 | valet_tld "$@" 343 | elif [[ $COMMAND == "valet" && $1 == "trust" ]]; then 344 | valet_trust "$@" 345 | elif [[ $COMMAND == "artisan" || $command == "art" ]]; then 346 | shift 1 347 | docker exec -i${TTY} \ 348 | -w $workdir \ 349 | butler_php_1 \ 350 | php artisan "$@" 351 | else 352 | docker exec -i${TTY} \ 353 | -w $workdir \ 354 | butler_php_1 \ 355 | $COMMAND "$@" 356 | fi 357 | elif [ "$1" == "secure" ];then 358 | valet_secure "$@" 359 | elif [ "$1" == "unsecure" ];then 360 | valet_unsecure "$@" 361 | elif [ "$1" == "proxy" ];then 362 | valet_proxy "$@" 363 | elif [ "$1" == "unproxy" ];then 364 | valet_unproxy "$@" 365 | elif [ "$1" == "tld" ];then 366 | valet_tld "$@" 367 | elif [ "$1" == "down" ];then 368 | docker-compose -p butler stop 369 | elif [ "$1" == "trust" ];then 370 | valet_trust "$@" 371 | else 372 | workdir=$(get_workdir) 373 | 374 | docker exec -i${TTY} \ 375 | -w $workdir \ 376 | butler_php_1 \ 377 | valet "$@" 378 | fi -------------------------------------------------------------------------------- /valet/cli/valet.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | command('install', function () { 51 | DnsMasq::install(Configuration::read()['tld']); 52 | 53 | output(PHP_EOL . 'Valet installed successfully!'); 54 | })->descriptions('Install the Valet services'); 55 | 56 | /** 57 | * Most commands are available only if valet is installed. 58 | */ 59 | if (is_dir(VALET_HOME_PATH)) { 60 | /** 61 | * Upgrade helper: ensure the tld config exists 62 | */ 63 | if (empty(Configuration::read()['tld'])) { 64 | Configuration::writeBaseConfiguration(); 65 | } 66 | 67 | /** 68 | * Get or set the TLD currently being used by Valet. 69 | */ 70 | $app->command('tld [tld]', function ($tld = null) { 71 | if ($tld === null) { 72 | return output(Configuration::read()['tld']); 73 | } 74 | 75 | DnsMasq::updateTld( 76 | $oldTld = Configuration::read()['tld'], $tld = trim($tld, '.') 77 | ); 78 | 79 | Configuration::updateKey('tld', $tld); 80 | 81 | Site::resecureForNewTld($oldTld, $tld); 82 | 83 | info('Your Valet TLD has been updated to [' . $tld . '].'); 84 | }, ['domain'])->descriptions('Get or set the TLD used for Valet sites.'); 85 | 86 | /** 87 | * Add the current working directory to the paths configuration. 88 | */ 89 | $app->command('park [path]', function ($path = null) { 90 | Configuration::addPath($path ?: getcwd()); 91 | 92 | info(($path === null ? "This" : "The [{$path}]") . " directory has been added to Valet's paths."); 93 | })->descriptions('Register the current working (or specified) directory with Valet'); 94 | 95 | /** 96 | * Get all the current sites within paths parked with 'park {path}' 97 | */ 98 | $app->command('parked', function () { 99 | $parked = Site::parked(); 100 | 101 | table(['Site', 'SSL', 'URL', 'Path'], $parked->all()); 102 | })->descriptions('Display all the current sites within parked paths'); 103 | 104 | /** 105 | * Remove the current working directory from the paths configuration. 106 | */ 107 | $app->command('forget [path]', function ($path = null) { 108 | Configuration::removePath($path ?: getcwd()); 109 | 110 | info(($path === null ? "This" : "The [{$path}]") . " directory has been removed from Valet's paths."); 111 | }, ['unpark'])->descriptions('Remove the current working (or specified) directory from Valet\'s list of paths'); 112 | 113 | /** 114 | * Register a symbolic link with Valet. 115 | */ 116 | $app->command('link [name] [--secure]', function ($name, $secure) { 117 | $linkPath = Site::link(getcwd(), $name = $name ?: basename(getcwd())); 118 | 119 | info('A [' . $name . '] symbolic link has been created in [' . $linkPath . '].'); 120 | 121 | if ($secure) { 122 | $this->runCommand('secure ' . $name); 123 | } 124 | })->descriptions('Link the current working directory to Valet'); 125 | 126 | /** 127 | * Display all of the registered symbolic links. 128 | */ 129 | $app->command('links', function () { 130 | $links = Site::links(); 131 | 132 | table(['Site', 'SSL', 'URL', 'Path'], $links->all()); 133 | })->descriptions('Display all of the registered Valet links'); 134 | 135 | /** 136 | * Unlink a link from the Valet links directory. 137 | */ 138 | $app->command('unlink [name]', function ($name) { 139 | info('The [' . Site::unlink($name) . '] symbolic link has been removed.'); 140 | })->descriptions('Remove the specified Valet link'); 141 | 142 | /** 143 | * Secure the given domain with a trusted TLS certificate. 144 | */ 145 | $app->command('secure [domain]', function ($domain = null) { 146 | $url = ($domain ?: Site::host(getcwd())) . '.' . Configuration::read()['tld']; 147 | 148 | Site::secure($url); 149 | 150 | info('The [' . $url . '] site has been secured with a fresh TLS certificate.'); 151 | })->descriptions('Secure the given domain with a trusted TLS certificate'); 152 | 153 | /** 154 | * Stop serving the given domain over HTTPS and remove the trusted TLS certificate. 155 | */ 156 | $app->command('unsecure [domain]', function ($domain = null) { 157 | $url = ($domain ?: Site::host(getcwd())) . '.' . Configuration::read()['tld']; 158 | 159 | Site::unsecure($url); 160 | 161 | info('The [' . $url . '] site will now serve traffic over HTTP.'); 162 | })->descriptions('Stop serving the given domain over HTTPS and remove the trusted TLS certificate'); 163 | 164 | /** 165 | * Create an Nginx proxy config for the specified domain 166 | */ 167 | $app->command('proxy domain host', function ($domain, $host) { 168 | 169 | Site::proxyCreate($domain, $host); 170 | 171 | })->descriptions('Create an Nginx proxy site for the specified host. Useful for docker, mailhog etc.'); 172 | 173 | /** 174 | * Delete an Nginx proxy config 175 | */ 176 | $app->command('unproxy domain', function ($domain) { 177 | 178 | Site::proxyDelete($domain); 179 | 180 | })->descriptions('Delete an Nginx proxy config.'); 181 | 182 | /** 183 | * Display all of the sites that are proxies. 184 | */ 185 | $app->command('proxies', function () { 186 | $proxies = Site::proxies(); 187 | 188 | table(['Site', 'SSL', 'URL', 'Host'], $proxies->all()); 189 | })->descriptions('Display all of the proxy sites'); 190 | 191 | /** 192 | * Determine which Valet driver the current directory is using. 193 | */ 194 | $app->command('which', function () { 195 | require __DIR__ . '/drivers/require.php'; 196 | 197 | $driver = ValetDriver::assign(getcwd(), basename(getcwd()), '/'); 198 | 199 | if ($driver) { 200 | info('This site is served by [' . get_class($driver) . '].'); 201 | } else { 202 | warning('Valet could not determine which driver to use for this site.'); 203 | } 204 | })->descriptions('Determine which Valet driver serves the current working directory'); 205 | 206 | /** 207 | * Display all of the registered paths. 208 | */ 209 | $app->command('paths', function () { 210 | $paths = Configuration::read()['paths']; 211 | 212 | if (count($paths) > 0) { 213 | output(json_encode($paths, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); 214 | } else { 215 | info('No paths have been registered.'); 216 | } 217 | })->descriptions('Get all of the paths registered with Valet'); 218 | 219 | /** 220 | * Generate a publicly accessible URL for your project. 221 | */ 222 | $app->command('share', function () { 223 | warning("It looks like you are running `cli/valet.php` directly, please use the `valet` script in the project root instead."); 224 | })->descriptions('Generate a publicly accessible URL for your project'); 225 | 226 | /** 227 | * Echo the currently tunneled URL. 228 | */ 229 | // $app->command('fetch-share-url [domain]', function ($domain = null) { 230 | // output(Ngrok::currentTunnelUrl($domain ?: Site::host(getcwd()) . '.' . Configuration::read()['tld'])); 231 | // })->descriptions('Get the URL to the current Ngrok tunnel'); 232 | 233 | /** 234 | * Start the daemon services. 235 | */ 236 | $app->command('start', function () { 237 | 238 | })->descriptions('Start the Butler services'); 239 | 240 | /** 241 | * Restart the daemon services. 242 | */ 243 | $app->command('restart', function () { 244 | 245 | })->descriptions('Restart the Butler services'); 246 | 247 | /** 248 | * Reload the daemon services. 249 | */ 250 | $app->command('reload', function () { 251 | 252 | })->descriptions('Reload the Butler services (Use this after .env change)'); 253 | 254 | /** 255 | * Stop the daemon services. 256 | */ 257 | $app->command('stop', function () { 258 | 259 | })->descriptions('Stop the Butler services'); 260 | 261 | /** 262 | * Determine if this is the latest release of Valet. 263 | */ 264 | $app->command('on-latest-version', function () use ($version) { 265 | if (Valet::onLatestVersion($version)) { 266 | output('Yes'); 267 | } else { 268 | output(sprintf('Your version of Valet (%s) is not the latest version available.', $version)); 269 | output('Upgrade instructions can be found in the docs: https://laravel.com/docs/valet#upgrading-valet'); 270 | } 271 | })->descriptions('Determine if this is the latest version of Valet'); 272 | 273 | /** 274 | * Install the sudoers.d entries so password is no longer required. 275 | */ 276 | $app->command('trust [--off]', function () { 277 | 278 | })->descriptions('Add sudoers files for Brew and Valet to make Valet commands run without passwords', [ 279 | '--off' => 'Remove the sudoers files so normal sudo password prompts are required.', 280 | ]); 281 | 282 | /** 283 | * Configure or display the directory-listing setting. 284 | */ 285 | $app->command('directory-listing [status]', function ($status = null) { 286 | $key = 'directory-listing'; 287 | $config = Configuration::read(); 288 | 289 | if (in_array($status, ['on', 'off'])) { 290 | $config[$key] = $status; 291 | Configuration::write($config); 292 | return output('Directory listing setting is now: ' . $status); 293 | } 294 | 295 | $current = isset($config[$key]) ? $config[$key] : 'off'; 296 | output('Directory listing is ' . $current); 297 | })->descriptions('Determine directory-listing behavior. Default is off, which means a 404 will display.', [ 298 | 'status' => 'on or off. (default=off) will show a 404 page; [on] will display a listing if project folder exists but requested URI not found', 299 | ]); 300 | 301 | /** 302 | * Output diagnostics to aid in debugging Valet. 303 | */ 304 | $app->command('diagnose [-p|--print] [--plain]', function ($print, $plain) { 305 | info('Running diagnostics... (this may take a while)'); 306 | 307 | Diagnose::run($print, $plain); 308 | 309 | info('Diagnostics output has been copied to your clipboard.'); 310 | })->descriptions('Output diagnostics to aid in debugging Valet.', [ 311 | '--print' => 'print diagnostics output while running', 312 | '--plain' => 'format clipboard output as plain text', 313 | ]); 314 | } 315 | 316 | /** 317 | * Load all of the Valet extensions. 318 | */ 319 | foreach (Valet::extensions() as $extension) { 320 | include $extension; 321 | } 322 | 323 | /** 324 | * Run the application. 325 | */ 326 | $app->run(); 327 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # About Butler 2 | 3 | Butler is a replacement for [Laravel Valet](https://github.com/laravel/valet) that works inside Docker. So, no more tinkering with brew, fixing things, etc when things go south. Since Butler is based on Laravel Valet, please take a look at [Laravel Valet Documentation](https://laravel.com/docs/master/valet) before using Butler. 4 | 5 | Butler codebase is 100% taken from Laravel Valet and a few codebase was inspired (taken) from [Laravel Sail](https://github.com/laravel/sail). Since Valet was designed for MacOS, a few tweak from main code need to be changed inside Butler to give the same experience as using Laravel Valet. 6 | 7 | # Butler Manifesto 8 | 9 | I hate it when things doesn't work on my machine after I have setup everything. Things just don't work sometimes and I believe you have face the same problem. When I create Butler, it is because brew service give ton shit of error. Not to mention, when upgrading your Mac sometimes you face new error. 10 | 11 | Like every programmer, instead of fixing broken things. Why not make a new solution? I like how Laravel Valet works but to deal with errors (not causing by Laravel Valet), it just consumed my daily life experience in developing my product. To combat this, Butler was born. 12 | 13 | To make things simple inside your development machine, Butler should make your life easy without having to install PHP, Nginx or DNSmasq inside your Mac. Thus, keeping your Mac clean and you can easily setup your development environment when you buy a new Mac with your hard earned money. 14 | 15 | Butler aim to replicate the simplicity of using Laravel Valet and thus I will not add other cool shit feature to Butler if it does not available inside Laravel Valet. Any **PR** that add a feature which not exist inside Valet will **be rejected** without hesitation. This project is my first project in Docker because I want to learn how to use Docker. There will be part of this code which you will feel like a **n00b** who wrote this project, and that is because it is. If you have any improvement to make, don't hesitate to make PR or the noob code will stay forever. 16 | 17 | # Todo 18 | 19 | - [ ] valet share 20 | - [ ] valet fetch-share-url 21 | - [ ] valet unsecure --all 22 | 23 | # TLDR; 24 | 25 | ``` 26 | $ git clone https://github.com/RunCloudIO/butler.git 27 | $ cd butler 28 | $ git checkout tags/$(curl --silent "https://api.github.com/repos/RunCloudIO/butler/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') 29 | $ ./install.sh 30 | $ cd www/default 31 | $ mkdir mysite 32 | $ cd mysite 33 | $ echo " index.php 34 | $ # update DNS to 127.0.0.1 35 | $ open -a "Google Chrome" http://mysite.test 36 | ``` 37 | 38 | # Installation 39 | 40 | Requirement: 41 | 42 | 1. [Docker](https://www.docker.com/) 43 | 44 | To start with Butler, clone this repository and run `install.sh` 45 | 46 | ``` 47 | $ git clone https://github.com/RunCloudIO/butler.git 48 | $ cd butler 49 | $ git checkout tags/$(curl --silent "https://api.github.com/repos/RunCloudIO/butler/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') 50 | $ ./install.sh 51 | ``` 52 | **IMPORTANT PART**. After the installation succeeded, change your DNS inside System Preferences > Network > Advanced > DNS to 53 | ``` 54 | 127.0.0.1 55 | ``` 56 | 57 | Failure to do so **will prevent** you from running custom domain TLD. 58 | 59 | If you have **moved** the folder to a different path, simply run `install.sh` inside the new path to make sure `butler` command know about it. 60 | 61 | # Upgrade 62 | 63 | To update, just 64 | 65 | ``` 66 | $ cd /path/to/butler 67 | $ git pull 68 | $ git checkout tags/$(curl --silent "https://api.github.com/repos/RunCloudIO/butler/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') 69 | $ ./install.sh 70 | ``` 71 | 72 | `install.sh` command will not replace your `docker-compose.yaml` and `.env`. But if we release a new update to that compose file, you can just delete your compose file and running `install.sh` will re-add latest `docker-composer.yaml`. 73 | 74 | # Tips & Tricks 75 | ### Alias 76 | To make your life easier, it is better to use your daily command rather than invoking `butler` directly. Before doing this, make sure you have **REMOVE** Laravel Valet completely. So, here it is. Edit `~/.bash_profile` and append these lines: 77 | 78 | ``` 79 | alias valet="butler" 80 | alias php="butler php" 81 | alias composer="butler composer" 82 | ``` 83 | 84 | After that, `source ~/.bash_profile` and you may use `valet`, `php`, `composer` just like you have installed them natively. 85 | 86 | ### Connecting to host 87 | 88 | Since your application is running inside a container, you can't use `127.0.0.1` to connect to database, Redis, etc. To solve this, Butler retain the functionality from Docker for Mac where you can call host by their domain name. Inside your application, you need to change from `127.0.0.1` to `host.docker.internal` to connect to host. 89 | # Comparison With 90 | 91 | ### Laravel Valet 92 | 93 | As stated, Butler aiming to follow Laravel Valet closely. Thus, it should be same with Laravel Valet in term of usage and experience. The only difference is, Butler use Docker rather than installing dependency using `brew` directly into MacOS. 94 | 95 | Butler aim to keep development machine clean without installing `php`, `nginx` and `dnsmasq`. That is all what Laravel Valet is doing. It doesn't matter if you want to install other tools (`mysql`, `redis`, `supervisor`) directly inside your MacOS because it is not in Laravel Valet scope. 96 | 97 | ### Laravel Sail 98 | 99 | Laravel Sail is new development tools from Laravel. Same as Butler, it is using Docker to accomplish the task. However, Laravel Sail is aiming for per project based rather than setup once and forget everything. You also need to setup Sail for each Laravel instance that you have to make use of Sail. And **Sail only works** with Laravel. 100 | 101 | If you dig down into Sail codes, you can see that it is installing every binary inside single Docker container. The problem happen when you want to launch multiple Sail instance. For each Sail instance, you need to configure different port for different project if you want to keep everything up at the same time. Although this can be solve by using [Traefik](https://hub.docker.com/_/traefik), you still need to learn how to use Traefik and configure Sail configuration to use Traefik. So, each of your Laravel Sail instance will have Traefik configuration rather than setup once (Laravel Valet) and run forever. 102 | 103 | # Usage 104 | 105 | ### Basic usage 106 | You will have `butler` installed inside `/usr/local/bin/butler`. Thus, you can invoke `butler` command anywhere. 107 | 108 | `butler` command without any argument is same as running `valet` without any argument. You also can run `butler valet` if you prefer it that way. 109 | 110 | Valet default path was set to `/var/www/default`. So you may create your 1st project inside there, which is inside host is `www/default` directory. 111 | 112 | ### Butler specific command 113 | 114 | ```bash 115 | $ butler start # start butler process 116 | $ butler reload # reload processes if you change .env or docker-compose.yaml 117 | $ butler reset # reset everything to original state but keep your item in mounted folder 118 | $ butler restart # restart all butler services 119 | $ butler stop # stop all butler services 120 | ``` 121 | 122 | ### Using PHP and Composer 123 | 124 | Since you are not installing any PHP inside your Mac, you can run php using `butler php`. Thus, running `php artisan migrate` can be run using `butler php artisan migrate`. Or if you prefer the shortest way `butler artisan migrate`, or `butler art migrate`. 125 | 126 | Same as using composer, you can run `butler composer create-project laravel/laravel example-app` to install Laravel. 127 | 128 | **PLEASE TAKE NOTE** that running PHP based command (php, valet, composer, artisan) only supported on **`DEFAULT_WWW_PATH`** that you have set inside **`.env`**. 129 | 130 | Running PHP based command ouside of `DEFAULT_WWW_PATH` folder is equivalent to running the command inside `/var/www/`. If you need to run outside that folder, you need to manually mount your folder and interact directly with the Docker container. 131 | 132 | ### Change PHP version 133 | 134 | Since we are using Docker, **changing PHP version** is **easier** than ever. You just need to update `.env` by changing `BUTLER_PHP_VERSION` to either version *8.0, 7.4, 7.3, 7.2, 7.1, or 7.0*. Then just issue `butler reload` for it to take effect. 135 | 136 | ### Laravel Valet park and link command 137 | 138 | You can only run `butler valet park` or `butler valet link` inside`DEFAULT_WWW_PATH` defined in `.env`. If you run these command outside the `DEFAULT_WWW_PATH` directory, it will automatically run your command inside `/var/www` in the container. 139 | 140 | You may create another folder inside `DEFAULT_WWW_PATH` and register it as new parked paths or linked path. So you can divide your codebase per project basis inside here. To give you the idea, take a look at the sample below. 141 | 142 | ``` 143 | ... 144 | └── www 145 | ├── default 146 | │   ├── mysite 147 | │   │   └── index.php 148 | │   ├── mysite2 149 | │   │   └── index.php 150 | │   └── mysite3 151 | │   └── index.php 152 | ├── link1 153 | │   └── index.php 154 | ├── link2 155 | │   └── index.php 156 | ├── project1 157 | │   ├── backend 158 | │   │   └── index.php 159 | │   └── frontend 160 | │   └── index.php 161 | └── project2 162 | ├── backend 163 | │   └── index.php 164 | └── frontend 165 | └── index.php 166 | ``` 167 | Laravel Valet command 168 | 169 | ``` 170 | $ cd 171 | $ butler park default 172 | $ butler link link1 173 | $ butler link link2 174 | $ butler park project1 175 | $ butler park project2 176 | ``` 177 | 178 | ### Moving working directory 179 | 180 | You probably didn't like the idea of having `www` folder inside this cloned repo. For example, you clone this project into `/Users//Documents/tools/butler` and getting into that `www` directory is too much. 181 | 182 | To change this, you need to update `.env` file and `DEFAULT_WWW_PATH` to a new path, let say your Desktop. Make sure to use absolute path when defining `DEFAULT_WWW_PATH`. Give it a reload (`butler reload`) and check whether your site still registered with Valet, using `butler parked` or `butler links`. 183 | 184 | ### Laravel Queue 185 | 186 | To run backend process (eg: Laravel Queue), you need process manager. Two widely known process managers are [Supervisor](http://supervisord.org/) and [PM2](https://www.npmjs.com/package/pm2). You **can't** run both of this process manager inside Docker container because it does not have the php binary and butler script inside the container. So both of this software need to be installed natively inside your system. 187 | 188 | For Supervisor, you can create the config as follow: 189 | 190 | ```conf 191 | [program:] 192 | environment=NOTTY=true 193 | command=butler php artisan queue:work --tries=1 194 | directory=/path/to/laravel 195 | redirect_stderr=true 196 | autostart=true 197 | autorestart=true 198 | user= 199 | numprocs=1 200 | process_name=%(program_name)s_%(process_num)s 201 | ``` 202 | 203 | For PM2, you may create `pm2-queue.yaml` with below content: 204 | 205 | ```yaml 206 | apps: 207 | - name: 208 | script: NOTTY=true butler php artisan queue:work --tries=1 209 | exec_mode: fork 210 | instances: 1 211 | ``` 212 | 213 | Then start PM2 with `pm2 start pm2-queue.yaml` 214 | 215 | ### Docker networking config 216 | 217 | It may be odd to see this package included Docker networking config with static IP Address for each services. It is needed, let me tell you why. 218 | 219 | First of all, you can see we are running **two** instances of DNSMasq. It needed two because, 1 is for our Mac to resolve the .test (or something else) domain and another one is for the container to resolve the .test domain. 220 | 221 | Why do we need it? Ok, if you are running a simple PHP application, it would not make any sense in this. But, if your PHP Application call other .test domain, probably calling API, it will resolve to webserver service. Without having the internal-dns, all container will only resolve to `127.0.0.1`. So, php container need to know that .test domain will be pointed to webserver service. 222 | 223 | # Troubleshooting Guides 224 | 225 | ### Valet 404 Page 226 | 227 | Check whether your site exists using either command 228 | 229 | ``` 230 | $ butler parked 231 | $ butler links 232 | $ butler proxies 233 | ``` 234 | 235 | If it does not exist, inside any, check whether the path has been loaded inside valet 236 | 237 | ``` 238 | $ butler paths 239 | ``` -------------------------------------------------------------------------------- /valet/cli/Valet/Brew.php: -------------------------------------------------------------------------------- 1 | cli = $cli; 40 | $this->files = $files; 41 | } 42 | 43 | /** 44 | * Ensure the formula exists in the current Homebrew configuration 45 | * 46 | * @param string $formula 47 | * @return bool 48 | */ 49 | function installed($formula) 50 | { 51 | $result = $this->cli->runAsUser("brew info $formula --json"); 52 | 53 | // should be a json response, but if not installed then "Error: No available formula ..." 54 | if (starts_with($result, 'Error: No')) { 55 | return false; 56 | } 57 | 58 | $details = json_decode($result); 59 | 60 | return !empty($details[0]->installed); 61 | } 62 | 63 | /** 64 | * Determine if a compatible PHP version is Homebrewed. 65 | * 66 | * @return bool 67 | */ 68 | function hasInstalledPhp() 69 | { 70 | $installed = $this->installedPhpFormulae()->first(function ($formula) { 71 | return $this->supportedPhpVersions()->contains($formula); 72 | }); 73 | 74 | return !empty($installed); 75 | } 76 | 77 | /** 78 | * Get a list of supported PHP versions. 79 | * 80 | * @return \Illuminate\Support\Collection 81 | */ 82 | function supportedPhpVersions() 83 | { 84 | return collect(static::SUPPORTED_PHP_VERSIONS); 85 | } 86 | 87 | function installedPhpFormulae() 88 | { 89 | return collect( 90 | explode(PHP_EOL, $this->cli->runAsUser('brew list --formula | grep php')) 91 | ); 92 | } 93 | 94 | /** 95 | * Get the aliased formula version from Homebrew 96 | */ 97 | function determineAliasedVersion($formula) 98 | { 99 | $details = json_decode($this->cli->runAsUser("brew info $formula --json")); 100 | 101 | if (!empty($details[0]->aliases[0])) { 102 | return $details[0]->aliases[0]; 103 | } 104 | 105 | return 'ERROR - NO BREW ALIAS FOUND'; 106 | } 107 | 108 | /** 109 | * Determine if a compatible nginx version is Homebrewed. 110 | * 111 | * @return bool 112 | */ 113 | function hasInstalledNginx() 114 | { 115 | return $this->installed('nginx') 116 | || $this->installed('nginx-full'); 117 | } 118 | 119 | /** 120 | * Return name of the nginx service installed via Homebrew. 121 | * 122 | * @return string 123 | */ 124 | function nginxServiceName() 125 | { 126 | return $this->installed('nginx-full') ? 'nginx-full' : 'nginx'; 127 | } 128 | 129 | /** 130 | * Ensure that the given formula is installed. 131 | * 132 | * @param string $formula 133 | * @param array $options 134 | * @param array $taps 135 | * @return void 136 | */ 137 | function ensureInstalled($formula, $options = [], $taps = []) 138 | { 139 | if (! $this->installed($formula)) { 140 | $this->installOrFail($formula, $options, $taps); 141 | } 142 | } 143 | 144 | /** 145 | * Install the given formula and throw an exception on failure. 146 | * 147 | * @param string $formula 148 | * @param array $options 149 | * @param array $taps 150 | * @return void 151 | */ 152 | function installOrFail($formula, $options = [], $taps = []) 153 | { 154 | info("Installing {$formula}..."); 155 | 156 | if (count($taps) > 0) { 157 | $this->tap($taps); 158 | } 159 | 160 | output('['.$formula.'] is not installed, installing it now via Brew... 🍻'); 161 | if ($formula !== 'php' && starts_with($formula, 'php') && preg_replace('/[^\d]/', '', $formula) < '73') { 162 | warning('Note: older PHP versions may take 10+ minutes to compile from source. Please wait ...'); 163 | } 164 | 165 | $this->cli->runAsUser(trim('brew install '.$formula.' '.implode(' ', $options)), function ($exitCode, $errorOutput) use ($formula) { 166 | output($errorOutput); 167 | 168 | throw new DomainException('Brew was unable to install ['.$formula.'].'); 169 | }); 170 | } 171 | 172 | /** 173 | * Tap the given formulas. 174 | * 175 | * @param dynamic[string] $formula 176 | * @return void 177 | */ 178 | function tap($formulas) 179 | { 180 | $formulas = is_array($formulas) ? $formulas : func_get_args(); 181 | 182 | foreach ($formulas as $formula) { 183 | $this->cli->passthru('sudo -u "'.user().'" brew tap '.$formula); 184 | } 185 | } 186 | 187 | /** 188 | * Restart the given Homebrew services. 189 | * 190 | * @param 191 | */ 192 | function restartService($services) 193 | { 194 | $services = is_array($services) ? $services : func_get_args(); 195 | 196 | foreach ($services as $service) { 197 | if ($this->installed($service)) { 198 | info("Restarting {$service}..."); 199 | 200 | $this->cli->quietly('sudo brew services stop '.$service); 201 | $this->cli->quietly('sudo brew services start '.$service); 202 | } 203 | } 204 | } 205 | 206 | /** 207 | * Stop the given Homebrew services. 208 | * 209 | * @param 210 | */ 211 | function stopService($services) 212 | { 213 | $services = is_array($services) ? $services : func_get_args(); 214 | 215 | foreach ($services as $service) { 216 | if ($this->installed($service)) { 217 | info("Stopping {$service}..."); 218 | 219 | $this->cli->quietly('sudo brew services stop '.$service); 220 | } 221 | } 222 | } 223 | 224 | /** 225 | * Determine if php is currently linked. 226 | * 227 | * @return bool 228 | */ 229 | function hasLinkedPhp() 230 | { 231 | return $this->files->isLink(BREW_PREFIX.'/bin/php'); 232 | } 233 | 234 | /** 235 | * Get the linked php parsed. 236 | * 237 | * @return mixed 238 | */ 239 | function getParsedLinkedPhp() 240 | { 241 | if (! $this->hasLinkedPhp()) { 242 | throw new DomainException("Homebrew PHP appears not to be linked. Please run [valet use php@X.Y]"); 243 | } 244 | 245 | $resolvedPath = $this->files->readLink(BREW_PREFIX.'/bin/php'); 246 | 247 | /** 248 | * Typical homebrew path resolutions are like: 249 | * "../Cellar/php@7.4/7.4.13/bin/php" 250 | * or older styles: 251 | * "../Cellar/php/7.4.9_2/bin/php 252 | * "../Cellar/php55/bin/php 253 | */ 254 | preg_match('~\w{3,}/(php)(@?\d\.?\d)?/(\d\.\d)?([_\d\.]*)?/?\w{3,}~', $resolvedPath, $matches); 255 | 256 | return $matches; 257 | } 258 | 259 | /** 260 | * Gets the currently linked formula by identifying the symlink in the hombrew bin directory. 261 | * Different to ->linkedPhp() in that this will just get the linked directory name, 262 | * whether that is php, php74 or php@7.4 263 | * 264 | * @return string 265 | */ 266 | function getLinkedPhpFormula() 267 | { 268 | $matches = $this->getParsedLinkedPhp(); 269 | return $matches[1] . $matches[2]; 270 | } 271 | 272 | /** 273 | * Determine which version of PHP is linked in Homebrew. 274 | * 275 | * @return string 276 | */ 277 | function linkedPhp() 278 | { 279 | $matches = $this->getParsedLinkedPhp(); 280 | $resolvedPhpVersion = $matches[3] ?: $matches[2]; 281 | 282 | return $this->supportedPhpVersions()->first( 283 | function ($version) use ($resolvedPhpVersion) { 284 | $resolvedVersionNormalized = preg_replace('/[^\d]/', '', $resolvedPhpVersion); 285 | $versionNormalized = preg_replace('/[^\d]/', '', $version); 286 | return $resolvedVersionNormalized === $versionNormalized; 287 | }, function () use ($resolvedPhpVersion) { 288 | throw new DomainException("Unable to determine linked PHP when parsing '$resolvedPhpVersion'"); 289 | }); 290 | } 291 | 292 | /** 293 | * Restart the linked PHP-FPM Homebrew service. 294 | * 295 | * @return void 296 | */ 297 | function restartLinkedPhp() 298 | { 299 | $this->restartService($this->getLinkedPhpFormula()); 300 | } 301 | 302 | /** 303 | * Create the "sudoers.d" entry for running Brew. 304 | * 305 | * @return void 306 | */ 307 | function createSudoersEntry() 308 | { 309 | $this->files->ensureDirExists('/etc/sudoers.d'); 310 | 311 | $this->files->put('/etc/sudoers.d/brew', 'Cmnd_Alias BREW = '.BREW_PREFIX.'/bin/brew * 312 | %admin ALL=(root) NOPASSWD:SETENV: BREW'.PHP_EOL); 313 | } 314 | 315 | /** 316 | * Remove the "sudoers.d" entry for running Brew. 317 | * 318 | * @return void 319 | */ 320 | function removeSudoersEntry() 321 | { 322 | $this->cli->quietly('rm /etc/sudoers.d/brew'); 323 | } 324 | 325 | /** 326 | * Link passed formula. 327 | * 328 | * @param $formula 329 | * @param bool $force 330 | * 331 | * @return string 332 | */ 333 | function link($formula, $force = false) 334 | { 335 | return $this->cli->runAsUser( 336 | sprintf('brew link %s%s', $formula, $force ? ' --force': ''), 337 | function ($exitCode, $errorOutput) use ($formula) { 338 | output($errorOutput); 339 | 340 | throw new DomainException('Brew was unable to link [' . $formula . '].'); 341 | } 342 | ); 343 | } 344 | 345 | /** 346 | * Unlink passed formula. 347 | * @param $formula 348 | * 349 | * @return string 350 | */ 351 | function unlink($formula) 352 | { 353 | return $this->cli->runAsUser( 354 | sprintf('brew unlink %s', $formula), 355 | function ($exitCode, $errorOutput) use ($formula) { 356 | output($errorOutput); 357 | 358 | throw new DomainException('Brew was unable to unlink [' . $formula . '].'); 359 | } 360 | ); 361 | } 362 | 363 | /** 364 | * Get the currently running brew services. 365 | * 366 | * @return \Illuminate\Support\Collection 367 | */ 368 | function getRunningServices() 369 | { 370 | return collect(array_filter(explode(PHP_EOL, $this->cli->runAsUser( 371 | 'brew services list | grep started | awk \'{ print $1; }\'', 372 | function ($exitCode, $errorOutput) { 373 | output($errorOutput); 374 | 375 | throw new DomainException('Brew was unable to check which services are running.'); 376 | } 377 | )))); 378 | } 379 | 380 | /** 381 | * Tell Homebrew to forcefully remove all PHP versions that Valet supports. 382 | * 383 | * @return string 384 | */ 385 | function uninstallAllPhpVersions() 386 | { 387 | $this->supportedPhpVersions()->each(function ($formula) { 388 | $this->uninstallFormula($formula); 389 | }); 390 | 391 | return 'PHP versions removed.'; 392 | } 393 | 394 | /** 395 | * Uninstall a Homebrew app by formula name. 396 | * @param string $formula 397 | * 398 | * @return void 399 | */ 400 | function uninstallFormula($formula) 401 | { 402 | $this->cli->runAsUser('brew uninstall --force '.$formula); 403 | $this->cli->run('rm -rf '.BREW_PREFIX.'/Cellar/'.$formula); 404 | } 405 | 406 | /** 407 | * Run Homebrew's cleanup commands. 408 | * 409 | * @return string 410 | */ 411 | function cleanupBrew() 412 | { 413 | return $this->cli->runAsUser( 414 | 'brew cleanup && brew services cleanup', 415 | function ($exitCode, $errorOutput) { 416 | output($errorOutput); 417 | } 418 | ); 419 | } 420 | } 421 | --------------------------------------------------------------------------------