git@xxxx访问 git@xxxx访问其实就是使用的ssh服务

搭建步骤:以 root 用户执行,服务器地址 192.168.1.100

#创建git用户
adduser git

#切换到git用户
su git
cd
mkdir .ssh && chmod 700 .ssh
touch .ssh/authorized_keys && chmod 600 .ssh/authorized_keys

#创建git仓库
mkdir test.git
cd test.git
git init

#设置git用户的登录密码
passwd
#退出,回到root用户环境
exit

#在客户端创建ssh密匙
ssh-keygen -t rsa
#将对应的公钥添加到服务器git用户ssh认证文件中
ssh-copy-id git@192.168.1.100

#测试能不能访问服务器上的仓库,如果不报错即可访问了
git ls-remote -h git@192.168.1.100:test.git HEAD

#尝试clone仓库到本地
git clone git@192.168.1.100:test.git

应该注意,目前所有这些用户也可以登录服务器并以git用户身份获得 shell 。如果你想限制它,你必须将用户的 shell 更改为git-shell

# 查看现有shell
cat /etc/shells

# 确保在您的系统上安装了git-shell,一般是 /usr/bin/git-shell
which git-shell

# 将上面输出的git-shell的路径添加到/etc/shells最后,保存
sudo -e /etc/shells  

#将git用户的登录shell改成git-shell
sudo chsh git -s $(which git-shell)

现在192.168.1.100将得到这个结果了,这样就不能远程登录了,但是可以正常使用git命令推送和拉取代码,同时也不能使用ssh-copy-id来复制用户公钥,需要手动去服务器上添加

ssh git@192.168.1.100
fatal: Interactive git shell is not enabled.
hint: ~/git-shell-commands should exist and have read and execute access.
Connection to gitserver closed.

此时,用户仍然可以使用 SSH 端口转发来访问 git 服务器能够访问的任何主机。如果您想防止这种情况发生,您可以编辑该authorized_keys文件并将以下选项添加到您要限制的每个公钥之前:

no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty

结果应如下所示:

cat ~/.ssh/authorized_keys

no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCB007n/ww+ouN4gSLKssMxXnBOvf9LGt4LojG6rs6hPB09j9R/T17/x4lhJA0F3FR1rP6kYBRsWj2aThGw6HXLm9/5zytK6Ztg3RPKK+4kYjh6541NYsnEAZuXz0jTTyAUfrtU3Z5E003C4oxOj6H0rfIF1kKI9MAQLMdpGW1GYEIgS9EzSdfd8AcCIicTDWbqLAcU4UpkaX8KyGlLwsNuuGztobF8m72ALC/nLF6JLtPofwFBlgc+myivO7TCUSBdLQlgMVOFq1I2uPWQOkOWQAHukEOmfjy2jctxSDBQ220ymjaNsHT4kgtZg2AYYgPqdAv8JggJICUvax2T9va5 gsg-keypair

no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDEwENNMomTboYI+LJieaAY16qiXiH3wuvENhBG...

现在 Git 网络命令仍然可以正常工作,但用户将无法获得 shell。正如输出所述,您还可以在git用户的主目录中设置一个目录,git-shell以稍微自定义命令。例如,您可以限制服务器将接受的 Git 命令,或者您可以自定义用户在尝试 SSH 时看到的消息。运行git help shell以获取有关自定义 shell 的更多信息。

实现HTTP访问
apache 实现
让我们来看看一个非常基本的设置。我们将使用 Apache 作为 CGI 服务器进行设置。如果您没有 Apache 设置,您可以在 Linux 机器上使用以下内容进行设置:

sudo apt-get install apache2 apache2-utils
a2enmod cgi alias env

这也使得mod_cgi,mod_alias和mod_env模块,这些都需要这正常工作。

将仓库存放的目录(以/srv/git为例)的用户组设置为 www-data以便 Web 服务器可以读取和写入访问存储库,因为运行 CGI 脚本的 Apache 实例(默认情况下)将作为该用户运行:

chgrp -R www-data /srv/git

