日々Derailment

技術的なことの備忘録。脱線事故が起きまくってるので、なるべくrails wayを守れるよう頑張ります。

【Rails】PostgreSQLのバイナリ型で画像を扱う

経緯

  • 画像のアップロード機能を追加したい
  • アップロードしたデータはアップロードしたユーザー以外にみられたくない
  • S3などの外部ストレージに保存したくない

これらの条件を考えた時に、バイナリ型でDBに突っ込むのが一番手取り早いと思いこの実装にいたる。
また、アップローダー系のGemは外部ストレージに送る場合でない限りさほどメリットがなさそうだったので、バリデーションも自前で作る。

環境

概要

  • binaryデータとContentTypeをDBに保存
  • Base64エンコードしてインライン画像化する

実装

1.マイグレーションファイルの作成
rails g model Image user:references data:binary content_type:string uploaded_at:datetime
# db/migrate/xxxxxxxxxxxxxxxx_create_images.rb
class CreateImages < ActiveRecord::Migration[6.0]
  def change
    create_table :images do |t|
      t.references :user, null: false, foreign_key: true, type: :uuid
      t.binary :data, null: false, limit: 10.megabyte, comment: 'データ'
      t.string :content_type, null: false, comment: 'ContentType'
      t.datetime :uploaded_at, default: -> { 'CURRENT_TIMESTAMP' }, null: false, comment: 'アップロード日'

      t.timestamps
    end
  end
end

※binaryのデフォルトのファイルサイズが64kbなので、limitでファイルサイズの設定をする必要あり。

2.マイグレーション実行
rails db:migrate
3.テストファイルの追加
# test/models/image_test.rb
require 'test_helper'

class ImageTest < ActiveSupport::TestCase
  include ActionDispatch::TestProcess

  test 'when upload_file size is over 10MB, it is invalid' do
    file = fixture_file_upload('files/invalid_image_1.jpg', 'image/jpeg')
    image = Image.new(upload_file: file)
    image.valid?
    assert_includes image.errors[:data], 'のファイルサイズは10MB以下にしてください'
  end

  test 'when upload_file content_type is not image/png or image/jpeg, it is invalid' do
    file = fixture_file_upload('files/invalid_image_2.pdf', 'application/pdf')
    image = Image.new(upload_file: file)
    image.valid?
    assert_includes image.errors[:content_type], 'はファイルの種類に問題があります'
  end
end

ActionDispatch::TestProcessをincludeすることでfixture_file_uploadを使えるようになる。
これでActionDispatch::Http::UploadedFileを(フォームからファイルアップロードした際に作られるオブジェクト)を擬似的に扱う。

4.バリデーション追加
# app/validators/content_type_validator.rb
class ContentTypeValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    return if value.nil?
    return if options[:allow_content_type].include?(value)

    record.errors.add(attribute, 'はファイルの種類に問題があります')
  end
end
# app/validators/size_limit_validator.rb
class SizeLimitValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    return if value.nil?
    return if options[:limit] >= value.size

    record.errors.add(attribute, "のファイルサイズは#{convert_size(options[:limit], value)}以下にしてください")
  end

  def convert_size(limit_size, value)
    return convert_kb(limit_size) if 100.kilobyte >= value.size
    return convert_mb(limit_size) if 100.megabyte >= value.size

    convert_gb(limit_size)
  end

  def convert_kb(limit_size)
    "#{(limit_size / (2 ** 10).to_f).round}KB"
  end

  def convert_mb(limit_size)
    "#{(limit_size / (2 ** 20).to_f).round}MB"
  end

  def convert_gb(limit_size)
    "#{(byte_size / (2 ** 30).to_f).round}GB"
  end
end

ContentTypeとファイルサイズのカスタムバリデータを作成する。
モデルに書くよりもカスタムバリデータとして個別に実装した方が見やすいと思うので、こちらに実装。

5.モデルの実装
# app/models/Image.rb
class Image < ApplicationRecord
  ALLOW_CONTENT_TYPE = ['image/jpeg', 'image/png']

  belongs_to :user

  attr_accessor :upload_file

  before_validation -> { self.data = upload_file.read }, if: -> { upload_file }
  before_validation -> { self.content_type = upload_file.content_type }, if: -> { upload_file }

  validates :data, presence: true, size_limit: { limit: 10.megabyte }
  validates :content_type, presence: true, content_type: { allow_content_type: ALLOW_CONTENT_TYPE }
end
6.テスト実行
$ rails test test/model/image_test.rb

Running via Spring preloader in process 57608
Run options: --seed 3934

# Running:

..

Finished in 0.689962s, 2.8987 runs/s, 5.7974 assertions/s.
2 runs, 4 assertions, 0 failures, 0 errors, 0 skips
7.コントローラーの作成
class ImagesController < ApplicationController
  def new
     @image = Image.new
  end

  def create
     @image = Image.new(image_params)
     if @image.save
        # 省略
     else
        # 省略
     end
  end

  private
     def image_params
        params.require(:image).permit(:upload_file)
     end
end
8.ビューの作成(フォーム)
<%= form_with model: @image do |f| %>
   <%= f.label :upload_file %>
   <%= f.file_field :upload_file %>
   <%= f.submit %>
<% end %>
9.画像表示用のメソッド追加
# app/models/Image.rb
class Image < ApplicationRecord
  ALLOW_CONTENT_TYPE = ['image/jpeg', 'image/png']

  belongs_to :user

  attr_accessor :upload_file

  before_validation -> { self.data = upload_file.read }, if: -> { upload_file }
  before_validation -> { self.content_type = upload_file.content_type }, if: -> { upload_file }

  validates :data, presence: true, size_limit: { limit: 10.megabyte }
  validates :content_type, presence: true, content_type: { allow_content_type: ALLOW_CONTENT_TYPE }

  # 追加
  def image_url
    "data:#{content_type};base64,#{Base64.encode64(image)}"
  end
end

画像のインライン用のフォーマットを作成する。
バイナリデータをBase64エンコードすることで作成可能。

10.ビューの作成(画像表示)
<%= image_tag @image.image_url %>
おまけ fixtures
# test/fixtures/images.yml
image_1:
  user: user_1
  image: !binary <%= Base64.strict_encode64(File.binread(Rails.root.join('test', 'fixtures', 'files', 'valid_image_1.jpg').to_s)) %>
  content_type: image/jpeg
  uploaded_at: 2020-05-25 15:07:23

バイナリデータ突っ込むの地味に苦戦した...

後書き

DBにバイナリデータを保存する形での画像アップロードの実装方法についてでした。
send_dataで画像のエンドポイントを作るパターンもありましたが、手間だったのでdata URIを直接メソッドで実装してしみました。
インライン画像にする場合のメリットとしては

  • httpリクエストが減る

デメリットとしては

  • データ容量が増える(約40%増)
  • キャッシュされない

あたりがあります。

今回はデータへのアクセス制限をRails内で完結したいということでパフォーマンス目的での実装じゃないのでその辺りはあまり考慮していません。
ただ、ライブラリ等無しでも案外簡単にやれたので、さすがRailsという感じ。

github actionsでのruby2.6.6対応

ruby 2.6.6 が先月末にリリースされました。
セキュリティFixなので早めに対応しましょう、という話になったので対応を進めていたところ、github actionsでruby 2.6.6が見つからないとエラーが出た。
どうやらactions/ruby-setup@v1だとまだ対応していない模様。

そこで、ruby公式のリポジトリruby/ruby-setup@v1なら最新版に対応しているので下記のように変更します。 before

#.github/workflows/main.yml
   ...
    - name: Set up Ruby 2.6
      uses: actions/setup-ruby@v1
      with:
        ruby-version: 2.6.6
   ...

