Overleaf Docker编译复现计划

Overleaf Docker编译复现计划

请添加图片描述

Overleaf Pro可以支持不同年份的Latex镜像自由选择编译,这实在是一个让人看了心痒痒的功能。但是很抱歉,这属于Pro付费功能。但是我研究了一下,发现其实和Docker编译相关的代码,社区版的很多代码都没有被Overleaf删完。这就给我们复现提供了可能。甚至可以说只要配好了环境变量,略微修改就可以用啦!

一、需要改动的代码

Overleaf本质是一个微服务的例子,所有的微服务都在services文件夹里面。要实现Overleaf Docker编译复现计划,理论上需要改动

  • clsi是负责编译的微服务,他的全程是:Common LaTeX Service Interface (CLSI)
  • web是前端的服务,也负责后端的服务(这是一个很不好的例子,大概来说是GET服务拉前端页面,POST就是后端的API)
    • 其余的微服务比如Realtime就是用来实时显示编辑者的活动,关系不大,感兴趣的自行研究
  • 然后就是搭建者自己要设置好环境变量
    • 有哪些是允许用户使用的Latex镜像
    • 是否开启容器编译

再来说说编译过程是怎么样的:

  • 用户在前端点编译按钮
  • 后端web微服务进程获取用户的编译设置(从数据库?或者什么来源)、发送请求给clsi微服务处理
  • 具体的编译过程可以看clsi微服务的Readme文档,还算比较详细
  • 然后clsi根据环境变量,选择是在本地shell执行,还是另外启动一个Docker编译
    • 如果在本地shell执行,那么需要在sharelatex下载好Texlive
    • 如果在容器中执行,会创建一个新的容器,把编译的临时文件夹挂载到这个新的容器
    • 容器执行完之后退出,gc垃圾回收需要容器里面的cron定期删除

这里补充一句,Overleaf社区版的本质就是把一堆微服务全部跑在一个容器Sharelatex里面,所以为什么学校的Overleaf隔一段时间就可能爆炸,大概率就是编译某个项目卡死了、人多了或者什么原因,这也足以看出Overleaf的刀法。

1)环境变量

首先要让用户在前端能够看到容器镜像的选择列表,追踪前端的镜像选择的下拉栏目的标签,翻译后一路追踪,发现web/config/settings.defaults.js中的文件,还有clsisetting总结一下环境变量,大概有这些:

// ###########################################################################
// 下面的是clsi微服务的配置选项
DOCKER_RUNNER = true    # 是否开启Docker编译
TEXLIVE_IMAGE = ""      # 默认的texlive的镜像# 如果没设置,则为 quay.io/sharelatex/texlive-full:2017.1
TEXLIVE_IMAGE_USER = (默认是tex) # 到时候根据
COMPILE_GROUP_DOCKER_CONFIGS // compileGroupDockerConfigs = {//    priority: { 'HostConfig.CpuShares': 100 }//    beta: { 'dotted.path.here', 'value'}// }
APPARMOR_PROFILE = 不知道? // 没看出来干什么的// 可以用的编译镜像,用空格分割开来!
ALLOWED_IMAGES = texlive/texlive-full:2024 texlive/texlive-full:2023
// 对应的代码if (process.env.ALLOWED_IMAGES) {try {module.exports.clsi.docker.allowedImages =process.env.ALLOWED_IMAGES.split(' ')} catch (error) {console.error(error, 'could not apply allowed images setting')process.exit(1)}}// 这个还是clsi的环境变量
COMPILES_HOST_DIR = (似乎已经废弃? )
// 如果 SANDBOXED_COMPILES_SIBLING_CONTAINERS == true
// 就会用兄弟容器来跑沙箱编译? 然后执行下面的
// 我至今没懂什么兄弟容器是什么意思,好怪,可能就是个自己起的名词罢了
// settings.path.sandboxedCompilesHostDir = process.env.SANDBOXED_COMPILES_HOST_DIR
SYNCTEX_BIN_HOST_PATH = (目前没看到使用这个变量的地方,可能相关代码被删了)// ###########################################################################
// Web容器要配置下面的东西 
SANDBOXED_COMPILES = "true"
TEX_LIVE_DOCKER_IMAGE = 默认的镜像?
COMPILER_PATH
SANDBOXED_COMPILES_HOST_DIR
SANDBOXED_COMPILES_SIBLING_CONTAINERS = "true"// 最后:
// 注意把宿主机的docker的sock文件挂载进去
// socketPath: '/var/run/.sock',
2)Web部分要改的内容

