サーバのバックアップファイルを Google ドライブに退避させる


TL;DR
---
* gdrive を使って Google ドライブにアクセスする
* `bkup_gdrive.sh FILE [FOLDER]` で圧縮＋暗号化＋アップロードする


環境
---
* CentOS 6.6, 6.7
* gdrive 1.9.0 [GitHub](https://github.com/prasmussen/gdrive)

gdrive コマンドの導入
---
CUI で Google ドライブにアクセスできる gdrive というツールを使う。
公式サイトを参考にコマンドを導入して Google の OAth2 認証をするだけ。
今回は Linux x64 だが、Windows, Mac 向けのバイナリも提供されている。

``` bash
$ wget -O drive https://drive.google.com/uc?id=0B3X9GlR6Embnb095MGxEYmJhY2c  
$ mv drive /usr/sbin/drive  
$ chmod 755 /usr/sbin/drive
```

`drive` コマンド初回実行時に認証用の URL が出力され、認証キーの入力が求められる。
別途ブラウザから出力された URL にアクセスして認証コード取得してコピペしてあげればいい。
※下記の認証用 URL はダミー

``` bash
$ drive
Go to the following link in your browser:  
https://accounts.google.com/o/oauth2/auth?client_id=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&response_type=code&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive&state=state

Enter verification code: (認証コード入力)
```

認証成功するとドバーッと gdrive の help が出力される。


アップロードする
---

細かいオプション付けずにファイル単体をアップロードしてみる。
※下記ハッシュ等の一意的な情報はダミー

``` bash
$ echo hello > test.txt
$ drive upload --file test.txt

Id: vjBwDRvBT4MzCAD97cPhMJZg8Gpk
Title: test.txt
Size: 6.0 B
Created: 2016-01-28 10:54:29
Modified: 2016-01-28 10:54:29
Owner: hoge
Md5sum: 1zbEOm0DC864l6Eu7BSuYPOo4SesXuFN
Shared: False
Parents: oqD9gj6ERKhII58ysa8
MIME Type: text/plain; charset=utf-8
Uploaded 'test.txt' at 3.0 B/s, total 6.0 B
```

Google ドライブ側を確認してみると、期待通りアップロードされていた。
共有もデフォルトで「なし」になっている。

![スクリーンショット_012816_110644_AM.jpg](https://qiita-image-store.s3.amazonaws.com/0/48194/0b58542e-69e1-44b0-cfdf-7be76c48bd7b.jpeg)

ディレクトリもアップロードしてみたが、これも元の階層のまま期待通り行われた。

``` bash
$ mkdir -p a/b/c
$ echo hello > a/test_a.txt
$ echo hello > a/b/test_b.txt
$ echo hello > a/b/c/test_c.txt

$ drive upload --file a
...
ファイル毎にアップロード結果が出力される
...
```

Google ドライブ上も階層通り。

![スクリーンショット_012816_111754_AM.jpg](https://qiita-image-store.s3.amazonaws.com/0/48194/1a9ac638-ab6f-5ce7-fdcb-12ad51997214.jpeg)

更に `--parent` にフォルダの Id を指定すれば、そのフォルダ以下にアップロードすることもできた。

``` bash
$ drive upload --parent 0B4bqtchTaaHaherscOVZwZ21KV0E --file hoge.txt
```

サーバ上で生成したバックアップファイルをアップロードしたところ、実施した環境では 17 GB のファイルを 30 分程で完了した。
8.9 MB/s 出ていたので思ってたより速い。

### 版管理はできない

Google ドライブではファイルの変更履歴を持てるが、`drive` コマンドは対応していないようだ。

全く変更していないファイルを立て続けに `upload` すると、同じ名前の別ファイルとして Google ドライブ上に保存されてしまった。

![スクリーンショット_012816_011949_PM.jpg](https://qiita-image-store.s3.amazonaws.com/0/48194/c73cf5dd-ffd5-08ed-2357-2062b6d07e2e.jpeg)

試しに `--parent` オプションに更新したいファイルの Id を指定してみたら、そもそもアップロードすら正常に行えなかった。

ファイルを検索する
---
`drive list` コマンドの `--query` オプションは下記のクエリが使えるとのこと。
[Search for Files - Google Developers](https://developers.google.com/drive/search-parameters)

テキストファイルを検索してみる。

``` bash
$ drive list --query "mimeType='text/plain'"
Id                             Title        Size    Created               
r49doc17uLXwBYVFWDb9skaV5PAm   test_a.txt   6.0 B   2016-01-28 11:12:57   
k0SEmtZe8upP1OD8JKKNO6Jc9s4r   test_b.txt   6.0 B   2016-01-28 11:12:54   
93tlLHdoxM7ThRbc5SMVkfvvgyRq   test_c.txt   6.0 B   2016-01-28 11:12:51   
2hy4Sd6whw3iGpxKEAlhCnu9VIET   test.txt     6.0 B   2016-01-28 10:54:29
```

フォルダを検索してみる。

``` bash
$ drive list --query "mimeType='application/vnd.google-apps.folder'"
Id                             Title         Size    Created               
0B4bqtchTaaHzX3dlanBJaDlJd00   c             0.0 B   2016-01-28 11:12:49   
0B4bqtchTaaHzRmhna3BiUnpPNm8   b             0.0 B   2016-01-28 11:12:48   
0B4bqtchTaaHzMUVOOVZwZ21KV0E   a             0.0 B   2016-01-28 11:12:48
```

日付で絞った結果が欲しかったのだが、`modifiedTime` を使ったクエリを送ると `googleapi: Error 400: Invalid query, invalid` と怒られてしまった。
日付の書式や比較演算子を変えてみたものの上手くいかず。



ダウンロードする
---
`drive list` で Id を確認して `drive download` する。

``` bash
$ drive list
Id                             Title                                      Size       Created               
nuNO6OlKSHmRoaluoC1Ypjge8O9y   test.txt                                   15.0 B     2016-01-28 10:54:24

$ drive download -i nuNO6OlKSHmRoaluoC1Ypjge8O9y
Downloaded 'test.txt' at 15.0 B/s, total 15.0 B
```

フォルダのダウンロードはサポートされていないようだ。


暗号化する
---

アップロード先の Google さんは相変わらず利用規約がジャイアニズム。

> [Google 利用規約 – ポリシーと規約 – Google](http://www.google.com/policies/terms/)
> 本サービスにユーザーがコンテンツをアップロード、提供、保存、送信、または受信すると、ユーザーは **Google（および Google と協働する第三者）に対して**、そのコンテンツについて、使用、ホスト、保存、複製、変更、派生物の作成（たとえば、Google が行う翻訳、変換、または、ユーザーのコンテンツが本サービスにおいてよりよく機能するような変更により生じる派生物などの作成）、（公衆）送信、出版、公演、上映、（公開）表示、および配布を行うための**全世界的なライセンスを付与することになります。**

なので、暗号化してからアップロードする。

``` bash
### 暗号化
$ openssl aes-256-cbc -e -in 暗号化対象ファイル -out 出力ファイル -pass pass:パスワード文字

### 復号化
$ openssl aes-256-cbc -d -in 復号化対象ファイル -out 出力ファイル -pass pass:パスワード文字
```

暗号化のアルゴリズムやパスワードの与え方はお好みで。
パスワードを標準入力したいならば、`-pass pass:...` を省けば OK。

> [man OpenSSL](https://www.openssl.org/)


バックアップ用スクリプト
---

バックアップは自動化させるので、指定したバックアップ対象を圧縮＋暗号化＋アップロードさせるスクリプトを作成した。

暗号化パスワードはスクリプト内 `PASSWORD` に設定。
古いバックアップファイルは Google ドライブ上から削除する。
ファイル作成日からの経過日数が `KEEP_DAYS` を超えたものが削除対象。

### 使い方

`backup_gdive.sh TARGET [FOLDER_NAME]`

* TARGET: 
  バックアップ対象のファイルかディレクトリ。
* FOLDER_NAME
  Google ドライブ上のバックアップ先フォルダ名。
  指定しなければ TARGET の名前を使う。

### スクリプト
https://gist.github.com/aviscaerulea/b9aa37f4231b4511bcbc

``` bash:bkup_gdrive.sh
#!/bin/bash -eu

readonly TARGET=$1
readonly TARGET_BASENAME=$(basename $TARGET)
readonly GDRIVE_DIR=${2:-$TARGET_BASENAME}
readonly PASSWORD=pass
readonly KEEP_DAYS=10

if [ ! -e $TARGET ]
then
    echo "File not found: $TARGET" >&2
    exit 1
fi

renice -n 10 -p $$ > /dev/null

### Find/Create Backup folder
GDRIVE_DIR_ID=$(drive list --noheader --query "mimeType='application/vnd.google-apps.folder' and title='$GDRIVE_DIR'" | awk '{print $1}')

if [ -z $GDRIVE_DIR_ID ]
then
    GDRIVE_DIR_ID=$(drive folder -t "$GDRIVE_DIR" | awk '/^Id:/{print $NF}')
    if [ -z $GDRIVE_DIR_ID ]
    then
        echo "Folder not created: $GDRIVE_DIR" >&2
        exit 1
    fi
fi


### Upload Encypted File
tar zcf - -C $(dirname $TARGET) $TARGET_BASENAME | openssl aes-256-cbc -e -pass pass:$PASSWORD | drive upload --stdin --parent $GDRIVE_DIR_ID --title ${TARGET_BASENAME}_`date +%Y%m%d`.tar.gz.bkup


### House Keeping
LIMIT_TIMESTAMP=$(date -d "$KEEP_DAYS days ago" +%s)
drive list --noheader --query "'$GDRIVE_DIR_ID' in parents" | while read ln
do
    ITR_ID=$(echo $ln | awk '{print $1}')
    ITR_DATE=$(echo $ln | awk '{print $(NF-1),$NF}')

    if [ $(date -d "$ITR_DATE" +%s) -lt $LIMIT_TIMESTAMP ]
    then
        drive delete --id $ITR_ID
    fi
done
```

処理内容は以下の通り。

* プロセス優先度は低めの 10 に変更
* アップロードする Google ドライブのフォルダを検索（無ければ作る）
* バックアップ対象を 圧縮 → 暗号化 → アップロード
    * 中間ファイルは作らない代わりにメモリを消費すると思われる
* アップロード先フォルダ内のファイル日付をゴリゴリ比較して、保持期間を過ぎてたら削除

gdrive で日時を条件に絞れれば、もう少しシンプルになるのですが。
あと、既に別途 tar.gz 圧縮済みのファイルが対象だと更に圧縮しちゃって微妙なので、追々直したい。

ひとまず個人的な要件は満たしていますが、変なところがあればご指摘ください。
bash 前提で POSIX 準拠はしていません。


参考
---
* [Backing up a Directory to Google Drive on CentOS 7](http://timothy-quinn.com/backing-up-linux-server-files-to-google-drive/)
* [Bash script to backup data to google drive](http://stackoverflow.com/questions/33063673/bash-script-to-backup-data-to-google-drive)