さくらVPS(Rocky Linux) + laravel + Inetria(vue3)でbroadcastの実装メモ(laravel-websockets + SSL)

外部サービスPusherの代わりに代替オープンソースのlaravel-websocketsでbroadcastを使う。
websocket用に6001など、別途ポートを開きたくない場合nginxのSSL(443)からアクセスさせて
nginxのリバースプロキシでlaravel-Websockets(6001)にアクセスさせる。

#### 1. laravel-websocketsを導入
composerコマンドで導入、`vendor:publish`でmigrateファイルとconfigファイルをコピーする。
```bash
$ composer require beyondcode/laravel-websockets
$ php artisan vendor:publish --provider="BeyondCode\LaravelWebSockets\WebSocketsServiceProvider" --tag="migrations"
$ php artisan migrate
$ php artisan vendor:publish --provider="BeyondCode\LaravelWebSockets\WebSocketsServiceProvider" --tag="config"
```
`config/websockets.php`は編集なし。
`config/broadcast.php`のpusherの要素が古い場合があるので更新しておく。
laravel9xだと以下のような感じ。
```php:config/broadcast.php
        'pusher' => [
            'driver' => 'pusher',
            'key' => env('PUSHER_APP_KEY'),
            'secret' => env('PUSHER_APP_SECRET'),
            'app_id' => env('PUSHER_APP_ID'),
            'options' => [
                'host' => env('PUSHER_HOST', 'api-'.env('PUSHER_APP_CLUSTER', 'mt1').'.pusher.com'),
                'port' => env('PUSHER_PORT', 443),
                'scheme' => env('PUSHER_SCHEME', 'https'),
                'encrypted' => true,
                'cluster' => env('PUSHER_APP_CLUSTER'),
                'useTLS' => env('PUSHER_SCHEME', 'https') === 'https',
            ],
            'client_options' => [
                // Guzzle client options: https://docs.guzzlephp.org/en/stable/request-options.html
            ],
        ],
```
これに合わせて`.env`ファイルの`PUSHER_*`系の値を追加or更新
```php:.env
# for pusherjs
#PUSHER_APP_ID=xxxxxx
#PUSHER_APP_KEY=yyyyyyyyyyyyyyyy
#PUSHER_APP_SECRET=zzzzzzzzzzzzzzzzzz
#PUSHER_APP_CLUSTER=ap3
# 以下はpuhserではそのまま
#PUSHER_HOST=
#PUSHER_PORT=443
#PUSHER_SCHEME=https

# for laravel-websockets
# ID、KEY、SECRETは1
# CLUSTERは初期値(mt1)
PUSHER_APP_ID=1
PUSHER_APP_KEY=1
PUSHER_APP_SECRET=1
# リバースプロキシーではhttp://127.0.0.1:6001/で待ち受けるため以下の設定
PUSHER_HOST=127.0.0.1
PUSHER_PORT=6001
PUSHER_SCHEME=http
PUSHER_APP_CLUSTER=mt1
```
laravel-websocketsを起動するには`php artisan websocket:serve`コマンドなので
supervisorでデーモン化する。
`--host=127.0.0.1`を指定してローカルIPで待ち受けるように設定。
numprocsは1でマルチプロセスにはしないようにする。
```bash
$ sudo vi /etc/supervisord.d/laravel-websocket.ini
[program:laravel-websocket-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/html/app/artisan websockets:serve --host=127.0.0.1
autostart=true
autorestart=true
user=root
numprocs=1
redirect_stderr=true
stdout_logfile=/var/www/html/app/storage/logs/laravel-websocket.log
```
`ss -atn`などで6001ポートで待ち受けてるか確認。

#### 2. ドメインを用意する。
例)
black.jp：laravelのbackend及びfrontendを実装するドメイン
socket.black.jp：websocket用のドメイン
ドメインに対応するIPアドレスの向き先を設定しておく。

