分类
nodejs

node.js 里使用 fifo

0. 需求

前两天 Showman 遇到一个需求:

  1. 我们需要在服务器端录制视频
  2. 录制视频的过程主要由 node.js 控制,借助 puppeteer 操作浏览器
  3. 但是也会需要执行一些 shell 命令,此时为安全考虑,我们会启动一个封闭的临时环境给用户执行
  4. 这些封闭环境是用户进程间共用的,不会随时启动随时销毁
  5. 所以 node.js 就需要在其它环境里执行一些操作,返回内容,等待执行完毕后再继续下面的

于是我的同事就让我用 fifo。

1. 什么是 fifo

我以前没有用过 fifo,所以搜索了一下。

FIFO 特殊文件(同具名管道)与管道类似,只是可以用访问文件系统的方式来访问它。它可以被多个进程同时打开和读写。当进程通过 FIFO 交换数据时,内核将直接在内部交换数据,而不会写入到文件系统中。因此,FIFO 特殊文件在文件系统中没有内容;文件系统的入口(即文件)只是作为引用方式,让各进程能够使用文件名来访问管道。

原文:https://man7.org/linux/man-pages/man7/fifo.7.html

管道大家应该都知道,把 A 进程的输出直接输入到 B 进程里,加快处理速度。fifo 与管道的差别就是 fifo 可以通过文件路径直接访问,用起来更简单。

2. 在命令行里使用 fifo

创建 fifo,使用 mkfifo 命令:

mkfifo xxx.fifo

写入内容到 fifo:

echo "something" > xxx.fifo

读取 fifo:

cat xxx.fifo

因为 fifo 是管道,内容直接走内核,所以实际上硬盘上不会存储任何内容。如果我们在写入之后再 cat fifo,就不会得到任何内容。

3. 在 node.js 里使用 fifo

在 node.js 里使用 fifo 需要用 fs.opennet.Socket。因为我需要在执行完毕后继续下一步,所以进行了 Promise 封装:

try {
  // 为避免执行时间过长导致进程超时,不断输出些内容
  const interval = setInterval(() => {
    log('termlang is processing...');
  }, 3E4);
  await new Promise((resolve, reject) => {
    // 打开名为 $basename-$lineno.sp.fifo 的管道
    open('./$basename-$lineno.sp.fifo', constants.O_RDONLY | constants.O_NONBLOCK, (err, fd) => {
      if (err) {
        clearInterval(interval);
        reject(err);
      }
      const pipe = new Socket({fd});
      pipe.on('data', data => {
        data = data.toString();
        // 以输出内容包括 finished 或 errored 为结束标记
        if (/(finished|errored)/.test(data)) {
          resolve(data);
        }
      });
    });
  });
  clearInterval(interval);
} catch (err) {
  log(err);
}

4. 总结

作为半路出家的前端,我对系统、对 Linux 一直缺乏了解。所以类似管道这种东西,我一直也不太熟悉,这次算学会了一个新技能,记录分享一下。

分类
linux

在 Fedora 34 上启动 VNC DISPLAY

大家可以先阅读 使用 Node.js 驱动 FFmpeg 在 Linux + vncserver 下完成视频录制 了解产品目标和技术选型。

前两天在系统更新里看到 Fedora 34 发布,作为更新党,我当然迫不及待就升级了。升级过程蛮顺利的,升级后,系统里的“在线账户”也能正常走 VPN 了,感觉还蛮好的。

然后,前两天需要调试录视频的程序,发现新系统的 tigerVNC 有一个巨大的变化:不再支持用 vncserver 命令创建虚拟显示器,必须用 systemctl start service,目的是方便绑定系统启动,因为很多服务器的运维需要自动化。

不过这可苦了我。我是系统运维菜鸡,基本只能照抄文章,搞了半天也没搞好。不过感谢开源,在 GitHub issue 里讨论的只言片语让我知道了其实 vncserver 是个脚本,它调用的其实是 Xvnc 这个命令。