接下来,我们需要向 Apache 配置添加一些内容,以将其git-http-backend作为处理程序运行,以处理进入/gitWeb 服务器路径的任何内容。

SetEnv GIT_PROJECT_ROOT /srv/git
SetEnv GIT_HTTP_EXPORT_ALL
ScriptAlias /git/ /usr/lib/git-core/git-http-backend/

如果您省略GIT_HTTP_EXPORT_ALL环境变量,那么 Git 将只为未经身份验证的客户端提供包含git-daemon-export-ok文件的存储库,就像 Git 守护程序(git-daemon-export-ok)所做的那样。

最后,您将要告诉 Apache 允许请求git-http-backend并以某种方式对写入进行身份验证,可能使用像这样的 Auth 块:

<Files "git-http-backend">
    AuthType Basic
    AuthName "Git Access"
    AuthUserFile /srv/git/.htpasswd
    Require expr !(%{QUERY_STRING} -strmatch '*service=git-receive-pack*' || %{REQUEST_URI} =~ m#/git-receive-pack$#)
    Require valid-user
</Files>

这将要求您创建一个.htpasswd包含所有有效用户密码的文件。以下是将“schacon”用户添加到文件的示例:

$ htpasswd -c /srv/git/.htpasswd schacon

有很多方法可以让 Apache 对用户进行身份验证,您必须选择并实现其中之一。这只是我们能想到的最简单的例子。您几乎肯定还想通过 SSL 进行设置,以便对所有这些数据进行加密。

我不想在 Apache 配置细节讲太多,因为您很可能使用不同的服务器或有不同的身份验证需求。
这里只是说明 Git 带有一个 CGI 处理程序 git-http-backend,当调用它时将完成所有协商以通过 HTTP 发送和接收数据。
它本身不实现任何身份验证,但可以在调用它的 Web 服务器层轻松控制。
您几乎可以使用任何支持 CGI 的 Web 服务器来执行此操作,因此请选择您最了解的服务器。

有关在 Apache 中配置身份验证的更多信息,请在此处查看 Apache 文档:https://httpd.apache.org/docs/current/howto/auth.html

nginx实现
nginx配置

# 配置以 /git 开始的虚拟目录
server {
    listen               80;
    server_name          git.example.com;

    auth_basic           "Git Access";
    auth_basic_user_file /etc/nginx/.htpasswd_git;
    error_log            /var/log/nginx-git-error.log;
    access_log           /var/log/nginx-git-access.log;
    client_max_body_size 0;

    root /srv/git/;

    location ~ /git(/.*) {

        fastcgi_pass  unix:/var/run/fcgiwrap.socket;
        include       fastcgi_params;
        fastcgi_param SCRIPT_FILENAME     /usr/libexec/git-core/git-http-backend;
        fastcgi_param GIT_PROJECT_ROOT    /srv/git;
        fastcgi_param REMOTE_USER         $remote_user;
        fastcgi_param PATH_INFO           $1;
        fastcgi_read_timeout              600;
        client_max_body_size 100M;
    }
}

参考 nginx ngx http auth basic module , 用户认证文件格式如下:
/etc/nginx/.htpasswd_git

# comment
name1:password1
name2:password2:comment
name3:password3

可以使用 htpasswd 命令创建用户, 如果服务器上没有这个命令的话, 可以输入命令 apt-get install apache2-utils 来安装这个命令, 安装了这个命令之后, 就可以使用它来创建认证用户了, 比如要创建用户 user1, 输入命令如下:

htpasswd /etc/nginx/.htpasswd_git user1

支持以下密码类型:
使用 crypt() 函数加密; 可以使用 Apache HTTP Server 发行版中的“htpasswd”实用程序或“openssl passwd”命令生成;
使用基于 MD5 的密码算法 (apr1) 的 Apache 变体进行散列; 可以使用相同的工具生成;
由 RFC 2307 中描述的“{scheme}data”语法(1.0.3+)指定; 当前实现的方案包括 PLAIN(一个示例,不应使用)、SHA(1.3.13)(不应使用纯 SHA-1 散列)和 SSHA(一些软件包使用的加盐 SHA-1 散列,特别是 OpenLDAP 和 Dovecot)。

创建 git 代码库
上面配置的 git 跟目录是 /srv/git , 我们在这个目录下初始化一个空的代码库, 命令如下:

cd /srv/git
git init --bare test.git
将test.git用户和用户组设置为nginx进程可以读写的用户

重启 nginx 并测试
输入命令重启 nginx 并测试 git 服务:

nginx -s reload
git clone https://192.168.1.100/git/test.git

问题
来自这个网友遇到的问题:https://stackoverflow.com/questions/58437697/multiple-simultaneous-requests-with-git-http-backend-on-nginx-fastcgi-proxy
未经验证:
从测试来看,如果一次有多个请求,代理将失败(代码为 504)
它为每个新的 http 请求生成一个新的 git-http-backend 实例。编译它(需要安装 go 语言)通过

go build git-http-multi-backend.go

并运行它

./git-http-multi-backend -r /Path/To/Repos

这将使它监听 :80(端口可以更改)。您现在只需将 nginx 配置更改为类似

location ~ /git(/.*) {
    proxy_pass http://localhost:80;
}

这不是最优雅的解决方案,因为完全可以通过摆弄 nginx 配置来实现相同的效果。
git-http-multi-backend 工具的作者甚至谈到了它。

您需要增加可以生成的 FCGI_CHILDREN 的最大数量。就我而言,我编辑了 /etc/init.d/fcgiwrap 并将 FCGI_CHILDREN 设置为例如 3。现在,我可以一次检出/克隆/推送 3 个存储库。
哦顺便说一句:虽然编辑这个脚本可能被认为不是一个好习惯。我只是想,将脚本放入 /etc/default/fcgiwrap 并使用FCGI_CHILDREN=3也可以完成这项工作,并且单独保留启动脚本

问题
工作中经常发现一些第三方写的docker容器运行有问题,这时我们会通过docker logs命令观察容器的运行日志。很可惜,有时容器中运行的程序仅从日志很难查明问题。这时我们会通过docker exec在目标容器中执行某些命令以探查问题,有时却发现一些镜像很精简,连基本的sh、bash、netstat等命令都没包含。这时就很尴尬了,诊断问题很困难。

docker-debug工具,这个工具的使用方法也很简单,参考以下命令:

# Suppose the container below is a container which should be checked
docker run -d --name dev -p 8000:80 nginx:latest
# Enter a shell where we can access the above container's namespaces (ipc, pid, network, etc, filesystem)
docker-debug dev bash -l

docker-debug的实现原理
简单说执行docker-debug命令也会使用一个包含了常用诊断命令的镜像启动一个诊断容器,该诊断容器将在目标容器相关的命名空间中运行,这样在这个容器中就可以访问目标容器的ipc, pid, network, etc, filesystem,然后使用docker exec命令在诊断容器运行命令,并将docker exec运行命令的输入输出pipe到docker-debug命令的输入输出上。

另外,还发现类似的工具kube-debug,以后诊断pod中的问题方便多了。

Electronhttps://www.electronjs.org/docs
electron-builder:打包工具

项目github地址:
https://github.com/electron/electron
https://github.com/electron/electron-quick-start

Electron是一个使用 JavaScript、HTML 和 CSS 构建桌面应用程序的框架。 嵌入 Chromium 和 Node.js 到 二进制的 Electron 允许您保持一个 JavaScript 代码代码库并创建 在Windows上运行的跨平台应用 macOS和Linux——不需要本地开发 经验。

electron 桌面开发工具 与用户操作系统和node的集成方法文档
https://www.electronjs.org/zh/docs/latest/tutorial/examples

一些开发经验:

创建 BrowserWindow 窗口

const {app, BrowserWindow} = require('electron')

let remoteURL, debug

debug = !app.isPackaged
if (debug) {
  remoteURL = 'https://www.xxx.com/notifyDeskApp/login.html';
  //忽略证书的检测
  app.commandLine.appendSwitch('ignore-certificate-errors')
} else {
  remoteURL = 'https://www.xxx.com/notifyDeskApp/login.html';
}

let trayIcon = path.join(__dirname, "icon", "notify-logo.ico");

let mainWindow, logined, quit

function createWindow () {
  // Create the browser window.
  mainWindow = new BrowserWindow({
    width: 400,
    height: 300,
    fullscreenable: false,
    maximizable: false,
    icon: trayIcon,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js')
    }
  })

  mainWindow.setAppDetails({
    appIconPath: trayIcon,
  })

  // and load the index.html of the app.
  mainWindow.loadURL(remoteURL)
  // mainWindow.loadFile('index.html')

  mainWindow.on('minimize',(e)=>{
    if (logined) {
      mainWindow.hide();
    }
  })

  mainWindow.on('close',(e)=>{
    if (logined && !quit) {
      e.preventDefault()
      mainWindow.hide();
    } else {
      app.quit()
    }
  })

}

