Docker版DropboxクライアントをCentOS7サーバで動作させる

# はじめに

以前、「[Linux版dropboxをsystemdでサービスとして起動する](https://qiita.com/little-forest/items/138daa7348328d721bc5)」という記事を書いたが、最近Linux版のDropboxクライアントのシステム要件が変更になり、CentOS7では動作しなくなってしまった。そこで、新たにDocker版のDropboxクライアントコンテナと、その起動用のSystemdのユニットファイルを作成したので紹介する。

## 2020/11/07 追記

* Dropboxクライアントのバージョンアップに伴い、Alpine ではライブラリ依存を解決できず起動できなくなってしまったので、[Ubuntu18.04 ベースのイメージ(`littlef/dropbox:1.1.0`)]((https://hub.docker.com/r/littlef/dropbox))に作り直しました。 コンテナのイメージサイズは大きくなってしまいますが、きちんと動作するようになりました。
* 新しいバージョンで動かすには、一度サービスを止めてから既存のイメージを削除して再起動してください。

```
$ sudo systemctl stop dropbox@XXX
$ docker rmi docker.io/littlef/dropbox:latest
$ sudo systemctl start dropbox@XXX
```


## 2019/12/16 追記

* 最新のDropboxクライアントがxfsに対応するようになったので、本文後半の対応(ファイルシステムの問題)は不要になりました。
* Dropboxクライアントの変更に伴い、公開していた[Dockerコンテナ](https://hub.docker.com/r/littlef/dropbox)が起動に失敗するようになっていたので、修正版を公開しました。 使い方等は[Githubリポジトリ](https://github.com/little-forest/docker-dropbox)をご覧ください。

# Linux版Dropboxクライアントのシステム要件

2018年10月頃、LinuxのDropboxクライアントに加わった仕様変更は以下の通り。

* glibc2.19 以降が必要になった
* ~~Dropboxクライアントの同期先ディレクトリのファイルシステムが ext4 のみとなった~~ (2019年12月段階ではxfsにも対応した)

詳細は https://help.dropbox.com/ja-jp/desktop-web/system-requirements を参照。

一方、現時点で最新のCentOS7(7.6.1810)は以下のような状況で、どちらの条件も満たさない。

1. glibc のバージョンは 2.17
1. 標準のファイルシステムは XFS

ファイルシステムについては、ext4でフォーマットしなおせば良いが、今さらDropboxだけのために戻したくはない。

# 解決策

## 1. glibc 問題の解決

### Dropbox クライアントを Docker コンテナで実行する

glibc はあらゆるコマンドから利用されているので、おいそれとバージョンを上げるわけにはいかない。このため、要件を満たすバージョンの glibc がインストールされた Docker コンテナ上で Dropbox を動作させることでクリアした。

glibcがインストールされた Alpine Linux の Docker イメージ([frolvlad/alpine-glibc])にDropboxクライアントをインストールしたものを用意する。同様の Docker イメージはいくつか公開されているが、今回はいくつか修正したい要件があったので、[dylansm/dropbox](https://github.com/dylansm/dropbox)を元に[自作](https://github.com/little-forest/docker-dropbox)した。

さらにこのイメージを systemd 経由で簡単に起動できるようにしたので、本稿の後半で紹介する。


### Dropbox同期ディレクトリのowner問題を解決する

ホスト側に用意したDropbox同期ディレクトリ(通常は```~/Dropbox```)をDockerコンテナ側にマウントするが、その際に問題となるのがUID、GIDの問題。コンテナ内で動作するプロセスは root 権限で実行されるため、このプロセスが作成したファイルのオーナーは、ホスト側でも root になってしまう。

このため、コンテナ内のDockerクライアントは、ホスト側のDropbox同期ディレクトリのオーナーと同じUID、GIDのユーザーとして実行する必要がある。(この解決方法については、「[dockerでvolumeをマウントしたときのファイルのowner問題](https://qiita.com/yohm/items/047b2e68d008ebb0f001)」を参考にさせていただいた。)

## 2.ファイルシステム問題の解決

※ 2019年12月現在、Dropboxクライアントがxfsにも対応するようになったので、本対応は不要になった

### ファイルをループバックマウントする

既存のパーティション内に用意したファイルをループバックマウント[^loopback]し、ext4でフォーマットして、Dockerコンテナにマウントすることで解決。この解決策に関しては、「[btrfsでDropboxを使う](https://qiita.com/ponkotuy/items/8a1cef4686b9ee5987c6)」を参考にさせていただいた。この記事はAlpineLinuxでの作成例。CentOS7でもほぼ同じだが、いちおう紹介しておく。

[^loopback]: 任意のファイルをブロックデバイスとして扱うLinuxの仕組み。

まず、イメージファイルを作成。どこに作成しても構わないが、ここではホームディレクトリ配下で ```~/.img/dropbox.img``` という名前で1Gbytesのファイルを作成した。

ファイルはスパースファイルなので、最初から1Gの容量を使用するわけではない。

```shell-session
$ mkdir ~/.img
$ truncate -s 1G ~/.img/dropbox.img
```

作成したファイルを、ext4 でフォーマットする。対象がブロックデバイスではないので確認のプロンプトが表示されるが、```y```を回答して進める。

```shell-session
$ mkfs.ext4 ~/.img/dropbox.img
mke2fs 1.42.9 (28-Dec-2013)
dropbox.img is not a block special device.
Proceed anyway? (y,n) y
Discarding device blocks: done
Filesystem label=
OS type: Linux
Block size=4096 (log=2)
Fragment size=4096 (log=2)
Stride=0 blocks, Stripe width=0 blocks
655360 inodes, 2621440 blocks
131072 blocks (5.00%) reserved for the super user
First data block=0
Maximum filesystem blocks=2151677952
80 block groups
32768 blocks per group, 32768 fragments per group
8192 inodes per group
Superblock backups stored on blocks:
        32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632

Allocating group tables: done
Writing inode tables: done
Creating journal (32768 blocks): done
Writing superblocks and filesystem accounting information: done
```

次に、Dropbox同期用ディレクトリ ```~/Dropbox``` を作成する。

```shell-session
$ mkdir ~/Dropbox
```

このディレクトリに先ほど作成したファイルをループバックマウントする。マウントにはroot権限が必要だが、その際に```~/Dropbox```のオーナーがrootになってしまうので、chownコマンドで修正しておく。

```shell-session
$ sudo mount -o loop -t ext4 ~/.img/dropbox.img ~/Dropbox
$ sudo chown -R ユーザ名:グループ名 ~/Dropbox
```

ただしくマウントできたか、確認してみる。ディレクトリのファイルシステムが ```/dev/loop*```、タイプが```ext4```となっているので、ただしくフォーマット・マウントできていることがわかる。

```shell-session
$ df -T ~/Dropbox
Filesystem     Type 1K-blocks    Used Available Use% Mounted on
/dev/loop2     ext4  10190100 7964172   1685256  83% /home/ユーザ名/Dropbox
```

### systemdのマウントユニットとして保存する

システム起動時に上記のファイルを自動実行するようにしておく。従来なら ```/etc/fstab``` に記述するが、せっかくなので systemd のマウントユニットを作成してみる。

たとえば、同期ディレクトリが ```/home/bob/Dropbox``` の場合、以下のような内容で ```/etc/systemd/system/home-bob-Dropbox.mount``` という名前のファイルを作成する。

```
[Unit]
Description=Mount Dropbox directory
After=network-online.target
Wants=network-online.target

[Mount]
What=/home/bob/.img/dropbox.img
Where=/home/bob/Dropbox
Options=loop
Type=ext4
TimeoutSec=30

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

ここで、mountファイルのファイル名は、マウント先パスのスラッシュ(```/```)をハイフン(```-```)に置き換えたものにするルール。なので、```/home/bob/Dropbox```ならば、```home-bob-Dropbox.mount``` としなくてはならない。

マウントファイルを作成したら、systemd に読み込ませ、

```shell-session
$ sudo systemctl daemon-reload
```

以下のコマンドでマウントできる。

```shell-session
$ sudo systemctl start home-bob-Dropbox.mount
```

さらに、以下のコマンドで自動マウントするようにしておく。

```shell-session
$ sudo systemctl enable home-bob-Dropbox.mount
```

# systemdでDocker版Dropboxの起動する

### unitファイルのダウンロードと登録

systemdのunitファイルを Github からダウンロードして、```/etc/systemd/system```配下に配置する。

```shell-session
$ cd /etc/systemd/system
$ sudo curl -o dropbox@.service https://raw.githubusercontent.com/little-forest/docker-dropbox/master/dropbox%40.service
$ sudo systemctl daemon-reload && echo OK
OK
```

### Dropboxサービスの起動

systemctlコマンドで、サービスを起動。テンプレートユニットにしてあるので、`@`以降にはドロップボックス同期ディレクトリのオーナーユーザー名を指定する。ここでは、同期ディレクトリが```/home/bob/Dropbox```なので、```dropbox@bob```とする。

また、サービス起動直後にログを確認できるように、journalctlも実行するようにした。

```shell-session
$ sudo systemctl start dropbox@bob.service && sudo journalctl -fu dropbox@bob.service
```

初回起動時はDockerイメージをダウンロードするため、少々時間がかかる。しばらくすると、以下のようなメッセージが表示されるので、表示されたURLをブラウザで表示してDropboxアカウントでログインすると、リンクされる。

```
This computer isn't linked to any Dropbox account...
Please visit https://www.dropbox.com/cli_link_nonce?nonce=**************************************** to link this device.
```

しばらく待つと、同期がはじまる。

### ステータスの確認

筆者が作成したDockerイメージ([little-forest/docker-dropbox](https://github.com/little-forest/docker-dropbox))では、ホスト側からDropboxの管理コマンドを実行してステータス等を確認できるようにした。

ホスト側から以下のように実行することで、同期ステータスを確認することができる。

```shell-session
$ docker exec -it <コンテナ名> /dropbox status
Up to date
```

本稿で紹介したsystemdのユニットファイルからDockerコンテナを起動した場合、コンテナ名は```dropbox_<systemdサービスのインスタンス名>``` となっている。```dropbox@bob.service``` で起動していれば、```dropbox_bob``` がコンテナ名。

### (参考)Dropboxクライアントサービスのユニットファイル

ユニットファイルは以下のような感じ。単にDockerコンテナを起動するだけでなく、```ExecStartPre```ディレクティブを使って事前チェックや下準備をするようにしている。

```
[Unit]
Description=Dropbox client (user: %i)
Requires=docker.service
After=docker.service
 
[Service]
Type=simple
Environment=DBOX_HOME=/home/dbox

# check user existence
ExecStartPre=/bin/bash -c "id %i > /dev/null 2>&1 || (echo '[ERROR] User not found. : %i'; exit 1; )"
ExecStartPre=/bin/bash -c "if [[ ! -d /home/%i ]]; then echo '[ERROR] Directory not found. : /home/%i'; exit 1; fi"

# prepare user's uid & gid
ExecStartPre=/bin/bash -c "echo USER_ID=`id %i | sed -re 's/.*uid=([0-9]+).*/\1/'` > /tmp/dropbox_%i"
ExecStartPre=/bin/bash -c "echo GROUP_ID=`id %i | sed -re 's/.*uid=([0-9]+).*/\1/'` >> /tmp/dropbox_%i"

# make dropbox directory
ExecStartPre=/bin/bash -c "[[ -d /home/%i/Dropbox ]] || (mkdir /home/%i/Dropbox; chown %i:%i /home/%i/Dropbox;)"
ExecStartPre=/bin/bash -c "[[ -d /home/%i/.dropbox ]] || (mkdir /home/%i/.dropbox; chown %i:%i /home/%i/.dropbox;)"

# stop & remove existence container
ExecStartPre=/bin/bash -c "/usr/bin/docker container ls -q -f name=dropbox_%i | xargs -r docker stop"
ExecStartPre=/bin/bash -c "/usr/bin/docker ps -a -q -f name=dropbox_%i | xargs -r docker rm"

ExecStart=/usr/bin/docker run --rm --name=dropbox_%i --env-file /tmp/dropbox_%i -v /home/%i/Dropbox:${DBOX_HOME}/Dropbox -v /home/%i/.dropbox:${DBOX_HOME}/.dropbox -v /etc/localtime:/etc/localtime:ro littlef/dropbox

ExecStop=/usr/bin/docker stop dropbox_%i

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

# おわりに

DropboxクライアントをDockerイメージ化し、systemdで管理されたサービスにしたことで、クライアントのシステム要件を満たさない、CentOS7 でも簡単にDropboxクライアントを運用できるようになった。

2019年4月からはDropboxの無料プランの制限がきつくなり、同時接続できるデバイスが3つにまでになってしまった。LinuxサーバをDropboxに接続すれば、家庭内に限ればLinux上のDropbox同期ディレクトリをSamba共有して、他のデバイスからDropboxを利用するといった使い方もできるようになる。