diff --git a/app/Services/Webserver/Nginx.php b/app/Services/Webserver/Nginx.php index 73bb4cfdf..40fbcbf9a 100755 --- a/app/Services/Webserver/Nginx.php +++ b/app/Services/Webserver/Nginx.php @@ -275,7 +275,7 @@ public function version(): string public function logs(): array { - return [ + $logs = [ new ServiceLog( key: 'nginx:error', serviceLabel: 'NGINX', @@ -291,5 +291,21 @@ public function logs(): array target: '/var/log/nginx/access.log', ), ]; + + $sites = $this->service->server->relationLoaded('sites') + ? $this->service->server->sites->sortBy('id') + : $this->service->server->sites()->orderBy('id')->get(['id', 'domain']); + + foreach ($sites as $site) { + $logs[] = new ServiceLog( + key: 'nginx:site:'.$site->id.':error', + serviceLabel: 'NGINX', + label: $site->domain.' error log', + source: ServiceLog::SOURCE_FILE, + target: '/var/log/nginx/'.$site->domain.'-error.log', + ); + } + + return $logs; } } diff --git a/resources/views/ssh/services/webserver/nginx/vhost.mustache b/resources/views/ssh/services/webserver/nginx/vhost.mustache index 822c99c19..9b0819ef3 100644 --- a/resources/views/ssh/services/webserver/nginx/vhost.mustache +++ b/resources/views/ssh/services/webserver/nginx/vhost.mustache @@ -28,7 +28,20 @@ server { listen [::]:80; server_name{{#force_ssl_domains}} {{name}}{{/force_ssl_domains}}; - return 301 https://$host$request_uri; + + {{#verification_key}} + location ^~ /.well-known/vito/{{verification_key}}/ { + allow all; + default_type text/plain; + add_header Cache-Control "no-store" always; + alias /var/lib/vito/verify/{{verification_key}}/; + try_files $uri =404; + } + {{/verification_key}} + + location / { + return 301 https://$host$request_uri; + } } {{/has_force_ssl_redirect}} diff --git a/tests/Feature/ServiceLogsTest.php b/tests/Feature/ServiceLogsTest.php index 35077a103..6cbf3ad7b 100644 --- a/tests/Feature/ServiceLogsTest.php +++ b/tests/Feature/ServiceLogsTest.php @@ -46,6 +46,20 @@ public function test_renders_catalogue_for_installed_services(): void $this->assertContains('php:8.2:user:vito', $keys); } + public function test_nginx_exposes_per_site_error_log(): void + { + $this->actingAs($this->user); + + $response = $this->get(route('logs.services', $this->server)); + + $catalogue = $response->viewData('page')['props']['catalogue']; + $entries = collect($catalogue)->keyBy('key'); + + $key = 'nginx:site:'.$this->site->id.':error'; + $this->assertTrue($entries->has($key)); + $this->assertSame('/var/log/nginx/'.$this->site->domain.'-error.log', $entries[$key]['display_target']); + } + public function test_services_without_has_logs_are_skipped(): void { $this->actingAs($this->user); diff --git a/tests/Feature/Webserver/VerificationBlockTest.php b/tests/Feature/Webserver/VerificationBlockTest.php index 51b9a9bee..8e0c17a1a 100644 --- a/tests/Feature/Webserver/VerificationBlockTest.php +++ b/tests/Feature/Webserver/VerificationBlockTest.php @@ -4,8 +4,10 @@ use App\Actions\Webserver\GenerateNginxConfig; use App\Enums\ServiceStatus; +use App\Enums\SslStatus; use App\Models\HostedDomain; use App\Models\Service; +use App\Models\Ssl; use App\Services\Webserver\Caddy; use Illuminate\Foundation\Testing\RefreshDatabase; use Tests\TestCase; @@ -89,6 +91,40 @@ public function test_caddy_renders_verification_handle_when_key_present(): void $this->assertStringContainsString('root * /var/lib/vito/verify/caddyKey99', $vhost); } + public function test_nginx_force_ssl_redirect_serves_and_exempts_verification_challenge(): void + { + $ssl = Ssl::factory()->create([ + 'server_id' => $this->server->id, + 'site_id' => $this->site->id, + 'status' => SslStatus::CREATED, + 'type' => 'letsencrypt', + 'domains' => [$this->site->domain], + ]); + + HostedDomain::factory()->primary()->create([ + 'site_id' => $this->site->id, + 'domain' => $this->site->domain, + 'ssl_id' => $ssl->id, + ]); + + $this->site->update([ + 'ssl_enabled' => true, + 'force_ssl' => true, + 'verification_key' => 'forcedKey55', + ]); + $this->site->refresh(); + + $vhost = $this->site->webserver()->generateVhost($this->site); + + $this->assertStringContainsString('location ^~ /.well-known/vito/forcedKey55/', $vhost); + $this->assertStringContainsString('alias /var/lib/vito/verify/forcedKey55/', $vhost); + $this->assertStringContainsString( + "location / {\n return 301 https://\$host\$request_uri;\n }", + $vhost, + 'The force-SSL port-80 redirect must be scoped to location / so the verification path is served instead of 301-redirected.' + ); + } + public function test_caddy_serves_verification_over_http_when_using_auto_https(): void { $this->switchToCaddy();