这是被隐藏的image-name选择栏目对应的tsx文件

services/web/frontend/js/features/editor-left-menu/components/settings/settings-image-name.tsx

具体内容:

export default function SettingsImageName() {const { t } = useTranslation()const { imageName, setImageName } = useProjectSettingsContext()const allowedImageNames = getMeta('ol-allowedImageNames') as| AllowedImageName[]| undefinedconst options: Array<Option> = useMemo(() =>allowedImageNames?.map(({ imageName, imageDesc }) => ({value: imageName,label: imageDesc,})) ?? [],[allowedImageNames])if ((allowedImageNames?.length ?? 0) === 0) {return null}return (<SettingsMenuSelectonChange={setImageName}value={imageName}options={options}label={t('tex_live_version')}name="imageName"/>)
}

然后找这个标签的来源:

meta(name="ol-allowedImageNames" data-type="json" content=allowedImageNames)

定位到:

overleaf/overleaf/services/web/app/src/Features/Project/ProjectController.js

继续:

const allowedImageNames = ProjectHelper.getAllowedImagesForUser(user)

找到了这个函数的定义:

const Settings = require('@overleaf/settings')function getAllowedImagesForUser(user) {const images = Settings.allowedImageNames || []if (user?.alphaProgram) {return images} else {return images.filter(image => !image.alphaOnly)}
}

这下路被堵住了,我不知道这个overleaf/setting包是干什么的。找了一个别的demo,发现就是每个微服务app里面的config文件里面写的键值对。那我只需要改web/config下面的配置就好了。

接下来的问题:allowedImageNames怎么写

{"alphaOnly": false,"imageName": "texlive-full:2022.1"
}// setting里面还要写:
imageRoot = 'docker-repo/subdir'// 我是傻逼,应该直接找他的测试目录里面的(我本来直接忽略了测试用例的js)
// 他自己都写好了测试用例,这就是数据格式,不得不说Overleaf啊
// 我真心觉得他就该开源的,整一个闭、开源结合多累,代码删删改改。
// imageDesc估计是用来描述镜像的,很可能是网站前端的展示的选项
imageRoot: 'docker-repo/subdir',
allowedImageNames: [{ imageName: 'texlive-0000.0', imageDesc: 'test image 0' },{ imageName: 'texlive-1234.5', imageDesc: 'test image 1' },
],// 再结合一下,完全正确!回顾之前的代码
// label就是用户选择的时候的选项,value是隐藏在背后的值const options: Array<Option> = useMemo(() =>allowedImageNames?.map(({ imageName, imageDesc }) => ({value: imageName,label: imageDesc,})) ?? [],[allowedImageNames])

那么,用户如果改变了编译的image呢?

  // 用户可以通过选择,改变当前project的编译的镜像// 根据Overleaf官网测试的,请求参数是 {imageName: "texlive-full:2022.1"}setImageName(projectId, imageName, callback) {if (!imageName || !Array.isArray(settings.allowedImageNames)) {return callback()}imageName = imageName.toLowerCase()const isAllowed = settings.allowedImageNames.find(allowed => imageName === allowed.imageName)if (!isAllowed) {return callback(new Error(`invalid imageName: ${imageName}`))}const conditions = { _id: projectId }const update = { imageName: settings.imageRoot + '/' + imageName }Project.updateOne(conditions, update, {}, callback)},

二、操作开始

理论存在,实践开始!用Github Codespace开始整活。

先拉两个镜像用来备选:

docker pull ghcr.io/xu-cheng/texlive-full:20240101
docker pull ghcr.io/xu-cheng/texlive-full:20220101

这里补一句,容器镜像的tag必须要是2021.1的格式,因为他代码里面有一个正则表达式匹配的match,就是靠:[年份]来匹配,然后设置环境变量的,其实我觉得这样好蠢啊,直接默认用容器镜像自带的不就好了吗?难道js的库不支持?没办法,为了能验证,只能自己改tag

然后改web容器的config/setting.default.js

imageRoot:'docker.io/texlive',
allowedImageNames: [{ imageName: 'texlive-full:2021.1', imageDesc: 'Tex2021' },{ imageName: 'texlive-full:2022.1', imageDesc: 'Tex2022' },
],

然后是环境变量配置,sharelatex容器配置,我至今把官方server pro的配置偷过来了。

