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
11 changes: 5 additions & 6 deletions app/Console/Server/ServerInfoCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'])
);
Expand Down
49 changes: 49 additions & 0 deletions app/Enums/Distribution.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

declare(strict_types=1);

namespace Bigpixelrocket\DeployerPHP\Enums;

/**
* Supported Linux distributions.
*
* Provides centralized distribution configuration and business logic.
*/
enum Distribution: string
{
case UBUNTU = 'ubuntu';
case DEBIAN = 'debian';
case FEDORA = 'fedora';
case CENTOS = 'centos';
case ROCKY = 'rocky';
case ALMA = 'alma';
case RHEL = 'rhel';
case AMAZON = 'amazon';

/**
* Get human-readable display name.
*/
public function displayName(): string
{
return match ($this) {
self::UBUNTU => '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<string>
*/
public static function slugs(): array
{
return array_map(fn (self $dist) => $dist->value, self::cases());
}
}
19 changes: 19 additions & 0 deletions app/Enums/DistributionFamily.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

declare(strict_types=1);

namespace Bigpixelrocket\DeployerPHP\Enums;

/**
* Distribution families for server provisioning.
*
* Groups distributions by their package management and system architecture.
*/
enum DistributionFamily: string
{
case DEBIAN = 'debian';
case FEDORA = 'fedora';
case REDHAT = 'redhat';
case AMAZON = 'amazon';

}
9 changes: 6 additions & 3 deletions app/Services/DigitalOcean/DigitalOceanAccountService.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<string, string> Array of image slug => description
*/
Expand All @@ -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}";
Expand All @@ -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);
Expand Down
13 changes: 12 additions & 1 deletion app/Traits/PlaybooksTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, string> $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)
Expand All @@ -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);

Expand Down Expand Up @@ -74,6 +80,11 @@ protected function executePlaybook(
try {
if ($streamOutput) {
// Streaming output in real-time
$this->io->writeln([
'<fg=cyan>'.$spinnerMessage.'</>',
'',
]);

$result = $this->ssh->executeCommand(
$server,
$scriptWithVars,
Expand Down
75 changes: 59 additions & 16 deletions playbooks/server-info.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
# ----
Expand Down Expand Up @@ -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
;;
Expand Down Expand Up @@ -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
Expand Down