一直跑 Minecraft 服务器也有点无聊,而且我毕竟不能总是玩 Minecraft。得给服务器找点活干,不如就来点自建服务,顺便试下 Rootless Podman 好不好使。

如果用 Docker,一般直接看官方安装文档就行了。Rootless Podman 下要踩些坑,话说我为啥要折腾红帽子的这东西啊……

这些项目都还在开发,所以下面的内容会比较有时效性;我也没 arm 或者 rv 的机器,下面操作都只在 amd64 Linux 下测试过。总之,仅供参考,如果有同学发现啥问题的话记得给我发个邮件。

code-server

code-server 提供有网页版的 VS Code。平板接上键盘鼠标,连上 code-server 操作十分舒适,上课拿来写代码完全没问题。code-server 比较轻,很多人就直接在平板上部署 code-server,不过我平板性能比较差,在平板上部署也不方便同步/多人协作,不如先在服务器上跑个试试。

code-server 官方提供有 Docker 镜像和部署文档,不过也可以稍微修改一下镜像,省下一点安装/重装后的时间。

这个镜像基于 Debian 11 (bullseye),除了没有什么冷门工具外用着都很好。

写个 Containerfile:

FROM codercom/code-server:latest
### 镜像里默认用户是 coder,安装程序的时候先给改成 root:
USER root
### 换镜像源,顺便推销一下隔壁学校的镜像站
### 镜像站是衡量大学水平的标准(不是),我这种没有自家镜像站的就只能算是大专了(笑)
RUN sed -i 's/deb.debian.org/mirrors.zju.edu.cn/g' /etc/apt/sources.list && \
apt update && DEBIAN_FRONTEND="noninteractive" apt -o Acquire::Retries=50 install \
### 两套 C/C++ 编译器,以及 Vim
gcc g++ clang vim \
### 安装 ipykernel,在 code-server 里启用 Jupyter 记事本的时候要用。别忘了也在 code-server 里装上对应插件
python3-ipykernel -y
### 把默认用户改回去
USER coder

保存为 Containerfile,构建一下:

### 镜像名字是随便取的,不必与上游镜像名字一样
podman build -t code-server .

之后就可以创建容器了:

podman create \
### 容器名字也是随便取的
--name code-server \
### 添加 SYS_PTRACE Capability,用 gdb、strace 和 ASan 之类的时候要用到
--cap-add sys_ptrace \
### 容器外面监听 8080 端口,转发到容器里面的 80 端口,即 code-server 的端口上
-p 8080:80 \
### 创建个名为 code-server 的卷(Volume),挂载到 /home/coder 上,这样把数据目录分离出来,后续重装或者迁移的时候会方便一点
### 卷的名字也是随便取的
### 重装的时候不删卷,创建新容器的时候仍然指定这个卷,这样老容器留下的 /home/coder 就挂载到新容器里了
-v code-server:/home/coder \
code-server

启动容器:

podman start code-server

启动之后别急着连,先改下 /home/coder/.config/code-server/config.yaml 里的密码。podman-exec 进容器,或者 podman-unshare 再 podman-mount 进去改都无所谓。

密码默认是明文保存的,如果想保存密码散列,文档里有写做法。

改完密码,重启一下容器:

podman restart code-server

之后用浏览器连上,开箱即用。

Overleaf

Overleaf,在线 LaTeX 编辑器,编译的时候会在服务器端生成 PDF(真够暴力的),让客户端渲染。总之,编译还是得等,不过起码不会把电脑的文件系统搞得很脏很脏,也不用在每台电脑上都装两千多个包的全量 texlive 了。稳定发行版上装 texlive 尚且很痛苦,要是在滚动发行版上,两星期还得更新一次就更……

其实直接在官网注册个免费用户就已经能用上了,但我用的时候写两行断三遍……想必是受到了大型混凝土复合结构的干扰。我的代理也没有足够稳定,而且学校电脑上也不好直接挂代理,那就扔服务器上吧。