这里注意,我们一般都是用Overleaf Toolkit安装的,所以他默认有一个data文件夹

  • data文件夹往下,里面的Sharelatex,就是放的编译容器的数据
  • 自己对照自己服务器的目录改,除非你用户名也叫ayaka
  • SYNCTEX_BIN_HOST_PATH这个好像不配也可以,说实话没找到里面哪里用到这个变量了的
SANDBOXED_COMPILES: "true"
SANDBOXED_COMPILES_SIBLING_CONTAINERS: "true"    #### IMPORTANT
SANDBOXED_COMPILES_HOST_DIR: "/home/ayaka/toolkit/data/sharelatex/data/compiles"  #### IMPORTANT
SYNCTEX_BIN_HOST_PATH: "/home/ayaka/toolkit/data/sharelatex/bin"  #### IMPORTANT
TEX_LIVE_DOCKER_IMAGE: "texlive/texlive:2023.01"

配置好docker-Compose文件后,开始在容器里面安装docker(建议你手动一行一行的执行,否则一键粘贴哪里炸了都不知道)安装好之后测试一下docker -v

# Add Docker's official GPG key:apt-get updateapt-get install -y ca-certificates curl gnupginstall -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg |  gpg --dearmor -o /etc/apt/keyrings/docker.gpgchmod a+r /etc/apt/keyrings/docker.gpg# Add the repository to Apt sources:
echo \"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \tee /etc/apt/sources.list.d/docker.list > /dev/nullapt-get updateapt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

重启容器,因为对Sharelatex容器的js代码的修改,必须要重启之后,才能生效!

然后就开始测试编译,结果发现错误(我没截屏,只能靠回忆),错误大概是:www-data用户不存在?突然想起clsi里面启动容器的时候,有一个选项就是User,定义的似乎就是www-data

那也就是要把xu-cheng的那个镜像,添加上www-data的用户就好咯?自己写了一个Dockerfile,然后继续测试编译,发现又报错了Path找不到

// 目录 services/clsi/app/js/DockerRunner.js// set the path based on the image yearconst match = image.match(/:([0-9]+)\.[0-9]+/)const year = match ? match[1] : '2014'env.PATH = `/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/texlive/${year}/bin/x86_64-linux/`

所以你还得进入容器里面看看,tex相关的可执行的目录在哪?然后把这个path改成正确的,我就不多说了,这个跟镜像有关系。

然后就遇到最烦的问题了:权限不够,我记得在那个日志里面找到Permission Deny,然后一通查找才知道,要把那个Latex编译容器里面的www-data的用户的UID、GID都设置为33,也就是说和sharelatex容器里面的UID/GID完全一样,才能保证读写顺畅,太离谱了。

一怒之下,爆改Dockerfile

# 使用方法 docker build
# FROM debian:testing-slimFROM ghcr.io/xu-cheng/texlive-full:20240101# Install the shadow package
RUN apk --no-cache add shadowRUN groupmod -g 340 xfs
RUN usermod -g 340 -u 340 xfs# 添加用户www-data,并将其添加到www-data组
RUN adduser -u 33 -g 33 --disabled-password -G www-data www-data

说实话都这么折腾了,还把人家原先是33好的uid、gid给改了,会不会引发别的问题,还不如自己去打一个碟跑Texlive,何苦呢?

说实话我还折腾过Texlive官方的那个镜像Docker,结果因为Unix内核太老了似乎,导致跑Xelatex的时候疯狂报错,说熵不够,随机性搞不定,我说实话也没找到任何资料,只能通过换镜像来解决这个问题了吧。

如果有人遇到类似的,或许可以参考一下。

三、尾声(GC垃圾回收)

overleaf的程序不会自己删除容器,好傻,还得靠我cron大法手动删除?好吧,收个尾!考验gpt脚本的时候到了。

写的时候记得别把正在跑的容器给删了,那就寄了。

#!/usr/bin/env bashset -euxecho "-------------------------"
echo "Delete container"
echo "-------------------------"# 获取所有已停止、挂了的容器的ID
stopped_containers=$(docker ps -q -f "status=exited" --filter "status=created" --filter "status=dead")# 循环遍历每个停止的容器
for container_id in $stopped_containers; do# 获取容器名称container_name=$(docker inspect --format '{{.Name}}' $container_id)# 移除名称以"project-"开头的容器if [[ $container_name == "/project-"* ]]; thenecho "Removing container: $container_name"docker rm -f $container_idfi
done

