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


はじめに

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


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

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


  • glibc2.19 以降が必要になった

  • Dropboxクライアントの同期先ディレクトリのファイルシステムが ext4 のみとなった

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

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


  1. glibc のバージョンは 2.17

  2. 標準のファイルシステムは XFS

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


解決策


1. glibc 問題の解決


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

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

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

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


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

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

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


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


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

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

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

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

$ mkdir ~/.img

$ truncate -s 1G ~/.img/dropbox.img

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

$ 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 を作成する。

$ mkdir ~/Dropbox

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

$ sudo mount -o loop -t ext4 ~/.img/dropbox.img ~/Dropbox

$ sudo chown -R ユーザ名:グループ名 ~/Dropbox

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

$ 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 に読み込ませ、

$ sudo systemctl daemon-reload

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

$ sudo systemctl start home-bob-Dropbox.mount

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

$ sudo systemctl enable home-bob-Dropbox.mount


systemdでDocker版Dropboxの起動する


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

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

$ 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も実行するようにした。

$ 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)では、ホスト側からDropboxの管理コマンドを実行してステータス等を確認できるようにした。

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

$ 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を利用するといった使い方もできるようになる。





  1. 任意のファイルをブロックデバイスとして扱うLinuxの仕組み。