Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 47 additions & 4 deletions app/Console/Site/SiteAddCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ protected function configure(): void
->addOption('repo', null, InputOption::VALUE_REQUIRED, 'Git repository URL')
->addOption('branch', null, InputOption::VALUE_REQUIRED, 'Git branch name')
->addOption('server', null, InputOption::VALUE_REQUIRED, 'Server name')
->addOption('php-version', null, InputOption::VALUE_REQUIRED, 'PHP version to use');
->addOption('php-version', null, InputOption::VALUE_REQUIRED, 'PHP version to use')
->addOption('www-mode', null, InputOption::VALUE_REQUIRED, 'WWW handling mode (redirect-to-root, redirect-to-www)');
}

// ----
Expand Down Expand Up @@ -106,6 +107,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
'repo' => $repo,
'branch' => $branch,
'phpVersion' => $phpVersion,
'wwwMode' => $wwwMode,
] = $siteInfo;

//
Expand Down Expand Up @@ -134,6 +136,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
'DEPLOYER_PERMS' => $permissions,
'DEPLOYER_SITE_DOMAIN' => $domain,
'DEPLOYER_PHP_VERSION' => $phpVersion,
'DEPLOYER_WWW_MODE' => $wwwMode,
],
true
);
Expand Down Expand Up @@ -162,11 +165,19 @@ protected function execute(InputInterface $input, OutputInterface $output): int
// Display next steps
// ----

$displayUrl = ($wwwMode === 'redirect-to-www')
? 'http://www.' . $domain
: 'http://' . $domain;

$this->io->writeln([
'Next steps:',
' • Site is accessible at <fg=cyan>https://' . $domain . '</>',
' • Update <fg=cyan>DNS records</> to point ' . $domain . ' to <fg=cyan>' . $server->host . '</>',
' • Site is accessible at <fg=cyan>' . $displayUrl . '</>',
' • Update <fg=cyan>DNS records</>:',
' - Point <fg=cyan>@</> (root) to <fg=cyan>' . $server->host . '</>',
' - Point <fg=cyan>www</> to <fg=cyan>' . $server->host . '</>',
' • Run <fg=cyan>site:https</> to enable HTTPS once you have your DNS records set up',
' • Deploy your application with <fg=cyan>site:deploy</>',
'',
]);

//
Expand All @@ -179,6 +190,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
'branch' => $branch,
'server' => $server->name,
'php-version' => $phpVersion,
'www-mode' => $wwwMode,
]);

return Command::SUCCESS;
Expand Down Expand Up @@ -293,7 +305,7 @@ private function selectPhpVersion(array $info): string|int
* Gather site details from user input or CLI options.
*
* @param array<string, mixed> $info Server information from serverInfo()
* @return array{domain: string, repo: string, branch: string, phpVersion: string}|null
* @return array{domain: string, repo: string, branch: string, phpVersion: string, wwwMode: string}|null
*/
protected function gatherSiteInfo(array $info): ?array
{
Expand All @@ -313,6 +325,36 @@ protected function gatherSiteInfo(array $info): ?array
return null;
}

// Normalize immediately after input
$domain = $this->normalizeDomain($domain);

//
// Determine WWW handling
// ----

$wwwModes = [
'redirect-to-root' => 'Redirect www to non-www',
'redirect-to-www' => 'Redirect non-www to www',
];

/** @var string|null $wwwMode */
$wwwMode = $this->io->getValidatedOptionOrPrompt(
'www-mode',
fn ($validate) => $this->io->promptSelect(
label: "How should 'www.{$domain}' be handled?",
options: $wwwModes,
default: 'redirect-to-root',
validate: $validate
),
fn ($value) => in_array($value, array_keys($wwwModes), true)
? null
: sprintf("Invalid WWW mode '%s'. Allowed: %s", is_scalar($value) ? $value : gettype($value), implode(', ', array_keys($wwwModes)))
);

if ($wwwMode === null) {
return null;
}

//
// Gather git details
// ----
Expand Down Expand Up @@ -370,6 +412,7 @@ protected function gatherSiteInfo(array $info): ?array
'repo' => $repo,
'branch' => $branch,
'phpVersion' => $phpVersion,
'wwwMode' => $wwwMode,
];
}
}
152 changes: 152 additions & 0 deletions app/Console/Site/SiteHttpsCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
<?php

declare(strict_types=1);

namespace Bigpixelrocket\DeployerPHP\Console\Site;

use Bigpixelrocket\DeployerPHP\Contracts\BaseCommand;
use Bigpixelrocket\DeployerPHP\Traits\PlaybooksTrait;
use Bigpixelrocket\DeployerPHP\Traits\ServersTrait;
use Bigpixelrocket\DeployerPHP\Traits\SitesTrait;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

