Google Drive の共有フォルダを Web サイトとして公開できる DriveToWeb(https://drv.tw)という素晴らしいサービスがあります。
しかしサービスが不安定で、肝心な時にページが見られなくて困ったことが度々ありました。
そこで今回は無料のサービスを組み合わせて、DriveToWeb と同様の Google Drive をホスティングするサーバーを作ってみます。
- ホスティングサーバー:Compute Engine(GCE)
- ストレージ:Google Drive
- ドメイン:DDNS Now
目指す環境は DriveToWeb と同様に静的ファイル(HTML、CSS、JS、画像など)を表示できれば良く、PHP などは動かせなくて良い事とします。
ホスティングサーバーの準備
Google Drive をホスティングするなら、同じ Google のサービスで揃えた方が気分が良いという安直な理由で Google Cloud(GCP)の Compute Engine(GCE)を選定しました。
ただし、GCE は転送量が 1GB/月 を超えると課金が発生するため、アクセス数の多いサイトや画像・動画がメインのサイトは、転送量制限の無い VPS などを使った方が無難だと思います。
Compute Engine の VM インスタンスを作成
GCP を無料枠で運用するためは、様々なポイントがあります。解説サイトを見ながら慎重に設定してください。
今回は下記の設定で VM インスタンスを作成しました。
- リージョン:us-west1 (オレゴン)
- マシンタイプ:e2-micro
- OS:Debian
- バージョン:Debian GNU/Linux 12 (bookworm)
- ブートディスクの種類:標準永続ディスク(30GB)
- ファイアウォール:HTTP トラフィックを許可する、HTTPS トラフィックを許可する
- 外部 IP:エフェメラル(初期設定)
予算アラートの作成
「お支払い」>「予算とアラート」から「予算を作成」し、想定外の課金があっても気づけるようにしておきましょう。
- 名前:無料利用枠
- 予算タイプ:指定額
- 目標金額:100円
今回は上記内容で設定しました。
初期設定
Compute Engine に戻り、VM インスタンスに SSH 接続して初期設定を行っていきます。
システムの更新
まずはシステムを最新にアップデートしましょう。apt と apt-get のどちらが良いのか作法が分かりませんが、apt-get でやっていくことにします。
1 2 | $ sudo apt-get update $ sudo apt-get upgrade |
※ 日々のアップデートは unattended-upgrades によって自動的に行われます。
スワップファイルの作成
今回作成した e2-micro はメモリが 1GB と少ないので、スワップファイルを作成しておくと良いでしょう。
1 2 3 4 5 6 | $ sudo mkdir /var/spool/swap $ sudo dd if=/dev/zero of=/var/spool/swap/swapfile bs=1M count=1024 $ sudo chmod 600 /var/spool/swap/swapfile $ sudo mkswap /var/spool/swap/swapfile $ sudo swapon /var/spool/swap/swapfile $ sudo vi /etc/fstab |
fstab を編集して、サーバー起動時に読み込むようにします。
1 2 | # 最終行に追加 /var/spool/swap/swapfile none swap sw 0 0 |
ファイアウォールの設定
GCE の無料枠に 1GB までの下りのネットワークが含まれていますが、中国とオーストラリアは例外で課金対象となります。SSH の総当たり攻撃を受けて課金が発生した例もあるようなので、ファイアウォールでブロックしておきましょう。
今回のファイアウォールの方針
- SSH(22)に関しては、GCE の管理画面から起動できるブラウザ版が使いやすかったので、思い切って SSH は管理画面からのアクセス(IAP)のみに制限します。
- Google Drive を Web として公開したい相手は日本国内のみで良いので、HTTP(80)と HTTPS(443)のアクセスは国内のみに制限します。
- その他のアクセスは全てブロックします。
nftables の設定
ファイアウォールには nftables を使います。
1 | $ sudo apt-get install nftables |
下記のサイトを参考に、日本国内の IPv4 アドレスを収集するスクリプトを作成しました。
1 | $ vi nftables_domestic.sh |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | #!/usr/bin/bash LIST=/etc/nftables.domestic wget http://nami.jp/ipv4bycc/cidr.txt.gz gunzip cidr.txt.gz echo "define domestic = {" > ${LIST} for COUNTRY in 'JP' do sed -n "s/^JP\t//p" cidr.txt | while read ADDRESS do echo " ${ADDRESS}," >> ${LIST} done done echo "}" >> ${LIST} rm cidr.txt |
スクリプトを実行して、日本国内の IPv4 アドレスのリストを作成します。
1 2 | $ chmod +x ~/nftables_domestic.sh $ sudo sh ~/nftables_domestic.sh |
HTTP(S) へのアクセスは、このリストに記載された IP アドレスに制限します。
また、SSH へのアクセスを IAP(Cloud Identity-Aware Proxy)に限定するためには「35.235.240.0/20」の範囲を許可すれば良さそうです。
nftables.conf を編集して、上記のリストを読み込みます。
1 | $ sudo vi /etc/nftables.conf |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | #!/usr/sbin/nft -f # 現在のルールセットを消去 flush ruleset # 日本国内の IP アドレスリストを読み込む include "/etc/nftables.domestic" table inet filter { set iap { type ipv4_addr flags interval # IAP の IP アドレス範囲 elements = { 35.235.240.0/20 } } set domestic { type ipv4_addr flags interval elements = $domestic } chain input { type filter hook input priority 0; policy drop; # Ping floods ip protocol icmp icmp type echo-request limit rate 10/second accept # 関連・確立済みトラフィックは許可する ct state established,related accept # 不正なトラフィックは全て破棄する ct state invalid drop # ループバックインターフェイスのトラフィックは全て許可する iif lo accept # 新しいエコー要求 (ping) は許可する ip protocol icmp icmp type echo-request ct state new accept ip protocol tcp tcp flags & (fin | syn | rst | ack) == syn ct state new jump TCP # 他のルールによって処理されなかったトラフィックは全て拒否する ip protocol tcp reject with tcp reset } chain forward { type filter hook forward priority 0; policy drop; } chain output { type filter hook output priority 0; policy accept; } chain TCP { # SSH は IAP のみ許可 tcp dport 22 ip saddr @iap accept # HTTP(S) は日本国内のみ許可 tcp dport { 80, 443 } ip saddr @domestic accept } } |
nftables を起動して、ファイアウォールを有効にします。
1 2 | $ sudo systemctl start nftables.service $ sudo systemctl enable nftables.service |
Google Drive のマウント
最初は Rclone を使って Google Drive とファイルを同期する方法 を考えていたのですが、Google Drive 上のフォルダを直接マウントする事ができる google-drive-ocamlfuse というものを見つけたので、今回はそれを使っていきます。
google-drive-ocamlfuse のインストール
まず最初に、google-drive-ocamlfuse をインストールするために必要な opam(OCaml Package Manager)と、関連パッケージをインストールしていきます。
1 | $ sudo apt-get install opam fuse libfuse-dev libcurl4-gnutls-dev libgmp-dev libsqlite3-dev pkg-config zlib1g-dev |
opam の初期化とアップデートをした後、google-drive-ocamlfuse をインストールします。
1 2 3 4 5 6 7 8 9 10 11 | $ opam init Do you want opam to modify ~/.profile? [N/y/f] (default is 'no', use 'f' to choose a different file) N $ opam update $ opam install google-drive-ocamlfuse Do you want to continue? [Y/n] y ∗ installed google-drive-ocamlfuse.0.7.32 Done. # Run eval $(opam env) to update the current shell environment |
1 時間近くかかりましたが、google-drive-ocamlfuse 0.7.32 をインストールできました。
google-drive-ocamlfuse を簡単に実行できるように「~/.opam/default/bin」へのパスを通しておきましょう。
1 | $ vi ~/.bashrc |
1 2 3 4 5 6 7 8 9 10 | # ~/.bashrc: executed by bash(1) for non-login shells. # see /usr/share/doc/bash/examples/startup-files (in the package bash-doc) # for examples PATH="$PATH:$HOME/.opam/default/bin" # If not running interactively, don't do anything case $- in *i*) ;; *) return;; esac |
1 2 | $ exit logout |
一度ログアウトし、再び SSH にログインすると反映されます。
google-drive-ocamlfuse と Google Drive の初回連携
今回の手順の中で一番ややこしい部分になります。
有効な API とサービス
- Google Cloud(GCP)の「API とサービス」を開きます。
- API とサービスのメニューにある「ライブラリ」を開きます。
- API ライブラリが表示されるので、「Google Drive API」を「有効化」します。
OAuth 同意画面
次に「OAuth 同意画面」を開き、User Type を「外部」で「作成」します。
- OAuth 同意画面
- アプリ名:(何でも良いので入力)
- ユーザー サポートメール:Google アカウントのメールアドレス
- デベロッパーの連絡先情報:(同上)
- その他の項目:(全て空欄)
- スコープ
- 「スコープを追加または削除」をクリック
- フィルタに「Google Drive API」と入力して絞り込む(17 件の項目が表示される)
- 「…/auth/drive.metadata」、「…/auth/drive.metadata.readonly」と「.../auth/drive.readonly」の 3 つのスコープにチェックを入れて「更新」
- 「制限付きのスコープ」に上記 3 つが追加されているのを確認したら「保存して次へ」
- テストユーザー
「+ ADD USERS」をクリックし、Google アカウントのメールアドレスを入力し「保存して次へ」- ユーザーを追加する必要はないので、そのまま「保存して次へ」
- 概要
- 確認して「ダッシュボードへ戻る」
OAuth 同意画面のダッシュボードから「アプリを公開」します。
公開ステータスが「本番環境」になっていれば OAuth 同意画面の設定は完了です。
認証情報
続いて「認証情報」へ移り、OAuth 2.0 クライアント ID を作成します。
- 「認証情報」を開き、「+ 認証情報を作成」をクリックします。
- 「OAuth クライアント ID」をクリック
- OAuth クライアント ID の作成の画面で下記を入力して「作成」
- アプリケーションの種類:ウェブ アプリケーション
- 名前:(何でも良いので入力)
- 承認済みのリダイレクト URI:http://127.0.0.1:8080/oauth2callback
- 「OAuth クライアントを作成しました」という画面になるので、開いたままにしておきます。
※閉じてしまった場合でも「Oauth クライアントをダウンロード」ボタンから再度表示できます。
google-drive-ocamlfuse と Google Drive API の連携設定
一旦 API とサービスから離れて、ブラウザでの SSH に戻ります。
作成した OAuth クライアントの「クライアント ID」と「クライアント シークレット」を使って、google-drive-ocamlfuse のヘッドレス認証を行います。
1 | $ google-drive-ocamlfuse -headless -id <クライアント ID> -secret <クライアント シークレット> |
すると、認証用の URL が表示されるので、アドレス(https://accounts.google.com 以降行末まで)をコピーしてブラウザで開きます。
1 2 | Please, open the following URL in a web browser: https://accounts.google.com/o/oauth2/auth?client_id=<クライアント ID>&redirect_uri=http%3A%2F%2F127.0.0.1%3A8080%2Foauth2callback&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive&response_type=code&access_type=offline&approval_prompt=force Please enter the verification code: |
ブラウザで Google アカウントにログインすると、「このアプリは Google で確認されていません」という画面になりますが「詳細」をクリックします。
OAuth 同意画面の「確認ステータス」で確認を受けていないため警告が表示されていますが、「アプリ名(安全ではないページ)に移動」をクリックします。
すると「アプリが Google アカウントへのアクセスを求めています」という画面になるので「続行」します。
次の画面で「サイトにアクセスできません」と表示されますが、これは正常な挙動です。
アドレスバーの「code=」と「&scope」に挟まれた部分の確認コードをコピーします。
1 | http://127.0.0.1:8080/oauth2callback?code=(確認コード)&scope=https://www.googleapis.com/auth/drive |
ブラウザでの SSH に戻り、確認コードを貼り付けます。
1 2 | Please enter the verification code: (確認コード) Access token retrieved correctly. |
Access token retrieved correctly. と表示されたら無事完了です。
マウントの確認
無事に連携はできたので、マウントしたドライブが見えるか確認しておきましょう。
まずはマウントするディレクトリを作成します。
1 2 3 | $ mkdir ~/GoogleDrive $ ls ~/GoogleDrive $ (空行) |
Google Drive をマウントして、一覧を表示します。
1 2 3 | $ google-drive-ocamlfuse ~/GoogleDrive $ ls ~/GoogleDrive $ (Google Drive のマイドライブのファイル・フォルダ) |
Google Drive をアンマウントします。
1 | $ fusermount -u ~/GoogleDrive |
公開フォルダを指定する
このままでは、Google Drive 内のすべてのファイルが見えてしまいます。
DriveToWeb では、Google Drive のルートディレクトリ(マイドライブ)の「public_html」フォルダを公開していましたので、同じように設定します。
Google Drive の公開したいフォルダ(public_html)を開きます。URL の最後のスラッシュ以降「https://drive.google.com/drive/folders/(フォルダ ID)」をコピーします。
google-drive-ocamlfuse の設定ファイルに、ルートフォルダを公開フォルダに設定し、読み取り専用にしておきます。
1 | $ vi ~/.gdfuse/default/config |
1 2 3 4 5 6 7 | read_only=false ↓ read_only=true root_folder= ↓ root_folder=(フォルダ ID) |
最初に接続した時のキャッシュを削除してから、再び一覧を取得します。
1 2 3 4 5 | $ rm ~/.gdfuse/default/cache/cache.db $ google-drive-ocamlfuse ~/GoogleDrive $ ls ~/GoogleDrive (公開フォルダのファイル一覧) $ fusermount -u ~/GoogleDrive |
FUSE のオプションを修正
デフォルトでは google-drive-ocamlfuse を実行したユーザーでないと、Google Drive をマウントしたディレクトリを参照することができません。
このままでは Nginx から見られませんので、他のユーザーからでも参照できるよう FUSE の設定ファイルを修正します。
1 | $ sudo vi /etc/fuse.conf |
「user_allow_other」を有効に(コメント解除)して保存します。
1 2 3 | #user_allow_other ↓ (コメントを解除する) user_allow_other |
google-drive-ocamlfuse のサービス化
サーバー起動時に、自動的に Google Drive をマウントするために、google-drive-ocamlfuse をサービス化します。
/etc/systemd/system/ に google-drive-ocamlfuse.service を新規作成します。
1 | $ sudo vi /etc/systemd/system/google-drive-ocamlfuse.service |
google-drive-ocamlfuse のコマンドは絶対パスで指定します。また、起動オプションに「-o allow_other」を追加して、マウントしたドライブを他のユーザーから参照できるようにします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | [Unit] Description=FUSE filesystem over Google Drive After=network.target [Service] User=ユーザー名 Group=ユーザー名 ExecStart=/home/ユーザー名/.opam/default/bin/google-drive-ocamlfuse /home/ユーザー名/GoogleDrive -o allow_other ExecStop=fusermount -u /home/ユーザー名/GoogleDrive Restart=always Type=forking [Install] WantedBy=multi-user.target |
google-drive-ocamlfuse.service をオン・オフして、意図した通りの挙動になる事を確認します。
1 2 3 4 5 6 | $ sudo systemctl start google-drive-ocamlfuse.service $ ls ~/GoogleDrive (公開フォルダのファイル一覧) $ sudo systemctl stop google-drive-ocamlfuse.service $ ls ~/GoogleDrive (空行) |
サーバーの起動時に、サービスを自動起動します。
1 2 | $ sudo systemctl enable google-drive-ocamlfuse.service Created symlink /etc/systemd/system/multi-user.target.wants/google-drive-ocamlfuse.service → /etc/systemd/system/google-drive-ocamlfuse.service. |
これでサーバー起動時に、Google Drive の公開フォルダを自動的にマウントするようにできました。
ドメインの設定
続いて、サーバーにアクセスするためのドメインを発行してくれるサービスを選定します。
一昔前までは Freenom で無料ドメイン(.tk など)が取得できたようですが、現在は状況が変わってできなくなりました。
今回はサブドメインを割り当ててくれるサービスの DDNS Now をチョイスしました。
GCE はサーバーを起動する度にグローバル IP アドレスが変更されるので、DDNS サービスとは非常に相性が良いです。
登録を済ませたら、ユーザ名(サブドメイン名)と API 用トークンを確認しておきましょう。
DDNS の自動更新
GCE の外部 IP がエフェメラルの場合、サーバーを起動する度に IP アドレスが変わります。そのため DDNS に登録された IP アドレスも更新する必要があります。DDNS Now では API が用意されており、とても簡単に更新することができます。
DDNS Now のマニュアルを参考に、更新用スクリプトを作成します。
1 | $ vi ~/DDNSNowRenew.sh |
1 2 | #!/bin/bash wget -O /var/log/DDNSNow_update.log "https://f5.si/update.php?domain=サブドメイン名&password=API用トークン" |
1 | $ chmod +x ~/DDNSNowRenew.sh |
次に、更新用スクリプトをサービスとして登録します。
1 | $ sudo vi /etc/systemd/system/ddnsnow-renew.service |
1 2 3 4 5 6 7 8 9 10 | [Unit] Description=/home/ユーザー名/DDNSNowRenew.sh [Service] ExecStart=/home/ユーザー名/DDNSNowRenew.sh Restart=no Type=simple [Install] WantedBy=multi-user.target |
1 2 | $ sudo systemctl start ddnsnow-renew.service $ sudo systemctl enable ddnsnow-renew.service |
サーバーの起動時に、自動的にサービスを起動することで DDNS が更新されるようになりました。
SSL 証明書の取得
Certbot で DNS-01 チャレンジ認証を行うための API も用意されているので、サイトの SSL 化も簡単にできます。
特に、今回はファイアウォールで日本国外からの HTTP アクセスを弾いているので HTTP-01 チャレンジは失敗しますが、DNS-01 チャレンジに対応しているおかげで証明書を取得することができます。
マニュアルの通りにコマンドを実行すれば、難なく証明書を取得することができました。
1 | $ sudo apt-get install certbot |
1 2 3 | $ wget -O DDNSNowAcmeSetup.sh "https://ddns.kuku.lu/files/DDNSNowAcmeSetup.txt" $ sh DDNSNowAcmeSetup.sh サブドメイン名 APIトークン $ sudo certbot certonly --server https://acme-v02.api.letsencrypt.org/directory --manual --preferred-challenges dns --manual-auth-hook ~/.ddnsnow/DDNSNowAcmeAuthHook.sh -d サブドメイン名.f5.si --agree-tos -m メールアドレス |
DDNS Now はユーザー目線の工夫が凝らされていて、とても素晴らしい!
Nginx でサイトを公開する
最後にサイトを公開するための Nginx をインストールしていきます。
1 | $ sudo apt-get install nginx |
Nginx の設定ファイルを、ドメイン名で作成します。
1 | $ sudo vi /etc/nginx/sites-enabled/サブドメイン名.f5.si |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | server { listen 80; listen 443 ssl http2; server_name サブドメイン名.f5.si; root /home/ユーザー名/GoogleDrive; index index.html; ssl_protocols TLSv1.2 TLSv1.3; ssl_certificate /etc/letsencrypt/live/サブドメイン名.f5.si/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/サブドメイン名.f5.si/privkey.pem; server_tokens off; add_header X-Frame-Options SAMEORIGIN; add_header X-Content-Type-Options nosniff; add_header X-XSS-Protection "1; mode=block"; add_header Strict-Transport-Security "max-age=15768000; includeSubdomains"; } |
Nginx を起動します。
1 2 | $ sudo systemctl start nginx.service $ sudo systemctl enable nginx.service |
SSL 証明書の自動更新
SSL 証明書は、certbot.timer によって自動的に更新されます。更新された際に、renewal-hook(deploy)で Nginx を再起動して反映するようにします。
1 2 3 | $ sudo touch /etc/letsencrypt/renewal-hooks/deploy/nginx $ sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/nginx $ sudo vi /etc/letsencrypt/renewal-hooks/deploy/nginx |
1 2 | #!/bin/bash systemctl restart nginx.service |
動作確認
以上で全ての設定が完了しました。動作を確認するために、一度サーバーシャットダウンします。
1 2 3 | $ sudo shutdown -h now Broadcast message from root@xxxxxxxxxx on pts/1: The system will power off now! |
- VM インスタンスを「開始 / 再開」して、外部 IP が変更されたことを確認します。
- DDNS Now にログインし、「Aレコード」が変更後の IPv4 アドレスになっているか確認します。
- ブラウザから https://サブドメイン名.f5.si を閲覧して Google Drive の内容が表示されるか確認します。
まとめ
転送量制限(1GB/月)や日本国内のみのアクセス制限という制約がありますが、Google Drive のホスティングサーバーを無料で構築できました。
DriveToWeb と併用することもできるので、DriveToWeb が繋がらないときだけ今回のサーバーを見てもらうなど、バックアップ的な使い方もできます。
コメント