那就好办了,我开始按图索骥,寻找 vncserverXvnc 之间的关联。最终找到解决方法如下:

  1. 修改 /etc/X11/Xwrapper.config,加入 allowed_users=anybody。这样才能直接使用 Xvnc 创建虚拟显示器,不然会报告只有 console 用户才能创建的错误。
  2. 使用 vncpasswd 命令创建密码文件,创建后的密码文件位于 ~/.vnc/passwd
  3. 然后用 Xvnc :5 -geometry 1280x720 -PasswordFile ~/.vnc/passwd 创建显示器,跟之前的命令很类似,不过需要 -PasswordFile 选项指定密码
  4. 使用 VNC viewer 登录 VNC,输入密码。(我不知道这一步是否必须)
  5. 可以继续使用 DISPLAY=:5

不过问题并没有完美解决,虽然我的 puppeteer JS 能跑,FFmpeg 也能录。但是 DISPLAY=:5 firefox https://cn.bing.com 只会在当前屏幕打开窗口,不知道为什么。留待以后解决吧。

最后吐槽下,这种稳定版里换大版本的行为真的要不得,开源团队也不能滥用自己的地位。


参考阅读

分类
linux

Linux 命令行科学上网

前些天因为工作需要,装了 Fedora 33 开发 Showman 的视频录制功能。准备顺势双机工作一段时间,自然也就需要给新系统配置好科学上网。这里简单记录一下过程,方便日后回查。

0. 更新系统

第一部当然是更新系统,保证系统组件均为最新版,这样可以规避大多数问题。

sudo dnf update

1. 使用 pip 安装 shadowsocks 客户端

Fedora 33 自带 python 3.9,所以直接使用 pip 安装 shadowsocks 客户端即可:

sudo pip install shadowsocks

这里可以用 sudo 也可以不用,差别就在于,sudo 之后会将执行脚本安装到 /usr/local/bin;而不带 sudo 则会安装到 ~/.local/bin。两者的执行环境不一样。我无意间使用了后者,所以后面也会按照后者来记录。

2. 配置客户端

接下来编辑配置文件,如果是图形界面,建议使用 IDE,纯命令行的话用 vim 也可以。配置文件一般放在 /etc/shadowsocks.json。内容大概如下,顾名思义,我就不一一解释了:

{
  "server":"server-ip",
  "server_port":8000,
  "local_address": "127.0.0.1",
  "local_port":1080,
  "password":"your-password",
  "timeout":600,
  "method":"aes-256-cfb"
}

与前面 Ubuntu 20.04 科学上网 一样,本文不打算介绍服务器的制备——其实我也不建议大家自己制备服务器,除非你本来就有一些资源或者需求。不然的话,以我的经验,比较出名的迷你 VPS(类似 Vultr,DO,$5/月甚至 $2.5/月),IP 大部分都在规则库里,流量大一点,比如看个视频,不出半个小时 IP 就被封了,然后一个月后解封,基本没法用。

如果你是 macOS 或者 iPhone,或者其它可以用 anyconnect 的系统,可以考虑直接买现成的,比如链接里这个

3. 配置系统代理

然后启动服务:

sslocal -c /etc/shadowsocks.json -d start
  • -c 用来指定配置文件的地址
  • -d 表示启动服务

这里可能会遇到几个问题(也是上次我放弃命令行转投 qt5 客户端的原因)。我们来逐个解决它们:

3.1 openssl 错误

执行后报错:

