Overleaf環境をローカルに構築して、変更をGit連携のオンラインプロジェクトに反映する

## この記事の概要
- Dockerを用いてオフラインOverleaf環境を構築し、日本語に対応させる
- オフラインOverleafにオンラインOverleafのgitリポジトリを読み込み、オフラインの変更をオンラインに反映させる（pushする）
  - pushは（基本的に）ただ一度だけできる
  - オンラインの変更をオフラインに反映させることはできない（pullできない）
- フライト中の執筆など、一時的にオフラインで編集したファイルを再度オンラインで編集する際に役に立つ

## はじめに
言わずと知れたオンラインTexエディタ、Overleafの最大の利点にして最大の欠点は、オンラインで完結することです。オンラインで完結するため、特に環境構築等必要なく利用できますが、当然オフライン環境で利用することはできません。
基本的にオンラインで作業する場合は概ね問題ありませんが、何らかの事情によりオンラインで執筆できない場合には苦労することもあります。たとえば電波の弱い場所で作業する場合や、フライト中に作業したい場合などですね。

Overleafからソースをダウンロードしてローカルで編集すればいい話ではありますが、ローカルに一から環境構築するのは面倒ですし、慣れたOverleafの環境を使いたいだろうと思います。

そこでおすすめするのが、Docker上にOverleafのローカル環境を構築することです。簡単に環境を構築できるようになっていて、Dockerを始めて利用する方にもおすすめです。
この記事では、
- Overleaf環境をDocker上で構築する方法
- その利用について
 - オンラインのOverleafのgitリポジトリを無理矢理読み込んで、ローカルでの変更をリモートに反映する方法

について解説していきます（ただし、git連携は不完全なものです）。

## Overleaf環境をDocker上で構築する
まず、自分の環境を紹介しておきます。

MacBookPro 14インチ、2021
Apple M1 Max
macOS 14.6


大まかな流れは以下の通りです。
- 各種サービス（bash, Git, Docker）の確認
- Overleafのgitリポジトリをcloneして持ってくる
- ガイドラインに沿って環境構築
- 日本語を利用可能にする

上から順に簡単にご紹介します。

### 各種サービスの確認
事前に、必要なサービスが入っているか確認して下さい。
```
bash --version
git --version
docker version
docker compose version
```
バージョンが表示されれば問題ありません。表示されない場合は導入されていないので、各自で導入をお願いします。

