├── 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 |
--------------------------------------------------------------------------------