after

#.github/workflows/main.yml
   ...
    - name: Set up Ruby 2.6
      uses: ruby/setup-ruby@v1
      with:
        ruby-version: 2.6.6
   ...

ruby公式だし、安定して更新されそうだから、今後もruby-setupで使うリポジトリはこれでいいかもしれない。

【メモ書き】テスト環境向けamazon linux2のEC2セットアップ2

  1. route53の設定
  2. nginxのインストール
  3. lets encrypt導入
  4. nginx設定
  5. unicorn起動

1.route53の設定

言うまでもなさそうなので端折ります。

2.nginxのインストール

extrasからnginxをインストール

sudo amazon-linux-extras install nginx1

3.lets encrypt導入

こちらを参考にした qiita.com

cd /var/www
git clone https://github.com/certbot/certbot
cd certbot
cp certbot-auto certbot-auto.bak
vi certbot-auto
# before
elif [ -f /etc/issue ] && grep -iq "Amazon Linux" /etc/issue ; then
  Bootstrap() {
    ExperimentalBootstrap "Amazon Linux" BootstrapRpmCommon
  }
  BOOTSTRAP_VERSION="BootstrapRpmCommon $BOOTSTRAP_RPM_COMMON_VERSION"

# after
 elif grep -i "Amazon Linux" /etc/issue > /dev/null 2>&1 || \
   grep 'cpe:.*:amazon_linux:2' /etc/os-release > /dev/null 2>&1; then
  Bootstrap() {
    ExperimentalBootstrap "Amazon Linux" BootstrapRpmCommon
  }
  BOOTSTRAP_VERSION="BootstrapRpmCommon $BOOTSTRAP_RPM_COMMON_VERSION"

書き換えたら実行

./certbot-auto certonly --standalone --no-self-upgrade -d your.domain -m xxx@your.domain --agree-tos --debug

4.nginx設定

basic認証の準備

sudo yum install httpd-tools
cd /etc/nginx/conf.d
sudo vi app_name.conf
error_log  /var/www/rails/app_name/log/nginx.error.log;
access_log /var/www/rails/app_name/log/nginx.access.log;
client_max_body_size 2G;
upstream app_server {
  server unix:/var/www/rails/app_name/tmp/sockets/.unicorn.sock fail_timeout=0;
}
server {
  listen 443 ssl;
  server_name your.domain;
  ssl_certificate     /etc/letsencrypt/live/your.domain/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/your.domain/privkey.pem;
  keepalive_timeout 5;
  root /var/www/rails/app_name/public;
  try_files $uri/index.html $uri.html $uri @app;
  location @app {
    auth_basic "Restricted";
    auth_basic_user_file /etc/nginx/.htpasswd;
    proxy_set_header X_FORWARDED_SSL on;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_redirect off;
    proxy_pass http://app_server;
  }
  error_page 500 502 503 504 /500.html;
  location = /500.html {
    root /var/www/rails/app_name/public;
  }
}

設定は適宜変更

5.unicornの起動

f:id:ys3128:20200406053923g:plain
ユニコーン起動

冗談はさておき

bundle exec unicorn_rails -c /var/www/rails/app_name/config/unicorn.conf.rb -D -E production

以上!

【メモ書き】テスト環境向けamazon linux2のEC2セットアップ1

linux2でテスト用の環境作りをしたので改めて手順まとめ。

ゴール

RDSとかELBとか使わずにhttps化してBasic認証アクセスするテスト環境作る。

概要

  1. VPC作成
  2. サブネット作成
  3. IGW作成
  4. ルートテーブル作成
  5. セキュリティグループ作成
  6. インスタンス作成
  7. セットアップ

ライブラリを一通りインストール

sudo yum install git make gcc-c++ patch openssl-devel libyaml-devel libffi-devel libicu-devel libxml2 libxslt libxml2-devel libxslt-devel zlib-devel readline-devel ImageMagick ImageMagick-devel

postgresqlセットアップ(RDS使わない版)

sudo rpm -ivh --nodeps https://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm
sudo sed -i 's/\$releasever/7/g' /etc/yum.repos.d/pgdg-redhat-all.repo
sudo yum install -y postgresql12 postgresql12-server postgresql12-contrib postgresql12-devel
PGSETUP_INITDB_OPTIONS='--encoding=UTF-8 --locale=C' sudo /usr/pgsql-12/bin/postgresql-12-setup initdb
sudo systemctl start postgresql-12

linuxのユーザーでpostgresqlのロール作成

su - postgres
psql
CREATE ROLE yyyy WITH CREATEDB CREATEROLE LOGIN PASSWORD 'zzzz'

epelのインストール

sudo amazon-linux-extras install -y epel

nodejsとyarnのインストール

curl -sL https://rpm.nodesource.com/setup_12.x | sudo bash -
sudo yum install -y nodejs
curl -sL https://dl.yarnpkg.com/rpm/yarn.repo | sudo tee /etc/yum.repos.d/yarn.repo
sudo yum install -y yarn

rbenvインストール

git clone https://github.com/sstephenson/rbenv.git ~/.rbenv
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
source .bash_profile
git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build
rbenv rehash

rubyインストール

rbenv install 2.6.5
rbenv global 2.6.5
rbenv rehash

git連携

vi .gitconfig
[user]
  name = USERNAME
  email = EMAIL

[color]
  ui = true

[url "github:"] 
    InsteadOf = https://github.com/
    InsteadOf = git@github.com:
cd .ssh
ssh-keygen -t rsa
vi config

configの内容

Host github
  Hostname github.com
  User git
  IdentityFile key名

githubssh keyを設定してgithub連携終わり
リポジトリをクローン

cd /
sudo chown xxx var
cd var
sudo mkdir www
sudo chown xxx www
cd www
sudo mkdir rails
sudo chown xxx rails
cd rails
git clone xxx

railsセットアップ

bundle install --without development test
yarn install --check-files

export RAILS_MASTER_KEY='xxx' #localのmaster.keyの値
または
EDITOR=vim bundle exec rails credentials:edit
vi config/master.key #localのmaster.keyをペースト

bundle exec rails db:create RAILS_ENV=production
bundle exec rails db:migrate RAILS_ENV=production

pgのインストールでエラーが出る場合はpg_configの場所を指定してやる

$ gem install pg -v '1.2.3' -- --with-pg-config=/usr/pgsql-12/bin/pg_config

次回はunicornとnginx, https化

バビロン見終わった【ネタバレ含む】

バビロン見終わった。

最終回で一気に酷評の嵐になってしまったのだけど、個人的にはとても面白かった。

 

この作品、テーマとしては結局終始、「善とは?悪とは?」って部分で、勧善懲悪的なものだったりとか、サスペンスって感じじゃないのではないのかなという気がしてる。

アレックスと正崎さんは善のメタファーで、曲世は悪のメタファーとして、作品上に存在しているということなんだと思う。(後述するけど、この善悪の定義もそもそも間違っている可能性はある)

そうなると、批判でよく見かける曲世の謎能力で萎えた、みたいな話に関してはあくまで曲世を悪たらしめる為のギミックだから、極端な話、ぶっちゃけどんな能力でもなんでも良いんじゃなかろうか。(ここでは自殺を起点に善悪の議題へ発展させるために、たまたま自殺に追い込めるってだけなのかと)

 

超雑な話の流れとしては「自殺は悪いことなのか?」という問題提起に対して、善のメタファーの二人が出した答えは「そもそも善悪とは?」といった問題に発展して、結論としては「善=続くこと」だった。

その結論を出した二人は、曲世と相対した結果二人とも死亡してしまい(正崎さんはたぶんだけど)、最後に曲世は正崎さんの子供と接触する、っていう一見するとバッドエンドっぽい終わりを迎える。

 

