Linuxで暗号化ディスク作ってOneDriveと同期（365は未対応）

live.com の OneDrive と Linux を同期した時のメモです。私が普段使っているLinux環境はホームディレクトリを暗号化していないので、そこと同期するのもなーということで暗号化されたディスクイメージを作ってそこと同期しています。今回は Lubuntu16.04(64bit) で動作確認を行いました。残念ながらOffice365 のOneDrive には対応していませんのでご注意を。

## どうやって？
OneDrive との同期は python で書かれた onedrived-dev というLinuxクライアントが https://github.com/xybu/onedrived-dev で公開されているのでそれを使います。暗号化されたディスクイメージは cryptsetup を使って作成します。

## onedrived-devのインストール
では早速インストールです。

まず python3 がインストールされていることを確認します。私の環境の場合はすでに3.5.2がインストールされていることが確認できました。もしpython3コマンドがなかったりバージョンが3.3より小さい場合はインストールの必要があるようですが、その必要はなさそうです。

```console:python3がインストールされていることを確認
$ python3 --version
Python 3.5.2
```

PIP をインストールします。PIPとsetuptoolsはパッケージからもインストールできるみたいですけど、そっちだとエラーになってしまったのでダウンロードしてインストールしています。他のところで影響がでないか心配ではありますが…。

```console:PIPをインストール
$ wget -O- https://bootstrap.pypa.io/get-pip.py | sudo python3
$ sudo pip3 install -U pip setuptools
```

続けて必要となりそうなパッケージをインストールします。

```console:必要となりそうなパッケージをインストール
$ sudo apt install build-essential python3-dev libssl-dev inotify-tools python3-dbus git
```

onedrived-dev のソースコードをゲットしてインストールします。

```console:onedrived-devのソースコードをゲットしてインストール
$ git clone https://github.com/xybu/onedrived-dev.git
$ cd onedrived-dev
$ python3 ./setup.py test
$ sudo pip3 install -e .
```