$ sslocal -c /etc/shadowsocks.json -d start
INFO: loading config from /etc/shadowsocks.json
 2021-05-05 15:17:00 INFO     loading libcrypto from /root/anaconda3/lib/libcrypto.so.1.1
 Traceback (most recent call last):
   File "/root/anaconda3/bin/sslocal", line 8, in <module>
     sys.exit(main())
   File "/root/anaconda3/lib/python3.9/site-packages/shadowsocks/local.py", line 39, in main
     config = shell.get_config(True)
   File "/root/anaconda3/lib/python3.9/site-packages/shadowsocks/shell.py", line 262, in get_config
     check_config(config, is_local)
   File "/root/anaconda3/lib/python3.9/site-packages/shadowsocks/shell.py", line 124, in check_config
     encrypt.try_cipher(config['password'], config['method'])
   File "/root/anaconda3/lib/python3.9/site-packages/shadowsocks/encrypt.py", line 44, in try_cipher
     Encryptor(key, method)
   File "/root/anaconda3/lib/python3.9/site-packages/shadowsocks/encrypt.py", line 82, in __init__
     self.cipher = self.get_cipher(key, method, 1,
   File "/root/anaconda3/lib/python3.9/site-packages/shadowsocks/encrypt.py", line 109, in get_cipher
     return m[2](method, key, iv, op)
   File "/root/anaconda3/lib/python3.9/site-packages/shadowsocks/crypto/openssl.py", line 76, in __init__
     load_openssl()
   File "/root/anaconda3/lib/python3.9/site-packages/shadowsocks/crypto/openssl.py", line 52, in load_openssl
     libcrypto.EVP_CIPHER_CTX_cleanup.argtypes = (c_void_p,)
   File "/root/anaconda3/lib/python3.9/ctypes/__init__.py", line 395, in __getattr__
     func = self.__getitem__(name)
   File "/root/anaconda3/lib/python3.9/ctypes/__init__.py", line 400, in __getitem__
     func = self._FuncPtr((name_or_ordinal, self))
 AttributeError: /root/anaconda3/lib/python3.9/lib-dynload/../../libcrypto.so.1.1: undefined symbol: EVP_CIPHER_CTX_cleanup

其中文件、行号可能有所不同,不过错误内容大多一致。

这是因为 OpenSSL 升级至 1.1.0 以上后,内部 API 有一些变化,废弃了 EVP_CIPHER_CTX_cleanup() 函数而引入了 EVE_CIPHER_CTX_reset(),shadowsocks 客户端处于无人维护的状态,没有适配这些变化。

好在修复方案并不复杂,我们只需要修改文件 ~/.local/lib/python3.9/site-packages/shadowsocks/crypto/openssl.py,将里面的 cleanup 都替换成 reset 即可。

3.2 permission denied /var/run/shadowsocks.pid

前面写过,因为我安装的时候没使用 sudo,所以把客户端装在当前用户的目录里。于是面临一个矛盾:

  1. 直接使用当前用户启动客户端,会报告标题里的错误
  2. 使用 root 即 sudo 启动客户端,会报告库有错误(即 3.1),因为我修改的是当前用户的本地库

这里有两个解决方案,一是手动创建 pid,然后修改权限:

sudo touch /var/run/shadowsocks.pid
sudo chmod 777 /var/run/shadowsocks.pid

这个方法重启后会失效,还得再跑一遍。所以我比较推荐另一种做法:使用 sudo -u $user -i 的方式,sudo 的同时仍然使用当前用户的环境(这个方案实测失败了,还要再研究下):

sudo -u meathill -i sslocal -c /etc/shadowsocks.pid -d start

4. 加入自动启动

上一步测试成功之后就可以把这段代码加入 /etc/rc.local,以便实现开机自动启动。

5. 其它步骤

接下来,需要配置 pac 文件、文件服务和系统代理,可以完全参考 Ubuntu 20.04 科学上网 一文。

其中下载 gfwlist.txt 时,如果访问不到 https://raw.githubusercontent.com/gfwlist/gfwlist/master/gfwlist.txt,可以试着修改 /etc/hosts,加入下一行:

199.232.28.133 raw.githubusercontent.com

完成剩余步骤后,配置成功。


在 macOS/iOS 设备上使用科学上网有更简单的方法,扫码可得:

分类
技术

使用 Node.js 驱动 FFmpeg 在 Linux + vncserver 下完成视频录制

自动化录制屏幕有很多用途,比如生成教学视频、生成产品文档,等等。对比人工,自动化有很多好处:

  1. 避免创作者的设备和环境问题(比如邻居装修、麦克风不好等)
  2. 避免创作者的语言、发音问题(比如普通话不标准、不会说某种语言)
  3. 录制环境出现变化,可以方便的重录(比如换个背景图,界面有升级)
  4. 就像写博客一样,任何时候,拿出电脑或者手机都能编辑一段

所以目前研究这方面应用的很多,我厂也是。我近期就投入大量时间在这项工作上面,现在终于有所成果,写篇博客分享一下。

0. 准备环境

首先推荐大家使用 Linux。Linux 开源,有很多开源免费的工具可以完成各种操作,不仅可以录屏,还可以很容易地模拟各种用户操作,给我们留下大量开发空间。

建议选择有图形界面的 Linux 发行版,我尝试过 fedora 33 和 Debian 10 树莓派版,都很容易配置。如果使用纯命令行版本,然后自己完成安装图形界面,比如 gnome,再完成剩下来的配置,会很麻烦。

然后记得把系统更新到最新版,以规避可能遇到的问题。

1. 配置 vncserver

如果只能在主屏录制,这个产品的实用性就会大打折扣。所以我们选择用 vncserver 创建虚拟屏幕,然后在虚拟屏幕上完成录制。如果需要的话,也可以随时用 vnc viewer 之类的软件连上 VNC 实时查看效果,非常方便。

有些系统自带 vncserver,比如 Debian 10 树莓派,那就不用安装。我们选用 fedora 33,需要手动安装,这里推荐 TigerVNC,安装使用都很方便:

sudo dnf install tigervnc

安装完成后,使用:

vncserver :5 -geometry 1280x720

就可以创建虚拟显示器了。其中,:5 是显示器 id,可以顺延,比如 :6:7、甚至 :99,至于上限在哪里我暂时不知道。-geometry 1280x720 是设定显示器分辨率为 1280×720。

另外,还可以使用下面的命令查看和关闭显示器:

 vncserver -list
 vncserver -kill :5 

1.1 测试

配置完成之后,可以用 Firefox 测试一下效果。

# 安装 firefox
sudo dnf install firefox

# 在指定虚拟显示器打开 firefox
DISPLAY=:5 firefox https://cn.bing.com

# 截图,可能需要安装 xwd
xwd -root -display :5 > screen.xwd

# 转换成 png,可能需要安装 ImageMagick
convert screen.xwd screen.png

然后把图片下载到本地,或者启动一个 http 服务器就能看到了。

1.2 关闭桌面

默认的 VNC server 会启动桌面,此时可能会要求我们登录什么的。我们在这套系统当中并不需要桌面,只要有显示器即可,所以可以修改 ~/.vnc/xstartup 禁用:

#!/bin/sh
 

unset SESSION_MANAGER
unset DBUS_SESSION_BUS_ADDRESS
# 把下面这行注释掉 
# exec /etc/X11/xinit/xinitr

2. 使用 FFmpeg 捕获屏幕内容

使用 FFmpeg 录屏比较简单,将输入源设置为指定显示器即可,formatx11grab,命令大体如下:

ffmpeg -y -f x11grab -video_size 1280x720 -framerate 30 -i :5.0+0,0 -c:v h264 a.mp4

其中,

  • -y 意思是自动覆盖前面生成的视频
  • :5.0+0,0 是使用刚才创建的 :5 显示器,用它的 0 号桌面,启动位置是 0,0 即左上角
  • -f x11grab 是使用 X server 抓取格式,Linux 下的图形界面一版是基于这个系统

注意,上面这条命令里参数的顺序很重要,否则可能遇到 Protocol not found 等错误。

3. 使用 node.js 驱动

最后只要用 node.js 的 child_process.spawn() 功能调用上面的命令即可。这段代码属于公司,我就不贴了,主要分享几点经验教训:

  1. 要用 spawn,因为录制过程中我们需要用输出来判断录制状态,exec 这种只能在结束时提供输出的没法用
  2. FFmpeg 会把输出输出到 stderr,理由不明,不过记得要用 stderr 来检查
  3. 录像完成,如果在命令行,按 q 或者 ctrl+C 都可以停止录像,并开始封装视频文件。在 node.js 里,我们可以调用 cp.kill('SIGINT') 。注意,调用之后,FFmpeg 子进程并没有立刻结束,它要把前面的录像进行封装,这个过程也是需要时间的,所以如果你接下来还要对视频文件进行操作,应该等待子进程彻底结束
  4. 判断录像开始的依据,我目前用的是:输出里包含 Output #0, mp4, to 'a.mp4'
  5. 判断录像结束,视频已经生成的依据,我用的是 cp.on('exit', onExit),然后在 onExit 里处理。注意,其它情况导致 ffpmeg 子进程退出时也会触发这个函数,所以我们必须检查 code。此时,在我的机器上,code 是 255,表示它是用户手动中止的,可以当作判断依据。

总结

剩下来的内容基本就是怎么驱动图形界面程序运行了。一般来说用 puppeteer 比较好,可以很容易的跟 node.js 联动,我厂的 showman 也是基于这个方案来实现的,最后贴一段视频,大家看下效果:

一段用上述技术生成的视频

参考阅读

分类
nodejs

使用 Node.js 驱动 FFMPEG 录屏

FFMPEG 功能非常强大,不仅能转换视频格式、压缩视频、添加字幕等,还能录制屏幕内容。使用 FFMPEG 录屏的方法很简单:

# Linux
ffmpeg -video_size 1024x768 -framerate 25 -f x11grab -i :0.0+100,200 output.mp4 

# macOS
ffmpeg -f avfoundation -list_devices true -i "" 

# Windows
ffmpeg -f gdigrab -framerate 30 -offset_x 10 -offset_y 20 -video_size 640x480 -show_region 1 -i desktop output.mkv 

更详细的介绍可以参考官方文档,这里不做太多摘抄。

使用 Node.js child_process 可以调用外部程序执行操作,详细的用法可以参考官方文档。大概来说,分为:

  1. exec/execFile 调用程序,当执行完成,一次性获得结果
  2. spawn 调用程序,过程中流式获得输出

我的录屏对时间点有非常高的要求,力求准确。所以我只能选择 spawn,然后通过检查日志输出得到准确的录制开始时间。

所以,我的代码大概如此:

class FfmpegRecorder {
  // 其它函数略去
  startRecording(args) {
    this.isFileReady = new Promise((resolve, reject) => {
      this._isFileReadyResolve = resolve;
      this._isFileReadyReject = reject;
    });
    this.recording = spawn(this.ffmpeg, args);
    
    this.recording.stdout.on('data', this.onStdData);
    this.recording.stderr.on('data', this.onStdErr);
  }
  stopRecording() {
    this.recording.kill('SIGINT');
    const path = resolve(process.cwd(), 'a.mp4');
    return path;
  }

  onStdData(data) {
    data = data.toString();
    this.stdout += data;
    if (this.stdout.indexOf('Output #0, mp4, to \'a.mp4\'') !== -1) {
      this._isFileReadyResolve();
      this._isFileReadyReject = null;
    }
  }
  onStdErr(data) {
    data = data.toString();
    this.stderr += data;
    if (this.stderr.indexOf('Output #0, mp4, to \'a.mp4\'') !== -1) {
      this._isFileReadyResolve();
      this._isFileReadyReject = null;
    }
  }
}

根据我在命令行里直接跑 ffmpeg 的结果,它会先初始化,然后开始录屏。录屏开始时,会输出 Output #0, mp4, to xxx.mp4 这样的日志,所以我就会反复检查 stdoutstderr,直到关键日志出现,然后告诉外面的程序开始录制了。

这里比较奇怪的是,日志输出应该是正常的,走 stdout 通道,结果只能从 stderr 通道获取。我为防万一,两边都留下了同样的代码。可能我对 Linux 理解不够,将来再研究一下为什么会这样吧。

上面的代码忽略了 onErroronExit 部分,有兴趣的同学请等我开源(公司代码)。

在 Linux,stopRecording(),即 kill('SIGINT')(相当于按下 Ctrl+C)之后,FFMPEG 会终止录屏,并且生成可以播放的视频文件。但是在 Windows,只会留下一个无法播放的文件。

通过观察命令行直接运行 ffmpeg 的结果和 node.js 保存的结果,我发现缺失了结束录制后处理视频文件的部分。实际上 FFMPEG 录屏时只会记录视频内容,录制结束后才会生成影片的meta 信息,而播放器必须读取后者才可以正常播放。所以我猜测在 Windows 下 kill('SIGINT') 会直接彻底杀死进程,而不仅仅是发送一个信号给 FFMPEG,并让它完成后续的工作。

做出判断后,我尝试用按下 q 的方式通知 FFMPEG 停止工作,并等待 5s。果然成功,于是我构建了新类,继承前面的 FfmpegRecorder,大概代码如下:

const endTag = /kb\/s:\d+.\d{2}/m;

class WindowsRecorder extend FfmpegRecorder {
  stopRecording() {
    // 向 child process 输入 `q`
    this.recording.stdin.setEncoding('utf8');
    this.recording.stdin.write('q');
    const p = new Promise((resolve, reject) => {
      this._stopResolve = resolve;
      this._stopReject = reject;
    });
    // 设置一个超时保护 15s
    setTimeout(() => {
      this._stopReject();
    }, 15E3);
    return p;
  }
  onStdData(data) {
    super.onStdData(data);
    if (this._stopResolve && endTag.test(this.stdout)) {
      const path = resolve(process.cwd(), 'a.mp4');
      this._stopResolve(path);
    }
  }
  onStdErr(data) {
    super.onStdErr(data);
    if (this._stopResolve && endTag.test(this.stderr)) {
      const path = resolve(process.cwd(), 'a.mp4');
      this._stopResolve(path);
    }
  }
}

为了功能完善,我没有选择等待固定的时间,而是继续检查日志,直到发现 endTag 标记。另外,我也留下了 15s 超时保护,避免某些暂时没遇到的问题破坏功能。

至此,功能基本稳定完成。

分类
linux

解决 WSL Ubuntu 20.04 下使用 apt 源安装 node.js 的问题

随着 Ubuntu 20.04 发布,各大平台都适配发布了对应版本的系统,Windows WSL 也不例外。如果你是新系统,直接在 Microsoft Store 里搜索并安装 Ubuntu 即可;如果你是老系统,已经装过以前的版本,那么需要先卸载再安装,如果直接安装 Ubuntu 20.04 会有多个不同版本的 Ubuntu 共存。

装完系统后,接着安装其它软件。我现在比较喜欢用包管理工具安装软件,因为容易更新,而我又是更新爱好者。所以按图索骥,找到 node.js 的二进制包安装指引,复制执行:curl -sL https://deb.nodesource.com/setup_14.x | sudo -E bash - ,结果报错:gpg: can't connect to the agent: IPC connect call failed

经过搜索,得知这是 WSL 版 Ubuntu 20.04 的问题,与 WSL1 有一些不兼容,在 WSL2 上就没这个问题了。解决方案是装一些工具:

sudo add-apt-repository ppa:rafaeldtinoco/lp1871129
sudo apt update
sudo apt install libc6=2.31-0ubuntu8+lp1871129~1 libc6-dev=2.31-0ubuntu8+lp1871129~1 libc-dev-bin=2.31-0ubuntu8+lp1871129~1 -y --allow-downgrades
sudo apt-mark hold libc6

然后问题就解决了。

这个 issue 里还记录了一些别的方案,包括上面方案的修正版,不过我用起来没问题,也就没继续往下看。感兴趣的同学可以研究一下。

分类
linux

解决 Ubuntu 20.04 下无法打开蓝牙的问题

我的 Ubuntu 20.04 启动之后经常丢掉蓝牙。表现是设置里能看到蓝牙,状态是关闭,但是无法打开,每次点击开关过一会儿就恢复了,也没有报错。

因为要连接音箱,没有蓝牙很不方便,就搜索了一下,最后发现下面的解决方案。虽然没理解问题根源,但是解决了就好。

sudo rmmod btusb
sleep 1
sudo modprobe btusb

参考链接:

  1. Bluetooth firmware upload failing after ‘suspend’
  2. Bluetooth used to be enabled, now disabled and won’t enable. Ubuntu 16.04
分类
linux

重置 MariaDB root 密码

以我最常用的 Ubuntu 为例,记录如何重置 MariaDB root 密码。MariaDB 是 MySQL 原作者在 MySQL 闭源之后的再起之作,基本能完全兼容 MySQL。在我的 Ubuntu 上,它的大部分命名都沿用 MySQL。

1. 停掉 mysql 服务

$ service mysql stop

2. 以特殊方式启动 mysql

使用 --skip-grant-tables 可以跳过用户权限检查,让你没有密码也能连接。此时为了避免安全问题,可以加上 --skip-networking,防止有人此时通过网络连接你的数据库。在本地环境下差别不大。

$ sudo mysqld_safe --skip-grant-tables --skip-networking &

这一步可能会启动失败,导致下一步连接时报错:ERROR 2002 (HY000): Can’t connect to local MySQL server through socket ’/var/run/mysqld/mysqld.sock’ (2),此时,可以这样修正:

$ sudo mkdir /var/run/mysqld
$ sudo chown mysql /var/run/mysqld

3. 连接数据库

$ mysql -u root

正常情况下就连上了。

4. 重置密码

mysql> FLUSH PRIVILEGES;
mysql> ALTER USER 'root'@'localhost' IDENTIFIED BY 'new_password';

5. 重启 mysql

按照参考文档里介绍的做法,需要

$ sudo kill `cat /var/run/mysqld/mysqld.pid`
# 或
$ sudo kill `/var/run/mariadb/mariadb.pid`

不过我实际操作的时候只要把(2)开启的进程关掉就好了。然后重启 mysql 服务:

$ service mysql restart

6. 完成

尝试一下连接,应该可以成功了:

$ mysql -uroot -p

7. Ghost 连接

虽然命令行连接成功,但是 ghost start 仍然报错,经查,是 Ghost 的数据库连接方式有点问题,修改 config.production.json,加入一行配置即可,大体如下:

{
  "client": "mysql",
  "connection": {
    ....
    "socketPath": "/var/run/mysqld/mysqld.sock",
  }
}
分类
linux

Ubuntu 20.04 科学上网

闲言少叙,直奔主题。

0. 服务器

首先,你得先准备个服务器。本文不讨论如何制备科学上网服务器。

如果你用 Mac 或者 iPhone,想玩 Clubhouse,可以考虑买个现成,比如链接里这个

1. 客户端

接下来,下载客户端。

我用的是 shadowsocks-qt5,先在 https://github.com/shadowsocks/shadowsocks-qt5/releases 这个页面下载打包好的 AppImage 文件。

右键点击文件,在“权限”选项卡里勾选“允许文件作为程序执行”,然后双击运行,配置服务器信息。配置完成,点击“连接”,顺利的话,就会显示连接成功。

在命令行里输入 gnome-session-properties,会启动如下图所示的“启动应用程序首选项”,把刚才下载的 AppImage 路径添加进来,这样每次启动 ss 客户端都会自动启动了。

启动应用程序首选项

2. 生成 pac

安装genpac

pip install genpac

Ubuntu 20.04 默认 python版本是 3,所以可以使用 pip3 替代上面的 pip。

使用在线的 gfwlist

genpac --pac-proxy "SOCKS5 127.0.0.1:1080" --gfwlist-proxy="SOCKS5 127.0.0.1:1080" --gfwlist-url=https://raw.githubusercontent.com/gfwlist/gfwlist/master/gfwlist.txt --output="autoproxy.pac"

使用离线的 gfwlist

如果你的机器没有完成配置,暂时无法访问到 gfwlist,也可以先想办法下载到这个文件,然后离线生成。

genpac --pac-proxy "SOCKS5 127.0.0.1:1080" --gfwlist-proxy="SOCKS5 127.0.0.1:1080" --gfwlist-local=你的路径/gfwlist.txt --output="autoproxy.pac"

3. 配置服务器

Ubuntu 安装时就集成了 Apache2,并且默认也会启动它,所以直接把刚才生成的 pac 文件放在 /var/www/html/ 目录下,就能通过 http://localhost/autoproxy.pac 访问到。

不过我不喜欢用 apache,一般都会用 nginx 或者 openresty 取代之。好在默认情况下,nginx 的 web 服务目录也是 /var/www/html,所以只要卸载 apache 然后重新安装 nginx 即可。另外因为这个服务只为使用 pac 文件,所以可以不用换源。

sudo apt remove apache2
sudo apt update
# 安装 openresty 需要的系统依赖
sudo apt-get -y install --no-install-recommends wget gnupg ca-certificates
# 添加 key
wget -O - https://openresty.org/package/pubkey.gpg | sudo apt-key add -
# 添加 openresty 官方源
echo "deb http://openresty.org/package/ubuntu $(lsb_release -sc) main" \
    | sudo tee /etc/apt/sources.list.d/openresty.list
# 在更新一次,然后安装
sudo apt update
sudo apt install openresty
sudo service openresty start

然后访问 http://localhost/autoproxy.pac,如果能打开,就说明配置成功。

4. 启动全局配置

如果,打开“设置”,找到“网络”,打开“网络代理”,选择“自动”,然后填入上面说的 pac 地址即可。

5. 在其它应用里完成配置

比如,打开 Firefox,在“首选项”里找到“网络设置”,然后选择“使用系统代理设置”即可。

分类
电子产品

Ubuntu 20.04 配置 TP-WDN5200H 无线网卡

前情提要:朋友赞助了 AMD 2950x + 技嘉 x399 MEG Creation,我配上其它组件,装了台新电脑。接下来把老电脑收拾一下,装上 Ubuntu 20.04,打算补齐 x86 64 Linux 平台。

结果卡在无线网卡配置上,Ubuntu 无法识别和使用。我的无线网卡是 TP-Link 的 WDN5200H 免驱 USB 网卡,我直接用型号搜索,很容易就找到介绍安装教程

原来这个网卡同时集成了一块小 U 盘,里面是驱动程序,方便没有网络的人安装。但是在 Ubuntu 下,会优先把它识别成 U 盘(但是我也没找到盘符),所以无线网络不工作。解决方案就是先安装驱动,然后用 usb_modeswitch 切换工作模式,然后系统就会把它识别成无线网卡,然后就可以了。

但在我这里,这套操作行不通,切换设备的时候,上面都一样,但最后会报错:

Use interface number 0
 with class 255
Error: can't use storage command in MessageContent with interface 0; interface class is 255, expected 8. Abort.

我用上面的错误信息作为关键词搜索,只能找到各种关于 huawei modem 的讨论,解决不了问题。折腾很久,我突然注意到,我的网卡 ID 似乎和教程里不太一样,我的是 obda:c811,而教程里是 obda:1a2b(分号前是设备id,分号后是产品id),是不是这个原因呢?我以 0bda:c811 作为关键词,很快找到这个答案

原来核心原因在于原先的驱动是基于 4.x 内核的,从 19.04 开始,Ubuntu 内核已经升级到 5.x,所以要用新驱动。所以我按照帖子里的指示,安装了新驱动。一番折腾之后,还是不行……

眼看周末过去,没时间继续折腾了,我只有到京东下单了一款 m2 接口的 Intel AX200 无线网卡(含蓝牙)——据说 Intel 网卡都是免驱的,而且 m2 接口肯定没有集成 U 盘这种幺蛾子。

周一,拿到新网卡,插上。果然,Intel 网卡就是免驱,工作正常;但是,我没买天线,而这个鬼网卡没有天线是绝对不能工作的(客服告诉我可以试试,不行再买天线,我……),于是我只好再下单了一套天线。然后我无意打开 Wi-Fi 一看,WTF,这是啥,Rtl8821CU?不就是我的 TP-WDN5200H 么?原来我昨天其实就弄好了,只是没有重启,没有生效……

至此,问题解决。