Overleaf 很重,单纯启动都要等很久,对 CPU 和 IO 的要求都很高,开工之前最好还是先留至少 10 GiB 空闲空间。

Overleaf 官方提供有使用 Docker 部署的脚本(但是我们显然用不了),和 docker-compose 预设(但是在业界傻叉红帽子的努力下,podman-compose 现在可以说是非常难用)

手抄 docker-compose.yml,手动创建容器。docker-compose.yml 里面定义了三个容器,sharelatexredismongo

sharelatex 基于Ubuntu 20.04 LTS,里面还没预装 XeTeX。我想用 xeCJK 写中文,给它写个 Containerfile 补全依赖:

FROM sharelatex/sharelatex
RUN sed -i 's/archive.ubuntu.com/opentuna.cn/g' /etc/apt/sources.list && \
sed -i 's/security.ubuntu.com/opentuna.cn/g' /etc/apt/sources.list && \
apt update && DEBIAN_FRONTEND="noninteractive" apt -o Acquire::Retries=50 install texlive-full -y

构建,多等一会:

podman build -t sharelatex .

Rootless Podman 默认每个容器有独立的网络命名空间(namespace),但没有自己的 IP,也不能直接相互通信。Overleaf 的三个容器需要相互通信,正巧容器所监听的端口互不相同,不如把这三个容器放在同一个 Pod 里,共享网络:

podman pod create --name overleaf -p 9080:80
podman create --name=mongo --pod overleaf -v mongo:/data/db mongo:4.4
podman create --name=redis --pod overleaf -v redis:/data redis:5
podman create --name=sharelatex \
-e SHARELATEX_APP_NAME="Overleaf Community Edition" \
-e SHARELATEX_MONGO_URL="mongodb://mongo/sharelatex" \
-e SHARELATEX_REDIS_HOST="redis" -e REDIS_HOST="redis" \
-e ENABLED_LINKED_FILE_TYPES="project_file,project_output_file" \
-e ENABLE_CONVERSIONS="true" -e EMAIL_CONFIRMATION_DISABLED="true" \
-e TEXMFVAR="/var/lib/sharelatex/tmp/texmf-var" \
--pod overleaf -v sharelatex:/var/lib/sharelatex sharelatex

SHARELATEX_APP_NAME 可以随便改,放 UTF-8 也没问题。

容器间互相通信,应该也可以通过设置 podman-network 来实现,不过我还没试过。

启动容器,记得留够系统资源,内存留至少 1 GiB 为好:

podman pod start overleaf

