WordPress on Lightsail をシェルスクリプト一撃で SSL 化 (証明書自動更新、HTTP からのリダイレクト付き)

# はじめに

Amazon Lightsail を使うと WordPress を簡単に構築できます。
しかし、SSL 化しようとすると以下のような設定が必要で少し面倒です。

* Let's Encrypt で証明書を作成して配置[^1]
[^1]: ロードバランサを使って SSL 化することもできますが、Lightsail のロードバランサは 18 USD / 月と少し高額です。
* 証明書は定期的に自動更新
* HTTP から HTTPS へのリダイレクト
* 内部リンクの http://~ から https://~ への置き換え

そこで、シェルスクリプト一撃 (+ ちょっと) でここまでセットアップできるようにしました。

※ Let's Encrypt には証明書の発行に制限数があります。ご注意ください (参考: [Rate Limits](https://letsencrypt.org/docs/rate-limits/))
※ パッケージのロックファイルによるエラーの対応として、ロックファイルを無理やり削除しています。ご注意ください

# WordPress 構築 & SSL 設定手順

## インスタンス作成

Lightsail で WordPress を構築するのは非常に簡単です。
プラットフォームで「Linux / Unix」を選択し、設計図で「WordPress」を選択するだけです。
インスタンス名を適当に入力してしばらく待てば、WordPress インストール済みのインスタンスが起動します。

## DNS 設定

インスタンスが起動したら、「ネットワーキング」の設定から静的 IP を付与します。
付与された IP に対して、DNS の A レコードを設定します。[^2]
[^2]: DNS 設定前にスクリプトを実行すると、Let's Encrypt による DNS の検証でエラーが発生します。

## SSL 化スクリプト実行

シェルスクリプト実行のため、Lightsail のインスタンスに SSH 接続します。

```
$ ssh -i xxx.pem bitnami@xxx.xxx.xxx.xxx
```