然后记得改cron的配置项

# 目录 etc/cron.d
* * * * *    root  /overleaf/cron/delete-docker.sh >> /var/log/sharelatex/cron-delete-projects.log 2>&1

反正每分钟删除一次就好了,免得机器上有太多没用的容器。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/616653.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

docker部署mongo过程

1、拉取MongoDB镜像&#xff0c;这里拉取最新版本。 docker pull mongo2、运行容器 docker run -d --name mongo -p 27017:27017 \ -e MONGO_INITDB_ROOT_USERNAMEadmin \ -e MONGO_INITDB_ROOT_PASSWORD123456 \ mongo:latest --auth#由于 mongodb 默认情况下&#xff0c;…

【Linux】编写第一个小程序:进度条

文章目录 1. 预备知识1.1 简单认识几个函数1.1.1 sleep()1.1.2 fflush()1.1.3 usleep()1.1.4 memset() 1.2 缓冲区1.3 回车与换行 2. 编写入门版的进度条2.1 基本逻辑2.2 美化效果2.3 代码实现2.4 执行效果 3. 编写升级版的进度条3.1 代码实现3.2 执行效果 1. 预备知识 1.1 简…

OpenHarmony—子系统开发之内核概述

内核简介 用户最常见到并与之交互的操作系统界面&#xff0c;其实只是操作系统最外面的一层。操作系统最重要的任务&#xff0c;包括管理硬件设备&#xff0c;分配系统资源等&#xff0c;我们称之为操作系统内在最重要的核心功能。而实现这些核心功能的操作系统模块&#xff0…

【小白专用】C#关于角色权限系统

&#xff08;C#&#xff09;用户、角色、权限 https://www.cnblogs.com/huangwen/articles/638050.html 权限管理系统——数据库的设计&#xff08;一&#xff09; https://www.cnblogs.com/cmsdn/p/3371576.html 权限管理系统——菜单模块的实现&#xff08;二&#xff09; …

Java LeetCode刷题 单调栈

单调栈 单调栈概念 每日温度 单调栈 概念 单调栈&#xff08;Monotonic Stack&#xff09;是一个特殊的数据结构&#xff0c;它是一种栈&#xff0c;但具有单调性的特性。单调栈有两种类型&#xff1a;单调递增栈和单调递减栈。 在单调递增栈中&#xff0c;栈内的元素保持递…

Linux 【C编程】IO进阶— 阻塞IO、非阻塞IO、 多路复用IO、 异步IO

文章目录 1.阻塞IO与非阻塞IO1.1为什么有阻塞式&#xff1f;1.2非阻塞 2.阻塞式IO的困境3.并发IO的解决方案3.1非阻塞式IO3.2多路复用IO3.2.1什么是多路复用IO&#xff1f;3.2.1多路复用IO select原理3.2.1多路复用IO poll原理 3.3异步IO 1.阻塞IO与非阻塞IO 1.1为什么有阻塞式…

【教3妹学编程-算法题】统计出现过一次的公共字符串

3妹&#xff1a;哈哈哈哈哈哈&#xff0c;太搞笑了~ 呵呵呵呵呵呵 2哥&#xff1a;3妹干嘛呢&#xff0c; 笑的这么魔性&#xff01; 3妹&#xff1a;在看王牌对王牌&#xff0c;老搞笑了 2哥&#xff1a;这季好像没有贾玲吧。 3妹&#xff1a;是啊&#xff0c;听说贾玲去导电影…

Vue入门六(前端路由的概念与原理|Vue-router简单使用|登录跳转案例|scoped样式)

文章目录 前要&#xff1a;前端路由的概念与原理1&#xff09;什么是路由2&#xff09;SPA与前端路由3&#xff09;什么是前端路由4&#xff09;前端路由的工作方式 一、Vue-router简单使用1&#xff09;什么是vue-router2) vue-router 安装和配置的步骤① 安装 vue-router 包②…

React Native 桥接组件封装原生组件属性

自定义属性可以让组件具备更多的灵活性&#xff0c;所以有必要在JS 层通过自定义属性动态传值。 一、添加原生组件属性 因为 ViewManager 管理了整个组件的行为&#xff0c;所以要新增组件属性也需要在这里面&#xff08;如 InfoViewManager&#xff09;进行定义。 1、在Inf…