然后就会发现 openSUSE Leap 15.4 上总是启动失败,Fedora 37 上却没问题。检查一下是 /etc/my_init.d/10_syslog-ng.init (怎么还 my_init.d 了)执行失败了:

     26 syslogng_wait() {
     27     if [ "$2" -ne 0 ]; then
     28         return 1
     29     fi
     30
     31     RET=1
     32     for i in $(seq 1 30); do
     33         status=0
     34         syslog-ng-ctl stats >/dev/null 2>&1 || status=$?
     35         if [ "$status" != "$1" ]; then
     36             RET=0
     37             break
     38         fi
     39         sleep 1s
     40     done
     41     return $RET

syslog-ng 启动不起来,不过我也不想修了,直接修改脚本,在第 41 行超时也正常返回就是了……测试一下,缺个日志记录程序对 Overleaf 的基本功能影响不大。

启动之后进到 <主机>:<端口>/launchpad 页面,设置一下管理员用户,之后就能手动添加普通用户了。

Overleaf 启动慢,关闭也慢。停止容器的时候最好给等待时间调得足够久,不然程序被 SIGKILL 了就不好了。我一般都是等待一小时:

podman pod stop -t 3600 overleaf

部署完 Overleaf,感觉……这玩意写得太乱了,接下来只要不出问题,我肯定不愿意再碰它。

杂项

Mumble 和 kiwix-serve 对 Podman 的兼容性都还不错,基本没坑,所以不写了。

Nextcloud 和更复杂的东西,现在暂时用不到,所以鸽掉了。

总结

发现自己还是不熟很多入门技术,就业焦虑倍增。果然只要不是天龙人,轻松愉快的生活就只是一种幻想,实际上根本不存在。

另外,红帽子确实是业界傻叉

题外话

发现身边的人甚少使用 SSH 转发……也许都去用 VPN 了,毕竟 SSH 隧道无论从性能还是功能上都是比不上正经 VPN 的。

但是我对网络一窍不通……而且现在多数 Windows 机器预装有 OpenSSH,临时使用 SSH 转发也足够方便,同时对性能和 UDP 等的要求不高,就暂且开着 SSH 隧道了。

每次都让连服务器的人去查文档也太费时间了……

直接使用 OpenSSH 内建转发功能

设置专用于 TCP 转发的用户

打开 sshd_config

### 如果用户名为 tcpguest ,则应用下列规则。名字是随便写的
Match User tcpguest
        ### 禁用 ssh-agent 转发
        AllowAgentForwarding no
        ### 禁用套接字(socket)转发
        AllowStreamLocalForwarding no
        ### 对于远程 TCP 转发(服务器端监听端口,将传入流量转发到客户端某一端口),要求服务器只监听本地发起的连接
        GatewayPorts no
        ### 禁用远程 TCP 转发,只允许本地 TCP 转发。其实写了这句之后,上一句的值也没啥意义了……
        AllowTcpForwarding local
        ### 禁用 X11(某个用户越来越少了的图形协议)转发。其实下面写了 ForceCommand 之后,这句不写也没啥问题
        X11Forwarding no
        ### 对于本地 TCP 转发,允许客户端发起连接的端口。空格分隔
        PermitOpen 127.0.0.1:25565 127.0.0.1:8080 127.0.0.1:64738
        ### 设置横幅,纯文本文件,会在输入密码之前显示。用来整活很方便
        Banner /home/tcpguest/.ssh/banner
        ### 强制执行某一命令,这样 sftp 之类的也就用不了了。当然如果客户端制定了 -N,这命令也不会执行
        ForceCommand neofetch

客户端连接参数

ssh <用户名>@<主机名> -p <端口号> -v -N -T -L 1234:127.0.0.1:4321

-v:开启详尽模式,这样才能一眼看出连不通时是哪出了问题。

-N:不执行命令,单纯转发并不需要执行额外的命令(而且在有服务器设置的 ForceCommand 的前提下,命令执行完也会自动把连接断开)。

-T:不分配终端。

-L:本地转发,客户端监听 1234,传入的流量转发到服务器端 127.0.0.1(也就是服务器自己)的 4321 端口上。也可以在 -L 和上面的参数之间指定个监听 IP,表示只有指定 IP 能发起连接,比如写成 127.0.0.1:1234:127.0.0.1:4321 就会禁止其他机器连接客户端上的 1234 端口。

查看服务器的 SSH 指纹

其实我也没看到什么优雅的方法,所以就在服务器端 ssh-keyscan 127.0.0.1|ssh-keygen -lf - 了。

sshuttle

如果有一堆端口要转发,或者想要更好的性能,用 sshuttle 就是了。需要注意,用 sshuttle 就不能通过设置 ForceCommand 限制 shell 功能了,sshuttle 是要执行命令的。当然这种情况下,PermitOpen 也不会生效(因为 sshuttle 并没用到 SSH 本身提供的 TCP 隧道功能,当然不会被它的设置限制)。

关于 SSH 隧道

为什么不搞 TLS?可以,但这样如果自己给证书签名,就需要别人导入证书(这可比检查服务器的 SSH 指纹要麻烦);申请证书的话还要考虑包括有效期在内的各种麻烦事,我作业还没写完呢。

TLS 很好,但我服务器上那堆玩具的使用规模,还没有大到能和用 TLS 的成本相抵。对于个人和小组织内部的使用,SSH 已经比较简单了。