Skip to content

Commit

Permalink
Merge branch 'rcknr-cloudflared'
Browse files Browse the repository at this point in the history
  • Loading branch information
mattstauffer committed Aug 25, 2024
2 parents fc1b23b + ea57d98 commit 3f30051
Show file tree
Hide file tree
Showing 6 changed files with 154 additions and 113 deletions.
70 changes: 70 additions & 0 deletions cli/Valet/Cloudflared.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php

namespace Valet;

use GuzzleHttp\Client;

class Cloudflared
{
public function __construct(public CommandLine $cli, public Brew $brew)
{
}

public function currentTunnelUrl(string $domain): ?string
{
// Every cloudflared process will start a "metrics" web server where the
// Quick Tunnel URL will be mentioned under the /metrics endpoint; there
// can potentially be more than one process that matches, but the lsof
// command will show which one is actually functionally running
foreach (array_filter(explode(PHP_EOL, $this->cli->run('pgrep -fl cloudflared'))) as $process) {
// Get the URL shared in this process
preg_match('/(?<pid>\d+)\s.+--http-host-header\s(?<domain>[^\s]+).*/', $process, $pgrepMatches);

if (! array_key_exists('domain', $pgrepMatches) || ! array_key_exists('pid', $pgrepMatches)) {
continue;
}

if ($pgrepMatches['domain'] !== $domain) {
continue;
}

// Get the localhost URL for the metrics server
$lsof = $this->cli->run("lsof -iTCP -P -a -p {$pgrepMatches['pid']}");
preg_match('/TCP\s(?<server>[^\s]+:\d+)\s\(LISTEN\)/', $lsof, $lsofMatches);

if (! array_key_exists('server', $lsofMatches)) {
continue;
}

try {
// Get the cloudflared share URL from the metrics server output
$body = (new Client())->get("http://{$lsofMatches['server']}/metrics")->getBody();
preg_match('/userHostname="(?<url>.+)"/', $body->getContents(), $userHostnameMatches);
} catch (\Exception $e) {
return false;
}

if (array_key_exists('url', $userHostnameMatches)) {
return $userHostnameMatches['url'];
}
}

return false;
}

/**
* Return whether cloudflared is installed.
*/
public function installed(): bool
{
return $this->brew->installed('cloudflared');
}

/**
* Make sure cloudflared is installed.
*/
public function ensureInstalled(): void
{
$this->brew->ensureInstalled('cloudflared');
}
}
2 changes: 1 addition & 1 deletion cli/Valet/Expose.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class Expose
{
public function __construct(public Composer $composer, public CommandLine $cli) {}

public function currentTunnelUrl(?string $domain = null): ?string
public function currentTunnelUrl(string $domain): ?string
{
$endpoint = 'http://127.0.0.1:4040/api/tunnels';

Expand Down
2 changes: 1 addition & 1 deletion cli/Valet/Ngrok.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public function __construct(public CommandLine $cli, public Brew $brew) {}
/**
* Get the current tunnel URL from the Ngrok API.
*/
public function currentTunnelUrl(?string $domain = null): string
public function currentTunnelUrl(string $domain): string
{
// wait a second for ngrok to start before attempting to find available tunnels
sleep(1);
Expand Down
83 changes: 33 additions & 50 deletions cli/app.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ function (ConsoleCommandEvent $event) {

Upgrader::onEveryRun();

$share_tools = [
'cloudflared',
'expose',
'ngrok'
];

/**
* Install Valet and any required services.
*/
Expand Down Expand Up @@ -378,83 +384,60 @@ function (ConsoleCommandEvent $event) {
/**
* Echo the currently tunneled URL.
*/
$app->command('fetch-share-url [domain]', function ($domain = null) {
$app->command('fetch-share-url [domain]', function ($domain = null) use ($share_tools) {
$tool = Configuration::read()['share-tool'] ?? null;

switch ($tool) {
case 'expose':
if ($url = Expose::currentTunnelUrl($domain ?: Site::host(getcwd()))) {
output($url);
}
break;
case 'ngrok':
try {
output(Ngrok::currentTunnelUrl(Site::domain($domain)));
} catch (\Throwable $e) {
warning($e->getMessage());
}
break;
default:
info('Please set your share tool with `valet share-tool expose` or `valet share-tool ngrok`.');
if ($tool && in_array($tool, $share_tools) && class_exists($tool)) {
try {
output($tool::currentTunnelUrl(Site::domain($domain)));
} catch (\Throwable $e) {
warning($e->getMessage());
}
} else {
info('Please set your share tool with `valet share-tool`.');

return Command::FAILURE;
return Command::FAILURE;
}
})->descriptions('Get the URL to the current share tunnel (for Expose or ngrok)');
})->descriptions('Get the URL to the current share tunnel');

/**
* Echo or set the name of the currently-selected share tool (either "ngrok" or "expose").
*/
$app->command('share-tool [tool]', function (InputInterface $input, OutputInterface $output, $tool = null) {
$app->command('share-tool [tool]', function (InputInterface $input, OutputInterface $output, $tool = null)
use ($share_tools) {
if ($tool === null) {
return output(Configuration::read()['share-tool'] ?? '(not set)');
}

if ($tool !== 'expose' && $tool !== 'ngrok') {
warning($tool.' is not a valid share tool. Please use `ngrok` or `expose`.');
$share_tools_list = preg_replace('/,\s([^,]+)$/', ' or $1',
join(', ', array_map(fn($t) => "`$t`", $share_tools)));

if (! in_array($tool, $share_tools) || ! class_exists($tool)) {
warning("$tool is not a valid share tool. Please use $share_tools_list.");

return Command::FAILURE;
}

Configuration::updateKey('share-tool', $tool);
info('Share tool set to '.$tool.'.');

if ($tool === 'expose') {
if (Expose::installed()) {
// @todo: Check it's the right version (has /api/tunnels/)
// E.g. if (Expose::installedVersion)
// if (version_compare(Expose::installedVersion(), $minimumExposeVersion) < 0) {
// prompt them to upgrade
return;
}
info("Share tool set to $tool.");

if (! $tool::installed()) {
$helper = $this->getHelperSet()->get('question');
$question = new ConfirmationQuestion('Would you like to install Expose now? [y/N] ', false);
$question = new ConfirmationQuestion(
'Would you like to install '.ucfirst($tool).' now? [y/N] ',
false);

if ($helper->ask($input, $output, $question) === false) {
info('Proceeding without installing Expose.');
info('Proceeding without installing '.ucfirst($tool).'.');

return;
}

Expose::ensureInstalled();

return;
$tool::ensureInstalled();
}

if (! Ngrok::installed()) {
info("\nIn order to share with ngrok, you'll need a version\nof ngrok installed and managed by Homebrew.");
$helper = $this->getHelperSet()->get('question');
$question = new ConfirmationQuestion('Would you like to install ngrok via Homebrew now? [y/N] ', false);

if ($helper->ask($input, $output, $question) === false) {
info('Proceeding without installing ngrok.');

return;
}

Ngrok::ensureInstalled();
}
})->descriptions('Get the name of the current share tool (Expose or ngrok).');
return Command::SUCCESS;
})->descriptions('Get the name of the current share tool.');

/**
* Set the ngrok auth token.
Expand Down
1 change: 1 addition & 0 deletions cli/includes/facades.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,4 @@ class Site extends Facade {}
class Status extends Facade {}
class Upgrader extends Facade {}
class Valet extends Facade {}
class Cloudflared extends Facade {}
109 changes: 48 additions & 61 deletions valet
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,41 @@ else
fi

# If the command is the "share" command we will need to resolve out any
# symbolic links for the site. Before starting Ngrok, we will fire a
# process to retrieve the live Ngrok tunnel URL in the background.
# symbolic links for the site. Before starting the share tool, we will fire a
# process to retrieve the live the share tool tunnel URL in the background.
if [[ "$1" = "share" ]]
then
SHARETOOL="$("$PHP" "$DIR/cli/valet.php" share-tool)"

# Check for parameters to pass through to share tool (these will start with '-' or '--')
PARAMS=(${@:2})

for PARAM in ${PARAMS[@]}
do
if [[ ${PARAM:0:1} == '-' ]]; then
PARAMS=("${PARAMS[@]/$PARAM}") # Quotes when working with strings
fi
done

PARAMS=${PARAMS[@]}

HOST="${PWD##*/}"

# Find the first linked site for the current dir, if one exists
for linkname in ~/.config/valet/Sites/*; do
if [[ "$(readlink $linkname)" = "$PWD" ]]
then
HOST="${linkname##*/}"
break
fi
done

# Lowercase the host to match how the rest of our domains are looked up
HOST=$(echo "$HOST" | tr '[:upper:]' '[:lower:]')
TLD=$("$PHP" "$DIR/cli/valet.php" tld)
$(grep --quiet --no-messages 443 ~/.config/valet/Nginx/$HOST*)
SECURED=$?

if [[ $SHARETOOL = "ngrok" ]]
then
# ngrok
Expand All @@ -59,41 +88,13 @@ then
exit
fi

# Check for parameters to pass through to ngrok (these will start with '-' or '--')
PARAMS=(${@:2})
for PARAM in ${PARAMS[@]}
do
if [[ ${PARAM:0:1} != '-' ]]; then
PARAMS=("${PARAMS[@]/$PARAM}") # Quotes when working with strings
fi
done

PARAMS=${PARAMS[@]}

HOST="${PWD##*/}"

# Find the first linked site for the current dir, if one exists
for linkname in ~/.config/valet/Sites/*; do
if [[ "$(readlink $linkname)" = "$PWD" ]]
then
HOST="${linkname##*/}"
break
fi
done

TLD=$("$PHP" "$DIR/cli/valet.php" tld)

# Decide the correct PORT: uses 60 for secure, else 80
if grep --quiet --no-messages 443 ~/.config/valet/Nginx/$HOST*
then
if [[ $SECURED -eq 0 ]]; then
PORT=60
else
PORT=80
fi

# Lowercase the host to match how the rest of our domains are looked up
HOST=$(echo "$HOST" | tr '[:upper:]' '[:lower:]')

sudo -u "$USER" "$BREW_PREFIX/bin/ngrok" http "$HOST.$TLD:$PORT" --host-header=rewrite $PARAMS

exit
Expand All @@ -102,48 +103,34 @@ then
then

# expose
# Check for parameters to pass through to Expose (these will start with '-' or '--')
PARAMS=(${@:2})
for PARAM in ${PARAMS[@]}
do
if [[ ${PARAM:0:1} != '-' ]]; then
PARAMS=("${PARAMS[@]/$PARAM}") #Quotes when working with strings
fi
done

PARAMS=${PARAMS[@]}

HOST="${PWD##*/}"

# Find the first linked site for the current dir, if one exists
for linkname in ~/.config/valet/Sites/*; do
if [[ "$(readlink $linkname)" = "$PWD" ]]
then
HOST="${linkname##*/}"
break
fi
done

TLD=$("$PHP" "$DIR/cli/valet.php" tld)

# Decide the correct PORT: uses 443 for secure, else 80
if grep --quiet --no-messages 443 ~/.config/valet/Nginx/$HOST*
then
if [[ $SECURED -eq 0 ]]; then
PORT=443
else
PORT=80
fi

# Lowercase the host to match how the rest of our domains are looked up
HOST=$(echo "$HOST" | tr '[:upper:]' '[:lower:]')

sudo -u "$USER" expose share "$HOST.$TLD:$PORT" $PARAMS

exit

elif [[ $SHARETOOL = "cloudflared" ]]
then
# cloudflared
if [[ $SECURED -eq 0 ]]; then
SCHEME="https"
else
SCHEME="http"
fi

sudo -u "$USER" cloudflared tunnel --no-tls-verify --url "$SCHEME://localhost" \
--http-host-header "$HOST.$TLD" $PARAMS

exit

else
echo ''
echo "Please use 'valet share-tool ngrok' or 'valet share-tool expose'"
echo "Please use 'valet share-tool cloudflared', 'valet share-tool expose' or 'valet share-tool ngrok'"
echo "to set your preferred share tool."
exit
fi
Expand Down

0 comments on commit 3f30051

Please sign in to comment.