TeXRelay 管理者マニュアル
Sep 26, 2026 · @Koyo Yamamori
1. 構成の全体像
製品名は TeXRelay,コード上の名前は texapp(パス・サービス名・環境変数はこちら)。
| もの | 場所 |
|---|---|
| 公開 URL | https://texrelay.educpsychol.com/(旧 texapp.educpsychol.com も同じサーバ) |
| サーバ | Xserver VPS 162.43.43.174(Ubuntu 24.04,2GB RAM,2GB swap)。bbinsta.educpsychol.com(Insta Square)と canvas も同居 |
| コード | /opt/texapp/(server/ FastAPI,web/dist ビルド済みフロント,docker/texlive イメージ定義,deploy/) |
| 静的ファイル | /var/www/texapp/(nginx が配信。web/dist のコピー) |
| 設定 | /etc/texapp/texapp.env(制限値,Dropbox App key,管理者メール) |
| データ | /srv/texapp/:texapp.db(SQLite:許可リスト・セッション・ジョブ履歴・ソース登録),jobs/(コンパイル作業領域,30 分で削除),guests/,styles/ bib/ figures/(共通ソースの取得物,current が最新) |
| サービス | texapp.service(uvicorn,127.0.0.1:8010,ユーザー texapp)。8000 は Insta Square が使用 |
| nginx | /etc/nginx/sites-available/texapp(/api/ /ws/ を 8010 へ,それ以外は静的)。証明書は certbot |
| コンテナ | rootless podman(ユーザー texapp),イメージ localhost/texapp-texlive:2025。コンパイル 1 回ごとに使い捨て |
| ログ | journalctl -u texapp,ジョブごとの /srv/texapp/jobs/<id>/latexmk.out |
データの流れ:ブラウザが Dropbox を直接読み書き(トークンはブラウザの sessionStorage にだけあり,サーバは保持しない)→ コンパイル時だけ zip をサーバへ → コンテナで latexmk → PDF を 30 分保持。サーバに永続するのは DB と共通ソースだけで,バックアップが必要なのは /srv/texapp/texapp.db と /etc/texapp/texapp.env。
2. 日常の運用(管理画面)
ログイン後,ヘッダーの「管理」から。管理者にしかボタンは出ず,API もサーバ側で管理者判定しています。
許可リスト
ログインできる人の名簿。Dropbox に登録されているメールで照合します(大学メールと違う人がいる)。グループ(学生/スタッフなど)は表示用,略称はメンバー欄や編集権の表示に使う短い名前。「管理者」を付けるとその人もこの画面を使えます。削除すると次回からログインできなくなります(現在のセッションは「強制ログアウト」で切る)。最初の管理者は texapp.env の TEXAPP_ADMIN_EMAILS から自動登録されます。
プロジェクトへの参加はここでは決めません。Dropbox の共有フォルダのメンバーシップがそのまま権限です。共同研究ごとにメンバーが違う場合も,Dropbox 側で分かれます。
ゲストコード
「ゲストコードを発行」で XXXX-XXXX-XXXX 形式のコードが出ます。ラベルは誰に渡したかのメモ。有効 48 時間,同時に有効なコードは既定 20 件まで。ゲストの作業領域(/srv/texapp/guests/)は期限後に自動削除。ゲストは Dropbox に触れず,サンプルプロジェクト(/srv/texapp/sample/,内容は差し替え可)の複製でだけ作業します。
セッション
ログイン中の人の一覧。WS 列の●はブラウザが接続中。「強制ログアウト」で席を空けられます(同時ログイン上限は TEXAPP_MAX_SESSIONS,既定 6。20 分無操作で自動的に席を外れます)。
編集権
いま開かれているプロジェクトと編集権の持ち主。誰かが持ったまま離席して困っているときに「編集権を強制解除」。通常は 10 分無操作で自動解放されるので出番は少ないです。
3. 共通ソース(公式スタイル・研究室 bib・共通図ライブラリ)
3 つとも仕組みは同じです:管理画面に Dropbox の共有リンク(または HTTPS の URL)を登録 → サーバが取得して /srv/texapp/<kind>/<version>/ に展開,current を向ける → コンパイル時にコンテナに読み取り専用でマウント。1 時間ごとに再取得し,内容が変わっていれば新バージョンに切り替えます(直近 3 バージョンを保持)。「今すぐ更新」で即時取得。リンクを入れ直して「登録して取得」すると強制的に取り直します。
| 種類 | 登録するもの | コンテナ内 | 文書側の書き方 |
|---|---|---|---|
| 公式スタイル | .bbx/.cbx/.dbx/.sty/.cls を入れたフォルダの共有リンク | /styles(TEXINPUTS で再帰探索) | style=jpa のように名前だけ |
| 研究室 bib | .bib ファイルの共有リンク | /bib/<元のファイル名> | \addbibresource{biblatex.bib} |
| 共通図ライブラリ | 図を入れたフォルダの共有リンク | /figures(TEXINPUTS で再帰探索) | \includegraphics{name.pdf} |
現在の登録:公式スタイル=texapp_styles/biblatex/jpa.*(日本心理学会 2022 式),研究室 bib=biblio/biblatex.bib,共通図ライブラリ=figures。
注意:
- 共有リンクは
dl=0のままでよい(サーバがdl=1に直す)。リンクを知っていれば誰でもダウンロードできるので,公開して困るものは入れない。 - 展開後の合計が
TEXAPP_MAX_EXTRACTED_BYTES(500MB)を超えるとエラー。図ライブラリが大きくなったら上限を上げるかフォルダを分ける。 - プロジェクト内に同名ファイルがあると公式が優先され,ログに通知が出ます(仕様 3.2)。
- フォルダ内の
latexmkrcは安全のため捨てられます。 - スタイルを直したときは Dropbox 側を更新すれば 1 時間以内に反映。すぐに反映したければ「今すぐ更新」。
4. サーバ操作
SSH
ssh -p 10022 -i ~/.ssh/id_ed25519 root@162.43.43.174
22 と 10022 の両方で待ち受け。自宅回線は SSH を全ポート遡断するので,テザリングか大学から。HTTPS(アプリの利用)はどこからでも通る。Xserver のパネルのパケットフィルタで 22,10022,80,443 を開けてある。
サービス
systemctl status texapp # 状態
systemctl restart texapp # 再起動(セッションは DB にあるのでログインは切れない。実行中のコンパイルは止まる)
journalctl -u texapp -n 100 --no-pager # ログ
journalctl -u texapp -f # 追いかけ
systemctl is-active insta_square canvas nginx texapp # 同居サービスごと確認
設定ファイル /etc/texapp/texapp.env
変えたら systemctl restart texapp。主な項目:
| 変数 | 現在値 | 意味 |
|---|---|---|
TEXAPP_DROPBOX_APP_KEY | e5xwssm4e8wesz6 | Dropbox App の App key(公開してよい値) |
TEXAPP_ADMIN_EMAILS | 山森のメール | 初回の管理者 |
TEXAPP_DOCKER_BIN / TEXAPP_DOCKER_IMAGE | podman / localhost/texapp-texlive:2025 | コンテナ |
TEXAPP_DOCKER_MEMORY | 1536m | コンパイル 1 件のメモリ上限(2GB の VPS ではこれが上限) |
TEXAPP_COMPILE_TIMEOUT | 120 | 秒 |
TEXAPP_MAX_UPLOAD / TEXAPP_MAX_EXTRACTED_BYTES | 200MB / 500MB | zip と展開後の上限。nginx の client_max_body_size も合わせる |
TEXAPP_MAX_SESSIONS | 6 | 同時ログイン数 |
TEXAPP_IDLE_TIMEOUT / TEXAPP_LOCK_IDLE_RELEASE | 1200 / 600 | 在席タイムアウト/編集権の自動解放(秒) |
ディスクとメモリ
df -h / ; du -sh /srv/texapp/* ; free -m
sudo -iu texapp podman system df # イメージの容量(約 4GB)
sudo -iu texapp podman stats --no-stream # コンパイル中のメモリ実測
ジョブの作業領域は 30 分で自動削除されるので肥大化しません。共通ソースの古いバージョンは 3 世代で捨てられます。
バックアップ
cp /srv/texapp/texapp.db /root/backup/texapp-$(date +%F).db
cp /etc/texapp/texapp.env /root/backup/
この 2 つと /opt/texapp(コード,手元の zip と同じ)があれば別サーバに再構築できます。
5. アプリの更新手順
手元に texapp.zip(リポジトリ一式,web/dist ビルド済み)が届いたとき。
Mac(SSH が通る回線で)
cd ~/Downloads && rm -rf texapp && unzip -q texapp.zip
rsync -az -e "ssh -p 10022 -i ~/.ssh/id_ed25519" --exclude node_modules --exclude __pycache__ ./texapp/ root@162.43.43.174:/opt/texapp/
VPS
rsync -a --delete /opt/texapp/web/dist/ /var/www/texapp/ # フロント
chown -R texapp:texapp /opt/texapp/server && systemctl restart texapp && systemctl is-active texapp # サーバ
フロントだけの変更なら 1 行目だけ,サーバだけなら 2 行目だけでよい。利用者はブラウザで Cmd+Shift+R。DB のスキーマ変更があるときは起動時に自動で移行されます(server/texapp/db.py の _migrate)。
Python の依存が増えたとき(server/requirements.txt が変わったとき):
sudo -iu texapp /opt/texapp/server/.venv/bin/pip install -r /opt/texapp/server/requirements.txt
役割で見ると,手元のビルド(cd web && VITE_BASE=/ npm run build)は Claude 側で済ませて zip に含めているので,VPS に Node は不要です。
6. TeX Live イメージ(パッケージ・フォントの追加)
イメージの定義は /opt/texapp/docker/texlive/Dockerfile。TeX Live 2026 の scheme-medium に,日本語・latexextra・bibtexextra・pictures のコレクションと,これまでの検証で必要になったパッケージ(subfigure, tracklang, pdfcomment, textpos など)を足してあります。フォントは原ノ味,IPA/IPAex,Takao,Noto CJK。
パッケージを足す
Dockerfile の RUN tlmgr install … の行にパッケージ名を追加して再ビルド。「どのパッケージか」はログの xxx.sty not found の xxx を CTAN で検索。
フォントを足す
/opt/texapp/docker/texlive/fonts/ に .ttf/.otf を置いて再ビルド(ライセンス上サーバに置けるものに限る。ヒラギノは不可)。ビルド時に luaotfload のキャッシュを作っておくので,初回コンパイルが遅くなりません。
再ビルド
screen -S build
sudo -iu texapp podman build -t texapp-texlive:2025 /opt/texapp/docker/texlive 2>&1 | tee /root/texlive-build.log
# Ctrl-a d で抜ける,screen -r build で戻る
初回は 30–40 分(TeX Live のダウンロード)。パッケージ追加だけならキャッシュが効いて 15 分前後。成功すると Successfully tagged localhost/texapp-texlive:2025。サービスの再起動は不要(次のコンパイルから新イメージ)。古いイメージは sudo -iu texapp podman image prune。
知っておくこと:
- TeX Live 2026 のインストーラは古いプロファイルキー(
instdoc)を拒否する。Dockerfile は新しいキーにしてある。 tlmgr installは「すでに入っているパッケージ」を指定すると非 0 で終わるので,Dockerfile は終了コードを無視してkpsewhichで必要ファイルの存在を検証している。- コンテナは
--network=none --read-only --cap-drop=ALL,shell_escape=f,openin_any=p。biber は PAR 形式で実行時に展開するため/var/tmpだけ exec 可の tmpfs にしてある(/tmpは noexec)。
7. 制限値とリソース
VPS は 2GB RAM。同居サービス(Insta Square,canvas,nginx)と uvicorn で 400MB 前後を使うので,コンパイルコンテナに回せるのは 1.5GB が上限です。そのため:
- 同時コンパイルは 1 件(
TEXAPP_MAX_CONCURRENT_COMPILE=1)。待ち行列に並びます。ゼミで同時に 10 人が押すと最後の人は数分待つ。メモリを増やす(VPS のプラン変更)のが唐一の対策で,4GB なら 2 並列にできます。 - コンテナメモリ 1536m。beamer+PDF 図 15 枚で 768m では落ちました(
return code 137)。/tmpの tmpfs(512m)もこの上限に算入されます。 - タイムアウト 120 秒。A1 ポスターで 1 分前後なので余裕はあります。
- zip 200MB/展開 500MB/3000 ファイル。ポスター(PNG 30 枚 60MB)が通る値。nginx の
client_max_body_size 210mと対。 - レート:1 人 10 分に 20 回まで。ゲストは 1 日 30 回。
- セッション 6。在席は 20 分,編集権の自動解放は 10 分。
コンパイル中のメモリを実測するには,誰かがコンパイルしている間に sudo -iu texapp podman stats --no-stream。上限に張り付いているようなら,利用者に図の縮小(PNG は 300dpi で十分)を依頼します。
8. Dropbox App・DNS・証明書
Dropbox App(dropbox.com/developers/apps)
アプリ名 texapp,App key e5xwssm4e8wesz6,Scoped access/Full Dropbox。必要な Permissions:account_info.read,files.metadata.read,files.content.read,files.content.write。OAuth 2 の Redirect URIs に公開 URL(https://texrelay.educpsychol.com/ と旧 https://texapp.educpsychol.com/)。PKCE なので App secret は使いません。
Development モードのアプリはリンクできるユーザー数に上限があります(現在 Dropbox の規定では 50 人。App Console の Settings に現在の人数が出ます)。超えそうなら 「Apply for production」 を申請。
DNS(Xserver のドメイン設定)
texrelay.educpsychol.com と texapp.educpsychol.com の A レコード → 162.43.43.174。lab.educpsychol.com は共用サーバー側なので触らない。
証明書(Let’s Encrypt)
certbot が自動更新します(systemctl list-timers | grep certbot)。ホスト名を足したときは:
certbot --nginx -d texrelay.educpsychol.com -d texapp.educpsychol.com
nginx の server_name に両方を書いてから実行すると,certbot が HTTPS ブロックを書き換えます。nginx -t && systemctl reload nginx で確認。
9. トラブル対応
利用者マニュアル 10 章で解決しないもの。まず journalctl -u texapp -n 100 --no-pager と,直近ジョブの生ログ:
d=$(ls -t /srv/texapp/jobs | head -1); tail -40 /srv/texapp/jobs/$d/latexmk.out
| 症状 | 見るところ・対処 |
|---|---|
コンパイルが 1 秒で失敗,ログに level=error | podman が起動できない。XDG_RUNTIME_DIR が /run/user/999(texapp の UID)か,loginctl enable-linger texapp が有効か。sudo -iu texapp podman run --rm localhost/texapp-texlive:2025 lualatex --version で手動確認 |
return code 137 / Killed | メモリ不足。TEXAPP_DOCKER_MEMORY を上げる(2GB VPS では 1536m が上限)か利用者に図の縮小を依頼 |
biber: exec of … failed (errno=13) | /var/tmp の exec 可 tmpfs が外れている(compile.py を古い版に戻したときなど) |
引用が未解決,.bib は /bib/ にある | ls /srv/texapp/bib/current/ のファイル名と文書の \addbibresource が一致しているか |
| 「Internal Server Error」 | journal に Traceback。DB のスキーマ違いなら systemctl restart texapp で移行が走る |
| 利用者が「実行中」のまま | ブラウザの回線切替。サーバ側は終わっているはず(jobs/ を見る)。利用者にページ再読み込みを |
公式スタイルの取得エラー HTTP 4xx | 共有リンクが失効。Dropbox でリンクを作り直して登録し直す |
extracted size too large | 共通ソースが 500MB 超。TEXAPP_MAX_EXTRACTED_BYTES を上げるかフォルダを分ける |
SSH が Connection refused | 自宅回線の遡断。テザリングへ。サーバ側が疑わしいときは Xserver のコンソールから systemctl status ssh |
| ディスク逸迫 | /srv/texapp/jobs(自動削除されるはず),podman system df(古いイメージは podman image prune),journalctl --vacuum-time=30d |
セキュリティ上の前提(変えないこと):コンパイルは必ずコンテナ内(TEXAPP_COMPILE_RUNNER=docker。local は開発用で隔離なし),ネットワークなし,シェルエスケープ無効。Dropbox のトークンはサーバに渡らない。uvicorn は 127.0.0.1 にしか待ち受けない。