app.whenReady().then(() => {


  createWindow()

  app.on('activate', function () {
    // On macOS it's common to re-create a window in the app when the
    // dock icon is clicked and there are no other windows open.
    if (BrowserWindow.getAllWindows().length === 0) createWindow()
  })

})

显示隐藏窗口

  mainWindow.hide();
  mainWindow.restore()

渲染进程与主进程通信
创建窗口的时候配置 preload.js, main.js

  mainWindow = new BrowserWindow({
    width: 400,
    height: 300,
    fullscreenable: false,
    maximizable: false,
    icon: trayIcon,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js')
    }
  })

窗口渲染页面与preload通过window事件,来传递事件消息
窗口加载的网页中的js事件

            //客户端登录事件:字段名称说明是否可选类型默认值detail表示该事件中需要被传递的数据,在 EventListener 获取。可选Anynullbubbles表示该事件是否冒泡。可选Booleanfalsecancelable表示该事件能否被取消。可选Booleanfalse
            let eventInit = {bubbles: false, cancelable: false, detail: {token: token, trueName: trueName}}
            let loginedEvent = new CustomEvent('logined', eventInit)
            window.dispatchEvent(loginedEvent)

preload.js

const {ipcRenderer} = require('electron');

window.addEventListener('logined', (e) => {
    ipcRenderer.send('logined', e.detail);
})