[ngrok](https://ngrok.com/) をインストールします。ngrok のことはよく知らないのですがonedrived-devの実行に必要なようです。ngrok は https://ngrok.com/ にアクセスしてダウンロード後`unzip`コマンドで解凍します。解凍すると ngrok コマンドが作成されます。

```console:解凍
$ unzip ngrok-stable-linux-amd64.zip 
Archive:  ngrok-stable-linux-amd64.zip
  inflating: ngrok                   
```

先ほどonedrived-devディレクトリで作業していましたので、onedrived-devディレクトリにngrokが解凍されてしまいましたので、忘れないように場所はメモっときましょう。

これでインストールは完了です。


## 暗号化されたディスクイメージの作成
今回はOneDriveの容量と同じ5ギガの暗号化されたディスクイメージを作成し/media/onedriveにマウントしてみます。

まず暗号化に必要となる cryptsetup をインストールします。

```console:cryptsetupのインストール
$ sudo apt install cryptsetup
```

5ギガの空ファイルを作成します。

```console:5ギガの空ファイルを作成
$ fallocate -l 5G onedrive.img
```

cryptsetupを使って暗号化されたパーティションを初期化します。途中で本当に良いか？と聞いてくるので大文字で「YES」と入力し、続けてパスワードを入力します。

```console
$ cryptsetup -y luksFormat onedrive.img

WARNING!
========
This will overwrite data on onedrive.img irrevocably.

Are you sure? (Type uppercase yes): YES
Enter passphrase: 
Verify passphrase: 
```

先ほど作成したパーティションを開きます。開くときにパスワードを求められるので先ほど設定したパスワードを入力します。これで /dev/mapper/cryptroot_onedrive というパスで暗号化されたパーティションにアクセスできるようになります。

```console:パーティションを開く
$ sudo cryptsetup luksOpen onedrive.img cryptroot_onedrive
Enter passphrase for onedrive.img:
```

フォーマットします。

```console:初期化
$ sudo mkfs.ext4 -j /dev/mapper/cryptroot_onedrive
mke2fs 1.42.13 (17-May-2015)
Creating filesystem with 1310208 4k blocks and 327680 inodes
Filesystem UUID: d90fe66c-844e-4ea9-9dce-9d7458466d29
Superblock backups stored on blocks: 
	32768, 98304, 163840, 229376, 294912, 819200, 884736

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

/media/onedrive ディレクトリを作成しそこにマウントします。念のためパーミッションを設定しておきます。

```console:マウント＆パーミッション設定
$ sudo mkdir /media/onedrive
$ sudo mount /dev/mapper/cryptroot_onedrive /media/onedrive
$ sudo chown -R $USER /media/onedrive
```

これでディスクイメージの作成とマウントは完了です。なみにアンマウントする場合は以下のようにします。

```console:アンマウントする場合
$ sudo umount /media/onedrive
$ sudo cryptsetup luksClose cryptroot_onedrive
```

## onedrived-devの設定

onedrived-dev にアカウントを追加します。 アカウントを追加の追加は`onedrived-pref account add`コマンドを実行します。実行すると https://login.live.com で始まるURLが表示されて Paste URL here: で待機状態になりますので、 https://login.live.com で始まるURLをコピーしてブラウザのURL欄にペーストしてページを開きます。

```console:アカウントを追加
$ onedrived-pref account add
NOTE: To better manage your OneDrive accounts, onedrived needs permission to access your account info (e.g., email address to distinguish different accounts) and read/write your OneDrive files.

Paste this URL into your browser to sign in and authorize onedrived:

https://login.live.com/oauth20_authorize.srf?response_type=code&scope=wl.signin+wl.emails+wl.offline_access+onedrive.readwrite&client_id=00000000********&redirect_uri=https%3A%2F%2Flogin.live.com%2Foauth20_desktop.srf

The authentication web page will finish with a blank page whose URL starts with "https://login.live.com/oauth20_desktop.srf". Paste this URL after the prompt.
Paste URL here: 
```

URL欄にペーストしてページを開くと live.com のログイン画面が表示されますので、自分のIDとパスワードでログインします。するとアクセス許可の画面になりますので「Yes」ボタンをクリックします。

「Yes」ボタンをクリックすると真っ白の画面になるため失敗したのではないかと途方に暮れそうになりますが大丈夫です。重要なのはURL欄に表示されているURLなので、URL欄の https://login.live.com で始まるURLをコピーします。

そして先ほどのコンソール画面に戻り貼り付けます。Successfully authorized onedrived. と表示されれば追加は完了です。

```console:アカウントを追加（続き）
Paste URL here: https://login.live.com/oauth20_desktop.srf?code=Md*******-****-****-****-************&lc=1033

Successfully authorized onedrived.
Successfully added account for ******** (********@outlook.com)!

All OneDrive accounts associated with user "****":

  #  Account ID        Owner Name    Email Address
---  ----------------  ------------  ------------------------
  0  ****************  ********      ********@outlook.com
```

続けて使用するドライブを選択します。ドライブを選択するには`onedrived-pref drive set`コマンドを使います。コマンドを入力するとドライブの一覧が表示され番号を入力する状態になります。ドライブは1つしかないと思いますので最初の0を入力します。続けて同期するディレクトリを聞かれますので先ほど作成したディレクトリ /media/onedrive を指定します。

```console:ドライブの選択
$ onedrived-pref drive set
Reading drives information from OneDrive server...

All available Drives of authorized accounts:

  #  Account Email             Drive ID          Type      Quota                       Status
---  ------------------------  ----------------  --------  --------------------------  --------
  0  ********@outlook.com      ****************  personal  3.0 GB Used / 5.0 GB Total  active

Please enter row number of the Drive to add or modify (CTRL+C to abort): 0

Going to add/edit Drive "****************" of account "********@outlook.com"...
Enter the directory path to sync with this Drive [/home/****/OneDrive]: /media/onedrive
Syncing with directory "/media/onedrive"? [y/N]: y
Enter the path to ignore file for this Drive [/home/****/.config/onedrived/ignore_v2.txt]: 

Successfully configured Drive **************** of account ********@@outlook.com (****************):
  Local directory: /media/onedrive
  Ignore file path: /home/****/.config/onedrived/ignore_v2.txt
```

完了です

## 同期実行！
テストするためにフォアグランドで onedrived-dev を起動し同期が実行されることを確認します。

```console:同期を実行
$ NGROK=~/onedrived-dev/ngrok onedrived start --debug
```

`--debug`を外せばバックグラウンドで動かせるようですのですが私の場合は今だに`--debug`で動かしているので、動作検証はできていないです。

## 同期するためのスクリプト
暗号化されたイメージファイルをマウントして同期するまでを簡単に実行できるようにスクリプトにしておきます。

```bash:マウントして同期するスクリプト
#!/bin/sh

#マウントされていなかったらマウント
if [ -z "$(df -h | grep /media/onedrive)" ]; then
	sudo cryptsetup luksOpen onedrive.img cryptroot_onedrive
	sudo mount /dev/mapper/cryptroot_onedrive /media/onedrive
fi

#同期（マウントに失敗している場合は実行しない）
if [ -n "$(df -h | grep /media/onedrive)" ]; then
	NGROK=~/onedrived-dev/ngrok onedrived start --debug
fi
```

終わりです。

## おまけ
### cryptsetupで作成したイメージファイルは本当に暗号化されているの？
というわけで試してみました。cryptsetupで作成したイメージファイルとそうでないイメージファイルを作成し比較してみます。

まずはテスト用のファイルです。単純にabc..zが10行並んでいるだけのテキストファイルです。

```text:test.txt
abcdefghijklmnopqrstuvwxyz
abcdefghijklmnopqrstuvwxyz
abcdefghijklmnopqrstuvwxyz
abcdefghijklmnopqrstuvwxyz
abcdefghijklmnopqrstuvwxyz
abcdefghijklmnopqrstuvwxyz
abcdefghijklmnopqrstuvwxyz
abcdefghijklmnopqrstuvwxyz
abcdefghijklmnopqrstuvwxyz
abcdefghijklmnopqrstuvwxyz
```

4Mの小さなイメージファイルを作成し、先ほど作成したtest.txtをコピーします。

```console:イメージファイル作成
# 暗号化されていないイメージファイル
$ fallocate -l 4M a.img
$ mkfs.ext4 -j a.img
$ sudo mount a.img /mnt
$ sudo cp test.txt /mnt
$ sudo umount /mnt

# 暗号化されたイメージファイル
$ fallocate -l 4M b.img
$ cryptsetup -y luksFormat b.img
$ sudo cryptsetup luksOpen b.img cryptroot_b
$ sudo mkfs.ext4 -j /dev/mapper/cryptroot_b
$ sudo mount /dev/mapper/cryptroot_b /mnt
$ sudo cp test.txt /mnt
$ sudo umount /mnt
$ sudo cryptsetup luksClose cryptroot_b
```

先頭から少しダンプして目で比較してみます。見るからに差があることがわかります。ちょっと安心です。

```text:a.img（暗号化されてないイメージファイル）
$ hexdump -Cv a.img | head -16
00000000  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000010  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000020  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000030  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000040  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000050  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000060  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000070  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000080  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000090  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000000a0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000000b0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000000c0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000000d0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000000e0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000000f0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
```

```text:b.img（暗号化されたイメージファイル）
# hexdump -Cv b.img | head -16
00000000  4c 55 4b 53 ba be 00 01  61 65 73 00 00 00 00 00  |LUKS....aes.....|
00000010  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000020  00 00 00 00 00 00 00 00  78 74 73 2d 70 6c 61 69  |........xts-plai|
00000030  6e 36 34 00 00 00 00 00  00 00 00 00 00 00 00 00  |n64.............|
00000040  00 00 00 00 00 00 00 00  73 68 61 31 00 00 00 00  |........sha1....|
00000050  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000060  00 00 00 00 00 00 00 00  00 00 10 00 00 00 00 20  |............... |
00000070  97 51 16 74 15 bc b6 c8  4e a8 d3 32 37 75 f2 a9  |.Q.t....N..27u..|
00000080  f5 11 13 00 84 4e 41 30  2e 94 50 c0 b4 55 20 ef  |.....NA0..P..U .|
00000090  36 80 27 a6 a1 45 24 b8  0f 43 33 97 3c 36 1c ba  |6.'..E$..C3.<6..|
000000a0  17 df 3e cd 00 01 47 a3  65 34 63 30 36 32 65 62  |..>...G.e4c062eb|
000000b0  2d 63 64 35 38 2d 34 39  65 34 2d 39 32 62 36 2d  |-cd58-49e4-92b6-|
000000c0  33 61 31 39 32 30 39 38  61 38 66 38 00 00 00 00  |3a192098a8f8....|
000000d0  00 ac 71 f3 00 05 2c 80  d9 c9 7f b8 92 54 7d ad  |..q...,......T}.|
000000e0  f7 e2 a9 be ab 41 2c 39  0b 77 b1 bf cd 7b 73 c6  |.....A,9.w...{s.|
000000f0  65 e2 4c 0f 1b 94 cf 21  00 00 00 08 00 00 0f a0  |e.L....!........|
```

先ほどコピーしたファイルから文字列abcdを検索してみます。暗号化されていないイメージファイルからは10件検出できましたが、暗号化されたファイルからは検出できませんでした。

```console:文字列abcdの件数
$ strings a.img | grep -c abcd
10
$ strings b.img | grep -c abcd
0
```

### 空きがあるのにNo space left on deviceが出た時は？
ある日、同期してみたところ以下のようなエラーが出て同期できなくなりました。

```console:No_space_left_on_deviceエラー
[2018-07-01 21:27:08,282] DEBUG: Worker-0: Adding watcher for "/media/onedrive"
Exception in thread Worker-0:
Traceback (most recent call last):
  File "/usr/lib/python3.5/threading.py", line 914, in _bootstrap_inner
    self.run()
  File "/home/****/onedrived-dev/onedrived/od_threads.py", line 29, in run
    task.handle()
  File "/home/****/onedrived-dev/onedrived/od_tasks/merge_dir.py", line 168, in handle
    self.repo.context.watcher.add_watch(self.repo, self.local_abspath)
  File "/home/****/onedrived-dev/onedrived/od_watcher.py", line 55, in add_watch
    wd = self.notifier.add_watch(local_abspath, self.FLAGS)
  File "/usr/local/lib/python3.5/dist-packages/inotify_simple/inotify_simple.py", line 87, in add_watch
    return _libc_call(_libc.inotify_add_watch, self.fd, path, mask)
  File "/usr/local/lib/python3.5/dist-packages/inotify_simple/inotify_simple.py", line 52, in _libc_call
    raise OSError(errno, os.strerror(errno))
OSError: [Errno 28] No space left on device
```

dfコマンドで調べてみてもディスクに空きはあります。

```console:ディスクの空き
$ df -h /media/onedrive/
Filesystem                      Size  Used Avail Use% Mounted on
/dev/mapper/cryptroot_onedrive  4.8G  3.1G  1.5G  68% /media/onedrive
```

よく見るとエラーメッセージ中にinotify_add_watchという文字列があるので、試しにinotifyで監視できるファイル数の上限を変更してみます。

```console:inotifyで監視できるファイル数の上限を変更
#現在の上限を確認
$ sysctl fs.inotify.max_user_watches
fs.inotify.max_user_watches = 8192

#条件を倍の16384に変更
$ sudo sysctl fs.inotify.max_user_watches=16384
fs.inotify.max_user_watches = 16384
```

再度実行してみます。…無事同期されました。案外とあっけなく解決してしまいました。とりあえずめでたしめでたし…かな。