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[]
    | undefined

  const options: Array<Option> = useMemo(
    () =>
      allowedImageNames?.map(({ imageName, imageDesc }) => ({
        value: imageName,
        label: imageDesc,
      })) ?? [],
    [allowedImageNames]
  )

  if ((allowedImageNames?.length ?? 0) === 0) {
    return null
  }

  return (
    <SettingsMenuSelect
      onChange={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 update
 apt-get install -y ca-certificates curl gnupg
 install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg |  gpg --dearmor -o /etc/apt/keyrings/docker.gpg
 chmod 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/null
   
apt-get update
  
apt-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 year
    const 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-slim

FROM ghcr.io/xu-cheng/texlive-full:20240101

# Install the shadow package
RUN apk --no-cache add shadow

RUN 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 bash

set -eux

echo "-------------------------"
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-"* ]]; then
        echo "Removing container: $container_name"
        docker rm -f $container_id
    fi
done

然后记得改cron的配置项

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用Dockerfile构建镜像的详细指南

目录 前言 一、什么是 Dockerfile 二、使用 Dockerfile 定制镜像 开始构建镜像 上下文路径 三、指令详解 四、构建阿里云仓库 前言 Docker是一种流行的容器化平台&#xff0c;可以帮助开发人员和运维团队更轻松地构建、发布和运行应用程序。在Docker中&#xff0c;镜像是…

捷捷微电突发涨价函,Trench MOS产品线上调5%-10% | 百能云芯

近日&#xff0c;国产功率半导体厂商捷捷微电发布了一份《价格调整函》&#xff0c;宣布自2024年1月15日起&#xff0c;将对公司Trench MOS产品线的单价进行上调&#xff0c;上调幅度为5%-10%。 据悉&#xff0c;调整前已下的订单将继续按照原有单价和数量履行&#xff0c;而新…

java实现AES256对称加解密工具类

一、引入依赖包 引入相关依赖包 <dependency><groupId>org.bouncycastle</groupId><artifactId>bcprov-jdk15on</artifactId><version>1.70</version> </dependency> <!--lombok用于简化实体类开发--> <dependency&g…

Unity寻路A星算法

文章目录 实现步骤概览&#xff1a; 计算移动成本1. **定义移动成本函数**&#xff1a;2. **考虑不同类型的格子**&#xff1a;3. **动态调整成本**&#xff1a;4. **实际应用**&#xff1a; 优先级队列1. **初始化**&#xff1a;2. **节点评估**&#xff1a;3. **更新节点状态…

spring boot学习第八篇:通过spring boot、jedis实现秒单

参考&#xff1a;Redis实现分布式锁的7种方案 - 知乎 1、 准备数据库表&#xff0c;如下SQL表示库存表&#xff0c;有主键ID和库存数量字段 CREATE TABLE t_stock (id bigint(20) NOT NULL AUTO_INCREMENT,quantity bigint(20) NOT NULL,PRIMARY KEY (id) ) ENGINEInnoDB DEF…

未来气膜体育馆的发展趋势是什么?

未来气膜体育馆的发展趋势是多方面的&#xff0c;以下是其中几个方面的趋势。 起初&#xff0c;随着人们对体育运动的需求不断增加&#xff0c;气膜体育馆的建设和使用将成为一种趋势。气膜体育馆具有灵活性和可移动性的特点&#xff0c;可以快速搭建和拆除&#xff0c;能够适…

TOP 10 屏幕录制软件工具,可帮您轻松录制视频!

随着越来越多的人远程工作和学习&#xff0c;对可靠、高效的屏幕录制工具的需求变得越来越重要。屏幕录制已成为电子学习、游戏和视频创作的重要组成部分。然而&#xff0c;有这么多可用的屏幕录制工具&#xff0c;选择合适的工具可能具有挑战性。为了帮助您节省搜索时间和精力…

案例127:基于微信小程序的预约挂号系统

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;SSM JDK版本&#xff1a;JDK1.8 数据库&#xff1a;mysql 5.7 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.5.4 小程序框架&#xff1a;uniapp 小程序开发软件&#xff1a;HBuilder X 小程序…

如何去开发直播电商系统小程序

明确你的直播电商系统的功能和特性&#xff0c;包括用户注册、商品展示、购物车、支付结算、直播功能、评论互动等。根据需求确定系统的基本架构和主要模块。 技术选型&#xff1a;选择适合你的直播电商系统的技术栈。考虑前端框架&#xff08;如React、Vue.js&#xff09;、后…

基于等效消耗最小(ECMS)的电氢综合能源系统能量管理策略Simulink模型

0. 前言 常见的EMS控制策略为基于状态机&#xff08;State Machine Control&#xff09;、基于等效消耗最小&#xff08;Equivalent Consumption Minimization Strategy&#xff0c;ECMS&#xff09;及调度控制模式。本文着重介绍前两种&#xff0c;针对第一种控制策略可参考模…

Unity Urp 渲染管线 创建透明材质球

按照以上方式设置后就可以得到一个透明的材质球 Tips&#xff1a;Blending mode &#xff1a; alpha 和 Blending mode &#xff1a; additive都是完全透明效果具体差异暂时不知道

iis配置asp网站

1.安装IIS的ASP win7和win10都是一样的 下安装IIS时ASP一般被默认不选中的状态&#xff0c;因此需要打开IIS检查功能视图栏中是否存在ASP选项&#xff0c;若没有则需要从控制面板->程序和 功能->打开或关闭Windows功能->Internet信息服务->万维网服务->应用程序…

数据结构与算法:快速排序

数据结构与算法&#xff1a;快速排序 快速排序荷兰国旗问题霍尔版本递归优化小区间优化 PartSort优化三数取中 挖坑法前后指针法 非递归法 快速排序 荷兰国旗问题 想要理解快速排序&#xff0c;就先理解这个问题&#xff1a; [LeetCode75.颜色分类] 荷兰国旗是由红白蓝三色组…

低代码平台,新型应用程序开发神器

目前低代码平台如火如荼。这一新兴技术为企业提供了一种高效、灵活、快速开发应用程序的方法&#xff0c;并在短时间内取得了巨大成功。然而&#xff0c;我们不得不面对低代码平台的优劣以及其所带来的挑战。本文将深入探讨低代码平台在不同情况下的优劣势&#xff0c;并与您分…

Qt 国产嵌入式操作系统实现文字转语音功能(TTS)

1.简介 本示例使用的CPU&#xff1a;rk3588。 操作系统&#xff1a;kylin V10 架构&#xff1a;aarch64 在Windows端&#xff0c;我们很容易想到使用Qt自带的类QTextToSpeech来实现文字转语音功能&#xff0c;Qt版本得在5.11.0以上才支持。但是在嵌入式平台&#xff0c;尤其…

HubSpot社交媒体整合的好处有哪些?

HubSpot的社交媒体整合提供了许多好处&#xff0c;有助于用户更好地管理和优化其社交媒体活动。以下是一些使用HubSpot社交媒体整合的好处&#xff1a; 集中管理&#xff1a; 用户可以在HubSpot平台上集中管理多个社交媒体账户&#xff0c;无需切换到不同的平台。这简化了社交媒…

同步、异步无障碍:Python异步装饰器指南

一、引言 Python异步开发已经非常流行了&#xff0c;一些主流的组件像MySQL、Redis、RabbitMQ等都提供了异步的客户端&#xff0c;再处理耗时的时候不会堵塞住主线程&#xff0c;不但可以提高并发能力&#xff0c;也能减少多线程带来的cpu上下文切换以及内存资源消耗。但在业务…

2023一带一路暨金砖国家技能发展与技术创新大赛“网络安全”赛项省选拔赛样题卷①

2023金砖国家职业技能竞赛"网络安全" 赛项省赛选拔赛样题 2023金砖国家职业技能竞赛 省赛选拔赛样题第一阶段&#xff1a;职业素养与理论技能项目1. 职业素养项目2. 网络安全项目3. 安全运营 第二阶段&#xff1a;安全运营项目1. 操作系统安全配置与加固任务一Linux …

基于信号完整性的一些PCB设计建议

最小化单根信号线质量的一些PCB设计建议 1. 使用受控阻抗线&#xff1b; 2. 理想情况下&#xff0c;所有信号都应该使用完整的电源或地平面作为其返回路径&#xff0c;关键信号则使用地平面作为返回路径&#xff1b; 3. 信号的返回参考面发生变化时&#xff0c;在尽可能接近…

欧盟产品安全新规来袭,亚马逊发出紧急提醒(GPSR)要求

欧盟产品安全新规来袭&#xff0c;亚马逊发出紧急提醒&#xff08;GPSR&#xff09;要求 一、发布新规 这世界上唯一不变的事&#xff0c;或许就是变化本身。 在跨境电商领域&#xff0c;这个道理再次得到验证。近日&#xff0c;不少卖家都收到了一封来自亚马逊的通知。通知中…