window.addEventListener('logout', (e) => {
    ipcRenderer.send('logout', e.detail);
})

window.addEventListener('open-url', (e) => {
    ipcRenderer.send('open-url', e.detail);
})

window.addEventListener('exit', (e) => {
    ipcRenderer.send('exit', e.detail);
})

main.js

const {app,shell} = require('electron')
ipcMain.on('logined', (e, data) => {
  logined = true
  mainWindow.hide();
})

ipcMain.on('open-url', (e, data) => {
  console.log(data)
  shell.openExternal(data.url);
})

ipcMain.on('logout', (e, data) => {
  logined = false
  mainWindow.restore()
})

ipcMain.on('exit', (e, data) => {
  logined = false
  quit = true
  app.quit()
})

托盘

const {app, BrowserWindow, Menu, Tray , ipcMain, shell} = require('electron')

let tray = null

const showMainWindow = (menuItem, browserWindow, event) => {
  if (mainWindow) {
    mainWindow.restore()
    mainWindow.show()
    mainWindow.focus()
  }
}

const appExit = (menuItem, browserWindow, event) => {
  quit = true
  app.quit()
}

const openDevTools = (menuItem, browserWindow, event) => {
  // Open the DevTools.
  if (mainWindow)  mainWindow.webContents.openDevTools({ mode: 'detach' })
}

let firstrun = process.argv[1] == '--squirrel-firstrun'

const setAutoOpen = function () {
  let autoOpenSet = app.getLoginItemSettings()

  let option = {
    openAtLogin: !autoOpenSet.openAtLogin, // Boolean 在登录时启动应用
    openAsHidden: !autoOpenSet.openAsHidden, // Boolean (可选) mac 表示以隐藏的方式启动应用。~~~~
  }
  if (!debug) app.setLoginItemSettings(option)
  tray.displayBalloon({iconType: 'info', title: '操作提示', content: '开机自启动: ' + (option.openAtLogin ? '已开启' : '已关闭')})
}


