From 3f57075b74d86dda50e46ae98ca9c793109f9100 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucian=20V=C4=83c=C4=83roiu?= Date: Thu, 6 Nov 2025 17:01:59 +0200 Subject: [PATCH 1/2] feat: add Distribution and DistributionFamily enums Add centralized distribution configuration with support for Ubuntu, Debian, Fedora, CentOS, Rocky, Alma, RHEL, and Amazon Linux distributions. Includes display names, family grouping, and utility methods for playbook integration. --- app/Console/Server/ServerInfoCommand.php | 11 ++- .../ServerProvisionDigitalOceanCommand.php | 2 +- app/Enums/Distribution.php | 62 +++++++++++++++ app/Enums/DistributionFamily.php | 36 +++++++++ .../DigitalOceanAccountService.php | 9 ++- app/Traits/PlaybooksTrait.php | 13 +++- playbooks/server-info.sh | 75 +++++++++++++++---- 7 files changed, 181 insertions(+), 27 deletions(-) create mode 100644 app/Enums/Distribution.php create mode 100644 app/Enums/DistributionFamily.php diff --git a/app/Console/Server/ServerInfoCommand.php b/app/Console/Server/ServerInfoCommand.php index 428458e3..df5a6138 100644 --- a/app/Console/Server/ServerInfoCommand.php +++ b/app/Console/Server/ServerInfoCommand.php @@ -6,6 +6,7 @@ use Bigpixelrocket\DeployerPHP\Contracts\BaseCommand; use Bigpixelrocket\DeployerPHP\DTOs\ServerDTO; +use Bigpixelrocket\DeployerPHP\Enums\Distribution; use Bigpixelrocket\DeployerPHP\Traits\PlaybooksTrait; use Bigpixelrocket\DeployerPHP\Traits\ServersTrait; use Symfony\Component\Console\Attribute\AsCommand; @@ -111,12 +112,10 @@ protected function getServerInfo(ServerDTO $server): array|int */ protected function displayServerInfo(array $info): void { - $distroName = match ($info['distro'] ?? 'unknown') { - 'debian' => 'Debian/Ubuntu', - 'redhat' => 'RedHat/CentOS/Fedora', - 'amazon' => 'Amazon Linux', - default => 'Unknown', - }; + /** @var string $distroSlug */ + $distroSlug = $info['distro'] ?? 'unknown'; + $distribution = Distribution::tryFrom($distroSlug); + $distroName = $distribution?->displayName() ?? 'Unknown'; $permissionsText = match ($info['permissions'] ?? 'none') { 'root' => 'root', diff --git a/app/Console/Server/ServerProvisionDigitalOceanCommand.php b/app/Console/Server/ServerProvisionDigitalOceanCommand.php index 887fc77d..2663528a 100644 --- a/app/Console/Server/ServerProvisionDigitalOceanCommand.php +++ b/app/Console/Server/ServerProvisionDigitalOceanCommand.php @@ -281,7 +281,7 @@ protected function gatherProvisioningDeets(array $accountData): ?array fn ($validate) => $this->io->promptSelect( label: 'Select OS image:', options: $accountData['images'], - hint: 'Ubuntu and Debian only' + hint: 'Supported Linux distributions' ), fn ($value) => $this->validateDigitalOceanDropletImage($value, $accountData['images']) ); diff --git a/app/Enums/Distribution.php b/app/Enums/Distribution.php new file mode 100644 index 00000000..fed6c0fc --- /dev/null +++ b/app/Enums/Distribution.php @@ -0,0 +1,62 @@ + 'Ubuntu', + self::DEBIAN => 'Debian', + self::FEDORA => 'Fedora', + self::CENTOS => 'CentOS', + self::ROCKY => 'Rocky Linux', + self::ALMA => 'AlmaLinux', + self::RHEL => 'Red Hat Enterprise Linux', + self::AMAZON => 'Amazon Linux', + }; + } + + /** + * Get distribution family. + */ + public function family(): DistributionFamily + { + return match ($this) { + self::UBUNTU, self::DEBIAN => DistributionFamily::DEBIAN, + self::FEDORA => DistributionFamily::FEDORA, + self::CENTOS, self::ROCKY, self::ALMA, self::RHEL => DistributionFamily::REDHAT, + self::AMAZON => DistributionFamily::AMAZON, + }; + } + + /** + * Get all distribution slugs as array. + * + * @return array + */ + public static function slugs(): array + { + return array_map(fn (self $dist) => $dist->value, self::cases()); + } +} diff --git a/app/Enums/DistributionFamily.php b/app/Enums/DistributionFamily.php new file mode 100644 index 00000000..4c6bb101 --- /dev/null +++ b/app/Enums/DistributionFamily.php @@ -0,0 +1,36 @@ +value}"; + } + + /** + * Get all family names as array. + * + * @return array + */ + public static function names(): array + { + return array_map(fn (self $family) => $family->value, self::cases()); + } +} diff --git a/app/Services/DigitalOcean/DigitalOceanAccountService.php b/app/Services/DigitalOcean/DigitalOceanAccountService.php index e618f0e8..0fd4afb2 100644 --- a/app/Services/DigitalOcean/DigitalOceanAccountService.php +++ b/app/Services/DigitalOcean/DigitalOceanAccountService.php @@ -4,6 +4,7 @@ namespace Bigpixelrocket\DeployerPHP\Services\DigitalOcean; +use Bigpixelrocket\DeployerPHP\Enums\Distribution; use DigitalOceanV2\Entity\Image as ImageEntity; use DigitalOceanV2\Entity\Region as RegionEntity; use DigitalOceanV2\Entity\Size as SizeEntity; @@ -79,7 +80,7 @@ public function getAvailableSizes(): array } /** - * Get available OS images (filtered to Ubuntu and Debian only). + * Get available OS images (filtered to supported distributions). * * @return array Array of image slug => description */ @@ -96,11 +97,11 @@ public function getAvailableImages(): array $options = []; foreach ($images as $image) { /** @var ImageEntity $image */ - // Filter to only Ubuntu and Debian distributions + // Filter to supported distributions if ($image->status === 'available' && $image->public === true) { $distribution = strtolower($image->distribution ?? ''); - if (in_array($distribution, ['ubuntu', 'debian'], true)) { + if (in_array($distribution, Distribution::slugs(), true)) { $slug = $image->slug; if ($slug !== null && $slug !== '') { $options[$slug] = "{$image->distribution} {$image->name}"; @@ -109,6 +110,8 @@ public function getAvailableImages(): array } } + asort($options); + return $options; } catch (\Throwable $e) { throw new \RuntimeException('Failed to fetch images: ' . $e->getMessage(), 0, $e); diff --git a/app/Traits/PlaybooksTrait.php b/app/Traits/PlaybooksTrait.php index 1ae79802..d7c18d3c 100644 --- a/app/Traits/PlaybooksTrait.php +++ b/app/Traits/PlaybooksTrait.php @@ -32,6 +32,12 @@ trait PlaybooksTrait * Playbooks write YAML output to a temp file (DEPLOYER_OUTPUT_FILE). * Displays errors via IOService and returns Command::FAILURE on any error. * + * Standard playbook environment variables: + * - DEPLOYER_OUTPUT_FILE: Output file path (provided automatically) + * - DEPLOYER_DISTRO: Exact distribution - caller must provide via $playbookVars + * - DEPLOYER_FAMILY: Distribution family - caller must provide via $playbookVars + * - DEPLOYER_PERMS: User permissions (root|sudo|none) - caller must provide via $playbookVars + * * @param string $playbookName Playbook name without .sh extension (e.g., 'server-info', 'install-php', etc) * @param array $playbookVars Playbook variables to pass to the playbook (don't pass sensitive data) * @param bool $streamOutput Stream output in real-time (true) or show spinner and display all at end (false) @@ -44,7 +50,7 @@ protected function executePlaybook( array $playbookVars = [], bool $streamOutput = false ): array|int { - $projectRoot = dirname(__DIR__, 3); + $projectRoot = dirname(__DIR__, 2); $playbookPath = $projectRoot . '/playbooks/' . $playbookName . '.sh'; $scriptContents = $this->fs->readFile($playbookPath); @@ -74,6 +80,11 @@ protected function executePlaybook( try { if ($streamOutput) { // Streaming output in real-time + $this->io->writeln([ + ''.$spinnerMessage.'', + '', + ]); + $result = $this->ssh->executeCommand( $server, $scriptWithVars, diff --git a/playbooks/server-info.sh b/playbooks/server-info.sh index b53f1ddf..a67e1757 100755 --- a/playbooks/server-info.sh +++ b/playbooks/server-info.sh @@ -2,13 +2,14 @@ # # Gather Server Information # ---- -# This playbook detects distribution, permissions, and listening services. +# This playbook detects distribution, family, permissions, and listening services. # # Required Environment Variables: # DEPLOYER_OUTPUT_FILE - Output file path (provided automatically) # # Returns YAML with: -# - distro: debian|redhat|amazon|unknown +# - distro: ubuntu|debian|fedora|centos|rocky|alma|rhel|amazon|unknown +# - family: debian|fedora|redhat|amazon|unknown # - permissions: root|sudo|none # - ports: map of port numbers to process names @@ -17,35 +18,75 @@ export DEBIAN_FRONTEND=noninteractive # Validation if [[ -z $DEPLOYER_OUTPUT_FILE ]]; then - echo "Error: DEPLOYER_OUTPUT_FILE environment variable is required" + echo "Error: DEPLOYER_OUTPUT_FILE required" exit 1 fi # # Detect Linux Distribution # ---- -# Returns: debian|redhat|amazon|unknown +# Returns: exact distribution name (ubuntu|debian|fedora|centos|rocky|alma|rhel|amazon|unknown) detect_distro() { local distro='unknown' if [[ -f /etc/os-release ]]; then - if grep -qi 'amazon' /etc/os-release; then - distro='amazon' - elif grep -qi 'debian\|ubuntu' /etc/os-release; then - distro='debian' - elif grep -qi 'fedora\|centos\|rhel' /etc/os-release; then - distro='redhat' + # Try to get the ID field first for exact distro name + if grep -q '^ID=' /etc/os-release; then + distro=$(grep '^ID=' /etc/os-release | cut -d'=' -f2 | tr -d '"' | tr -d "'") + + # Normalize some common variations + case $distro in + almalinux) distro='alma' ;; + rocky | rockylinux) distro='rocky' ;; + rhel | redhat) distro='rhel' ;; + amzn) distro='amazon' ;; + esac fi elif [[ -f /etc/redhat-release ]]; then - distro='redhat' + # Fallback for older systems without /etc/os-release + if grep -qi 'centos' /etc/redhat-release; then + distro='centos' + elif grep -qi 'red hat' /etc/redhat-release; then + distro='rhel' + else + distro='unknown' + fi elif [[ -f /etc/debian_version ]]; then + # Fallback for Debian systems distro='debian' fi echo "$distro" } +# +# Detect Distribution Family +# ---- +# Returns: debian|fedora|redhat|amazon|unknown + +detect_family() { + local distro=$1 + local family='unknown' + + case $distro in + ubuntu | debian) + family='debian' + ;; + fedora) + family='fedora' + ;; + centos | rocky | alma | rhel) + family='redhat' + ;; + amazon) + family='amazon' + ;; + esac + + echo "$family" +} + # # Check User Permissions # ---- @@ -78,19 +119,19 @@ run_cmd() { # ---- ensure_tools() { - local distro=$1 perms=$2 + local family=$1 perms=$2 export DEPLOYER_PERMS=$perms # If the command is already installed, return command -v ss > /dev/null 2>&1 && return 0 command -v netstat > /dev/null 2>&1 && return 0 - case $distro in + case $family in debian) run_cmd apt-get update -q 2> /dev/null run_cmd apt-get install -y -q iproute2 2> /dev/null ;; - redhat | amazon) + fedora | redhat | amazon) run_cmd yum install -y -q iproute 2> /dev/null \ || run_cmd dnf install -y -q iproute 2> /dev/null ;; @@ -142,25 +183,27 @@ get_listening_services() { # ---- main() { - local distro permissions + local distro family permissions # # Gather basic info echo "✓ Detecting distribution..." distro=$(detect_distro) + family=$(detect_family "$distro") echo "✓ Checking permissions..." permissions=$(check_permissions) echo "✓ Cataloging services..." - ensure_tools "$distro" "$permissions" + ensure_tools "$family" "$permissions" # # Output YAML to file if ! cat > "$DEPLOYER_OUTPUT_FILE" <<- EOF; then distro: $distro + family: $family permissions: $permissions ports: EOF From e4a96fa8efc7d3fcd0491aa02b4b6f98020c51ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucian=20V=C4=83c=C4=83roiu?= Date: Thu, 6 Nov 2025 20:15:25 +0200 Subject: [PATCH 2/2] refactor(enums): remove unused methods from Distribution enums Removed playbookName() and names() from DistributionFamily, and family() from Distribution. These methods had no current usage and will be added back when actually needed per YAGNI principles. --- app/Enums/Distribution.php | 13 ------------- app/Enums/DistributionFamily.php | 17 ----------------- 2 files changed, 30 deletions(-) diff --git a/app/Enums/Distribution.php b/app/Enums/Distribution.php index fed6c0fc..1f4fea28 100644 --- a/app/Enums/Distribution.php +++ b/app/Enums/Distribution.php @@ -37,19 +37,6 @@ public function displayName(): string }; } - /** - * Get distribution family. - */ - public function family(): DistributionFamily - { - return match ($this) { - self::UBUNTU, self::DEBIAN => DistributionFamily::DEBIAN, - self::FEDORA => DistributionFamily::FEDORA, - self::CENTOS, self::ROCKY, self::ALMA, self::RHEL => DistributionFamily::REDHAT, - self::AMAZON => DistributionFamily::AMAZON, - }; - } - /** * Get all distribution slugs as array. * diff --git a/app/Enums/DistributionFamily.php b/app/Enums/DistributionFamily.php index 4c6bb101..70f41b9b 100644 --- a/app/Enums/DistributionFamily.php +++ b/app/Enums/DistributionFamily.php @@ -16,21 +16,4 @@ enum DistributionFamily: string case REDHAT = 'redhat'; case AMAZON = 'amazon'; - /** - * Get playbook name for this family. - */ - public function playbookName(): string - { - return "server-install-{$this->value}"; - } - - /** - * Get all family names as array. - * - * @return array - */ - public static function names(): array - { - return array_map(fn (self $family) => $family->value, self::cases()); - } }