#[AsCommand(
name: 'site:https',
description: 'Enable HTTPS for a site using Caddy automatic certificates'
)]
class SiteHttpsCommand extends BaseCommand
{
use PlaybooksTrait;
use ServersTrait;
use SitesTrait;

// ----
// Configuration
// ----

protected function configure(): void
{
parent::configure();

$this->addOption('domain', null, InputOption::VALUE_REQUIRED, 'Domain name');
}

// ----
// Execution
// ----

protected function execute(InputInterface $input, OutputInterface $output): int
{
parent::execute($input, $output);

$this->heading('Enable HTTPS');

//
// Select site
// ----

$site = $this->selectSite()
;
if (is_int($site)) {
return $site;
}

//
// Get server for site
// ----

$server = $this->getServerForSite($site);

if (is_int($server)) {
return $server;
}

$this->displayServerDeets($server);

//
// Get server info (verifies SSH connection and validates distribution & permissions)
// ----

$info = $this->serverInfo($server);

if (is_int($info)) {
return $info;
}

[
'distro' => $distro,
'permissions' => $permissions,
] = $info;

/** @var string $distro */
/** @var string $permissions */

//
// Get site configuration
// ----

$config = $this->getSiteConfig($info, $site->domain);

if ($config === null) {
$this->io->warning("Site '{$site->domain}' configuration not found on server");
$this->io->writeln([
'',
'It looks like this site has not been provisioned yet.',
'Run <fg=cyan>site:add</> to provision the site first.',
'',
]);

return Command::SUCCESS;
}

if ($config['php_version'] === 'unknown') {
$this->nay("Could not detect PHP version for '{$site->domain}' from server config; re-provision the site or run server:info to debug.");

return Command::FAILURE;
}

//
// Execute playbook
// ----

$result = $this->executePlaybook(
$server,
'site-https',
'Enabling HTTPS...',
[
'DEPLOYER_DISTRO' => $distro,
'DEPLOYER_PERMS' => $permissions,
'DEPLOYER_SITE_DOMAIN' => $site->domain,
'DEPLOYER_PHP_VERSION' => $config['php_version'],
'DEPLOYER_WWW_MODE' => $config['www_mode'],
],
true
);

if (is_int($result)) {
return $result;
}

$this->yay('HTTPS enabled successfully');

//
// Display next steps
// ----

$displayUrl = ($config['www_mode'] === 'redirect-to-www')
? 'https://www.' . $site->domain
: 'https://' . $site->domain;

$this->io->writeln([
'Your site is now accessible over HTTPS:',
' <fg=cyan>' . $displayUrl . '</>',
'',
]);

return Command::SUCCESS;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
2 changes: 2 additions & 0 deletions app/SymfonyApp.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use Bigpixelrocket\DeployerPHP\Console\Server\ServerRunCommand;
use Bigpixelrocket\DeployerPHP\Console\Site\SiteAddCommand;
use Bigpixelrocket\DeployerPHP\Console\Site\SiteDeleteCommand;
use Bigpixelrocket\DeployerPHP\Console\Site\SiteHttpsCommand;
use Bigpixelrocket\DeployerPHP\Console\Site\SiteListCommand;
use Bigpixelrocket\DeployerPHP\Console\Site\SiteSharedPullCommand;
use Bigpixelrocket\DeployerPHP\Console\Site\SiteSharedPushCommand;
Expand Down Expand Up @@ -166,6 +167,7 @@ private function registerCommands(): void
SiteListCommand::class,
SiteSharedPushCommand::class,
SiteSharedPullCommand::class,
SiteHttpsCommand::class,
];

foreach ($commands as $command) {
Expand Down
54 changes: 54 additions & 0 deletions app/Traits/ServersTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,37 @@ protected function serverInfo(ServerDTO $server): array|int
return $info;
}

/**
* Get configuration for a specific site from server info.
*
* @param array<string, mixed> $info Server information array
* @param string $domain Site domain
* @return array{php_version: string, www_mode: string, https_enabled: bool}|null Returns config or null if not found
*/
protected function getSiteConfig(array $info, string $domain): ?array
{
if (! isset($info['sites_config']) || ! is_array($info['sites_config'])) {
return null;
}

$config = $info['sites_config'][$domain] ?? null;

if (! is_array($config)) {
return null;
}

/** @var mixed $phpVer */
$phpVer = $config['php_version'] ?? 'unknown';
/** @var mixed $mode */
$mode = $config['www_mode'] ?? 'unknown';

return [
'php_version' => is_scalar($phpVer) ? (string) $phpVer : 'unknown',
'www_mode' => is_scalar($mode) ? (string) $mode : 'unknown',
'https_enabled' => filter_var($config['https_enabled'] ?? false, FILTER_VALIDATE_BOOLEAN),
];
}

/**
* Validate that server is running a supported distribution.
*
Expand Down Expand Up @@ -390,6 +421,29 @@ protected function displayServerInfo(array $info): void
}
}
}

// Display Sites Configuration if available
if (isset($info['sites_config']) && is_array($info['sites_config']) && count($info['sites_config']) > 0) {
$sitesItems = [];
foreach (array_keys($info['sites_config']) as $domain) {
$config = $this->getSiteConfig($info, (string) $domain);

if ($config === null) {
continue;
}

$php = $config['php_version'] === 'unknown' ? '?' : $config['php_version'];
$mode = $config['www_mode'] === 'unknown' ? '?' : $config['www_mode'];
$https = $config['https_enabled'] ? '<fg=green>HTTPS</>' : '<fg=yellow>HTTP</>';

$sitesItems[] = "{$domain}: PHP {$php}, {$mode}, {$https}";
}

if (count($sitesItems) > 0) {
$this->io->displayDeets(['Sites Config' => $sitesItems]);
$this->io->writeln('');
}
}
}

/**
Expand Down
16 changes: 16 additions & 0 deletions app/Traits/SitesTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ protected function validateSiteDomain(mixed $domain): ?string
return 'Domain must be a string';
}

$domain = $this->normalizeDomain($domain);

// Check format
$isValid = filter_var($domain, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME) !== false;
if (! $isValid) {
Expand All @@ -155,6 +157,20 @@ protected function validateSiteDomain(mixed $domain): ?string
return null;
}

/**
* Normalize domain name (lowercase and strip www.).
*/
protected function normalizeDomain(string $domain): string
{
$domain = strtolower(trim($domain));

if (str_starts_with($domain, 'www.')) {
$domain = substr($domain, 4);
}

return $domain;
}

/**
* Validate branch name is not empty.
*
Expand Down
Loading