って感じで、これは結構衝撃的なオチだったんだけど、このアレックスと正崎さんが曲世によって死ぬ、ってのはたぶん「善=続くこと」という見解が矛盾を孕んでいるということの表現なんだと思われる。

 

正崎さんはアレックスと話をするなかで、曲世を殺すといったわけだけど、その後出した結論からすると、矛盾が生まれる。だから、曲世を殺すことはできなかった、ってことなのかなと。

つまり、「悪が続くことは善なのか?」っていう矛盾を解消できないんだと思う。

一方、自殺法の掲げる終わる権利、終わること=善、っていうのは矛盾が無い以上、一つの善として成り立ってしまうんじゃないかな。

 

アレックス、正崎さん死ぬ。曲世生き残る。っていうのは一見バッドエンドなんだけれども、善として矛盾が無いのは自殺法サイドなわけで、そう考えると曲世が生き残るのはある種必然だし、ハッピーエンドとも取れてしまうのではないかな。

 

最近よく、芸能人の不倫報道とかに外野がやたらめったらどうこう言う、みたいなケースを多く見かける。ただ、これはあくまで日本だからそうなだけで、一夫多妻制の国だったら、そうはならないのではなかろうか。

 

善悪の倫理観は人それぞれあるし、その善・正義を振りかざすのは自由だけど、そこに矛盾が無い人はどのぐらいいるのか。

また、人類共通の善悪の定義は存在するのか?

 

そんなことを野崎まど氏はバビロンを通して問うているのかなとも思ったりした。

自分なりに穿った見方をしただけなので、異論はたくさんあると思うけど、とにかく個人的には面白かった。

ec2をlet's encrypt使ってhttps化する(Nginx)

ec2をhttps化する場合、elbやcloudfrontを使うケースが一般的だと思う。 が、コストが上がるので、テスト用の環境とかでそのコストがかけられない場合に自前で証明書を用意してhttps化する必要がある。 今回 let's encrypt(certbot)を使ってやってみたので、手順をまとめておく。

1.route53でドメインの設定をする

※ec2にelastic ipの紐づけ、サーバーにnginxの導入と設定が済んでいる前提
route53→ホストゾーンを選択→レコードの作成→ドメイン名を入力→Aレコード→値に紐づけたelastic ipを入力→レコードの保存
この時点で設定したドメインにアクセスして、nginxの画面が表示されること

2.certbotのインストール

サーバーにログイン

cd /var/www #場所はどこでもOK
git clone https://github.com/certbot/certbot
cd ./certbot

3.証明書の設定

 ./certbot-auto certonly --standalone --debug #依存関係のインストールが始まる
Requesting to rerun ./certbot-auto with root privileges...
Bootstrapping dependencies for Amazon... (you can skip this with --no-bootstrap)
yum は /usr/bin/yum です
yum はハッシュされています (/usr/bin/yum)
...
...
インストール  2 パッケージ
更新          2 パッケージ (+2 個の依存関係のパッケージ)

総ダウンロード容量: 9.6 M
Is this ok [y/d/N]: y #依存パッケージのインストール確認があるのでyを押してenter
Downloading packages:
(1/6): augeas-libs-1.0.0-5.7.amzn1.x86_64.rpm                                                                                                                                               | 345 kB  00:00:00     
(2/6): python27-devel-2.7.16-1.130.amzn1.x86_64.rpm                                                                                                                                         | 525 kB  00:00:00     
(3/6): python27-2.7.16-1.130.amzn1.x86_64.rpm                                                                                                                                               | 103 kB  00:00:00     
(4/6): ca-certificates-2018.2.22-65.1.21.amzn1.noarch.rpm                                                                                                                                   | 1.1 MB  00:00:00     
(5/6): python27-tools-2.7.16-1.130.amzn1.x86_64.rpm                                                                                                                                         | 712 kB  00:00:00     
(6/6): python27-libs-2.7.16-1.130.amzn1.x86_64.rpm                                                                                                                                          | 6.8 MB  00:00:01    
...
...
Creating virtual environment...
Installing Python packages...
Installation succeeded.
Traceback (most recent call last):
  File "/opt/eff.org/certbot/venv/bin/letsencrypt", line 7, in <module>
    from certbot.main import main
  File "/opt/eff.org/certbot/venv/local/lib/python2.7/dist-packages/certbot/main.py", line 2, in <module>
    from certbot._internal import main as internal_main
  File "/opt/eff.org/certbot/venv/local/lib/python2.7/dist-packages/certbot/_internal/main.py", line 10, in <module>
    import josepy as jose
  File "/opt/eff.org/certbot/venv/local/lib/python2.7/dist-packages/josepy/__init__.py", line 41, in <module>
    from josepy.interfaces import JSONDeSerializable
  File "/opt/eff.org/certbot/venv/local/lib/python2.7/dist-packages/josepy/interfaces.py", line 7, in <module>
    from josepy import errors, util
  File "/opt/eff.org/certbot/venv/local/lib/python2.7/dist-packages/josepy/util.py", line 7, in <module>
    import OpenSSL
  File "/opt/eff.org/certbot/venv/local/lib/python2.7/dist-packages/OpenSSL/__init__.py", line 8, in <module>
    from OpenSSL import crypto, SSL
  File "/opt/eff.org/certbot/venv/local/lib/python2.7/dist-packages/OpenSSL/crypto.py", line 12, in <module>
    from cryptography import x509
ImportError: No module named cryptography

ここでなぜかpythonのパッケージのインストールでこける
手動でインストール作業を進める

sudo /opt/eff.org/certbot/venv/bin/pip install cryptography
Collecting cryptography
  Using cached https://files.pythonhosted.org/packages/e2/67/4597fc5d5de01bb44887844647ab8e73239079dd478c35c52d58a9eb3d45/cryptography-2.8-cp27-cp27mu-manylinux1_x86_64.whl
Collecting cffi!=1.11.3,>=1.8 (from cryptography)
  Using cached https://files.pythonhosted.org/packages/93/5d/c4f950891251e478929036ca07b22f0b10324460c1d0a4434c584481db51/cffi-1.13.2-cp27-cp27mu-manylinux1_x86_64.whl
Requirement already satisfied: six>=1.4.1 in /opt/eff.org/certbot/venv/lib/python2.7/dist-packages (from cryptography)
Requirement already satisfied: ipaddress; python_version < "3" in /opt/eff.org/certbot/venv/lib/python2.7/dist-packages (from cryptography)
Requirement already satisfied: enum34; python_version < "3" in /opt/eff.org/certbot/venv/lib/python2.7/dist-packages (from cryptography)
Requirement already satisfied: pycparser in /opt/eff.org/certbot/venv/lib/python2.7/dist-packages (from cffi!=1.11.3,>=1.8->cryptography)
Installing collected packages: cffi, cryptography
Successfully installed cffi-1.13.2 cryptography-2.8
You are using pip version 9.0.1, however version 19.3.1 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.

もう一度実行

./certbot-auto certonly --standalone --debug
Requesting to rerun ./certbot-auto with root privileges...
Error: couldn't get currently installed version for /opt/eff.org/certbot/venv/bin/letsencrypt: 
Traceback (most recent call last):
  File "/opt/eff.org/certbot/venv/bin/letsencrypt", line 7, in <module>
    from certbot.main import main
  File "/opt/eff.org/certbot/venv/local/lib/python2.7/dist-packages/certbot/main.py", line 2, in <module>
    from certbot._internal import main as internal_main
  File "/opt/eff.org/certbot/venv/local/lib/python2.7/dist-packages/certbot/_internal/main.py", line 11, in <module>
    import zope.component
  File "/opt/eff.org/certbot/venv/local/lib/python2.7/dist-packages/zope/component/__init__.py", line 16, in <module>
    from zope.interface import Interface