app.whenReady().then(() => {

  //托盘
  tray = new Tray(trayIcon)
  const contextMenu = Menu.buildFromTemplate([
    { label: '显示主窗口', type: 'normal', click: showMainWindow },
    { label: '开机自启动', type: 'normal', click: setAutoOpen, visible: !debug },
    { label: '调试窗口', type: 'normal', click: openDevTools, visible: debug },
    { label: '退出客户端', type: 'normal', click: appExit },
  ])
  tray.setToolTip(app.name)
  tray.setContextMenu(contextMenu)

})

开机自启动

app.setLoginItemSettings({
    openAtLogin: true, // Boolean 在登录时启动应用
    openAsHidden: true, // Boolean (可选) mac 表示以隐藏的方式启动应用。~~~~
  })

开发与正式环境判断: 打包后 app.isPackaged 为 true

debug = !app.isPackaged

使用系统默认的浏览器打开一个网页,而不是客户端新开一个窗口

const {shell} = require('electron')
  shell.openExternal(data.url);

只允许开一个程序

const gotTheLock = app.requestSingleInstanceLock()
if (!gotTheLock) {
  return app.quit()
}

修改桌面通知时的程序标题

app.setAppUserModelId('myapp name')

electron-builder:配置参考 (electron-vue, package.json)

{
  ...
  "build": {  // electron-builder配置
    "productName":"xxxx",//项目名 这也是生成的exe文件的前缀名
    "appId": "xxxxx",//包名  
    "copyright":"xxxx",//版权  信息
    "compression": "store", // "store" | "normal"| "maximum" 打包压缩情况(store 相对较快),store 39749kb, maximum 39186kb
    "directories": {
        "output": "build" // 输出文件夹
    }, 
    "asar": false, // asar打包
    "extraResources":  { // 拷贝dll等静态文件到指定位置
        "from": "./app-update.yml",
        "to": "./b.txt"
    },
    "win": {  
        "icon": "xxx/icon.ico"//图标路径,
        "extraResources":  { // 拷贝dll等静态文件到指定位置(用于某个系统配置)
            "from": "./app-update.yml",
            "to": "./b.txt"
        }
    },
    "nsis": {
        "oneClick": false, // 一键安装
        "guid": "xxxx", //注册表名字,不推荐修改
        "perMachine": true, // 是否开启安装时权限限制(此电脑或当前用户)
        "allowElevation": true, // 允许请求提升。 如果为false,则用户必须使用提升的权限重新启动安装程序。
        "allowToChangeInstallationDirectory": true, // 允许修改安装目录
        "installerIcon": "./build/icons/aaa.ico", // 安装图标
        "uninstallerIcon": "./build/icons/bbb.ico", //卸载图标
        "installerHeaderIcon": "./build/icons/aaa.ico", // 安装时头部图标
        "createDesktopShortcut": true, // 创建桌面图标
        "createStartMenuShortcut": true, // 创建开始菜单图标
        "shortcutName": "xxxx" // 图标名称
    }
  }
  ...
}

https://github.com/joewalnes/websocketd

这个工具可以把普通命令行命令转成ws协议,web客户端可以通过websocket连接,互换数据;
当有连接过来的时候,会启动进程执行指定的命令,如果命令结束或者连接断开,则连接关闭,进程结束;
连接后,将客户端send过来的数据,作为STDIN标准输入,传入进程,进程的标准输出STDOUT会作为message消息发送给客户端(\n结束作为一条消息);
启动服务:

cat startNotify.sh

#!/usr/bin/env bash
websocketd --port=1234 ./demp.sh

./startNotify.sh
#或者后台运行
nohup ./startNotify.sh > /dev/null 2>&1 &

一个发什么回什么的服务端脚本代码大概这样子:demp.sh

#! /pathto/php
<?hp

while (!feof(STDIN)) {
    $line = fgets(STDIN);
    echo $line;
}

?>

建立websocket连接,web网页

