曾小树 发布的文章

Phabricator 是一个类似于 GitHub 的协作开发平台,类似于 。同样的,Phabricator 也能提供 Git 仓库托管服务。
Git 服务有基于 HTTP 与 SSH 两种协议的连接方式,但是 Phabricator 提供的基于 HTTP 的 Git 服务并不完善,比如它不支持 Shallow Clone (带 --depth 参数的 Git 拷贝方法),所以我们仍然需要用到基于 SSH 协议的连接方法。

但是官方提供的文档中要求我们使用一个单独的进程和端口来提供 SSH 服务,这样的话我们的 Git 链接中需要像下面这样带上端口号,会显得比较丑陋:

ssh://[email protected]:2222/source/libphutil.git

当然我们也可以让 Git 服务独占 22 端口,系统 SSH 服务使用另外一个端口,但是这样可能会导致 SSH 相关的服务出现各种问题(通常情况下更改 SSH 服务的端口并不会出现问题,但是我使用的云服务器商提供的某些服务依赖 22 端口的 SSH)。

所以这里我介绍一下如何让 Phabricator 的 Git 服务直接使用系统自带的 SSH 服务进程,这样我们就可以让两个服务同时使用默认的 22 端口。

除 Git 外,Phabricator 也能提供 Mercurial 和 Subversion 的托管服务,这篇文章的 SSH 配置对这两种仓库的托管也同样适用。Phabricator 只能安装于类 Unix 系统上,不能安装在 Windows 上。我下面使用的操作系统为 Debian 10,其他类 Unix 系统也可以作为参考。

开始配置

创建用户

首先为系统创建一个 git 用户:

sudo useradd -U -r -m -s /bin/sh git
  • -U: 创建一个同名的用户组并将用户放入用户组中
  • -r: 创建一个系统用户
  • -m: 同时创建用户的 home 目录
  • -s: 指定用户使用 shell (这里要配置为 /bin/sh)

创建 hook 文件

Phabricator 目录下 ./phabricator/resources/sshd 文件夹有个文件 phabricator-ssh-hook.sh,这就是 hook 文件的样本,你请求登录 git 用户时它将会被 sshd 进程调用。

我们首先把它复制到 /usr/local/sbin/phabricator-ssh-hook.sh,然后修改它的内容。需要修改的有 VCSUSERROOT 这两个变量。

VCSUSER=git
ROOT=/usr/share/nginx/html/phabricator
  • VCSUSER 为我们需要使用的系统用户的名称,这里我们使用前面创建的 git 用户
  • ROOT 是我们的 phabricator 文件夹的位置

注意确保 phabricator-ssh-hook.sh 具有可执行权限 (它的文件权限通常应为 755)。

修改 sshd 配置文件

/etc/ssh/sshd_config 文件添加以下内容:

Match User git
    AllowAgentForwarding no
    AllowTcpForwarding no
    PasswordAuthentication no
    AuthorizedKeysFile none
    AuthorizedKeysCommand /usr/local/sbin/phabricator-ssh-hook.sh
    AuthorizedKeysCommandUser git

这里配置了让 sshd 检测到 git 用户登录时调用 hook 脚本。

修改完成后务必使用以下命令检查 sshd_config 语法是否正确,否则你可能会无法再一次登入系统:

sudo sshd -t -f /etc/ssh/sshd_config

如果没有提示错误才可以重启 sshd 令配置生效:

sudo systemctl restart sshd

修改 sudoers 配置

Phabricator 的 SSH 用户 git 需要以 phd 用户的名义来运行 git 相关操作,这时就需要配置 sudoers 配置文件了。关于 Phabricator 三种用户之间的权限调用展开来写比较复杂,具体可以参阅官方文档 (中文版):「Diffusion 用户指南: 仓库托管」。


以上,我们就完成了全部配置。现在,已经可以通过 22 端口同时访问 SSH 服务和 Git 服务了。

参考资料

为了保证输入法能够输出所有你想要的汉字,Rime 默认使用了超大字符集。但超大字符集同时也会带来一个问题:「系统自带字体不一定能够显示所有的汉字」。
因此,会造成 Rime 输入法候选词中,出现很多方块问号,这些就是系统无法显示的生僻字。

RIME生僻字