ImportError: No module named interface

別のエラーでこける 今度はInterfaceがないよ、と言われる

sudo /opt/eff.org/certbot/venv/bin/pip install interface
Collecting interface
  Downloading https://files.pythonhosted.org/packages/f9/80/7283dfe76ceb7eb1868cf97b8c8cb3a988f80757921341f95d5420de2a6e/Interface-2.11.1.tar.gz
Collecting zope.interface (from interface)
  Downloading https://files.pythonhosted.org/packages/71/4d/cdbbbdebd56fa4e799169abdb388b0a762d1f1cac156192be48c318ccf17/zope.interface-4.7.1-cp27-cp27mu-manylinux1_x86_64.whl (164kB)
    100% |████████████████████████████████| 174kB 4.2MB/s 
Collecting zope.schema (from interface)
  Downloading https://files.pythonhosted.org/packages/3c/e6/5454a9d72372b73aec715bbded44a0311807dcf7869e2b7bcf196a8e92de/zope.schema-4.9.3-py2.py3-none-any.whl (89kB)
    100% |████████████████████████████████| 92kB 9.6MB/s 
Requirement already satisfied: setuptools in /opt/eff.org/certbot/venv/lib/python2.7/dist-packages (from zope.interface->interface)
Requirement already satisfied: zope.event in /opt/eff.org/certbot/venv/lib/python2.7/dist-packages (from zope.schema->interface)
Building wheels for collected packages: interface
  Running setup.py bdist_wheel for interface ... done
  Stored in directory: /root/.cache/pip/wheels/67/ff/9c/32ef7f63060382c02ba8b1f8f33fed59987378e119da38835f
Successfully built interface
Installing collected packages: zope.interface, zope.schema, interface
Successfully installed interface-2.11.1 zope.interface-4.7.1 zope.schema-4.9.3

もう一度実行

./certbot-auto certonly --standalone --debug
Requesting to rerun ./certbot-auto with root privileges...
Error: couldn't get currently installed version for /opt/eff.org/certbot/venv/bin/letsencrypt: 
Traceback (most recent call last):
  File "/opt/eff.org/certbot/venv/bin/letsencrypt", line 7, in <module>
    from certbot.main import main
  File "/opt/eff.org/certbot/venv/local/lib/python2.7/dist-packages/certbot/main.py", line 2, in <module>
    from certbot._internal import main as internal_main
  File "/opt/eff.org/certbot/venv/local/lib/python2.7/dist-packages/certbot/_internal/main.py", line 11, in <module>
    import zope.component
  File "/opt/eff.org/certbot/venv/local/lib/python2.7/dist-packages/zope/component/__init__.py", line 23, in <module>
    from zope.component.interfaces import IComponentArchitecture
  File "/opt/eff.org/certbot/venv/local/lib/python2.7/dist-packages/zope/component/interfaces.py", line 21, in <module>
    import zope.deferredimport
  File "/opt/eff.org/certbot/venv/local/lib/python2.7/dist-packages/zope/deferredimport/__init__.py", line 1, in <module>
    from zope.deferredimport.deferredmodule import initialize
  File "/opt/eff.org/certbot/venv/local/lib/python2.7/dist-packages/zope/deferredimport/deferredmodule.py", line 18, in <module>
    import zope.proxy
ImportError: No module named proxy

今度はproxyがないと言われる
さっきからzope関係で怒られるので、zopeを入れる

Collecting zope
  Downloading https://files.pythonhosted.org/packages/15/76/da15182338a25a6faf696a5e45ece2806754e8b113848386f3ecebebabd9/Zope-4.1.3-py2.py3-none-any.whl (2.8MB)
     |████████████████████████████████| 2.8MB 3.7MB/s 
Collecting DocumentTemplate>=3.0b9
  Downloading https://files.pythonhosted.org/packages/72/93/fe5743b0f24389e6016539ce3028c19e5cc0741fc27ea9c84caf461d0d45/DocumentTemplate-3.1b2-py2.py3-none-any.whl (89kB)
     |████████████████████████████████| 92kB 15.1MB/s 
Collecting zope.globalrequest
  Downloading https://files.pythonhosted.org/packages/39/5e/9b5a3c66aa3fa2d6e4e1ff1b661162778bed0a776a0bcc5353f0ece2749a/zope.globalrequest-1.5.tar.gz
Collecting zope.lifecycleevent
  Downloading https://files.pythonhosted.org/packages/15/4a/b7a1a421442dc700403d5aa710a84431e088200e069bbd279698affffeb2/zope.lifecycleevent-4.3-py2.py3-none-any.whl
Collecting PasteDeploy
  Downloading https://files.pythonhosted.org/packages/67/0c/faa9971b2e5e048b3b30008d04c72e4d5f63b42f48937c169acce2c5e70a/PasteDeploy-2.0.1-py2.py3-none-any.whl
Requirement already satisfied: zope.interface>=3.8 in /opt/eff.org/certbot/venv/lib64/python2.7/site-packages (from zope) (4.7.1)
Collecting Acquisition
  Downloading https://files.pythonhosted.org/packages/9f/d4/36228e04dc08be90e6b9b71707345c8ab2fe02c92d966254e020966856cd/Acquisition-4.6.tar.gz (65kB)
     |████████████████████████████████| 71kB 12.5MB/s 
Collecting transaction>=2.4
  Downloading https://files.pythonhosted.org/packages/e8/06/17b311f04ee623bea66f2f2a93a1b5691e383278d7d30980bba3f1c03ce9/transaction-3.0.0-py2.py3-none-any.whl (47kB)
     |████████████████████████████████| 51kB 10.1MB/s 
Collecting zope.i18nmessageid
  Downloading https://files.pythonhosted.org/packages/8d/5d/92f9473dd42bd50dc52ab9b3960164367f11699fe4dcb3bed8a4b6a6bdfa/zope.i18nmessageid-5.0.0-cp27-cp27mu-manylinux2010_x86_64.whl
Collecting zope.testing
  Downloading https://files.pythonhosted.org/packages/14/d3/7849b49ab07065cb30380ad9a0bf4ea87ecd8cc456be95d32a5c4d8a6389/zope.testing-4.7-py2.py3-none-any.whl (64kB)
     |████████████████████████████████| 71kB 13.2MB/s 
Requirement already satisfied: six in /opt/eff.org/certbot/venv/lib/python2.7/dist-packages (from zope) (1.13.0)
Collecting zope.exceptions
  Downloading https://files.pythonhosted.org/packages/fa/d8/c186f22885ef4e3c0f6ea22f27a8d99cdd59b87b64498854a35d5b1608f2/zope.exceptions-4.3-py2.py3-none-any.whl
Collecting zope.contenttype
  Downloading https://files.pythonhosted.org/packages/4d/6a/f9446830eb7f249823a8d23105db60d6648bf388423f1dcb9be94562f5b4/zope.contenttype-4.5.0-py2.py3-none-any.whl
Collecting zope.testbrowser
  Downloading https://files.pythonhosted.org/packages/e8/f9/e2a527c50b35166354abffb3e15aec32d83e56fdb2d9af9fe4f3dbc0110a/zope.testbrowser-5.5.1-py2.py3-none-any.whl (64kB)
     |████████████████████████████████| 71kB 13.2MB/s 
Collecting BTrees
  Downloading https://files.pythonhosted.org/packages/3e/bc/b47c73b2d2dfab9993fea0d98307618b454dfb60ceeb843caf2d6819a606/BTrees-4.6.1-cp27-cp27mu-manylinux1_x86_64.whl (1.3MB)
     |████████████████████████████████| 1.3MB 31.3MB/s 