从DETR到Mask2Former(1):DETR-segmentation结构全解析

网上关于DETR做的detection的解析很多&#xff0c;但是DETR做Segmentation的几乎没有&#xff0c;本文结合DETR的论文与代码&#xff0c;对DETR做一个详细的拆解。理解DETR是理解Mask2Former的基础。 首先得把DETR-segmentation给run起来。Github上DETR的repository&#xff0…

【python入门】day26:统计字符串中出现指定字符的次数

案例 实际上if name“main”:就相当于是 Python 模拟的程序入口 。由于模块之间相互引用&#xff0c;不同模块可能都有这样的定义&#xff0c;而入口程序只能有一个&#xff0c;选中哪个入口程序取决于 ** ** name** **的值。 代码 #-*- coding:utf-8 -*- #开发时间&#xff…

SQL-分页查询and语句执行顺序

&#x1f389;欢迎您来到我的MySQL基础复习专栏 ☆* o(≧▽≦)o *☆哈喽~我是小小恶斯法克&#x1f379; ✨博客主页&#xff1a;小小恶斯法克的博客 &#x1f388;该系列文章专栏&#xff1a;重拾MySQL &#x1f379;文章作者技术和水平很有限&#xff0c;如果文中出现错误&am…

Pytest插件pytest-cov:优雅管理测试覆盖率

在软件开发中&#xff0c;测试覆盖率是评估测试质量的关键指标之一。为了更方便地统计和管理测试覆盖率&#xff0c;Pytest插件"pytest-cov"应运而生。本文将介绍"pytest-cov"的基本用法和优雅管理测试覆盖率的方法。 什么是pytest-cov? pytest-cov 是Pyt…

Docker数据卷与拦截与目录拦截

目录 高级容器挂载技术深度解析引言数据卷挂载原理解析应用场景使用介绍 目录挂载原理解析应用场景使用介绍 总结 高级容器挂载技术深度解析 引言 容器技术的快速发展使得容器挂载技术变得愈发重要。在容器化应用中&#xff0c;数据卷挂载和目录挂载是两种常见的挂载方式&…

【Python机器学习】SVM——调参

下面是支持向量机一个二维二分类数据集的训练结果&#xff1a; import mglearn import matplotlib.pyplot as plt from sklearn.svm import SVCplt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False X,ymglearn.tools.make_handcrafted_dataset()…

ModuleNotFoundError: No module named ‘simple_knn‘

【报错】使用 AutoDL 复现 GaussianEditor 时引用 3D Gaussian Splatting 调用simple_knn 时遇到 ModuleNotFoundError: No module named ‘simple_knn‘ 报错&#xff1a; 【原因】 一开始以为是版本问题&#xff0c;于是将所有可能的版本都尝试了 (from versions: 0.1, 0.2…

笔试面试题——继承和组合

&#x1f4d8;北尘_&#xff1a;个人主页 &#x1f30e;个人专栏:《Linux操作系统》《经典算法试题 》《C》 《数据结构与算法》 ☀️走在路上&#xff0c;不忘来时的初心 文章目录 一、什么是菱形继承&#xff1f;菱形继承的问题是什么&#xff1f;二、什么是菱形虚拟继承&am…

SqlAlchemy使用教程(一) 原理与环境搭建

一、SqlAlchemy 原理及环境搭建 SqlAlchemy是1个支持连接各种不同数据库的Python库&#xff0c;提供DBAPI与ORM&#xff08;object relation mapper&#xff09;两种方式使用数据库。 DBAPI方式&#xff0c;即使用SQL方式访问数据库 ORM, 对象关系模型&#xff0c;是用 Python…

1.1 计算机网络在信息时代的作用

1.1 计算机网络在信息时代的作用 网络&#xff08;Network&#xff09;由若干结点&#xff08;Node&#xff09;和连接这些结点的链路&#xff08;Link&#xff09;所组成。网络中的结点可以是计算机、集线器、交换机或者路由器等。 图1-1 多个网络还可以通过路由器互连起来&a…

记录一下误删除libc.so.6的经历

起因&#xff1a; 在配置环境时&#xff0c;出现’GLIBCXX_3.4.29 not found’的错误&#xff0c;在解决这个问题的过程中&#xff0c;需要删除sudo rm /usr/lib/x86_64-linux-gnu/libstdc.so.6软连接&#xff0c;但是一不小心sudo rm /lib/x86_64-linux-gpu/libc.so.6&#xf…