<script>
        var ws = new WebSocket('ws://127.0.0.1:1234')
        ws.onopen = function () {
            console.log('ws connected')
            ws.onclose = function () {
                console.log('ws closed');
            }
            ws.onmessage = function (res) { console.log(res.data) }

            var count = 0;
            
            setInterval(function () {
                ws.send(count++)
            }, 2000)
        }
        ws.onerror = function (e) {console.log(e)}
</script>

ssl网页,要用wss协议连接,通过nginx反代吧

map $http_upgrade $connection_upgrade {
    default upgrade;
    '' close;
}

upstream notify_ws {
    server 127.0.0.1:1234; # appserver_ip:ws_port
}

server
{

location / {

         proxy_pass http://notify_ws;
         proxy_read_timeout 300s;
         proxy_send_timeout 300s;
         
         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_http_version 1.1;
         proxy_set_header Upgrade $http_upgrade;
         proxy_set_header Connection $connection_upgrade;
         
}

}

关键在于这两行:
最重要的就是在反向代理的配置中增加了如下两行,其它的部分和普通的HTTP反向代理没有任何差别。

proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;

这里面的关键部分在于HTTP的请求中多了如下头部:

Upgrade: websocket
Connection: Upgrade

这两个字段表示请求服务器升级协议为WebSocket。服务器处理完请求后,响应如下报文:

# 状态码为101
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: upgrade

告诉客户端已成功切换协议,升级为Websocket协议。握手成功之后,服务器端和客户端便角色对等,就像普通的Socket一样,能够双向通信。 不再进行HTTP的交互,而是开始WebSocket的数据帧协议实现数据交换。

这里使用map指令可以将变量组合成为新的变量,会根据客户端传来的连接中是否带有Upgrade头来决定是否给源站传递Connection头, 这样做的方法比直接全部传递upgrade更加优雅。

默认情况下,连接将会在无数据传输60秒后关闭,proxy_read_timeout参数可以延长这个时间。源站通过定期发送ping帧以保持连接并确认连接是否还在使用。

两个超时参数

proxy_read_timeout

语法 proxy_read_timeout time 默认值 60s 上下文 http server location 说明 该指令设置与代理服务器的读超时时间。它决定了nginx会等待多长时间来获得请求的响应。 这个时间不是获得整个response的时间,而是两次reading操作的时间。

proxy_send_timeout

语法 proxy_send_timeout time 默认值 60s 上下文 http server location 说明 这个指定设置了发送请求给upstream服务器的超时时间。超时设置不是为了整个发送期间,而是在两次write操作期间。 如果超时后,upstream没有收到新的数据,nginx会关闭连接

多次代理转发
工作中遇见过一种情况,就是某个域名在移动网络下面访问不了,这样的话我需要通过一个前段代理服务器做转发,这样就涉及到两次代理。

比如访问的websocket服务URL为:

wss://test.enzhico.net

这个在腾讯云公网IP上面,所有网络都能访问。另外一个域名board.xncoding.com解析到电信网络,部署在网关中心,这个域名腾讯云可以访问到。

在腾讯云主机上面:

map $http_upgrade $connection_upgrade {
    default upgrade;
    '' close;
}

server {
     server_name test.enzhico.net;
     location / {
         proxy_pass http://board.xncoding.com;
         proxy_read_timeout 300s;
         proxy_send_timeout 300s;
         #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_http_version 1.1;
         proxy_set_header Upgrade $http_upgrade;
         proxy_set_header Connection $connection_upgrade;
    }
    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/test.enzhico.net/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/test.enzhico.net/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}

上面唯一要注意的是忙,把proxy_set_header Host $host;这一行注释掉了。

而在网关中心主机上面:

map $http_upgrade $connection_upgrade {
    default upgrade;
    '' close;
}

upstream websocket {
    server localhost:8282; # appserver_ip:ws_port
}

server {
    listen 80;
    server_name board.xncoding.com;
    location / {
        proxy_pass http://websocket;
        proxy_read_timeout 300s;
        proxy_send_timeout 300s;
        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_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
    }
}

只需要最外层使用wss协议,里面的交互都使用ws协议,所以监听80端口即可。

参考文章:
https://www.xncoding.com/2018/03/12/fullstack/nginx-websocket.html
https://www.hi-linux.com/posts/42176.html