Collecting zope.proxy
  Downloading https://files.pythonhosted.org/packages/06/50/182f671b1d0067b1c7985bacfce12501ec5b6ce77f5cdfc1e8c88bd7e787/zope.proxy-4.3.3-cp27-cp27mu-manylinux2010_x86_64.whl (70kB)
     |████████████████████████████████| 71kB 12.8MB/s 
Requirement already satisfied: zope.component in /opt/eff.org/certbot/venv/lib/python2.7/dist-packages (from zope) (4.6)
Requirement already satisfied: zope.event in /opt/eff.org/certbot/venv/lib/python2.7/dist-packages (from zope) (4.4)
Collecting zope.size
  Downloading https://files.pythonhosted.org/packages/06/1a/78cbdffd3554c17993c6fc6f015f370bad4d33bdfb182e12ae769e2ce8d1/zope.size-4.3-py2.py3-none-any.whl
Requirement already satisfied: setuptools>=36.2 in /opt/eff.org/certbot/venv/lib/python2.7/dist-packages (from zope) (40.6.3)
Collecting zope.container
  Downloading https://files.pythonhosted.org/packages/2a/46/3fc8ffce38ea2cc4456844008eb678c8ec7587a1aa4180926d9844a5795c/zope.container-4.3.0-cp27-cp27mu-manylinux2010_x86_64.whl (115kB)
     |████████████████████████████████| 122kB 38.8MB/s 
Collecting zope.viewlet
  Downloading https://files.pythonhosted.org/packages/ac/9d/608bd0ee93b23ba035f0338044fa7e8ecd0bde0679f2f6a5603afc270a25/zope.viewlet-4.2.1-py2.py3-none-any.whl
Collecting zope.publisher
  Downloading https://files.pythonhosted.org/packages/a2/40/ab857f2dbf4e6554323ff9643a0ff9c2e94a1062312b5bba1fe2df9ca39d/zope.publisher-5.1.1.tar.gz (104kB)
     |████████████████████████████████| 112kB 41.4MB/s 
Collecting zope.tales>=3.5.0
  Downloading https://files.pythonhosted.org/packages/83/ef/6f6434fd41023b1765dcf300885d5a96d9652337f7b1e484e52b55e8a05c/zope.tales-5.0.1-py2.py3-none-any.whl
Collecting MultiMapping
  Downloading https://files.pythonhosted.org/packages/05/8a/e074091c47d3cfe3b070cd737f2cabe4c676ae00b9cea9edea9b887351ed/MultiMapping-4.1-py2.py3-none-any.whl
Collecting waitress
  Downloading https://files.pythonhosted.org/packages/64/a5/0bd48ad83baa2b9c0a4b7568b47aaec22ce1350ddd1cb73eb6c7bcb948f3/waitress-1.4.1-py2.py3-none-any.whl (147kB)
     |████████████████████████████████| 153kB 38.0MB/s 
Collecting RestrictedPython
  Downloading https://files.pythonhosted.org/packages/0b/8f/6a956c1c2eb58a9fd209c9f90124576d7ea830d7cc54c78e95cc967eb1d0/RestrictedPython-5.0-py2.py3-none-any.whl
Collecting ZODB
  Downloading https://files.pythonhosted.org/packages/78/27/346776a8c1d9bcdfa1559688ae8461ccb2e5e876f275732d71aaf8529c9b/ZODB-5.5.1-py2.py3-none-any.whl (412kB)
     |████████████████████████████████| 419kB 32.4MB/s 
Collecting zope.browserresource>=3.11
  Downloading https://files.pythonhosted.org/packages/4e/b8/9d98b52e62af3c1dd4a8f69a35922547122ee5dce6c5eee6d6f022a73873/zope.browserresource-4.4-py2.py3-none-any.whl (40kB)
     |████████████████████████████████| 40kB 9.0MB/s 
Collecting zope.browser
  Downloading https://files.pythonhosted.org/packages/35/ab/b15a1a5cb1be7468b630533346c5c97d128a82dbe4011ddec9b3bb090c9f/zope.browser-2.3-py2.py3-none-any.whl
Collecting zope.processlifetime
  Downloading https://files.pythonhosted.org/packages/27/e3/d8aa1548c145cb43112777a8304323aa3c8fa04c4962889257aa887b2e39/zope.processlifetime-2.3.0-py2.py3-none-any.whl
Collecting Persistence
  Downloading https://files.pythonhosted.org/packages/e7/66/d3e62ba3136572ac2cb1554f11a7963235440cf93a8f4f206ab5cd5b9f7e/Persistence-3.0.tar.gz
Collecting zope.sequencesort
  Downloading https://files.pythonhosted.org/packages/5b/35/83bc8095aa807567f63313a27ba8834d092a5c1f35e2b712fc2da7369724/zope.sequencesort-4.1.2-py2.py3-none-any.whl
Collecting AccessControl>=4.0b4
  Downloading https://files.pythonhosted.org/packages/7b/d5/1bc052831922caf6c69b36568f50370b055f53b9d6eeac3109b303772312/AccessControl-4.1.tar.gz (107kB)
     |████████████████████████████████| 112kB 39.7MB/s 
Collecting ZConfig>=2.9.2
  Downloading https://files.pythonhosted.org/packages/d2/33/533ba4b7e39e8fc16dbdf796a84c57b27956a72103ce4bfe2dd354f9fd2a/ZConfig-3.5.0-py2.py3-none-any.whl (134kB)
     |████████████████████████████████| 143kB 40.2MB/s 
Collecting zope.site
  Downloading https://files.pythonhosted.org/packages/96/f6/e2111b9d799ec223435e370722435a6b90163605e3e9371817f22fc9d3d0/zope.site-4.2.2-py2.py3-none-any.whl
Collecting zope.browsermenu
  Downloading https://files.pythonhosted.org/packages/bb/65/d407714ce3bac89f422649e828e86727413d5c1c3b4d7ebc6acc91ba234f/zope.browsermenu-4.4-py2.py3-none-any.whl
Collecting zExceptions>=3.4
  Downloading https://files.pythonhosted.org/packages/e2/c2/9af73d27364af53acb18b03640f920962ca85ab5d5f62cf1daaa110546ca/zExceptions-4.1-py2.py3-none-any.whl
Collecting zope.contentprovider
  Downloading https://files.pythonhosted.org/packages/e0/e7/e7549f2c4551250d829aa61e290aacc8fcca3d128852fa3864816afcfe14/zope.contentprovider-4.2.1-py2.py3-none-any.whl
Collecting zope.configuration
  Downloading https://files.pythonhosted.org/packages/f0/09/07a3040fe1ed88a4c4e3aa197d18697adbce4eb148a7580426638cd166e4/zope.configuration-4.3.1-py2.py3-none-any.whl (82kB)
     |████████████████████████████████| 92kB 16.0MB/s 
Collecting z3c.pt
  Downloading https://files.pythonhosted.org/packages/f0/48/62aee39ace9f311a872ce1f8548606e18cfeb78032dcc8dc143453cbaefe/z3c.pt-3.2.0-py2.py3-none-any.whl (52kB)
     |████████████████████████████████| 61kB 12.1MB/s 
Collecting zope.pagetemplate>=4.0.2
  Downloading https://files.pythonhosted.org/packages/25/1c/891dd2e4733ad93c8a58599c9fa442860de77b6f6b36554c32cca321599b/zope.pagetemplate-4.4.1-py2.py3-none-any.whl (43kB)
     |████████████████████████████████| 51kB 10.1MB/s 
