22
33namespace Modules \Platform \Console ;
44
5- use App \Enums \ActivityAction ;
6- use App \Traits \ActivityTrait ;
7- use Exception ;
85use Illuminate \Console \Command ;
9- use Illuminate \Support \Str ;
106use Modules \Platform \Models \Server ;
11- use Modules \Platform \Services \ServerSSHService ;
7+ use Modules \Platform \Services \ServerAcmeSetupService ;
128
139/**
14- * One-time acme.sh setup on a Hestia server.
10+ * ACME setup entrypoint for a Hestia server.
1511 *
1612 * Creates the 'asterossl' user, installs acme.sh, registers a Let's Encrypt
17- * account, and uploads the SSL helper scripts. This is a standalone command
18- * (not a provisioning step) — run it once per server before provisioning websites.
13+ * account, and uploads the SSL helper scripts. This command remains available
14+ * for manual repair runs even though standard server provisioning now performs
15+ * the same setup automatically.
1916 */
2017class ServerSetupAcmeCommand extends Command
2118{
22- use ActivityTrait;
23-
2419 protected $ signature = 'platform:server:setup-acme
2520 {server_id : The ID of the server to configure}
2621 {--force : Re-upload scripts even if server is already configured} ' ;
2722
2823 protected $ description = 'Install acme.sh and configure SSL tooling on a Hestia server. ' ;
2924
30- public function handle (ServerSSHService $ sshService ): int
25+ public function handle (ServerAcmeSetupService $ acmeSetupService ): int
3126 {
3227 $ serverId = $ this ->argument ('server_id ' );
3328 $ server = Server::query ()->findOrFail ($ serverId );
@@ -38,109 +33,16 @@ public function handle(ServerSSHService $sshService): int
3833 return self ::SUCCESS ;
3934 }
4035
41- // If already configured but --force is set, skip setup steps and only re-upload scripts
42- if ($ server ->acme_configured && $ this ->option ('force ' )) {
43- $ this ->info (sprintf ('Re-uploading SSL scripts to server "%s" (#%d)... ' , $ server ->name , $ server ->id ));
44-
45- $ this ->uploadHelperScripts ($ sshService , $ server );
46-
47- $ this ->info ('✅ Scripts re-uploaded successfully. ' );
48-
49- return self ::SUCCESS ;
50- }
51-
52- $ this ->info (sprintf ('Setting up acme.sh on server "%s" (#%d)... ' , $ server ->name , $ server ->id ));
53-
54- // Derive email from server name: "Hestia SG1" → "hestia-sg1@astero.net.in"
55- $ acmeEmail = Str::slug ($ server ->name ).'@astero.net.in ' ;
56-
57- // Step 1: Create asterossl user (idempotent — ignores if already exists)
58- $ this ->line ('Creating asterossl user... ' );
59- $ result = $ sshService ->executeCommand (
60- $ server ,
61- 'id asterossl &>/dev/null || useradd --system --shell /bin/bash --create-home --home-dir /home/asterossl asterossl ' ,
62- 120
63- );
64- throw_unless ($ result ['success ' ], Exception::class, 'Failed to create asterossl user: ' .($ result ['message ' ] ?? 'Unknown error ' ));
65-
66- // Step 2: Install acme.sh under asterossl user
67- $ this ->line ('Installing acme.sh... ' );
68- $ result = $ sshService ->executeCommand (
69- $ server ,
70- sprintf (
71- 'sudo -u asterossl -H bash -c "cd /tmp && curl -s https://get.acme.sh | sh -s -- email=%s" 2>&1 ' ,
72- escapeshellarg ($ acmeEmail )
73- ),
74- 180
75- );
76- throw_unless ($ result ['success ' ], Exception::class, 'Failed to install acme.sh: ' .($ result ['message ' ] ?? 'Unknown error ' ));
77-
78- // Step 3: Set default CA to Let's Encrypt and register account
79- $ this ->line ('Setting default CA to Let \'s Encrypt... ' );
80- $ result = $ sshService ->executeCommand (
81- $ server ,
82- 'sudo -u asterossl -H /home/asterossl/.acme.sh/acme.sh --set-default-ca --server letsencrypt 2>&1 ' ,
83- 30
84- );
85- throw_unless ($ result ['success ' ], Exception::class, 'Failed to set default CA: ' .($ result ['message ' ] ?? 'Unknown error ' ));
86-
87- $ this ->line ('Registering Let \'s Encrypt account... ' );
88- $ result = $ sshService ->executeCommand (
89- $ server ,
90- sprintf (
91- 'sudo -u asterossl -H /home/asterossl/.acme.sh/acme.sh --register-account --server letsencrypt -m %s 2>&1 ' ,
92- escapeshellarg ($ acmeEmail )
93- ),
94- 60
95- );
96- throw_unless ($ result ['success ' ], Exception::class, 'Failed to register LE account: ' .($ result ['message ' ] ?? 'Unknown error ' ));
36+ $ this ->info (sprintf (
37+ '%s server "%s" (#%d)... ' ,
38+ $ this ->option ('force ' ) ? 'Refreshing ACME scripts on ' : 'Setting up ACME on ' ,
39+ $ server ->name ,
40+ $ server ->id
41+ ));
9742
98- // Step 5: Create cert storage directory
99- $ this ->line ('Creating SSL store directory... ' );
100- $ result = $ sshService ->executeCommand ($ server , 'sudo -u asterossl -H mkdir -p /home/asterossl/.ssl-store ' , 30 );
101- throw_unless ($ result ['success ' ], Exception::class, 'Failed to create .ssl-store: ' .($ result ['message ' ] ?? 'Unknown error ' ));
102-
103- // Step 6: Upload helper scripts via SSH (base64 encoding — SFTP is unavailable on Hestia)
104- $ this ->line ('Uploading SSL helper scripts... ' );
105- $ this ->uploadHelperScripts ($ sshService , $ server );
106-
107- // Mark server as acme-configured
108- $ server ->acme_configured = true ;
109- $ server ->acme_email = $ acmeEmail ;
110- $ server ->save ();
111-
112- $ successMessage = sprintf ('acme.sh setup completed on server "%s" (email: %s) ' , $ server ->name , $ acmeEmail );
113- $ this ->logActivity ($ server , ActivityAction::UPDATE , $ successMessage );
114- $ this ->info ('✅ ' .$ successMessage );
43+ $ result = $ acmeSetupService ->setup ($ server , (bool ) $ this ->option ('force ' ));
44+ $ this ->info ($ result ['summary ' ]);
11545
11646 return self ::SUCCESS ;
11747 }
118-
119- /**
120- * Upload SSL helper scripts to the server via SSH (base64 encoded).
121- */
122- private function uploadHelperScripts (ServerSSHService $ sshService , Server $ server ): void
123- {
124- $ scriptsDir = '/usr/local/hestia/data/astero/bin ' ;
125- $ result = $ sshService ->executeCommand ($ server , sprintf ('mkdir -p %s ' , $ scriptsDir ), 30 );
126- throw_unless ($ result ['success ' ], Exception::class, 'Failed to create scripts directory: ' .($ result ['message ' ] ?? 'Unknown error ' ));
127-
128- $ localBinDir = base_path ('hestia/bin ' );
129- $ scripts = ['a-issue-wildcard-ssl ' , 'a-check-wildcard-ssl ' , 'a-renew-wildcard-ssl ' ];
130-
131- foreach ($ scripts as $ script ) {
132- $ localPath = $ localBinDir .'/ ' .$ script ;
133- $ remotePath = $ scriptsDir .'/ ' .$ script ;
134-
135- throw_unless (file_exists ($ localPath ), Exception::class, sprintf ('Local script not found: %s ' , $ localPath ));
136-
137- $ base64Content = base64_encode (file_get_contents ($ localPath ));
138- $ uploadResult = $ sshService ->executeCommand (
139- $ server ,
140- sprintf ('echo %s | base64 -d > %s && chmod 755 %s ' , escapeshellarg ($ base64Content ), $ remotePath , $ remotePath ),
141- 30
142- );
143- throw_unless ($ uploadResult ['success ' ], Exception::class, sprintf ('Failed to upload %s: %s ' , $ script , $ uploadResult ['message ' ] ?? 'Unknown error ' ));
144- }
145- }
14648}
0 commit comments