CentOSサーバにunoserverをインストールする

[LibreOffice Technology Advent Calendar 2023](https://adventar.org/calendars/9240) 7日目の記事です。

# unoserverとは

LibreOfficeには、バッチ的に起動してドキュメントファイルのフォーマットを変換する機能があります。

```
$ libreoffice --headless --convert-to pdf sample.odt
```

与えることができる入力形式としては、LibreOfficeネイティブのOpenDocument形式だけでなく、LibreOfficeが読み込むことのできる一般的なドキュメント形式（DOCXなど）や画像形式（PNGなど）のほとんどが含まれます。全貌は[Wikipediaの記事](https://en.wikipedia.org/wiki/LibreOffice)を確認するのがよいと思います。

この方法は単発の変換作業では問題ありませんが、変換サービスを提供するようなサーバ用途では、libreofficeをそのたびに起動するというコストがかかります。これに対して、LibreOfficeを常駐させて起動オーバーヘッドを軽減する目的のツールが[unoserver](https://github.com/unoconv/unoserver)です。サーバマシンでunoserverを起動しておくと、クライアントマシンではunoconvertコマンドにより変換することができます。

以下では、このunoserverをCentOSにインストールし稼働させるまでのなるべくストレートな手順をケースに分けて解説します。以下、CentOS Stream 8（とOracle Linux 8.8）およびCentOS Stream 9（以下Streamは省略）（いずれもサーバとしてインストール）で検証しています。各パッケージの有無やバージョン番号は、検証時点でのものとなります。

同じ作者による同じ目的の[unoconv](https://github.com/unoconv/unoconv)というツールもありますが、こちらはdeprecatedとなっており2年以上更新されていないので、取り上げません（問題なく動くことは確認しています）。


# Pythonの準備

## CentOS 8 --- python39のインストール

CentOS 8のplatform-pythonは3.6.8ですが、pipでunoserverをインストールするときに次のようにエラーになります。unoserverのソース内のpyproject.tomlには`requires-python = ">= 3.8"`と記載されているのに、なぜ`'>= 3.7'`と表示されるのかはわかりませんが。

```:CentOS8
# /usr/libexec/platform-python -m pip install unoserver
...
unoserver requires Python '>= 3.7' but the running Python is 3.6.8
```

後述する最新のLibreOfficeを公式サイトからダウンロードしてインストールした場合は、Python 3.8が付属するのでこれを使えば条件は満たします。しかしこのPythonにはpipもensurepipも入っていないので、get-pip.pyを入手してpipをインストールする必要があるなど、いろいろ不便です。

他にも新しいPythonが必要となるケースもあるかもしれませんので、CentOS 9に合わせて、ここではデフォルトリポジトリからPython 3.9を入れておきます。これによりpip3もインストールされます。3.11でもいいとは思いますが未検証です。

```:CentOS8
# dnf install -y python39
```

余談に近いですが、最新LibreOfficeの付属Pythonでpipをインストールし、これを使って何らかのパッケージをインストールすると、スクリプトのShebangでprogram/python.binが指定されます。ところがこのバイナリは必要なパスを一切事前設定しないので、そのままではスクリプトの起動ができません。スクリプトを修正してprogram/python（shスクリプト）を指定するか環境変数を事前に設定するようなラッパーを被せる必要があります。LibreOfficeのバージョン更新時にも注意が必要です。

もっとも、unoserverの場合はどうしても環境変数の設定が必要になるのでこの部分の手間はあまり変わりませんし、systemdで起動するならその設定ファイルで環境変数の設定が済みます。


## CentOS 9 --- pipのインストール

CentOS 8のplatform-pythonは3.9.18なので条件は満たします。ただし、素のままではpipがインストールされていませんので、もしまだならインストールしておきます。

```:CentOS9
# dnf install -y python3-pip
```


# LibreOfficeの準備

## 標準AppStreamからのLibreOfficeのインストール

次のようにすれば、CentOS 8ではLibreOffice 6.4.7.2、CentOS 9ではLibreOffice 7.1.8.1が標準AppStreamからインストールされます。ただし、LibreOffice 6.4では変換を開始しても暴走して戻ってこないケース[^1]があり、意図せずDoSになってしまうことがあります。7系で同様のケースがないとは言いきれませんが、今のところみつかっていませんので、CentOS 8では次の項にスキップして最新のLibreOfficeをインストールすることをお勧めします。

[^1]: LibreOffice 6.4が暴走した実績があるファイル例: HEICファイルの拡張子をJPGに変更したもの, 一部の（おそらく挿入されている図などの影響でレイアウトが大幅に変化しやすい）Wordファイル.

```
# dnf install -y libreoffice
```

libreoffice-langpack-jaは必要ありません(入れても問題はありません)。

依存してインストールされるlibreoffice-pyuno関連のファイルはplatform-pythonのsite-packagesに配置されます。CentOS 8ではplatform-pythonではなくPython 3.9を使用するので、これらのファイルをPython 3.9側にコピーする必要があります。

```:CentOS8
# cp /usr/lib64/python3.6/site-packages/{officehelper,uno,unohelper}.py /usr/lib64/python3.9/site-packages
```

## 最新のLibreOfficeのインストール

最新のLibreOffice（執筆時点で7.6.3.2）を使用したい場合は、[公式ダウンロードサイト](https://ja.libreoffice.org/download/download/)で、OSとしてLinux (64-bit) (rpm)を選択してダウンロードします。言語関連の追加ダウンロードは不要です。

これを展開してRPMSディレクトリ内のrpmファイルをインストールします。

```
# dnf install -y RPMS/*.rpm
```

さらに、LibreOfficeの起動に下記ライブラリを必要とするようなので、まだならインストールしておきます。

```
# ldd /opt/libreoffice7.6/program/{oosplash,soffice.bin} | grep 'not found'
	libXinerama.so.1 => not found
	libcups.so.2 => not found
# dnf install -y libXinerama cups-libs
```


# フォントの準備

## 日本語フォントのインストール

CentOS 9で日本語環境（langpacks-ja）をインストール済かlibreoffice-langpack-jaをインストールすれば、Noto系CJKフォントもインストールされています。

それ以外のケースでは、次のように明示的にインストールしておきます。

```
# dnf install -y google-noto-sans-cjk-ttc-fonts google-noto-serif-cjk-ttc-fonts
```

または、公式な配布元のGoogleから[Noto系](https://fonts.google.com/noto/fonts?noto.query=japanese)フォントをダウンロードして入れることもできますが、ウェイトのバリエーションが異なるので注意が必要かもしれません。Noto系以外のフォントも、変換前の文書で使われる可能性があって入手可能なものがあれば必要に応じて/usr/share/fonts以下にインストールしておくのがよいと思われます。[BIZ UD系](https://fonts.google.com/?query=biz+ud)や[IPA系](https://moji.or.jp/ipafont/)のフォントが代表的です。

## フォントエイリアスの設定

WindowsやOfficeに付属するフォントなどでライセンス上利用できないケースも多々あると思われます。そのような場合は利用したいフォントとその代替として利用できるフォントの対応を指定しておけば、オリジナルの雰囲気をあまり損なわずに変換が可能です。以下のfontconfig設定ファイルは私の環境用にエイリアスを用いて実装した例です。見やすくするには適宜改行を入れるべきですが、非常に長大になるのでフォントごとに1行にまとめてしまいました。欧文フォントや他のCJKフォントも含んだもっと完成度の高い設定ファイルがあれば欲しいです。

```:/etc/fonts/conf.d/30-ja-aliases.conf
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
    <alias><family>BIZ UDGothic</family> <default><family>Noto Sans CJK JP</family></default></alias>
    <alias><family>BIZ UDゴシック</family> <accept><family>BIZ UDGothic</family></accept> <default><family>Noto Sans CJK JP</family></default></alias>
    <alias><family>BIZ UDMincho</family> <default><family>Noto Serif CJK JP</family></default></alias>
    <alias><family>BIZ UD明朝</family> <accept><family>BIZ UDMincho</family></accept> <default><family>Noto Serif CJK JP</family></default></alias>
    <alias><family>BIZ UDMincho Medium</family> <accept><family>BIZ UDMincho</family></accept> <default><family>Noto Serif CJK JP</family></default></alias>
    <alias><family>BIZ UD明朝 Medium</family> <accept><family>BIZ UDMincho</family></accept> <default><family>Noto Serif CJK JP</family></default></alias>
    <alias><family>BIZ UDPGothic</family> <default><family>Noto Sans CJK JP</family></default></alias>
    <alias><family>BIZ UDPゴシック</family> <accept><family>BIZ UDPGothic</family></accept> <default><family>Noto Sans CJK JP</family></default></alias>
    <alias><family>BIZ UDPMincho</family> <default><family>Noto Serif CJK JP</family></default></alias>
    <alias><family>BIZ UDP明朝</family> <accept><family>BIZ UDPMincho</family></accept> <default><family>Noto Serif CJK JP</family></default></alias>
    <alias><family>BIZ UDPMincho Medium</family> <accept><family>BIZ UDPMincho</family></accept> <default><family>Noto Serif CJK JP</family></default></alias>
    <alias><family>BIZ UDP明朝 Medium</family> <accept><family>BIZ UDPMincho</family></accept> <default><family>Noto Serif CJK JP</family></default></alias>
    <alias><family>MS Gothic</family> <default><family>Noto Sans CJK JP</family></default></alias>
    <alias><family>ＭＳ ゴシック</family> <default><family>Noto Sans CJK JP</family></default></alias>
    <alias><family>MS Mincho</family> <default><family>Noto Serif CJK JP</family></default></alias>
    <alias><family>ＭＳ 明朝</family> <default><family>Noto Serif CJK JP</family></default></alias>
    <alias><family>MS PGothic</family> <default><family>Noto Sans CJK JP</family></default></alias>
    <alias><family>ＭＳ Ｐゴシック</family> <default><family>Noto Sans CJK JP</family></default></alias>
    <alias><family>MS PMincho</family> <default><family>Noto Serif CJK JP</family></default></alias>
    <alias><family>ＭＳ Ｐ明朝</family> <default><family>Noto Serif CJK JP</family></default></alias>
    <alias><family>MS UI Gothic</family> <default><family>Noto Sans CJK JP</family></default></alias>
    <alias><family>Meiryo UI</family> <default><family>Noto Sans CJK JP</family></default></alias>
    <alias><family>Meiryo</family> <default><family>Noto Sans CJK JP</family></default></alias>
    <alias><family>メイリオ</family> <default><family>Noto Sans CJK JP</family></default></alias>
    <alias><family>UD Digi Kyokasho NK-B</family> <default><family>Noto Sans CJK JP</family></default></alias>
    <alias><family>UD デジタル 教科書体 NK-B</family> <default><family>Noto Sans CJK JP</family></default></alias>
    <alias><family>UD Digi Kyokasho NK-R</family> <default><family>Noto Sans CJK JP</family></default></alias>
    <alias><family>UD デジタル 教科書体 NK-R</family> <default><family>Noto Sans CJK JP</family></default></alias>
    <alias><family>UD Digi Kyokasho NP-B</family> <default><family>Noto Sans CJK JP</family></default></alias>
    <alias><family>UD デジタル 教科書体 NP-B</family> <default><family>Noto Sans CJK JP</family></default></alias>
    <alias><family>UD Digi Kyokasho NP-R</family> <default><family>Noto Sans CJK JP</family></default></alias>
    <alias><family>UD デジタル 教科書体 NP-R</family> <default><family>Noto Sans CJK JP</family></default></alias>
    <alias><family>UD Digi Kyokasho N-B</family> <default><family>Noto Sans CJK JP</family></default></alias>
    <alias><family>UD デジタル 教科書体 N-B</family> <default><family>Noto Sans CJK JP</family></default></alias>
    <alias><family>UD Digi Kyokasho N-R</family> <default><family>Noto Sans CJK JP</family></default></alias>
    <alias><family>UD デジタル 教科書体 N-R</family> <default><family>Noto Sans CJK JP</family></default></alias>
    <alias><family>Yu Gothic UI</family> <default><family>Noto Sans CJK JP</family></default></alias>
    <alias><family>Yu Gothic UI Light</family> <default><family>Noto Sans CJK JP</family></default></alias>
    <alias><family>Yu Gothic UI Semibold</family> <default><family>Noto Sans CJK JP</family></default></alias>
    <alias><family>Yu Gothic UI Semilight</family> <default><family>Noto Sans CJK JP</family></default></alias>
    <alias><family>Yu Gothic</family> <default><family>Noto Sans CJK JP Light</family><family>Noto Sans CJK JP</family></default></alias>
    <alias><family>游ゴシック</family> <default><family>Noto Sans CJK JP Light</family><family>Noto Sans CJK JP</family></default></alias>
    <alias><family>Yu Gothic Light</family> <default><family>Noto Sans CJK JP Thin</family><family>Noto Sans CJK JP</family></default></alias>
    <alias><family>游ゴシック Light</family> <default><family>Noto Sans CJK JP Thin</family><family>Noto Sans CJK JP</family></default></alias>
    <alias><family>Yu Gothic Medium</family> <default><family>Noto Sans CJK JP Medium</family><family>Noto Sans CJK JP</family></default></alias>
    <alias><family>游ゴシック Medium</family> <default><family>Noto Sans CJK JP Medium</family><family>Noto Sans CJK JP</family></default></alias>
    <alias><family>Yu Mincho</family> <default><family>Noto Serif CJK JP</family></default></alias>
    <alias><family>游明朝</family> <default><family>Noto Serif CJK JP</family></default></alias>
    <alias><family>Yu Mincho Demibold</family> <default><family>Noto Serif CJK JP SemiBold</family><family>Noto Serif CJK JP</family></default></alias>
    <alias><family>游明朝 Demibold</family> <default><family>Noto Serif CJK JP SemiBold</family><family>Noto Serif CJK JP</family></default></alias>
    <alias><family>Yu Mincho Light</family> <default><family>Noto Serif CJK JP Light</family><family>Noto Serif CJK JP</family></default></alias>
    <alias><family>游明朝 Light</family> <default><family>Noto Serif CJK JP Light</family><family>Noto Serif CJK JP</family></default></alias>
</fontconfig>
```


# unoserverの準備と起動

## unoserverのインストール

unoserver自体はpip3（CentOS 9ではpipでも起動可）を使うことでインストールできます。rootで実行すると警告されますが、実行形式のスクリプトは/usr/local/binに、関連ファイルは/usr/local/lib/python3.9/site-packagesにインストールされるので、標準的なパッケージとぶつかることはまずないでしょう。

```
# pip3 install unoserver
```


## ポートの開放

unoserverはデフォルトで2003/TCPでクライアントからの接続を待ちます。認証は何もありませんので、ファイアウォールとLISTENするアドレス指定だけが防壁となります。素のCentOSではファイアウォールでほぼ全ポートがブロックされているはずですので、このポートを必要な範囲に開放します。NAT内等で全開放でよければ次のようになります。2003/TCPが他のサービス等で使用されているのであれば、適当な番号を選ぶとともに、unoserverとunoconvertの起動時に`--port`オプションで指定してください。

```
# firewall-cmd --permanent --add-port 2003/tcp
# firewall-cmd --reload
```

2003/TCP以外にも、2002/TCPをunoserverとLibreOfficeサーバ間で使用しますが、こちらはサーバ内部のみの通信なので特に理由がなければ何もしなくてもかまいません。


## unoserverの起動

systemdのサービスとして登録するため、設定ファイルを新規に作成します。設定ファイルは/usr/lib/systemd/system/unoserver.serviceとしていますが、独自のサービスは/etc/systemd/systemに置く主義ということであれば、そちらで。

```:AppStream版LibreOffice:/usr/lib/systemd/system/unoserver.service
[Unit]
Description=Unoserver for document conversions
Documentation=https://github.com/unoconv/unoserver
After=network.target

[Service]
Type=simple
Environment="LC_ALL=ja_JP.UTF-8"
ExecStart=/usr/local/bin/unoserver --interface 0.0.0.0 --uno-interface 127.0.0.1
TimeoutStopSec=3

[Install]
WantedBy=multi-user.target
```

設定ファイルでは、外部にサービスとして提供したいので`--interface`として0.0.0.0を指定していますが、IPアドレスが複数あっていずれかに制限したい場合はそれを指定します（この場合、`After=network.target`ではなく、`After=network-online.target`の方がいいかもしれません）。デフォルトは127.0.0.1です。`--uno-interface`はunoserverとLibreOfficeの接続用のインタフェースで通常は127.0.0.1なので省略できるといいのですが、デフォルトで`--interface`で指定した値になってしまう（というバグ（[報告済](https://github.com/unoconv/unoserver/issues/96)）らしきものがある）ので、明示しています。

また、テキストやCSVなど、フォントを選択するのに必要となる言語や文字エンコーディング情報の含まれない入力を与えた場合のデフォルトを、環境変数`LC_ALL`で設定しています。OSを日本語設定でインストールしてあれば不要かもしれません。

その他、CentOS 9でサービスのstopがうまくいかないことがあるので、TimeoutStopSecをデフォルトの90秒ではなく3秒にしています。

公式版の最新LibreOfficeをインストールした場合は、pyuno関連ファイルが置かれているディレクトリの環境変数設定、Uno Runtime Environmentの設定ファイルの環境変数設定、LibreOfficeの実行コマンド名のオプション指定を加える必要があるので、次のようになります。

```:公式版LibreOffice:/usr/lib/systemd/system/unoserver.service
[Unit]
Description=Unoserver for document conversions
Documentation=https://github.com/unoconv/unoserver
After=network.target

[Service]
Type=simple
Environment="LC_ALL=ja_JP.UTF-8"
Environment="PYTHONPATH=/opt/libreoffice7.6/program"
Environment="URE_BOOTSTRAP=vnd.sun.star.pathname:/opt/libreoffice7.6/program/fundamentalrc"
ExecStart=/usr/local/bin/unoserver --executable libreoffice7.6 --interface 0.0.0.0 --uno-interface 127.0.0.1
TimeoutStopSec=3

[Install]
WantedBy=multi-user.target
```

systemctlでサービスを有効にし、起動しておきます。

```
# systemctl enable unoserver
# systemctl start unoserver
```


# クライアントの準備と実行

クライアント側は、サーバと同じマシンやOSである必要はなく種々の状況がありうるので、それらを網羅して説明したりはしませんが、CentOS系やUbuntu系で適切なバージョンのPython 3とpipがインストールされていて全ユーザに提供するというケースであれば、サーバ側と同じようにunoserverをインストールしてください。

```
# pip3 install unoserver
```

必要なのは、/usr/local/bin/unoconvert（と、目的によってはunocompareも）なので、これを/usr/binにコピーまたはリンクするか、/usr/local/binにPATHを通すか、フルパスを指定して実行します。

変換は次のように実行します。

```
$ unoconvert --host SERVERIPADDRORNAME sample.docx sample.pdf
```

テスト目的などでVirtualBoxなどのゲストマシン上でunoserverを起動し、ホストマシンからNAT+ポートフォワーディングで接続する場合は一点だけ注意が必要です。`--host 127.0.0.1`と指定（または省略）すると、`--host-location remote`を指定しても、出力ファイルがサーバ側に作成されてしまうというバグ（[報告済](https://github.com/unoconv/unoserver/issues/97)）があることです。`--host`がデフォルト`127.0.0.1`と`localhost`のときだけ特別扱いされるので、`127.1`を指定するなどの抜け道はあります。

# おわりに

CentOSにunoserverをインストールする際に必要となるもろもろの準備や注意点を解説しました。小粒のツールなのに、OSの違い、Pythonの違い、LibreOfficeの違いなどで、種々のはまりポイントがあり、それらをすべて掻い潜らないとインストールに成功しないというおもしろさがありました。そういう試行錯誤は楽しめない/手間はかけられないという読者の役に立てば幸いです。

テキストやCSVでデフォルト以外の言語やエンコーディングを明示したいときの適切なフィルタ指定方法はよくわかっていません。

インストールのしやすさや機能・ドキュメントの不足がOSでのサポートや今後のバージョンアップ等で解消されていくといいですね。

うまくインストールできない……という環境がありましたら、コメントをください。

それでは楽しい変換ライフを！
Happy Converting!