Collecting zope.i18n[zcml]
  Downloading https://files.pythonhosted.org/packages/f8/95/48cb15ee969bb38406edc27957600874684783a615a814541e9576378498/zope.i18n-4.7.0.tar.gz (606kB)
     |████████████████████████████████| 614kB 21.1MB/s 
Collecting ExtensionClass
  Downloading https://files.pythonhosted.org/packages/c7/8c/96afee6311630dd9db25316bc000da740a5f7c2071556cd8e18ee810f375/ExtensionClass-4.4.tar.gz
Collecting zope.browserpage>=4.4.0.dev0
  Downloading https://files.pythonhosted.org/packages/71/43/d0285df546191c9351196f5958f35f7b78558c0cfa2683339bbc41dfa05f/zope.browserpage-4.4.0-py2.py3-none-any.whl
Requirement already satisfied: zope.deferredimport in /opt/eff.org/certbot/venv/lib/python2.7/dist-packages (from zope) (4.3.1)
Requirement already satisfied: zope.schema in /opt/eff.org/certbot/venv/lib/python2.7/site-packages (from zope) (4.9.3)
Collecting zope.ptresource
  Downloading https://files.pythonhosted.org/packages/1d/81/7e89cd6750b0bc310fd3e92fd4c252dbbcc135bd29d8d2d5cab88e9259c2/zope.ptresource-4.2.0-py2.py3-none-any.whl
Collecting zope.traversing
  Downloading https://files.pythonhosted.org/packages/2a/d2/bd77682d4efbd7c759cd076f9cca9f59c574ddad319b8e5d3ccd731fee7c/zope.traversing-4.3.1-py2.py3-none-any.whl (47kB)
     |████████████████████████████████| 51kB 10.1MB/s 
Collecting zope.security
  Downloading https://files.pythonhosted.org/packages/e9/4e/bedb7e84abdabbd998b03794c89805e79abf8cf1007ee6259e9c750377dc/zope.security-5.0-cp27-cp27mu-manylinux2010_x86_64.whl (177kB)
     |████████████████████████████████| 184kB 36.3MB/s 
Collecting zope.tal
  Downloading https://files.pythonhosted.org/packages/cf/22/f43c49c74b2aca37980a60f53f660590acc06fb83f2fde24df2a29f6e483/zope.tal-4.4-py2.py3-none-any.whl (139kB)
     |████████████████████████████████| 143kB 40.7MB/s 
Collecting DateTime
  Downloading https://files.pythonhosted.org/packages/73/22/a5297f3a1f92468cc737f8ce7ba6e5f245fcfafeae810ba37bd1039ea01c/DateTime-4.3-py2.py3-none-any.whl (60kB)
     |████████████████████████████████| 61kB 12.3MB/s 
Requirement already satisfied: ipaddress; python_version == "2.7" in /opt/eff.org/certbot/venv/lib/python2.7/dist-packages (from zope) (1.0.23)
Collecting zope.location
  Downloading https://files.pythonhosted.org/packages/9f/3b/4e7a859ebb06911ad118a77528e3c1af018ee5585c832e16151432f7e19b/zope.location-4.2-py2.py3-none-any.whl
Collecting zope.structuredtext
  Downloading https://files.pythonhosted.org/packages/0f/fd/7cdd7cc546dfdd0ab7c25812fc3c094d8790e40ca1a2bf739602c3190815/zope.structuredtext-4.3-py2.py3-none-any.whl (93kB)
     |████████████████████████████████| 102kB 15.9MB/s 
Collecting roman
  Downloading https://files.pythonhosted.org/packages/8d/f2/29d1d069555855ed49c74b627e6af73cec7a5f4de27c200ea0d760939da4/roman-3.2-py2.py3-none-any.whl
Collecting SoupSieve>=1.9.0
  Downloading https://files.pythonhosted.org/packages/81/94/03c0f04471fc245d08d0a99f7946ac228ca98da4fa75796c507f61e688c2/soupsieve-1.9.5-py2.py3-none-any.whl
Collecting zope.cachedescriptors
  Downloading https://files.pythonhosted.org/packages/81/6f/d668102e1bd4fba6cfb160e178477b4e5ade20ccac0b2b390d4f64d0bb9d/zope.cachedescriptors-4.3.1-py2.py3-none-any.whl
Requirement already satisfied: pytz>dev in /opt/eff.org/certbot/venv/lib/python2.7/dist-packages (from zope.testbrowser->zope) (2019.3)
Collecting WSGIProxy2
  Downloading https://files.pythonhosted.org/packages/9e/db/3e8d6877cc12de58ff67eecfab58acc50b2e2803381a06e21c78fa99713c/WSGIProxy2-0.4.6.tar.gz
Collecting WebTest>=2.0.30
  Downloading https://files.pythonhosted.org/packages/47/fd/569d7e5ce6ea92d8ff6dd7a91f125762d88b199f2b4e17d7030a703f241d/WebTest-2.0.33-py2.py3-none-any.whl
Collecting BeautifulSoup4
  Downloading https://files.pythonhosted.org/packages/c5/48/c88b0b390ae1f785942fc83413feb1268a1eb696f343d4d55db735b9bb39/beautifulsoup4-4.8.2-py2-none-any.whl (106kB)
     |████████████████████████████████| 112kB 38.8MB/s 
Collecting persistent>=4.1.0
  Downloading https://files.pythonhosted.org/packages/da/4d/2ec9bf8f6b4089ca575be54f160959aca3d3b6985bc04d9c33b5747a3096/persistent-4.5.1.tar.gz (107kB)
     |████████████████████████████████| 112kB 39.4MB/s 
Requirement already satisfied: zope.deprecation>=4.3.0 in /opt/eff.org/certbot/venv/lib/python2.7/dist-packages (from zope.component->zope) (4.4.0)
Collecting zope.hookable>=4.2.0
  Downloading https://files.pythonhosted.org/packages/d9/db/6d72ec5a306eeaf172a7e459e0039dd410d7e1911b0e2b54e492d7dab46b/zope.hookable-5.0.0-cp27-cp27mu-manylinux2010_x86_64.whl
Collecting zope.filerepresentation
  Downloading https://files.pythonhosted.org/packages/4b/bb/15bbd8e83c89a0bb2530c1b32d674d2ca9be556ef5f62d42d879d45cecbd/zope.filerepresentation-4.2.0-py2.py3-none-any.whl
Collecting zope.dottedname
  Downloading https://files.pythonhosted.org/packages/97/a4/5d593e104ce0348acd9638dbf9146cb0dae27603c60239c2429a789a4834/zope.dottedname-4.3-py2.py3-none-any.whl
Collecting zc.lockfile
  Downloading https://files.pythonhosted.org/packages/6c/2a/268389776288f0f26c7272c70c36c96dcc0bdb88ab6216ea18e19df1fadd/zc.lockfile-2.0-py2.py3-none-any.whl
Collecting zodbpickle>=1.0.1
  Downloading https://files.pythonhosted.org/packages/48/70/f1444e7fbdb162e8791cc4706fbd5c042e4ca767e6d1dc4763429728e39e/zodbpickle-2.0.0-cp27-cp27mu-manylinux2010_x86_64.whl (309kB)
     |████████████████████████████████| 317kB 32.9MB/s 
Collecting AuthEncoding
  Downloading https://files.pythonhosted.org/packages/9d/54/abeee20b4ce6885e194f2cb92b95ce87337281a5fac63298ab39a466e9ee/AuthEncoding-4.1-py2.py3-none-any.whl
Requirement already satisfied: funcsigs in /opt/eff.org/certbot/venv/lib/python2.7/dist-packages (from AccessControl>=4.0b4->zope) (1.0.2)
Collecting zope.annotation
  Downloading https://files.pythonhosted.org/packages/c4/15/43455099dc3517426886845c86def02e82e1c89851b193861f7d2b1d1f94/zope.annotation-4.7.0-py2.py3-none-any.whl