解决这个问题最简单粗暴的一个方法就是使用支持超大字符集的字体。这样所有的汉字都可以被正确显示,但是这样的话,候选词中会出现很多我们通常不会输入的生僻字词。

2013 年,国务院公布了由教育部、国家语言文字工作委员会组织制定的「通用规范汉字表」。其中共收录了 8105 个汉字,其中包含 3500 个日常基本用字,3000 个次常用字,以及 1605 个人名、地名、专业术语、教材文言文中的常用汉字。而通常我们平时使用的汉字一般在 3000 个左右,「规范汉字表」中的汉字完全可以满足绝大多数人的简体中文输入需求。

因此,我希望能够让 Rime 输入法仅输出「规范汉字表」中的汉字,这样不至于让输入法候选字词过多,又能兼顾我的常用输入需求。

而除了这 8105 个常用汉字以外,我还希望 Rime 能够像系统输入法一样,可以支持输出并显示 Emoji 表情字符,Emoji 字符也是我日常输入中用到比较多的字符。如果能够直接将 Emoji 字符集成到输入法中,那么是最好不过了的。

因此,我希望我的 Rime 输入法能够支持的字符有:

  1. 「规范汉字表」中的 8105 字
  2. Emoji 表情字符
  3. 有可能需要输入其他语言 (非中文) 的一些字符

而通过网络搜索,我找到了几种过滤生僻字的方法:

  1. 通过字符集过滤

    这种方法能够通过 Rime 内置的字符集(比如 GB2312, GBK, UTF-8 等等)过滤汉字。但是这种方式不能使用自定义字符集。

  2. 通过词典过滤

    通过词典过滤就是将所有可能用到的字、词全部制作成 Rime 的词典。这种方法太不优雅了,工作量大,而且后续维护起来也很不方便。

以上的方法都不能很好地满足我的需求,因此我找到了另外一种方法:「lua_filter」,通过 lua 脚本来过滤候选词。

librime-lua 是一个给 librime 添加了 lua 脚本的 Rime 插件。
并且官方发行的「小狼毫」(Windows) 和「鼠须管」(macOS) 都默认集成并启用了 librime-lua 插件。因此,可以通过 lua 编写一个自定义过滤器来实现生僻字过滤的功能。

至于 Linux 下的 Rime 输入法由各发型版打包,是否集成了 librime-lua 插件我不大清楚。

lua 脚本的位置应当放在 Rime 配置文件夹 中。

其中包括一个 rime.lua 脚本,与一个 lua 文件夹。rime.lua 是 Rime 读取 lua 脚本的入口,lua 过滤器的函数必须在 rime.lua 文件中声明。

但是如果把全部代码都写入到一个文件中的话,会让 rime.lua 文件变得很臃肿,因此可以把真正的脚本代码放入 lua/charset.lua 中,然后在 rime.lua 中引入它。

--[[
charset_filter: 生僻字过滤器
--]]

-- 从 txt 文件中读取全部字符
-- return: string
local function read_chars(filename)
  local chars = ""
  for line in io.lines(filename) do
    chars = chars..line
  end
  return chars
end

-- 自定义字符集列表 (字符串列表),候选词只能包括列表中的字符,否则将被过滤掉
local charsets = {
  -- 通过 read_chars 读取 txt 文件中的所有字符并添加到字符集列表中
  -- 将相应的字符写入 txt 文件中,然后通过 read_chars 在这里引入它就可以
  -- 这里为了区分不同的字符集,将它们写入了不同的 txt 文件中,但是你也可以只使用一个 txt 文件
  -- 这里似乎只能使用 txt 文件的绝对路径,怎么使用相对文件路径我还没有搞清楚

  -- lua 中通过 .. 连接两个字符串,而 os.getenv("HOME") 表示我的 HOME 目录 (Windows 下不能这样)
  -- os.getenv("HOME").."/Library/Rime/lua/chars/emoji.txt" 等同于 "/Users/zengxs/Library/Rime/lua/chars/emoji.txt"
  read_chars(os.getenv("HOME").."/Library/Rime/lua/chars/emoji.txt"),  -- emoji 字符集
  read_chars(os.getenv("HOME").."/Library/Rime/lua/chars/symbols.txt"),  -- 符号字符集
  read_chars(os.getenv("HOME").."/Library/Rime/lua/chars/alphanum.txt"),  -- 数字字母字符集
  read_chars(os.getenv("HOME").."/Library/Rime/lua/chars/mandarin.txt"),  -- 通用规范汉字表
}

