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..1f4fea28 --- /dev/null +++ b/app/Enums/Distribution.php @@ -0,0 +1,49 @@ + '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 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..70f41b9b --- /dev/null +++ b/app/Enums/DistributionFamily.php @@ -0,0 +1,19 @@ + 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