Skip to content

feat: server install deploy keys - #75

Merged
loadinglucian merged 9 commits into
mainfrom
feat/server-install-deploy-keys
Nov 9, 2025
Merged

feat: server install deploy keys#75
loadinglucian merged 9 commits into
mainfrom
feat/server-install-deploy-keys

Conversation

@loadinglucian

@loadinglucian loadinglucian commented Nov 9, 2025

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features

    • Deploy keys are now generated and returned during server installation for easier verification.
  • Bug Fixes / Validation

    • Server info now enforces permission checks (root or sudo) alongside distribution validation.
    • Server name format is validated for allowed characters.
  • Refactor

    • Installation flow unified for Debian/Ubuntu with modular setup steps.
    • Demo-site setup now requires an existing deployer user (no automatic creation).
  • Documentation

    • Validation descriptions updated to include permission checks.

- Validate server names contain only letters, numbers, hyphens, and underscores
- Improve user experience with clear validation error messages
- Require deployer user to already exist in demo-site.sh
- Remove user creation logic from demo site playbook
- Update output to reflect 'existing' deployer user status
- Better separation of installation phases
- Generate SSH deploy keys automatically during server installation
- Setup deployer user with proper permissions and group membership
- Configure PHP-FPM and Caddy integration with deployer user
- Return deploy public key for Git provider configuration
- Add comprehensive user and directory permission setup
- Show generated deploy public key for Git provider configuration
- Pass server name to installation playbook for SSH key comments
- Improve user experience with clear next steps after installation
- Consolidate server-install.sh by removing duplicate code between Ubuntu/Debian branches
- Remove DEPLOYER_FAMILY environment variable and related helper functions
- Update playbook documentation to clarify Ubuntu/Debian-only support
- Hardcode debian family values (www-data user, php8.4-fpm service)
- Remove distribution family branching from demo-site.sh
- Update PlaybooksTrait documentation
Add validateServerPermissions() method to ServersTrait and integrate
it into getServerInfo() to ensure servers have root or sudo permissions
before proceeding with operations that require elevated privileges.
Remove duplicate distribution and permissions validation code from
ServerInstallCommand. Now uses getServerInfo() which handles both
validations centrally, reducing code duplication and improving
maintainability.
Update comments across server commands to mention that getServerInfo()
validates both distribution and permissions, not just distribution.
@coderabbitai

coderabbitai Bot commented Nov 9, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

Refactors server installation and demo-site playbooks to require/validate an existing deployer user, add server permission validation, introduce deploy key generation/capture and DEPLOYER_SERVER_NAME, and modularize server-install steps; several CLI command comments/docblocks were updated to reflect permission validation.

Changes