curl で [Gist](https://gist.github.com/os1ma/ac6f8e39ec8d01ee01b79aacf2e07f39) からシェルスクリプトを取得して実行します。

```bash
$ curl -LO https://gist.githubusercontent.com/os1ma/ac6f8e39ec8d01ee01b79aacf2e07f39/raw/f9824aeda4208d15f7380b25bf276e2b5e60295a/setup_lightsail_wordpress_letsencrypt.sh
$ chmod +x setup_lightsail_wordpress_letsencrypt.sh
$ ./setup_lightsail_wordpress_letsencrypt.sh www.example.com mail@example.com
```

※ 3 つ目のコマンドのドメインとメールアドレスは適切に書き換えてください

## WordPress の管理画面で SSL 有効化

以下のコマンドで WordPress 管理画面のパスワードを確認します。

```bash
$ cat "${HOME}/bitnami_application_password"
```

https://<設定したドメイン>/wp-login.php から WordPress の管理画面にログインします。

※ デフォルトのユーザ名は user です。

ダッシュボードの「Go ahead, activate SSL」ボタンをクリックすれば、SSL 化完了です。

## 最後に忘れずに

サーバ上で以下のコマンドを実行し、wp-config.php を編集できないようにします。

```bash
chmod g-w "${HOME}/apps/wordpress/htdocs/wp-config.php"
```

Lightsail の管理画面からファイアウォールの設定を変更して、SSH を接続不可にすることも忘れずに。

# スクリプトの内容

スクリプトの主な処理を説明します。
全文は記事の最後または [Gist](https://gist.github.com/os1ma/ac6f8e39ec8d01ee01b79aacf2e07f39) を参照してください。

※ スクリプトの書き方は、[Shell Style Guide - Google](https://google.github.io/styleguide/shell.xml) を参考にしています。

## パッケージロックファイルを削除

私が試した限りでは、Let's Encrypt をインストールして証明書を発行する際に apt と dpkg 関係のロックファイルが原因でエラーが発生しました。
どれだけ待ってもロックファイルが消えず、`ps aux | grep apt` などで引っかかるプロセスなどもなかったため、諦めてロックファイルは削除することにしています。

```bash
#
# パッケージロックファイルを削除
#
delete_package_lock_files() {
  local package_lock_files=(
    '/var/lib/apt/lists/lock'
    '/var/cache/apt/archives/lock'
    '/var/lib/dpkg/lock'
    '/var/lib/dpkg/lock-frontend'
  )

  for lock_file in "${package_lock_files[@]}"; do
    if [[ -e "${lock_file}" ]]; then
      sudo rm "${lock_file}"
    fi
  done
  sudo dpkg --configure -a
}
```

参考
[Lightsail Launch Script vs. DPKG Lock](https://community.bitnami.com/t/lightsail-launch-script-vs-dpkg-lock/63885)
[E: Could not get lock /var/lib/dpkg/lock-frontend - open (11: Resource temporarily unavailable) [duplicate]](https://askubuntu.com/questions/1109982/e-could-not-get-lock-var-lib-dpkg-lock-frontend-open-11-resource-temporari)

## Let's Encrypt で SSL 証明書をセットアップ

証明書のセットアップとしては、関数内で以下の手順を実行しています。

* Let's Encrypt のクライアントインストール
* SSL 証明書の発行
* 証明書を Apache の設定ディレクトリに配置
* Apache を再起動

```bash
#
# Let's Encrypt で SSL 証明書をセットアップ
#
setup_cert_with_letsencrypt() {
  local letsencrypt_home="${SCRIPT_DIR}/letsencrypt"

  if [[ ! -e "${letsencrypt_home}" ]]; then
    git clone https://github.com/letsencrypt/letsencrypt
  fi

  "${letsencrypt_home}/letsencrypt-auto" certonly \
    --webroot \
    -w /opt/bitnami/apps/wordpress/htdocs/ \
    -d "${DOMAIN_NAME}" \
    -m "${EMAIL}" \
    --agree-tos \
    --non-interactive
  
  sudo cp "/etc/letsencrypt/live/${DOMAIN_NAME}/fullchain.pem" /opt/bitnami/apache2/conf/server.crt
  sudo cp "/etc/letsencrypt/live/${DOMAIN_NAME}/privkey.pem" /opt/bitnami/apache2/conf/server.key

  sudo /opt/bitnami/ctlscript.sh restart apache
}
```

参考
[Let's Encrypt 総合ポータル - コマンド解説（コマンドリファレンス）](https://free-ssl.jp/command/)
[LightsailでWordPress（SSL化もする）](https://qiita.com/foursue/items/8e685b52cfddf05875ee)

## 証明書更新の cron 設定

証明書の更新スクリプトの作成と、そのスクリプトの cron への登録を実施しています。

```bash
#
# 証明書更新用スクリプトを作成し、cron で毎週日曜日の朝4時に実行されるよう設定
#
set_cert_update_cron() {
  local cert_update_script_dir="${HOME}/bin"
  local cert_update_script="${cert_update_script_dir}/update_cert.sh"

  mkdir -p "${cert_update_script_dir}"

  cat << EOT > "${cert_update_script}"
#!/bin/bash

# --force-renewal をつけていないため、期限まで30日以内の場合に更新される
sudo /home/bitnami/letsencrypt/certbot-auto renew
sudo cp /etc/letsencrypt/live/${DOMAIN_NAME}/fullchain.pem /opt/bitnami/apache2/conf/server.crt
sudo cp /etc/letsencrypt/live/${DOMAIN_NAME}/privkey.pem /opt/bitnami/apache2/conf/server.key
sudo /opt/bitnami/ctlscript.sh restart apache
EOT

  chmod +x "${cert_update_script}"
  echo "0 4 * * 0 ${cert_update_script}" | crontab
}
```

参考
[Amazon Lightsail に SSL 証明書設置 with Let’s Encrypt(自動更新)](https://hayashier.com/lightsail-letsencrypt/)

## Really Simple SSL プラグインインストール

WordPress の SSL 化に関しては、様々なプラグインがあります。
その中でも [Really Simple SSL](https://ja.wordpress.org/plugins/really-simple-ssl/) というプラグインが

* HTTP から HTTPS へのリダイレクト
* 内部リンクの http://~ から https://~ への置き換え

といったものを全部やってくれるようだったので、このプラグインを使うことにしました。

プラグインは wp コマンドを使えば CLI でインストール可能ということだったので、シェルスクリプト内で wp コマンドを使っています。[^3]

[^3]: Lightsail の WordPress のインスタンスには wp コマンドがインストール済みのため、特にセットアップ不要で使えます。

```bash
#
# Really Simple SSL プラグインのインストール
#
install_ssl_plugin() {
  local wp_config="${HOME}/apps/wordpress/htdocs/wp-config.php"

  chmod g+w "${wp_config}"
  wp plugin install really-simple-ssl
  wp plugin activate really-simple-ssl
}
```

参考
[WordPressを一瞬でHTTPS化するプラグイン「Really Simple SSL」の使い方](https://naifix.com/really-simple-ssl/)
[プラグインで簡単！WordPressの常時SSL対応](https://zenlogic.jp/aossl/operation/wp-plugin/)
[wp-cli コマンド一覧](https://qiita.com/oreo3@github/items/3fafed000c15545ac34b)

# おわりに

シェルスクリプトは楽しい !

# スクリプト全文

```bash
#!/bin/bash
#
# Lightsail WordPress の Let's Encrypt による SSL 化スクリプト
#
# 第 1 引数: ドメイン
# 第 2 引数: メールアドレス
#
# 使用例) ./setup_lightsail_wordpress_letsencrypt.sh example.com mail@example.com
#

set -o errexit
set -o nounset
set -o pipefail
set -o xtrace

readonly DOMAIN_NAME="$1"
readonly EMAIL="$2"

readonly SCRIPT_DIR="$(cd "$(dirname "$0")"; pwd)"

#
# パッケージロックファイルを削除
#
delete_package_lock_files() {
  local package_lock_files=(
    '/var/lib/apt/lists/lock'
    '/var/cache/apt/archives/lock'
    '/var/lib/dpkg/lock'
    '/var/lib/dpkg/lock-frontend'
  )

  for lock_file in "${package_lock_files[@]}"; do
    if [[ -e "${lock_file}" ]]; then
      sudo rm "${lock_file}"
    fi
  done
  sudo dpkg --configure -a
}

#
# Let's Encrypt で SSL 証明書をセットアップ
#
setup_cert_with_letsencrypt() {
  local letsencrypt_home="${SCRIPT_DIR}/letsencrypt"

  if [[ ! -e "${letsencrypt_home}" ]]; then
    git clone https://github.com/letsencrypt/letsencrypt
  fi

  "${letsencrypt_home}/letsencrypt-auto" certonly \
    --webroot \
    -w /opt/bitnami/apps/wordpress/htdocs/ \
    -d "${DOMAIN_NAME}" \
    -m "${EMAIL}" \
    --agree-tos \
    --non-interactive
  
  sudo cp "/etc/letsencrypt/live/${DOMAIN_NAME}/fullchain.pem" /opt/bitnami/apache2/conf/server.crt
  sudo cp "/etc/letsencrypt/live/${DOMAIN_NAME}/privkey.pem" /opt/bitnami/apache2/conf/server.key

  sudo /opt/bitnami/ctlscript.sh restart apache
}

#
# 証明書更新用スクリプトを作成し、cron で毎週日曜日の朝4時に実行されるよう設定
#
set_cert_update_cron() {
  local cert_update_script_dir="${HOME}/bin"
  local cert_update_script="${cert_update_script_dir}/update_cert.sh"

  mkdir -p "${cert_update_script_dir}"

  cat << EOT > "${cert_update_script}"
#!/bin/bash

# --force-renewal をつけていないため、期限まで30日以内の場合に更新される
sudo /home/bitnami/letsencrypt/certbot-auto renew
sudo cp /etc/letsencrypt/live/${DOMAIN_NAME}/fullchain.pem /opt/bitnami/apache2/conf/server.crt
sudo cp /etc/letsencrypt/live/${DOMAIN_NAME}/privkey.pem /opt/bitnami/apache2/conf/server.key
sudo /opt/bitnami/ctlscript.sh restart apache
EOT

  chmod +x "${cert_update_script}"
  echo "0 4 * * 0 ${cert_update_script}" | crontab
}

#
# Really Simple SSL プラグインのインストール
#
install_ssl_plugin() {
  local wp_config="${HOME}/apps/wordpress/htdocs/wp-config.php"

  chmod g+w "${wp_config}"
  wp plugin install really-simple-ssl
  wp plugin activate really-simple-ssl
}

#
# Main
#
main() {
  delete_package_lock_files

  setup_cert_with_letsencrypt

  set_cert_update_cron

  install_ssl_plugin
}

main "$@"
```