### Overleafのgitリポジトリをcloneして持ってくる
[Quick start guide](https://github.com/overleaf/toolkit/blob/master/doc/quick-start-guide.md)を参考に環境を構築していきます。
ここでは、overleaf toolkitの方を使ってみます（toolkitではない普通のoverleafも存在するのですが、違いがわかっていません......）。
まず、適当なディレクトリにリポジトリをcloneします。
```
$ git clone https://github.com/overleaf/toolkit.git ./overleaf-toolkit
```
### 環境構築
cloneができたら、cloneしてきたディレクトリに入ります。
```
$ cd ./overleaf-toolkit
```
#### 初期設定と事前準備
ガイドラインに従って環境構築をしていきます。
まずは初期設定です。
```
$ bin/init
```
設定ファイルが適切にコピーされていることを確認して下さい。
```
$ ls config
overleaf.rc     variables.env     version
```
また、事前に手動でフォルダを作成しておきます（存在しないと以降のプロセスでエラーが発生します）。
```
$ mkdir data/overleaf data/mongo data/redis data/git-bridge
```
上記ができたら、起動を試みます。Dockerが起動していることを確認の上、下記を実行して下さい。
事前にお伝えすると、エラーになる場合があります。

```
$ bin/up
```
私の環境では、Mac環境では下記のようなエラーが発生します。
```
---------------------  ERROR  -----------------------
  Invalid MONGO_VERSION: MONGO_VERSION=6.0

  MONGO_VERSION must start with the actual major version of mongo, followed by a dot.
  Example: MONGO_IMAGE=my.dockerhub.com/custom-mongo
           MONGO_VERSION=6.0-custom
---------------------  ERROR  -----------------------
```
これは、```lib/shared-functions.sh```の```read_variable()```および```read_configuration()```関数の正規表現パターンに誤りがあるためなので、該当部分を下記のように修正します（[参考リンク](https://github.com/overleaf/toolkit/issues/287)）。
修正前
```sh
function read_variable() {
  local name=$1
  grep -E "^$name=" "$TOOLKIT_ROOT/config/variables.env" \
  | sed -r "s/^$name=([\"']?)(.+)\1\$/\2/"
}

function read_configuration() {
  local name=$1
  grep -E "^$name=" "$TOOLKIT_ROOT/config/overleaf.rc" \
  | sed -r "s/^$name=([\"']?)(.+)\1\$/\2/"
}
```
修正後
```sh
function read_variable() {
  local name=$1
  grep -E "^$name=" "$TOOLKIT_ROOT/config/variables.env" \
  | sed -r "s/^$name=[\"']?([^\"']+)[\"']?\$/\1/"
}

function read_configuration() {
  local name=$1
  grep -E "^$name=" "$TOOLKIT_ROOT/config/overleaf.rc" \
  | sed -r "s/^$name=[\"']?([^\"']+)[\"']?\$/\1/"
}
```

また、Windows環境では次のようなエラーが発生しました
```sh
Error response from daemon: mount denied:
the source path "/Users/username/Desktop/overleaf/overleaf-toolkit/data/overleaf:C:/Program Files/Git/var/lib/overleaf:rw"
too many colons
```
明らかにコンテナ内パスの指定が狂っているので、手動で書き換えます。`config/version`にdocker imageのバージョン設定項目があるので、これを確認し、major version（一番左の数字）の値によって`lib/docker-compose.base.yml`の`volumes`の部分を下記のようにします。
major versionが5以上
```
volumes:
            - "${OVERLEAF_DATA_PATH}:/var/lib/overleaf"
```
major versionが5未満
```
volumes:
            - "${OVERLEAF_DATA_PATH}:/var/lib/sharelatex"
```
この対処は、本来`bin/docker-compose`で行われる処理を、直接手動で設定し直しているものになります。理由は不明ですが、windowsでは正常に動作していないようです。

#### コンテナの起動
満を持して、コンテナを起動してみます。
```
$ bin/up
```
初回の起動には少々時間がかかるので、気長にお待ちください。
ほとんどの方は問題なく起動できるかと思いますが、例えばoverleafの使用するポート80がすでに使用されている場合、下記のようなエラーが発生します。
```
Error response from daemon: Ports are not available: exposing port TCP 127.0.0.1:80 -> 0.0.0.0:0: failed to connect to /var/run/com.docker.vmnetd.sock: is vmnetd running?: dial unix /var/run/com.docker.vmnetd.sock: connect: connection refused
```
本来、```lib/config-seed/overleaf.rc```の```OVERLEAF_PORT```を適当に変更することで対応可能なはずですが、私の環境ではこれを変更しても解決しませんでした。
そのため、このような場合は```lib/docker-compose.base.yml```のport設定を直接書き換えます。例えば下記のようにします。
```yml
ports:
      - "${OVERLEAF_LISTEN_IP:-127.0.0.1}:8080:80"
```
起動ができたら、http://localhost/launchpad （ポート番号を変更した場合は対応するポート番号に変更）にアクセス可能かご確認ください。下記のようなアカウント作成画面が表示されれば、起動できています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3661623/a1953e99-ad6d-ca2d-e12f-29e3becab518.png)


二回目以降の起動は、
```
$ bin/up
```
でも
```
$ bin/start
```
でも可能です。
コンテナを停止する場合は、ctrl+cか、もしくは
```
$ bin/stop
```
で可能です。

### 日本語対応
環境は構築できましたが、最低限のパッケージしかインストールされていません。今回は面倒なので、Tex Liveの環境を更新してすべてのパッケージをインストールします。
まず、起動しているコンテナに入り込みます（別のターミナルウィンドウから実行して下さい）。
windows環境の場合は、必要に応じて`winpty`を先頭につけてください。
```sh
$ docker exec -it sharelatex bash
```
続いて、下記コマンドで全パッケージのインストールをしていきます（ものすごく（私の環境で4時間くらい）時間がかかります）。
お時間や容量がなく、とりあえず日本語化できればよい方は、後者のコマンドで日本語化に必要なパッケージのみインストールすることも可能です。
全パッケージインストール
```sh
$ tlmgr update --self
$ tlmgr install scheme-full
```
日本語化関係のみインストール（三分程度で終わります）
```sh
$ tlmgr update --self
$ tlmgr install collection-langjapanese
```
インストールが完了したら、シンボリックリンクを作成します（[参考リンク](https://zenn.dev/satlas/articles/22fc44df2a701d)）。
まず、texliveのバージョンとプラットフォームを確認します。
texliveのversionを確認する（`{year}`とする）
```sh
$ ls /usr/local/texlive/
```
プラットフォームを確認する（`{platform}`とする）
```sh
$ ls /usr/local/texlive/{year}/bin/
```
上記の値を用いて、以下のコマンドを実行。`{year}`と`{platform}`の書き換えを忘れないように注意。
```sh
$ ln -s /usr/local/texlive/{year}/bin/{platform}/uplatex /usr/local/bin/uplatex
$ ln -s /usr/local/texlive/{year}/bin/{platform}/upbibtex /usr/local/bin/upbibtex
$ ln -s /usr/local/texlive/{year}/bin/{platform}/platex /usr/local/bin/platex
$ ln -s /usr/local/texlive/{year}/bin/{platform}/pbibtex /usr/local/bin/pbibtex
$ ln -s /usr/local/texlive/{year}/bin/{platform}/mendex   /usr/local/bin/mendex
```
実行できたら、コンテナを出ましょう。
```
$ exit
```
インストールが完了しても、このままコンテナを停止したら全て消えてしまいます。
イメージとしてコミットしておきましょう。
```
$ docker commit sharelatex sharelatex/sharelatex:with-texlive-full
```
また、次回以降このコミットしたコンテナから起動するように、```bin/docker-compose```の```export IMAGE=...```の部分を下記のように書き換えます。
```yml
export IMAGE="$image_name:with-texlive-full"
```
以上で日本語化は終了です。これにてすべての環境構築が完了しました。

## Overleaf（ローカル）の使い方
まず、管理者アカウント登録をします。
先ほどのhttp://localhost/launchpad のページにアクセスし、お好きなメールアドレスとパスワードを入力して下さい。するとログイン画面が表示されるので、たった今登録したアカウントでログインをしましょう。
ログインすると下記のような画面に飛ぶので、"Start Using Our Overleaf Instance"から利用を開始しましょう。
![スクリーンショット 2024-10-02 1.10.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3661623/2e692f09-bb4e-2138-d2bb-33ecaa00fae8.png)
あとは"Create a new project"からプロジェクトを作成するだけです。

たとえば、[日本バーチャルリアリティ学会大会のテンプレート](https://ja.overleaf.com/latex/templates/vrsj-annual-conf-template/mchsqbrvyxqs)をオンラインのOverleafで開いて、sourceをZipで保存したものを開いてみます。
"Upload Project"からZipファイルをアップロードするだけです。

最初はエラーが出ますが、コンパイラをLaTexに変更するとエラーなくコンパイルできると思います。これで、問題なく日本語のTexファイルをコンパイルできることが確認できました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3661623/bc64e6bf-17f5-d481-6b18-b0c5f418b91d.png)

あとの使用感はほとんどオンラインのOverleafと同一です。同じようにファイルを編集して下さい。

## オンラインのOverleafのgitリポジトリを読み込んでオフラインで編集→オンラインに反映させる
さて、本題です。オフラインで作業可能とはいえ、基本的には共同編集者と同時に編集できるオンラインでの編集がメインであると思います。このローカルOverleafは一時的にオンラインにアクセスできない場合に利用するでしょう。

そこで、Overleafのgit連携機能を利用して、オンラインとオフラインで同期を可能にすることを目指しました。
結論から言うと、この試みは半分しか成功していません。

ローカルからpushしてオンラインに反映することは（一度だけ）できるものの、pullでオンラインの変更を反映することはできませんでした。
### overleaf-toolkitをgitリポジトリでなくす
今後の作業で邪魔になるので、```overleaf-toolkit```内の```.git```フォルダを削除/リネームして除去して下さい。gitリポジトリ内部にgitリポジトリがあると操作が面倒なためです。
たとえば
```
overleaf-toolkit $ mv .git .git_rename
```


### オンラインのプロジェクのgitリポジトリをcloneする
まずはじめに、オンラインのプロジェクトのgitリポジトリを作成し、リンクとトークンを入手します（画像はトークン入手済みなのでトークンの記載なし）。
![スクリーンショット 2024-10-02 1.36.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3661623/f3aa84e4-8c14-b0d6-5b60-6b5e6036fd0f.png)
適当な場所に該当のリポジトリをcloneして下さい。
```
% git clone https://git@git.overleaf.com/{projectID}
Cloning into '{projectID}'...
remote: Counting objects: 43, done
remote: Finding sources: 100% (43/43)
remote: Getting sizes: 100% (25/25)
remote: Total 43 (delta 14), reused 43 (delta 14)
Unpacking objects: 100% (43/43), 3.57 MiB | 2.09 MiB/s, done.
```
### .gitフォルダをリネームして、cloneしたリポジトリをZipに固める
cloneできたら、リポジトリ内部の```.git```ファイルを非表示フォルダでなくします。つまり、先頭をドットでなくしてください。これは、zipファイルからプロジェクトを読み込んだ場合に非表示フォルダは無視されるためです。
たとえば
```
$ mv {projectID}/.git {projectID}/git
```
とします。
リネームしたら、リポジトリを丸ごとZipに固めます。
```
zip -r OfflineProject {projectID}
```
### オフラインのOverleafでZipファイルを読み込む
オフラインOverleafで新規プロジェクトとしてZipファイルを読み込みます。読み込めたら、エディタ上で```git```フォルダを再び```.git```にリネームします。
続いて```.gitignore```ファイルを作成して、内容を下記のようにします。
![スクリーンショット 2024-10-02 2.12.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3661623/4b7c1ba8-f159-8c74-4941-5b93b76d42d1.png)
内容
```
output.*
```
ここまでで下準備は完了です。
### オフラインで編集を行う
自由に編集を行います。なぜかうまくコンパイルできない場合がありますが、キャッシュを削除するとコンパイルできることが多いです。
### リモートリポジトリにpushする
オフラインでの編集が不要になったら、最後にpushで変更を反映します。まずは、プロジェクトを一度コンパイルして下さい（コンパイル時にフォルダが生成されます）。
その後、```overleaf-toolkit```の中の、該当のプロジェクトに対応するフォルダを探します。場所は
```
overleaf-toolkit/data/overleaf/data/compiles/
```
内のどれかになっているはずです。フォルダ名はIDになっているので、フォルダ名で見分けをつけることはできません。フォルダを特定したら、gitでadd, commit, pushします。
```
overleaf-toolkit $ cd data/overleaf/data/compiles/{projectID}
```
```
git add .
git commit
git push
```
以上でリモートリポジトリに変更が反映されます。
なお、この操作を連続で行うと不具合が発生するので、ローカルからリモートに作業環境を切り替えるタイミングの最後に実行することをお勧めします。色々頑張れば解消できることはありますが、面倒です。
つまり**pushは一度きりと思ったほうが無難**なので注意して下さい。
### ローカルのプロジェクトを削除する/バックアップだと思って残す
ローカルのプロジェクトはもう用済みなので、削除するか単なるバックアップだと思って残すかくらいしか使い道はありません。pullもpushもできないので、オンラインとの連携は絶望的です。

以上で、ローカルで行った編集をオンラインに反映する方法の紹介は終了です。
## まとめ
本記事では、オフラインでのOverleaf環境構築から、日本語対応、そしてオンラインへの変更反映方法に関する紹介をしました。
オンラインへの変更反映は高頻度の交替には向きませんが、たとえばフライトの際に一時的にオフラインで作業し、到着したらオンラインでしか作業しない場合など、オンラインとオフラインの行き来が一度きり or 低頻度なら使える方法かと思います。


## 展望
現在、リモートの変更をgitを用いて簡単にローカルに反映する方法は（少なくとも私には）わかっていません。「さっきのディレクトリでpullすればいいんじゃないの？」と思われるかもしれませんが、先ほどのディレクトリは「コンパイル時に一時的に生成されるファイル群」に過ぎず、おそらく大元のファイル群はmongoDBで管理されています。
つまり、先ほどのファイル群を置き換えてもなんら意味はありません（どうせコンパイルで消える）。

一方で、リモートリポジトリの変更をmongoDBに反映する機能が存在するかも不明（ローカルのOverleafにはそもそもgit連携機能等がないので、おそらく存在しなさそう）で、少なくとも現在は判明していません。

結局、オンラインで追加で行った編集を最も簡単にオフラインに反映する方法は、現状では上記の作業を繰り返すことです。

展望としては、なんとかしてmongoDBにgitリポジトリの変更を読ませることで、push pull共に可能にすることが考えられます。
可能な方法をご存知の方がいたら、教えて下さい......。

## 参考リンク
https://github.com/overleaf/toolkit/blob/master/doc/quick-start-guide.md
https://zenn.dev/uchidaryo/articles/setup-local-overleaf
https://github.com/overleaf/toolkit/issues/287
https://zenn.dev/satlas/articles/22fc44df2a701d