Collecting Chameleon>=2.4
  Downloading https://files.pythonhosted.org/packages/9e/12/ad477cf8fef73154d78081575d5c8be8d3ce25e5bfe55c63e14dcb793822/Chameleon-3.6.2.tar.gz (121kB)
     |████████████████████████████████| 122kB 39.4MB/s 
Collecting python-gettext
  Downloading https://files.pythonhosted.org/packages/86/64/1475ff167cf6e6b6f9e50973900011cb53be8260010e3d016ad3778c4565/python-gettext-4.0.tar.gz
Collecting backports.functools-lru-cache; python_version < "3"
  Downloading https://files.pythonhosted.org/packages/da/d1/080d2bb13773803648281a49e3918f65b31b7beebf009887a529357fd44a/backports.functools_lru_cache-1.6.1-py2.py3-none-any.whl
Collecting webob
  Downloading https://files.pythonhosted.org/packages/06/e1/4acd2b4327fceb4c6446bdbca515f807ab83188526fd654940c00bcf8cc3/WebOb-1.8.5-py2.py3-none-any.whl (113kB)
     |████████████████████████████████| 122kB 39.4MB/s 
Requirement already satisfied: cffi in /opt/eff.org/certbot/venv/lib64/python2.7/site-packages (from persistent>=4.1.0->BTrees->zope) (1.13.2)
Requirement already satisfied: pycparser in /opt/eff.org/certbot/venv/lib/python2.7/dist-packages (from cffi->persistent>=4.1.0->BTrees->zope) (2.19)
Building wheels for collected packages: zope.globalrequest, Acquisition, zope.publisher, Persistence, AccessControl, zope.i18n, ExtensionClass, WSGIProxy2, persistent, Chameleon, python-gettext
  Building wheel for zope.globalrequest (setup.py) ... done
  Created wheel for zope.globalrequest: filename=zope.globalrequest-1.5-cp27-none-any.whl size=7309 sha256=a84a53ecc4d9c49cec78cc0bd4f1e6f7843b91dc06b039d2070203987fb60757
  Stored in directory: /root/.cache/pip/wheels/fa/73/04/d892eec2021dc3081a6f98224f352e704b7dc6eef1d9708608
  Building wheel for Acquisition (setup.py) ... done
  Created wheel for Acquisition: filename=Acquisition-4.6-cp27-cp27mu-linux_x86_64.whl size=99338 sha256=59ab97aad8b07f2d8d000b7d4c5d6699eefced9f36160ddcc013eb6be00a4f1c
  Stored in directory: /root/.cache/pip/wheels/a4/1f/b5/2c56fcb413432e0031175feeccadcd63b3eb634008ca80eab1
  Building wheel for zope.publisher (setup.py) ... done
  Created wheel for zope.publisher: filename=zope.publisher-5.1.1-py2.py3-none-any.whl size=127862 sha256=25893fc30a420f0a2c439928397b3de14b82d4c57cf84a894a54c285a8a0cf2c
  Stored in directory: /root/.cache/pip/wheels/a8/e2/73/0d8e8158a130a40b72df680ba9e6e0c45355d16eb51e347af4
  Building wheel for Persistence (setup.py) ... done
  Created wheel for Persistence: filename=Persistence-3.0-cp27-cp27mu-linux_x86_64.whl size=23570 sha256=7fd1384873f3f93dbee2aec05033459d256e5e6bb9a8c76b4f4aa71205ddc37c
  Stored in directory: /root/.cache/pip/wheels/a0/a9/b1/f1e0a1ce9dc105a0a2b112ee455dcb6b262b19f4b13e118c5a
  Building wheel for AccessControl (setup.py) ... done
  Created wheel for AccessControl: filename=AccessControl-4.1-cp27-cp27mu-linux_x86_64.whl size=165839 sha256=9897768fcb2d3e001457d96d21eeed5d805e1563c247aad812cb64b7664b86c9
  Stored in directory: /root/.cache/pip/wheels/26/7b/92/b1e6d2f189fa72de5f5ec3d961ee845ceee14bddefac7c1d66
  Building wheel for zope.i18n (setup.py) ... done
  Created wheel for zope.i18n: filename=zope.i18n-4.7.0-py2.py3-none-any.whl size=798313 sha256=e8f0033f3b26116216620382b062e4172fb720ac0d799c296ee503e5370ff536
  Stored in directory: /root/.cache/pip/wheels/73/d9/92/5295974a727273fb1d55fd52dc124d3595612b39f008c6e304
  Building wheel for ExtensionClass (setup.py) ... done
  Created wheel for ExtensionClass: filename=ExtensionClass-4.4-cp27-cp27mu-linux_x86_64.whl size=79389 sha256=b25cda257a81012be66e92d5e52b83dcb56f1c43c1757fd89b41d32971660873
  Stored in directory: /root/.cache/pip/wheels/d1/28/3f/b3cafcbcc36f4f7d18a742accaaa7c6d682209870065f8350d
  Building wheel for WSGIProxy2 (setup.py) ... done
  Created wheel for WSGIProxy2: filename=WSGIProxy2-0.4.6-cp27-none-any.whl size=10299 sha256=c234ecad95335dcff4d5e845949efe166513c10f6ce329acd4e5b43a08c7ff0a
  Stored in directory: /root/.cache/pip/wheels/d3/3e/e8/8c32373938a76ef8de88765952f614a2017cc13ed9a492c77e
  Building wheel for persistent (setup.py) ... done
  Created wheel for persistent: filename=persistent-4.5.1-cp27-cp27mu-linux_x86_64.whl size=196588 sha256=6a4e5d6393ba4f09f5b9710ff83ee83b9f881404d6e29fb5ee23dda1a00aa05a
  Stored in directory: /root/.cache/pip/wheels/57/31/8b/118824eaca4ccd94cd81a4859d869f1dfdcadaffdb95eab2aa
  Building wheel for Chameleon (setup.py) ... done
  Created wheel for Chameleon: filename=Chameleon-3.6.2-cp27-none-any.whl size=182325 sha256=5afc970b6172029655fbbdac30cf8675a89231535a9811675a37860b75488443
  Stored in directory: /root/.cache/pip/wheels/a8/bd/97/881bca79c5cb776f87cde0a7143c91245d1a6ecbe2c9a4e7bc
  Building wheel for python-gettext (setup.py) ... done
  Created wheel for python-gettext: filename=python_gettext-4.0-cp27-none-any.whl size=17092 sha256=6ddfe9adf6230b5c4f5c397a5f1b7feeb4026e89254074b9f22cdb7b86145359
  Stored in directory: /root/.cache/pip/wheels/f4/57/22/7abf03f3d407d10ced8ca3f60e5b6fd3ad88dca94ea8f89e77