-- 验证一个字符编码是否在自定义的字符集中
local function is_valid_char(char_code)
  for i=1, #(charsets) do
    for p, code in utf8.codes(charsets[i]) do
      if (code == char_code) then
        return true
      end
    end
  end
  return false
end

-- 检查字符串是否符合要求
local function is_valid_string(text)
  for p, char_point in utf8.codes(text) do
    if (not is_valid_char(char_point)) then
      return false
    end
  end
  return true
end

local function filter(input)
  for cand in input:iter() do
    if (is_valid_string(cand.text)) then
      yield(cand)
    end
  end
end

return filter

然后修改 rime.lua

-- charset_filter 将会是字符集过滤器的名称
charset_filter = require("charset")

然后在 luna_pinyin.custom.yaml 中启用这个过滤器:

patch:
  # engine/filters/+ 表示在现有的「过滤器列表」末尾再添加下面的过滤器
  engine/filters/+:
    - [email protected]_filter

然后重新部署就就可以看到字符集已经生效了。


关于我制作的「通用规范汉字表」的 txt 文件可见:https://gist.github.com/zengxs/3c277a02a3bfa9a556a09a6b497ad061


参考资料

虚拟内存是操作系统物理内存不足时对系统内存的一个补充。

操作系统在内存不足时,会将物理内存中不常用的内存页 (一段内存区块) 保存到磁盘上,这个过程被称为「换页」。

虚拟内存是物理内存的延伸,它能让系统跨越物理配置的限制,对内存容量进行「升级」,用磁盘替代物理内存,让机器看起来像是有了更多内存。但要注意的是,如果大量使用虚拟内存,系统产生过多换页行为,将会导致系统变得非常缓慢。毕竟磁盘的 IO 性能比起真实的物理内存的 IO 性能要慢好几个数量级。

所以虚拟内存通常只是用于缓解机器的内存压力,要彻底解决内存问题,仍然需要升级机器的配置。因此虚拟内存的容量通常不宜设得过大,因为过大也没有用。而通常的,虚拟内存应当设置为物理内存大小的 1~2 倍。

虚拟内存在 Linux 下所被存储的磁盘空间又被称之为「交换空间」 (swap)。

如果是手动安装的 Linux 发行版,那么在系统安装的时候就会提示你是否设置交换空间,或默认就为你设置了交换空间。而这时所设置的交换空间通常在磁盘中单独占一个分区。

而如果是云服务器所提供的操作系统,出于性能考虑,所以默认没有设置交换空间,这一点可以通过 free 命令查看:

$ free -h
              total        used        free      shared  buff/cache   available
Mem:           592M        223M         88M         16M        280M        259M
Swap:            0B          0B          0B

其中 Swap 显示为 0B, 0B, 0B 表示系统中没有配置交换空间。

而我这里简单介绍一下如何在云服务器上配置交换空间。

由于在云服务器上,磁盘已经进行分区,想要在不影响现有系统的情况下重新对磁盘分区来设置交换分区困难略大,所以这里介绍一下如何通过交换文件的方式实现虚拟内存。

首先通过 dd 命令创建一个所需大小的空文件用来作为交换文件,这里以 1024 M 的交换文件为例:

sudo dd if=/dev/zero of=/swapfile count=1024 bs=1M

这里 dd 命令将文件写入到 /swapfile 的位置,每个块为 1M 大小,写入 1024 个块,一共是 1024M。

然后由于内存中的数据应当是需要保密的,不能让其他进程读取交换空间的内容,需要对 /swapfile 的读写权限加以限制:

sudo chown root:root /swapfile
sudo chmod 0600 /swapfile

然后格式化交换文件:

sudo mkswap /swapfile

挂载交换空间:

sudo swapon /swapfile

再次使用 free 命令查看挂载是否成功:

$ free -h
              total        used        free      shared  buff/cache   available
Mem:           592M        219M         48M         16M        324M        266M
Swap:          1.0G          0B        1.0G

Swap 为 1.0G, 0B, 1.0G 表示挂载成功了。

如果需要卸载交换空间,则需要使用命令 swapoff:

sudo swapoff /swapfile