#### 3. nginxで各ドメインの設定を行う。
black.jpにはlaravelのドキュメントルートの設定
```conf:/etc/nginx/conf.d/black.jp.conf
server {
    listen       80;
    server_name  black.jp;

    root         /var/www/html/black.jp/public;
    index        index.php index.html index.htm;

    access_log  /var/log/nginx/black.jp/access.log  main;
    error_log   /var/log/nginx/black.jp/error.log warn;

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
    location ^~ /.well-known/acme-challenge/ {
        root /var/www/html/letsencrypt;
    }

    location = /.well-known/acme-challenge/ {
        return 404;
    }
    return 301 https://$host$request_uri;
}
server {
    listen       443 ssl;
    server_name  black.jp;
    charset      utf-8;

    access_log  /var/log/nginx/black.jp/access.log  main;
    error_log   /var/log/nginx/black.jp/error.log warn;

    root         /var/www/html/black.jp/public;
    index        index.php index.html index.htm;

    ssl_protocols TLSv1.3 TLSv1.2;
    ssl_prefer_server_ciphers on;
    ssl_ciphers          'ECDH !aNULL !eNULL !SSLv2 !SSLv3';
    add_header           Strict-Transport-Security "max-age=31536000; includeSubdomains";
    ssl_session_cache    shared:SSL:10m;
    ssl_session_timeout  10m;
    ssl_certificate /etc/letsencrypt/live/black.jp/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/black.jp/privkey.pem;

    #error_page  404              /404.html;
    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

    location ^~ /.well-known/acme-challenge/ {
        root /var/www/html/letsencrypt;
    }

    location = /.well-known/acme-challenge/ {
        return 404;
    }

    location / {
        index  index.php index.html index.htm;
        try_files $uri $uri/ /index.php?$query_string;
    }
    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
    #
    location ~ \.php$ {
        fastcgi_pass   unix:/run/php-fpm/www.sock;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME  /$document_root$fastcgi_script_name;
        include        fastcgi_params;
    }
}
```
socket.black.jpのnginxのconfは
ssl_certificateとssl_certificate_keyは後述のcertbotコマンドで生成するパスとなるため
コメント化、httpsへのリダイレクトもコメント化しておく。
```conf:/etc/nginx/conf.d/socket.black.jp.conf
server {
    listen       80;
    listen       [::]:80;
    server_name  socket.black.jp;

    root         /var/www/html/black.jp/public;
    index        index.php index.html index.htm;

    access_log  /var/log/nginx/black.jp/access.log  main;
    error_log   /var/log/nginx/black.jp/error.log warn;

    location ^~ /.well-known/acme-challenge/ {
        root /var/www/html/letsencrypt;
    }

    location = /.well-known/acme-challenge/ {
        return 404;
    }
    return 301 https://$host$request_uri;
}

server {
    listen       443 ssl;
    listen       [::]:443 ssl;
    server_name  socket.black.jp;

    access_log  /var/log/nginx/black.jp/access.log  main;
    error_log   /var/log/nginx/black.jp/error.log warn;

    root         /var/www/html/black.jp/public;
    index        index.php index.html index.htm;

    ssl_protocols TLSv1.3 TLSv1.2;
    ssl_prefer_server_ciphers on;
    ssl_ciphers          'ECDH !aNULL !eNULL !SSLv2 !SSLv3';
    add_header           Strict-Transport-Security "max-age=31536000; includeSubdomains";
    ssl_session_cache    shared:SSL:10m;
    ssl_session_timeout  10m;
    ssl_certificate /etc/letsencrypt/live/black.jp/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/black.jp/privkey.pem;

    location ^~ /.well-known/acme-challenge/ {
        root /var/www/html/letsencrypt;
    }

    location = /.well-known/acme-challenge/ {
        return 404;
    }
    # ここからlaravel-websocketsへのリバースプロキシーの設定
    location / {
        proxy_pass http://127.0.0.1:6001; #could be localhost if Echo and NginX are on the same box
        proxy_set_header Host  $host;
        proxy_set_header X-Real-IP          $remote_addr;
        proxy_set_header X-Forwarded-For    $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto  https;
        proxy_set_header X-VerifiedViaNginx yes;
        proxy_read_timeout     60;
        proxy_connect_timeout  60;
        proxy_redirect         off;

        # Allow the use of websockets
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }
}
```
### 4. Let's encryptでドメインのSSL証明書を発行
```bash
# certbot certonly --webroot -w /var/www/html/letsencrypt -d black.jp -d socket.black.jp
```
コマンド実行し、SSL証明書が生成されたら、httpsへのリダイレクトと
ssl_certificateとssl_certificate_keyのコメントを解除し、`systemctl reload nginx`する。
他の端末などからopensslコマンドで証明書が有効か確認
```bash
$ openssl s_client -connect socket.black.jp:443 -showcerts
```

#### 5. Laravel-Echo(Js側)の設定
`bootstrap.js`を書き換える。
laravel-echoはWebsocketのラッパーライブラリで外部サービスpusherとablyに対応している。
pusher互換のwebsocketとして
laravel-websockets、soketiもある。
利用する場合は別途pusher-jsも必要。
`socket.io`の場合は`socket-io-client`が必要。
pusher互換のwebsocketの場合は、`broadcaster: 'pusher'`、
socket.ioの場合は`broadcaster: 'socket-io'`

wsHostとwssHostで自前のサーバのドメインとポートを指定する。
```typescript:resources/js/boostrap.js
(略)
import Echo from 'laravel-echo';

window.Pusher = require('pusher-js');

window.Echo = new Echo({
    broadcaster: 'pusher',
    key: process.env.MIX_PUSHER_APP_KEY,
    cluster: process.env.MIX_PUSHER_APP_CLUSTER,
    forceTLS: true,
    wsHost: 'socket.black.jp',
    wssPort: 443
});
```
次に試すこと
リバースプロキシを使わずに別ポート(6001)から直接アクセス