Successfully built zope.globalrequest Acquisition zope.publisher Persistence AccessControl zope.i18n ExtensionClass WSGIProxy2 persistent Chameleon python-gettext
Installing collected packages: zope.structuredtext, ExtensionClass, Acquisition, zope.sequencesort, AuthEncoding, persistent, BTrees, DateTime, Persistence, RestrictedPython, transaction, zope.proxy, zope.i18nmessageid, zope.location, zope.security, zope.browser, zope.configuration, zope.contenttype, zope.exceptions, python-gettext, zope.i18n, zope.publisher, zExceptions, zope.testing, AccessControl, roman, DocumentTemplate, zope.traversing, zope.globalrequest, zope.lifecycleevent, PasteDeploy, backports.functools-lru-cache, SoupSieve, zope.cachedescriptors, webob, WSGIProxy2, BeautifulSoup4, waitress, WebTest, zope.testbrowser, zope.size, zope.filerepresentation, zope.dottedname, zope.container, zope.tales, zope.contentprovider, zope.tal, zope.pagetemplate, zope.browserpage, zope.viewlet, MultiMapping, zc.lockfile, zodbpickle, ZConfig, ZODB, zope.browserresource, zope.processlifetime, zope.annotation, zope.site, zope.browsermenu, Chameleon, z3c.pt, zope.ptresource, zope, zope.hookable
Successfully installed AccessControl-4.1 Acquisition-4.6 AuthEncoding-4.1 BTrees-4.6.1 BeautifulSoup4-4.8.2 Chameleon-3.6.2 DateTime-4.3 DocumentTemplate-3.1b2 ExtensionClass-4.4 MultiMapping-4.1 PasteDeploy-2.0.1 Persistence-3.0 RestrictedPython-5.0 SoupSieve-1.9.5 WSGIProxy2-0.4.6 WebTest-2.0.33 ZConfig-3.5.0 ZODB-5.5.1 backports.functools-lru-cache-1.6.1 persistent-4.5.1 python-gettext-4.0 roman-3.2 transaction-3.0.0 waitress-1.4.1 webob-1.8.5 z3c.pt-3.2.0 zExceptions-4.1 zc.lockfile-2.0 zodbpickle-2.0.0 zope-4.1.3 zope.annotation-4.7.0 zope.browser-2.3 zope.browsermenu-4.4 zope.browserpage-4.4.0 zope.browserresource-4.4 zope.cachedescriptors-4.3.1 zope.configuration-4.3.1 zope.container-4.3.0 zope.contentprovider-4.2.1 zope.contenttype-4.5.0 zope.dottedname-4.3 zope.exceptions-4.3 zope.filerepresentation-4.2.0 zope.globalrequest-1.5 zope.hookable-5.0.0 zope.i18n-4.7.0 zope.i18nmessageid-5.0.0 zope.lifecycleevent-4.3 zope.location-4.2 zope.pagetemplate-4.4.1 zope.processlifetime-2.3.0 zope.proxy-4.3.3 zope.ptresource-4.2.0 zope.publisher-5.1.1 zope.security-5.0 zope.sequencesort-4.1.2 zope.site-4.2.2 zope.size-4.3 zope.structuredtext-4.3 zope.tal-4.4 zope.tales-5.0.1 zope.testbrowser-5.5.1 zope.testing-4.7 zope.traversing-4.3.1 zope.viewlet-4.2.1

再度実行すると動き出す
メールアドレスを聞かれるので入力する

Requesting to rerun ./certbot-auto with root privileges...
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator standalone, Installer None
Enter email address (used for urgent renewal and security notices) (Enter 'c' to
cancel): xxx@yyy.com

規約に同意するか聞かれるので、Aを入力してenter

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please read the Terms of Service at
https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf. You must
agree in order to register with the ACME server at
https://acme-v02.api.letsencrypt.org/directory
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(A)gree/(C)ancel: A

メールアドレスを Electronic Frontier Foundation等々に共有してもいいか聞かれるので、お好みで。

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Would you be willing to share your email address with the Electronic Frontier
Foundation, a founding partner of the Let's Encrypt project and the non-profit
organization that develops Certbot? We'd like to send you email about our work
encrypting the web, EFF news, campaigns, and ways to support digital freedom.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: N

ドメインを聞かれるので入力

Please enter in your domain name(s) (comma and/or space separated)  (Enter 'c'
to cancel): zzz.com
Obtaining a new certificate
Performing the following challenges:
http-01 challenge for zzz.com
Waiting for verification...
Cleaning up challenges

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at:
   /etc/letsencrypt/live/zzz.com/fullchain.pem
   Your key file has been saved at:
   /etc/letsencrypt/live/zzz.com/privkey.pem
   Your cert will expire on 2020-03-25. To obtain a new or tweaked
   version of this certificate in the future, simply run certbot-auto
   again. To non-interactively renew *all* of your certificates, run
   "certbot-auto renew"
 - Your account credentials have been saved in your Certbot
   configuration directory at /etc/letsencrypt. You should make a
   secure backup of this folder now. This configuration directory will
   also contain certificates and private keys obtained by Certbot so
   making regular backups of this folder is ideal.
 - If you like Certbot, please consider supporting our work by:

   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le

無事インストール完了。
/etc/letsencrypt/live/zzz.com/fullchain.pem
/etc/letsencrypt/live/zzz.com/privkey.pem
にそれぞれ証明書と鍵が保存されている。

Nginxの設定

設定を修正する。

cd /etc/nginx/conf.d/xxx.conf
# xxx.conf
server {
  listen 443 ssl;
  ssl_certificate     /etc/letsencrypt/live/zzz.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/zzz.com/privkey.pem;
}

EC2の設定を修正

EC2→セキュリティグループ→サーバーのセキュリティグループを選択→インバウント→編集→タイプ:https, ソース:カスタム、0.0.0.0/0→保存

まとめ

めちゃくちゃお手軽。
途中エラーでハマったけど、対処の仕方を知っていれば15分程度で導入できると思う。
いい時代ですね。

RubyのTimeの比較は同じ日付・時・分・秒でもfalseが返ることがある

まとめ

  • Timeの比較は1秒以下をみるので、パッと見同じ日付・時・分・秒でもfalseが返ることがある。
  • 同じ日付・時・分・秒をtrueにするには1秒以下をto_iで切り捨てる

本筋

Railsで時刻を比較する機会はなかなか多いと思う。
時刻の比較で、テストを書いていたときに同じケースを比較してtrueが返ってきてほしいところをfalseが返ってくるケースがあった。
最初ActiveSupport::TimeWithZoneと比較してる中で何かあるのかなと思った。

github.com
こういう挙動を調べるのにTraceLocationはとても手っ取り早くてとても助かる。

Generated by trace_location at 2019-11-03 16:48:00 +0900

~/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/activesupport-6.0.0/lib/active_support/time_with_zone.rb:225

ActiveSupport::TimeWithZone#<=>
def <=>(other)
  utc <=> other
end
# called from (pry):10

~/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/activesupport-6.0.0/lib/active_support/time_with_zone.rb:64

ActiveSupport::TimeWithZone#utc
def utc
  @utc ||= period.to_utc(@time)
end
# called from ~/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/activesupport-6.0.0/lib/active_support/time_with_zone.rb:226

~/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/activesupport-6.0.0/lib/active_support/core_ext/time/calculations.rb:292

Time#compare_with_coercion
def compare_with_coercion(other)
  # we're avoiding Time#to_datetime and Time#to_time because they're expensive
  if other.class == Time
    compare_without_coercion(other)
  elsif other.is_a?(Time)
    compare_without_coercion(other.to_time)
  else
    to_datetime <=> other
  end
end
alias_method :compare_without_coercion, :<=>
alias_method :<=>, :compare_with_coercion
# called from ~/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/activesupport-6.0.0/lib/active_support/time_with_zone.rb:226

alias_methodで最終的に<=>メソッドが走るので、要は最終的にActiveSupport::TimeWithZoneであれ、Timeに変換して比較しているだけであって、そこは関係ないと。

で、なのでTimeの比較を調べてみる。

docs.ruby-lang.org

丁寧に同一時刻の比較を書いてくれている。
Timeの比較は1秒以下の値を見てそこの大小を比較していることがわかる。
なので、パッと見同じ日付・時・分・秒でもfalseが返ることがあるということだ。

これをtrueにしたい場合、ミリ秒以下を切り捨てればよいので、to_iとかで比較してあげると良い。