Cohort / File(s) Summary
CLI comment/doc updates
app/Console/Server/ServerAddCommand.php, app/Console/Server/ServerInfoCommand.php, app/Console/Server/ServerLogsCommand.php, app/Console/Server/ServerProvisionDigitalOceanCommand.php, app/Traits/PlaybooksTrait.php
Comments and docblock text updated to state server info validation includes "distribution & permissions" and to clarify accepted DEPLOYER_DISTRO values (ubuntu
Server install command
app/Console/Server/ServerInstallCommand.php
Reworked install verification: removed DEPLOYER_FAMILY, added DEPLOYER_SERVER_NAME, destructures server info (distro & permissions), captures deploy_public_key from playbook output, refactored verifyInstallation() to accept optional deployKey and return dynamic next-steps lines.
Server info & validation trait
app/Traits/ServersTrait.php
getServerInfo() now validates distribution and permissions; added `validateServerPermissions(array $info): array
Demo site playbook
playbooks/demo-site.sh
Replaced automatic deployer creation with require_deployer_user() (validate existing user); removed PHP-FPM user/service discovery and dynamic user/group management; removed DEPLOYER_FAMILY checks; changed YAML output deployer_user value to existing.
Server-install playbook
playbooks/server-install.sh
Modularized install with setup_repositories(), ensure_deployer_user(), setup_deploy_user(), configure_deployer_groups(), setup_deploy_key(), setup_deploy_directories(); unified Ubuntu/Debian handling, added deploy key generation and deploy_public_key in output, removed DEPLOYER_FAMILY in favor of DEPLOYER_SERVER_NAME.

Sequence Diagram(s)

sequenceDiagram
    participant CLI as ServerInstallCommand
    participant Playbook as server-install.sh
    participant Server as Remote Server

    CLI->>Playbook: run with DEPLOYER_SERVER_NAME, DEPLOYER_DISTRO, DEPLOYER_PERMS
    Playbook->>Playbook: setup_repositories()
    Playbook->>Playbook: ensure_deployer_user()
    Playbook->>Playbook: setup_deploy_user()
    Playbook->>Playbook: configure_deployer_groups()
    Playbook->>Playbook: setup_deploy_directories()
    Playbook->>Playbook: setup_deploy_key()
    Playbook->>Server: install packages & configure services

    rect rgb(200,220,255)
      Note over Playbook,Server: Deploy key generation
      Playbook->>Server: write /home/deployer/.ssh/id_rsa.pub
      Server-->>Playbook: return deploy_public_key
    end

    Playbook-->>CLI: YAML output (includes deploy_public_key)
    CLI->>CLI: extract deploy_public_key
    CLI->>CLI: verifyInstallation(url, deployKey?)
    CLI-->>CLI: produce Next steps including deploy key block
Loading
sequenceDiagram
    participant CLI as DemoSiteCommand
    participant Playbook as demo-site.sh
    participant Server as Remote Server

    rect rgb(255,220,220)
      Note over Playbook: Old (removed) flow
      Playbook->>Playbook: create_deployer_user()  -- removed
      Playbook->>Server: manage PHP-FPM users/services  -- removed
    end

    rect rgb(220,255,220)
      Note over Playbook: New flow (validation)
      CLI->>Playbook: run demo-site
      Playbook->>Server: require_deployer_user() (verify exists)
      alt user exists
        Playbook->>Playbook: continue demo setup
      else user missing
        Playbook-->>CLI: error -> instruct to run server:install
      end
    end

    Playbook-->>CLI: YAML output (deployer_user: existing)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Pay special attention to:
    • app/Console/Server/ServerInstallCommand.php — threading of optional deployKey through verification and next-steps formatting.
    • app/Traits/ServersTrait.php — correctness of permission checks and name validation path/return semantics.
    • playbooks/server-install.sh & playbooks/demo-site.sh — distro-conditional repository setup, deploy key generation/output format, and removal of previous user/service management paths.

Possibly related PRs

  • bigpixelrocket/deployer-php#68 — overlapping changes touching ServerInstallCommand, ServersTrait and playbooks (install/verification and server-info validation).
  • bigpixelrocket/deployer-php#62 — related edits to ServerInfoCommand and server info validation surface.
  • bigpixelrocket/deployer-php#65 — related work on distribution handling and ServerInfoCommand adjustments.

Poem

🐰 Hops and keys, a tidy tweak,

No family flags, just names we seek.
Permissions checked, the deployer found,
Keys returned and steps laid down.
A rabbit cheers — the deploy's sound! 🥕🔑

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: server install deploy keys' clearly and concisely describes the primary change: adding deploy key functionality to the server installation process.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/server-install-deploy-keys

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
playbooks/server-install.sh (2)

45-51: Consider extracting shared helper to a common library.

The run_cmd() function is duplicated in both playbooks/server-install.sh and playbooks/demo-site.sh. This violates the DRY principle.

Consider extracting common playbook helpers to a shared library file that both playbooks can source, e.g., playbooks/common.sh:

#!/usr/bin/env bash
# playbooks/common.sh

run_cmd() {
	if [[ $DEPLOYER_PERMS == 'root' ]]; then
		"$@"
	else
		sudo -n "$@"
	fi
}

Then source it in both playbooks:

# Source common helpers
source "$(dirname "${BASH_SOURCE[0]}")/common.sh"

As per coding guidelines


186-285: Consider splitting this large function.

The install_all_packages() function handles multiple distinct responsibilities: prerequisites, repositories, system utilities, main packages, and PHP configuration. While functional, this could be split into smaller, focused functions for better maintainability.

Example structure:

install_all_packages() {
	install_prerequisites
	setup_repositories
	update_package_lists
	install_system_utilities
	install_main_packages
	install_and_configure_php
}
app/Traits/ServersTrait.php (1)

62-70: Consider short-circuiting on first validation failure.

The current implementation calls both validation methods even if the first fails, potentially displaying multiple error messages to the user.

Apply this diff to short-circuit on first failure:

     // Validate server distribution and permissions
     $distroResult = $this->validateServerDistribution($info);
+    if (is_int($distroResult)) {
+        return Command::FAILURE;
+    }
+    
     $permissionsResult = $this->validateServerPermissions($info);
-
-    if (is_int($distroResult) || is_int($permissionsResult)) {
+    if (is_int($permissionsResult)) {
         return Command::FAILURE;
     }

     return $info;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ed6759c and a05dc13.

📒 Files selected for processing (9)
  • app/Console/Server/ServerAddCommand.php (1 hunks)
  • app/Console/Server/ServerInfoCommand.php (1 hunks)
  • app/Console/Server/ServerInstallCommand.php (7 hunks)
  • app/Console/Server/ServerLogsCommand.php (1 hunks)
  • app/Console/Server/ServerProvisionDigitalOceanCommand.php (1 hunks)
  • app/Traits/PlaybooksTrait.php (1 hunks)
  • app/Traits/ServersTrait.php (4 hunks)
  • playbooks/demo-site.sh (4 hunks)
  • playbooks/server-install.sh (9 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.php

📄 CodeRabbit inference engine (.cursor/rules/00-main.mdc)

**/*.php: Eliminate single-use methods: inline if a method is called only once
Cache computed values: initialize expensive calculations in the constructor
Avoid method call overhead: prefer direct property access when appropriate

Files:

  • app/Console/Server/ServerProvisionDigitalOceanCommand.php
  • app/Console/Server/ServerLogsCommand.php
  • app/Traits/ServersTrait.php
  • app/Traits/PlaybooksTrait.php
  • app/Console/Server/ServerInfoCommand.php
  • app/Console/Server/ServerAddCommand.php
  • app/Console/Server/ServerInstallCommand.php
🧬 Code graph analysis (4)
app/Traits/ServersTrait.php (2)
app/Services/IOService.php (1)
  • info (474-477)
app/Contracts/BaseCommand.php (1)
  • nay (184-188)
playbooks/demo-site.sh (1)
playbooks/server-install.sh (1)
  • run_cmd (45-51)
playbooks/server-install.sh (1)
playbooks/demo-site.sh (1)
  • run_cmd (36-42)
app/Console/Server/ServerInstallCommand.php (2)
app/Services/IOService.php (2)
  • info (474-477)
  • promptSpin (428-441)
app/Traits/PlaybooksTrait.php (1)
  • executePlaybook (45-180)
🪛 GitHub Check: phpstan
app/Console/Server/ServerInstallCommand.php

[failure] 83-83:
Parameter #4 $playbookVars of method Bigpixelrocket\DeployerPHP\Console\Server\ServerInstallCommand::executePlaybook() expects array<string, string>, array<string, mixed> given.

🔇 Additional comments (15)
app/Console/Server/ServerProvisionDigitalOceanCommand.php (1)

167-167: LGTM!

The comment accurately reflects the expanded validation scope that now includes permissions alongside distribution validation.

app/Console/Server/ServerLogsCommand.php (1)

65-65: LGTM!

The comment update correctly reflects the enhanced validation logic.

app/Traits/PlaybooksTrait.php (1)

37-37: LGTM!

The documentation now explicitly lists the supported distributions, improving clarity.

playbooks/server-install.sh (6)

136-184: LGTM!

The modular repository setup function properly handles both Ubuntu and Debian distributions with appropriate idempotency checks.


302-313: LGTM!

The deployer user creation logic is properly idempotent and uses appropriate flags.


315-355: LGTM!

Proper group configuration with necessary service restarts to apply membership changes. Good handling of the www-data user potentially not existing.


388-441: LGTM!

The SSH key generation follows security best practices:

  • Uses modern ed25519 algorithm
  • Sets correct permissions (700/.ssh, 600/private, 644/public)
  • Properly idempotent with existence checks
  • Empty passphrase is appropriate for automated deployment scenarios

443-481: LGTM!

The directory setup properly enforces ownership and permissions, with 750 (rwxr-x---) allowing the deployer user and group members (caddy/www-data) appropriate access.


509-550: LGTM!

The main execution flow correctly sequences the installation steps and captures the deployment key for downstream verification.

playbooks/demo-site.sh (2)

47-57: LGTM!

The precondition validation properly checks for the deployer user and provides clear guidance to run server:install first.


112-113: LGTM!

The hardcoded socket path is correct for both Ubuntu and Debian distributions, simplifying the code appropriately.

app/Console/Server/ServerAddCommand.php (1)

87-87: LGTM!

The comment accurately reflects the expanded validation.

app/Console/Server/ServerInfoCommand.php (1)

59-59: LGTM!

The comment correctly describes the enhanced validation behavior.

app/Traits/ServersTrait.php (2)

102-119: LGTM!

The permission validation correctly enforces root or sudo access requirements with appropriate error handling.


350-353: LGTM!

The format validation prevents potentially problematic characters in server names, improving security and reliability.

Comment on lines +70 to 87
[
'distro' => $distro,
'permissions' => $permissions,
] = $info;

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

$result = $this->executePlaybook(
$server,
'server-install',
'Installing server...',
[
'DEPLOYER_DISTRO' => $distro,
'DEPLOYER_FAMILY' => $family,
'DEPLOYER_PERMS' => $permissions,
'DEPLOYER_SERVER_NAME' => $server->name,
],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Serialize permissions before passing to playbook

executePlaybook() builds env vars with escapeshellarg((string) $value). When $permissions is the structured array returned by getServerInfo(), this cast produces the literal string "Array", so the playbook never receives the actual permission data and phpstan flags the type mismatch. Normalize the permissions to a string (e.g., JSON) before calling the playbook:

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

+        if (!is_string($permissions)) {
+            try {
+                $permissions = json_encode($permissions, JSON_THROW_ON_ERROR);
+            } catch (\JsonException $e) {
+                $this->io->error('Failed to encode server permissions: ' . $e->getMessage());
+
+                return Command::FAILURE;
+            }
+        }
...
-                'DEPLOYER_PERMS' => $permissions,
+                'DEPLOYER_PERMS' => $permissions,

This keeps the playbook contract intact and resolves the phpstan error.

🧰 Tools
🪛 GitHub Check: phpstan

[failure] 83-83:
Parameter #4 $playbookVars of method Bigpixelrocket\DeployerPHP\Console\Server\ServerInstallCommand::executePlaybook() expects array<string, string>, array<string, mixed> given.

🤖 Prompt for AI Agents
In app/Console/Server/ServerInstallCommand.php around lines 70 to 87, the code
passes $permissions (an array) directly into the playbook env which casts to the
literal "Array"; serialize the permissions to a string before passing them (for
example: $permissions = json_encode($permissions, JSON_THROW_ON_ERROR) or
fallback json_encode($permissions) if exceptions are not desired) and then pass
that serialized string as DEPLOYER_PERMS in the executePlaybook env array so the
playbook receives the real data and phpstan type mismatch is resolved.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
app/Console/Server/ServerInstallCommand.php (1)

70-76: Verify permissions is serialized as a string (duplicate concern from past review)

The past review identified that executePlaybook expects array<string, string> but $permissions extracted from $info may be an array structure, which would be cast to the literal string "Array" when passed to the playbook.

The docblock at line 76 asserts @var string $permissions, but this doesn't guarantee the actual type from getServerInfo(). If the server-info playbook now returns permissions as a JSON string or if validation converts it, this is resolved. Otherwise, the phpstan error and functional issue remain.

Run the following script to verify how permissions are returned from getServerInfo and server-info playbook:

#!/bin/bash
# Check how permissions are structured in getServerInfo return and server-info playbook output

# Find server-info playbook to see what it outputs for permissions
fd -t f "server-info.sh" --exec cat {}

# Check getServerInfo implementation to see if it transforms permissions
ast-grep --pattern $'function getServerInfo($$$) {
  $$$
}'

# Check for any JSON encoding or string conversion of permissions
rg -nP --type=php 'permissions.*json_encode|json_encode.*permissions' -A 3 -B 3

Also applies to: 88-88, 112-112

🧹 Nitpick comments (1)
app/Console/Server/ServerInstallCommand.php (1)

130-132: Consider trimming whitespace from deploy key (optional)

The deploy key extraction logic properly validates that deploy_public_key exists, is a string, and is not the placeholder 'unknown'. However, it doesn't handle potential leading/trailing whitespace that might come from the playbook output.

Consider applying this diff to trim whitespace:

-        $deployKey = isset($result['deploy_public_key']) && is_string($result['deploy_public_key']) && $result['deploy_public_key'] !== 'unknown'
-            ? $result['deploy_public_key']
+        $deployKey = isset($result['deploy_public_key']) && is_string($result['deploy_public_key']) && trim($result['deploy_public_key']) !== '' && $result['deploy_public_key'] !== 'unknown'
+            ? trim($result['deploy_public_key'])
             : null;

This also guards against empty strings while removing any accidental whitespace.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a05dc13 and db3b410.

📒 Files selected for processing (1)
  • app/Console/Server/ServerInstallCommand.php (8 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.php

📄 CodeRabbit inference engine (.cursor/rules/00-main.mdc)

**/*.php: Eliminate single-use methods: inline if a method is called only once
Cache computed values: initialize expensive calculations in the constructor
Avoid method call overhead: prefer direct property access when appropriate

Files:

  • app/Console/Server/ServerInstallCommand.php
🧬 Code graph analysis (1)
app/Console/Server/ServerInstallCommand.php (3)
app/Services/IOService.php (2)
  • info (474-477)
  • promptSpin (428-441)
app/Traits/PlaybooksTrait.php (1)
  • executePlaybook (45-180)
app/Traits/ServersTrait.php (2)
  • validateServerDistribution (72-93)
  • ServersTrait (26-386)
🔇 Additional comments (3)
app/Console/Server/ServerInstallCommand.php (3)

89-89: LGTM - DEPLOYER_SERVER_NAME addition

The addition of DEPLOYER_SERVER_NAME properly passes the server name to the playbook. The value from $server->name is correctly typed and aligns with the AI summary indicating DEPLOYER_FAMILY was replaced with DEPLOYER_SERVER_NAME.


169-169: LGTM - Method signature enhancement

The addition of the optional $deployKey parameter to verifyInstallation is well-typed with ?string and maintains backward compatibility while supporting the new deploy key feature.


197-214: LGTM - Dynamic next steps output

The refactoring of the verification result from static to dynamic next steps is well-executed. The code:

  • Builds a base set of instructions that always apply
  • Conditionally includes the deploy key with clear user guidance when available
  • Uses appropriate formatting with color tags for readability
  • Maintains the existing return structure while making it more flexible

@loadinglucian
loadinglucian merged commit 94eb2f9 into main Nov 9, 2025
5 checks passed
@loadinglucian
loadinglucian deleted the feat/server-install-deploy-keys branch November 9, 2025 10:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant