Hubot を自宅サーバで運用

やりたいこと

  • Hubot の永続化
    • systemd を使用
    • Hubot は専用のユーザー(hubot)で起動する
    • 後述のリポジトリフックの関係から、hubot ユーザーで systemctl が利用できるようにする
      → systemd のユーザーインスタンス(systemd –user)を使用
  • 併設するリモートリポジトリに push したら、Hubot のサービスが再起動
    → bare リポジトリと post-receive フック

前提

  • サーバは Ubuntu 18.04 LTS (Bionic Beaver)
  • 使用するユーザー
    • vagrant:作業用
    • hubot:Hubot 実行用
  • クライアントは Windows
  • プロンプト ユーザー名@example はサーバでの作業
  • プロンプト mistymagich@mistymagich-pc はクライアント PC(Windows)での作業
  • Hubot アダプタは Slack
    ※あからじめ Slack | Slack Developer Kit for Hubot にしたがって、myhubot というユーザー名で API Token を生成し ユーザー myhubot を チャンネルに追加しておく
  • Hubot で使用する node, npm はシステムの NodeJS 環境によらないように、nodenv を使用してインストールする

環境構築

  1. 構築に必要なパッケージをインストール

    vagrant@example:~$ sudo apt install -y git g++ make
    
  2. Hubot 実行ユーザーの作成
    vagrant@example:~$ sudo useradd -m -s /bin/bash -U hubot
    vagrant@example:~$ sudo su - hubot
    hubot@example:~$
    
  3. node,npm インストール
    1. nodenv インストール
      hubot@example:~$ git clone https://github.com/nodenv/nodenv.git ~/.nodenv
      hubot@example:~$ cd ~/.nodenv && src/configure && make -C src
      hubot@example:~/.nodenv$ echo 'export PATH="$HOME/.nodenv/bin:$PATH"' >> ~/.bashrc
      hubot@example:~/.nodenv$ echo 'eval "$(nodenv init -)"' >> ~/.bashrc
      hubot@example:~/.nodenv$ exec $SHELL -l
      hubot@example:~/.nodenv$ mkdir -p "$(nodenv root)"/plugins
      hubot@example:~/.nodenv$ git clone https://github.com/nodenv/node-build.git "$(nodenv root)"/plugins/node-build
      
      # インストール状況確認
      hubot@example:~/.nodenv$ curl -fsSL https://github.com/nodenv/nodenv-installer/raw/master/bin/nodenv-doctor | bash
      Checking for `nodenv' in PATH: /home/hubot/.nodenv/bin/nodenv
      Checking for nodenv shims in PATH: OK
      Checking `nodenv install' support: /home/hubot/.nodenv/plugins/node-build/bin/nodenv-install (node-build 4.6.3-19-g60355310)
      Counting installed Node versions: none
      There aren't any Node versions installed under `/home/hubot/.nodenv/versions'.
      You can install Node versions like so: nodenv install 2.2.4
      Auditing installed plugins: OK
      
    2. NodeJS インストール

      現時点での最新 LTS バージョンである 10.16.3 をインストール

      hubot@example:~/.nodenv$ nodenv install 10.16.3
      hubot@example:~/.nodenv$ nodenv global 10.16.3
      hubot@example:~/.nodenv$ nodenv versions
      * 10.16.3 (set by /home/hubot/.nodenv/version)
      hubot@example:~/.nodenv$ which node
      /home/hubot/.nodenv/shims/node
      hubot@example:~/.nodenv$ which npm
      /home/hubot/.nodenv/shims/npm
      
  4. ベアリポジトリの設置
    1. ベアリポジトリの作成
      hubot@example:~$ mkdir ~/myhubot.git
      hubot@example:~$ cd ~/myhubot.git/
      hubot@example:~/myhubot.git$ git init --bare --shared
      
    2. クライアントから SSH 経由でリポジトリにアクセスできるように自身の公開鍵を登録
      hubot@example:~$ mkdir -p ~/.ssh
      hubot@example:~$ chmod 700 ~/.ssh
      hubot@example:~$ echo 'ssh-rsa AAAAB...........' >> ~/.ssh/authorized_keys
      hubot@example:~$ chmod 600 ~/.ssh/authorized_keys
      
  5. クライアント PC で Hubot を作成

    いったんクライアント PC に戻り、 Getting Started With Hubot | HUBOT にしたがって Hubot を作成する

    • この例は Windows 10 の Windows Subsystem for Linux (WSL)を使っています。
    • Git と NodeJS がインストールされており、npm が使用できる状態です。
    1. Hubot を新規作成
         mistymagich@mistymagich-pc:/mnt/d/Works/example$ npm install -g yo generator-hubot
         mistymagich@mistymagich-pc:/mnt/d/Works/example$ mkdir myhubot
         mistymagich@mistymagich-pc:/mnt/d/Works/example$ cd myhubot
         mistymagich@mistymagich-pc:/mnt/d/Works/example/myhubot$ yo hubot --defaults --adapter slack
                             _____________________________
                             /                             \
         //\              |      Extracting input for    |
         ////\    _____    |   self-replication process   |
         //////\  /_____\   \                             /
         ======= |[^_/\_]|   /----------------------------
         |   | _|___@@__|__
         +===+/  ///     \_\
         | |_\ /// HUBOT/\\
         |___/\//      /  \\
                 \      /   +---+
                 \____/    |   |
                 | //|    +===+
                     \//      |xx|
      
         create bin/hubot
         create bin/hubot.cmd
         create Procfile
         create README.md
         create external-scripts.json
         create hubot-scripts.json
         create .gitignore
         create package.json
         create scripts/example.coffee
         create .editorconfig
                             _____________________________
         _____              /                             \
         \    \             |   Self-replication process   |
         |    |    _____    |          complete...         |
         |__\\|   /_____\   \     Good luck with that.    /
         |//+  |[^_/\_]|   /----------------------------
         |   | _|___@@__|__
         +===+/  ///     \_\
         | |_\ /// HUBOT/\\
         |___/\//      /  \\
                 \      /   +---+
                 \____/    |   |
                 | //|    +===+
                     \//      |xx|
      
         npm notice created a lockfile as package-lock.json. You should commit this file.
         + hubot-google-images@0.2.7
         + hubot-diagnostics@1.0.0
         + hubot-pugme@0.1.1
         + hubot-help@1.0.1
         + hubot-maps@0.0.3
         + hubot-heroku-keepalive@1.0.3
         + hubot-google-translate@0.2.1
         + hubot-redis-brain@1.0.0
         + hubot-scripts@2.17.2
         + hubot@3.3.2
         + hubot-rules@1.0.0
         + hubot-shipit@0.2.1
         + hubot-slack@4.7.1
         added 159 packages from 127 contributors and audited 274 packages in 29.657s
         found 0 vulnerabilities
         mistymagich@mistymagich-pc:/mnt/d/Works/example/myhubot$ git init
         Initialized empty Git repository in /mnt/d/Works/example/myhubot/.git/
         mistymagich@mistymagich-pc:/mnt/d/Works/example/myhubot$ git add .
         mistymagich@mistymagich-pc:/mnt/d/Works/example/myhubot$ git commit -m "Initial commit"
         [master (root-commit) c6a6ed2] Initial commit
         11 files changed, 1529 insertions(+)
         create mode 100644 .editorconfig
         create mode 100644 .gitignore
         create mode 100644 Procfile
         create mode 100644 README.md
         create mode 100644 bin/hubot
         create mode 100644 bin/hubot.cmd
         create mode 100644 external-scripts.json
         create mode 100644 hubot-scripts.json
         create mode 100644 package-lock.json
         create mode 100644 package.json
         create mode 100644 scripts/example.coffee
         mistymagich@mistymagich-pc:/mnt/d/Works/example/myhubot$ git remote add deploy ssh://hubot@example.jp:/home/hubot/myhubot.git
         mistymagich@mistymagich-pc:/mnt/d/Works/example/myhubot$ git push deploy master
         Counting objects: 15, done.
         Delta compression using up to 4 threads.
         Compressing objects: 100% (11/11), done.
         Writing objects: 100% (15/15), 17.93 KiB | 2.24 MiB/s, done.
         Total 15 (delta 0), reused 0 (delta 0)
         To ssh://example.jp:/home/hubot/myhubot.git
         * [new branch]      master -> master
      
    2. 起動に必要なファイルの追加・変更
      • 起動

        後述の systemctl --user start hubot の際に npm start で Hubot が起動できるように、npm-scripts を追加する

        ローカル開発用に npm run dev も追加

        • (変更) package.json
          @@ -21,5 +21,9 @@
            },
            "engines": {
              "node": "0.10.x"
          +  },
          +  "scripts": {
          +    "start": "bin/hubot -a slack",
          +    "dev": "bin/hubot"
            }
          }
          
      • 起動時設定

        アダプターや追加するスクリプトの設定を環境変数で行うことが多いので、npm start 時にそれらを定義した環境設定ファイルを読み込むための bootstrap スクリプトを追加し、それを組み込む

        • (追加) bin/bootstrap
          #!/bin/bash
          
          export NODE_ENV=${NODE_ENV:-development}
          
          if [ -f .env ]; then
              source .env
          fi
          
          if [ -f .env.${NODE_ENV} ]; then
              source .env.${NODE_ENV}
          fi
          
          • (変更) bin/hubot
              @@ -1,7 +1,7 @@
              -#!/bin/sh
              +#!/bin/bash
          
               set -e
          
              +source bin/bootstrap
               npm install
               export PATH="node_modules/.bin:node_modules/hubot/node_modules/.bin:\$PATH"
          
    3. 環境変数定義ファイルを追加

      上記で .env ファイルの読み込みに対応できたので、ファイルを新規作成する

      使い方としては、.env はひな形で必要な環境変数を列挙またはデフォルト値のセットだけを行い、各環境ごとの設定値は .env からコピーして .env.${NODE_ENV} にそれぞれ定義する

      .env.${NODE_ENV} は Git の管理下にはおかない

      • .env
        # hubot
        ## ログレベル(error,warn,info,debug)
        export HUBOT_LOG_LEVEL=error
        
        # slack
        ## API Token
        export HUBOT_SLACK_TOKEN=
        
      • .env.development

        npm run dev の時はアダプタはデフォルト(シェル)なので、アダプタ関連の設定値は不要

            export HUBOT_LOG_LEVEL=debug
        
    4. npm run dev のテスト
      mistymagich@mistymagich-pc:/mnt/d/Works/example$ npm run dev
      myhubot> myhubot echo test
      myhubot> [Tue Mmm dd 2019 14:20:14 GMT+0900 (GMT+09:00)] DEBUG Message 'myhubot echo test' matched regex //^\s*[@]?myhubot[:,]?\s*(?:ECHO (.*)$)/i/; listener.options = { id: null }
      [Tue Mmm dd 2019 14:20:14 GMT+0900 (GMT+09:00)] DEBUG Executing listener callback for Message 'myhubot echo test'
      test
      myhubot> exit
      
    5. 上記ファイルをコミット&プッシュ

      このとき、Windows だと bin/hubot の実行属性が取れているとことがあるので、明示的に付与する。

      mistymagich@mistymagich-pc:/mnt/d/Works/example/myhubot$ echo ".env.*" >> .gitignore
      mistymagich@mistymagich-pc:/mnt/d/Works/example/myhubot$ git update-index --add --chmod=+x bin/hubot
      mistymagich@mistymagich-pc:/mnt/d/Works/example/myhubot$ git add .gitignore bin/hubot package.json .env bin/bootstrap
      mistymagich@mistymagich-pc:/mnt/d/Works/example/myhubot$ git commit
      mistymagich@mistymagich-pc:/mnt/d/Works/example/myhubot$ git push deploy master
      
  6. Hubot 設置先にクローン
    hubot@example:~$ git clone myhubot.git ~/myhubot
    Cloning into 'myhubot'...
    done.
    hubot@example:~$ ls -F
    myhubot/  myhubot.git/
    hubot@example:~$ ls -F myhubot
    Procfile  README.md  bin/  external-scripts.json  hubot-scripts.json  package-lock.json  package.json  scripts/
    hubot@example:~$ cd myhubot
    hubot@example:~/myhubot$ npm install
    
  7. 本番用環境設定ファイルを作成
    hubot@example:~$ cd ~/myhubot
    hubot@example:~/myhubot$ cp .env .env.production
    
    • .env.production

      .env.${NODE_ENV}.env の値を上書きするので、デフォルト値そのまままのような、上書きが不要な場合は省略できる(この例だと HUBOT_LOG_LEVEL)

      export HUBOT_SLACK_TOKEN=xoxb-YOUR-TOKEN-HERE
      
  8. Hubot 起動テスト
    hubot@example:~/myhubot$ NODE_ENV=production npm start
    
    > myhubot@0.0.0 start /home/hubot/myhubot
    > bin/hubot -a slack
    
    audited 274 packages in 1.459s
    found 0 vulnerabilities
    
    [Tue Mmm dd 2019 05:57:52 GMT+0000 (Coordinated Universal Time)] INFO hubot-slack adapter v4.7.1
    warn: SlackDataStore is deprecated and will be removed in the next major version. See project documentation for a migration guide.
    warn: SlackDataStore is deprecated and will be removed in the next major version. See project documentation for a migration guide.
    [Tue Mmm dd 2019 05:57:52 GMT+0000 (Coordinated Universal Time)] INFO Logged in as @myhubot in workspace ADF Project
    [Tue Mmm dd 2019 05:57:54 GMT+0000 (Coordinated Universal Time)] INFO Connected to Slack RTM
    [Tue Mmm dd 2019 05:57:54 GMT+0000 (Coordinated Universal Time)] WARNING Loading scripts from hubot-scripts.json is deprecated and will be removed in 3.0 (https://github.com/github/hubot-scripts/issues/1113) in favor of packages for each script.
    
    Your hubot-scripts.json is empty, so you just need to remove it.
    [Tue Mmm dd 2019 05:57:54 GMT+0000 (Coordinated Universal Time)] INFO hubot-redis-brain: Using default redis on localhost:6379
    [Tue Mmm dd 2019 05:57:54 GMT+0000 (Coordinated Universal Time)] ERROR hubot-heroku-keepalive included, but missing HUBOT_HEROKU_KEEPALIVE_URL. `heroku config:set HUBOT_HEROKU_KEEPALIVE_URL=$(heroku apps:info -s | grep web.url | cut -d= -f2)`
    

    qownnotes-media-oRtdQv

  9. systemd による永続化

    参考:systemd/ユーザー – ArchWiki

    Hubot を永続化するために systemd を使用する。通常であれば systemctl start hubot / systemctl stop hubot を行う場合 root 権限が必要だが、systemd にはユーザー用に systemd インスタンスを実行させてサービスを管理できる機能が提供されているので、今回はそれを使用する

    1. systemd のユーザーインスタンスを自動起動

      参考:systemd/ユーザー#systemd のユーザーインスタンスを自動起動 – ArchWiki

      systemd のユーザーインスタンスはデフォルトではユーザーのログイン時に実行され、ユーザーのセッションが閉じられた時に終了します。今回はユーザーのログイン関係なく、Hubot が起動し続けて欲しいので、ブート時にインスタンスが起動するようにします。

      hubot@example:~$ loginctl enable-linger hubot
      
      # loginctl enable-linger hubot 実行前
      hubot@example:~$ ps faux
      USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
      :
      :
      vagrant   3539  0.0  0.1  76624  7492 ?        Ss   02:57   0:00 /lib/systemd/systemd --user
      vagrant   3540  0.0  0.0 193872  2652 ?        S    02:57   0:00  \_ (sd-pam)
      
      # loginctl enable-linger hubot 実行後
      hubot@example:~$ ps faux
      USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
      :
      :
      vagrant   3539  0.0  0.1  76624  7492 ?        Ss   02:57   0:00 /lib/systemd/systemd --user
      vagrant   3540  0.0  0.0 193872  2652 ?        S    02:57   0:00  \_ (sd-pam)
      hubot     9931  0.0  0.1  76628  7516 ?        Ss   06:02   0:00 /lib/systemd/systemd --user
      hubot     9932  0.0  0.0 193872  2652 ?        S    06:02   0:00  \_ (sd-pam)
      
    2. ユニットファイル作成
      hubot@example:~$ mkdir -p ~/.config/systemd/user
      hubot@example:~$ cat <> ~/.config/systemd/user/hubot.service
      ; Hubot systemd service unit file
      
      [Unit]
      Description=Hubot
      
      [Service]
      Type=simple
      WorkingDirectory=/home/hubot/myhubot
      
      Restart=always
      RestartSec=10
      
      ; Configure Hubot environment variables, use quotes around vars with whitespace as shown below.
      Environment="NODENV_ROOT=/home/hubot/.nodenv"
      Environment="NODE_ENV=production"
      
      ExecStart=/home/hubot/.nodenv/bin/nodenv exec npm start
      
      [Install]
      WantedBy=default.target
      EOT
      
    3. サービス起動
      hubot@example:~$ export XDG_RUNTIME_DIR="/run/user/$UID"
      hubot@example:~$ systemctl --user daemon-reload
      hubot@example:~$ systemctl --user start hubot
      hubot@example:~$ systemctl --user status hubot
      ● hubot.service - Hubot
      Loaded: loaded (/home/hubot/.config/systemd/user/hubot.service; disabled; vendor preset: enabled)
      Active: active (running) since Tue 2019-08-27 06:18:17 UTC; 6s ago
      Main PID: 10148 (npm)
      CGroup: /user.slice/user-1002.slice/user@1002.service/hubot.service
              ├─10148 npm
              ├─10171 sh -c bin/hubot -a slack
              └─10172 node node_modules/.bin/coffee /home/hubot/myhubot/node_modules/.bin/hubot --name myhubot -a slack
      
      Mmm dd 06:18:19 example nodenv[10148]: found 0 vulnerabilities
      Mmm dd 06:18:20 example nodenv[10148]: [Tue Mmm dd 2019 06:18:20 GMT+0000 (Coordinated Universal Time)] INFO hubot-slack adapter v4.7.1
      Mmm dd 06:18:20 example nodenv[10148]: warn: SlackDataStore is deprecated and will be removed in the next major version. See project documentation for a migration guide.
      Mmm dd 06:18:20 example nodenv[10148]: warn: SlackDataStore is deprecated and will be removed in the next major version. See project documentation for a migration guide.
      Mmm dd 06:18:21 example nodenv[10148]: [Tue Mmm dd 2019 06:18:21 GMT+0000 (Coordinated Universal Time)] INFO Logged in as @myhubot in workspace ADF Project
      Mmm dd 06:18:21 example nodenv[10148]: [Tue Mmm dd 2019 06:18:21 GMT+0000 (Coordinated Universal Time)] INFO Connected to Slack RTM
      Mmm dd 06:18:21 example nodenv[10148]: [Tue Mmm dd 2019 06:18:21 GMT+0000 (Coordinated Universal Time)] WARNING Loading scripts from hubot-scripts.json is deprecated and will be removed in 3.0 (
      Mmm dd 06:18:21 example nodenv[10148]: Your hubot-scripts.json is empty, so you just need to remove it.
      Mmm dd 06:18:21 example nodenv[10148]: [Tue Mmm dd 2019 06:18:21 GMT+0000 (Coordinated Universal Time)] INFO hubot-redis-brain: Using default redis on localhost:6379
      Mmm dd 06:18:21 example nodenv[10148]: [Tue Mmm dd 2019 06:18:21 GMT+0000 (Coordinated Universal Time)] ERROR hubot-heroku-keepalive included, but missing HUBOT_HEROKU_KEEPALIVE_URL. `heroku con
      

      ログイン時に XDG_RUNTIME_DIR をセットするようにしておく

      echo 'export XDG_RUNTIME_DIR="/run/user/$UID"' >> ~/.bashrc
      
    4. 自動起動
      hubot@example:~$ systemctl --user enable hubot
      Created symlink /home/hubot/.config/systemd/user/default.target.wants/hubot.service → /home/hubot/.config/systemd/user/hubot.service.
      
  10. Git hook スクリプト追加

    ベアリポジトリに post-receive フックを追加

    リポジトリプッシュ時にコードを最新にしたうえで、Hubot を再起動させる

    hubot@example:~$ cat <> myhubot.git/hooks/post-receive
    #!/bin/bash
    
    export XDG_RUNTIME_DIR="/run/user/$UID"
    
    cd ../myhubot
    git --git-dir=.git pull
    systemctl --user restart hubot
    EOT
    hubot@example:~$ chmod +x myhubot.git/hooks/post-receive
    

運用

scripts/example.coffeeにあるコードを使って確認してみる

現在の状態、まだスクリプトを追加していないので、何も起こらない

qownnotes-media-aAGRDn

クライアント PC でコードを変更

mistymagich@mistymagich-pc:/mnt/d/Works/example/myhubot$ git diff
diff --git a/scripts/example.coffee b/scripts/example.coffee
index 7c9839c..1d44f5e 100644
--- a/scripts/example.coffee
+++ b/scripts/example.coffee
@@ -10,8 +10,8 @@

 module.exports = (robot) ->

-  # robot.hear /badger/i, (res) ->
-  #   res.send "Badgers? BADGERS? WE DON'T NEED NO STINKIN BADGERS"
+  robot.hear /badger/i, (res) ->
+    res.send "Badgers? BADGERS? WE DON'T NEED NO STINKIN BADGERS"
   #
   # robot.respond /open the (.*) doors/i, (res) ->
   #   doorType = res.match[1]

コミット&プッシュ

mistymagich@mistymagich-pc:/mnt/d/Works/example/myhubot$ git commit
mistymagich@mistymagich-pc:/mnt/d/Works/example/myhubot$ git push deploy master
Counting objects: 4, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (4/4), 352 bytes | 176.00 KiB/s, done.
Total 4 (delta 2), reused 0 (delta 0)
remote: From /home/hubot/myhubot
remote:    f499e14..e551510  master     -> origin/master
remote: Updating f499e14..e551510
remote: Fast-forward
remote:  scripts/example.coffee | 4 ++--
remote:  1 file changed, 2 insertions(+), 2 deletions(-)
To ssh://example.jp:/home/hubot/myhubot.git
   a450ad8..e551510  master -> master

プッシュと同時にコードの更新と Hubbot の再起動が行われ、追加したスクリプトが反映されている

qownnotes-media-yqiScT

その他運用

  • npm install したパッケージのみが更新した場合はどうするのか?

    クライアント PC 側で npm install or update などで更新、 npm run dev で動作確認、変更された package-lock.json を含めてコミットしてプッシュ

エラー事例

ここまでくるのに遭遇したエラー

  • systemctl --user start hubot で Hubot サービス起動時に「Failed to connect to bus: No such file or directory」
    hubot@example:~$ systemctl --user daemon-reload
    Failed to connect to bus: No such file or directory
    

    起動が失敗するのは、systemd – systemctl –user not available for www-data user – Unix & Linux Stack Exchange より、環境変数 XDG_RUNTIME_DIR が設定されていないため。systemd --userを実行してみると、以下のようなエラー出力になる

    hubot@example:~$ systemd --user
    Trying to run as user instance, but $XDG_RUNTIME_DIR is not set.
    

    そのため

    export XDG_RUNTIME_DIR="/run/user/$UID" であらかじめ設定しておく

    また、loginctl enable-lingerを実行していないと、シェルを閉じたときに同時に /run/user/$UID ディレクトリが削除される ため起動した Hubot が終了します

  • systemctl --user start hubotで Hubot サービス起動時に下記エラーが発生する

    Mmm dd 07:30:43 example systemd[1928]: Started Hubot.
    Mmm dd 07:30:43 example systemd[2434]: hubot.service: Failed to determine supplementary groups: Operation not permitted
    Mmm dd 07:30:43 example systemd[2434]: hubot.service: Failed at step GROUP spawning /home/hubot/.nodenv/bin/nodenv: Operation not permitted
    Mmm dd 07:30:43 example systemd[1928]: hubot.service: Main process exited, code=exited, status=216/GROUP
    Mmm dd 07:30:43 example systemd[1928]: hubot.service: Failed with result 'exit-code'.
    

    raspberry pi3 – Systemctl – Failed at step Group spawning – Stack Overflowより、ユニットファイルからService セクションUser, Groupを削除する

  • ユニットファイルには Install セクションを必ず書く

    サーバー起動時に非 root ユーザーで systemd を使ってサービスを立ち上げる – Qiita

    linux – systemd:特定のサービスを変更する権限を与えられていないユーザに許可する – コードログ

  • ユニットファイル内でExecStart=npm startでサービスを起動しようとしたら、Executable path is not absolute: npm start

    Use with systemd · nodenv/nodenv Wikiより明示的に nodenv を指定する

    ExecStart=/home/hubot/.nodenv/bin/nodenv exec npm start

  • Hubot のサンプルユニットファイル との差分

    @@ -1,27 +1,20 @@
    ; Hubot systemd service unit file
    -; Place in e.g. `/etc/systemd/system/hubot.service`, then `systemctl daemon-reload`   and `service hubot start`.
    
    [Unit]
    Description=Hubot
    -Requires=network.target
    -After=network.target
    
    [Service]
    Type=simple
    -WorkingDirectory=/path/to/hubot
    -User=change-to-hubot-user
    +WorkingDirectory=/home/hubot/myhubot
    
    Restart=always
    RestartSec=10
    
    ; Configure Hubot environment variables, use quotes around vars with whitespace as   shown below.
    -Environment="HUBOT_aaa=xxx"
    -Environment="HUBOT_bbb='yyy yyy'"
    +Environment="NODENV_ROOT=/home/hubot/.nodenv"
    +Environment="NODE_ENV=production"
    
    -; Alternatively multiple environment variables can loaded from an external file
    -;EnvironmentFile=/etc/hubot-environment
    -
    -ExecStart=/path/to/hubot/bin/hubot --adapter zzz
    +ExecStart=/home/hubot/.nodenv/bin/nodenv exec npm start
    
    [Install]
    -WantedBy=multi-user.target
    +WantedBy=default.target
    
    
広告

自己署名証明書を生成してくれるdockerコンテナOMGWTFSSLを試す

ローカルでの開発などでLet’s Encryptを使用できないときに、従来の方法で自己署名証明書が必要な際、このOMGWTFSSLを使って証明書を生成すると、プライベート認証局の証明書とそれで署名したサーバ証明書を得ることができます。

プライベート認証局の証明書をブラウザに登録し、サーバ証明書をApache,NGIXなどに設定すると、例外設定せずにhttpsでアクセスすることができます。

paulczar/omgwtfssl: SSL certificate generation for developers who don’t TLS good

OpenSSLを使った従来の方法

OMGWTFSSLを使った方法

前提

  • OSはUbuntu 18.04
  • WEBサーバとしてApacheを使用
  • Dockerはインストール済み
  • foo.example.jpというドメイン名でアクセスできるように設定済み

証明書の生成とインストール

  1. OMGWTFSSLコンテナを実行
    $ docker run -e SSL_SUBJECT="foo.example.jp" -v /tmp/certs:/certs  paulczar/omgwtfssl
    ----------------------------
    | OMGWTFSSL Cert Generator |
    ----------------------------
    
    --> Certificate Authority
    ====> Generating new CA key ca-key.pem
    Generating RSA private key, 2048 bit long modulus
    ....................................................+++
    .+++
    e is 65537 (0x10001)
    ====> Generating new CA Certificate ca.pem
    ====> Generating new config file openssl.cnf
    ====> Generating new SSL KEY key.pem
    Generating RSA private key, 2048 bit long modulus
    ....................................................................................................................................................................................................................+++
    ....................................................+++
    e is 65537 (0x10001)
    ====> Generating new SSL CSR key.csr
    ====> Generating new SSL CERT cert.pem
    Signature ok
    subject=/CN=foo.example.jp
    Getting CA Private Key
    ====> Complete
    keys can be found in volume mapped to /certs
    
    ====> Output results as YAML
    ---
    ca_key: |
      -----BEGIN RSA PRIVATE KEY-----
      :
      :
      :
      -----END RSA PRIVATE KEY-----
    
    ca_crt: |
      -----BEGIN CERTIFICATE-----
      :
      :
      :
      -----END CERTIFICATE-----
    
    ssl_key: |
      -----BEGIN RSA PRIVATE KEY-----
      :
      :
      :
      -----END RSA PRIVATE KEY-----
    
    ssl_csr: |
      -----BEGIN CERTIFICATE REQUEST-----
      :
      :
      :
      -----END CERTIFICATE REQUEST-----
    
    ssl_crt: |
      -----BEGIN CERTIFICATE-----
      :
      :
      :
      -----END CERTIFICATE-----
    
  2. 生成されたファイルの確認
    $ ls -l /tmp/certs
    total 32
    -rw-r--r-- 1 root root 1679 Apr 15 07:51 ca-key.pem
    -rw-r--r-- 1 root root  973 Apr 15 07:51 ca.pem
    -rw-r--r-- 1 root root   17 Apr 15 07:51 ca.srl
    -rw-r--r-- 1 root root 1066 Apr 15 07:51 cert.pem
    -rw-r--r-- 1 root root  997 Apr 15 07:51 key.csr
    -rw-r--r-- 1 root root 1675 Apr 15 07:51 key.pem
    -rw-r--r-- 1 root root  241 Apr 15 07:51 openssl.cnf
    -rw-r--r-- 1 root root 3791 Apr 15 07:51 secret.yaml
    

    プライベート認証局の証明書と秘密鍵、サーバ証明書とその秘密鍵などが生成されている

  3. サーバ証明書を移動

    ApacheのデフォルトSSL設定の/etc/apache2/sites-available/default-ssl.confにある、SSLCertificateFileSSLCertificateKeyFileのパスがそれぞれ、/etc/ssl/certs//etc/ssl/private/にあるので、それにならってファイルを移動

    $ sudo mv /tmp/certs/cert.pem /etc/ssl/certs/
    $ sudo mv /tmp/certs/key.pem /etc/ssl/private/
    
  4. サイトの追加

    Apacheにfoo.example.jp用の設定を追加する

    $ sudo vi /etc/apache2/sites-available/999-foo.conf
    
    <VirtualHost *:443>
        ServerName foo.example.jp
    
        DocumentRoot /var/www/html
    
        SSLEngine On
        SSLCertificateFile      /etc/ssl/certs/cert.pem
        SSLCertificateKeyFile   /etc/ssl/private/key.pem
    </VirtualHost>
    

    必要に応じてSSLOptionsなども追加する

  5. ApacheのSSLモジュール有効化

    $ sudo a2enmod ssl
    
  6. サイト設定を有効化
    $ sudo a2ensite 999-foo
    
  7. Apache再起動
    $ sudo systemctl restart apache2
    

ブラウザに認証局証明書のインストール

https://foo.example.jp にアクセスすると、証明書の発行元不明エラーが出るのでブラウザに認証局証明書をインストールする

  • Firefox
    firefoxでfoo.example.jpにアクセス
    SSL警告内容詳細
  • Chrome
    chromeでfoo.example.jpにアクセス

Firefoxの場合

  1. Firefoxオプションを開く

    オプション→プライバシーとセキュリティー→証明書

    firefox証明書オプション

  2. 証明書マネージャーを開く

    証明書を表示ボタンを押下して証明書マネージャーを開く

    firefox証明書マネージャー

  3. 認証局証明書をインポート

    1. 認証局証明書タブを選択して、その下のインポートボタンを押下

    2. /tmp/certs/ca.pemをダウンロードやファイル移動などで取得し、それを選択

    3. 証明書のインポートダイアログが表示されるので、この認証局によるウェブサイトの識別を信頼するにチェックしてOKボタンを押下

      認証局証明書のインポート

    正常にインポートできていれば、認証局証明書のリストにtest-caが追加されている

    認証局証明書のインポート後

  4. 再度Firefoxで https://foo.example.jp にアクセス

    認証局証明書のインストール後

Chrome(Windows)の場合

Windows版のChromeはインターネットオプションにある証明書を使用しているため、Chromeの設定から開く方法と、コントロールパネルから開く方法がある

  1. 証明書のインポートウィザートでインポートする証明書を選択する

    その際、デフォルトの拡張子がX.509証明書(*.cer, *.crt)となっていることがあるため、拡張子がpemのca.pemが表示されない

    ファイル名横のリストをすべてのファイル(*.*)に変更することで選択可能になる

    証明書インポートウィザード1

  2. インポートする先(証明書ストア)は信頼されたルート証明機関を選択する

    証明書インポートウィザード2

  3. セキュリティ警告ダイアログが表示されるのでOKを押下

  4. 正常にインポートできていれば、信頼されたルート証明機関のリストにtest-caが追加されている

    Chromeでルート証明書インポート後

  5. https://foo.example.jp にChromeでアクセス

    Chromeでルート証明書インストール後

    Firefoxと違ってエラー(NET::ERR_CERT_COMMON_NAME_INVALID)が表示されたまま

    Chrome58で、HTTPSの自己証明書が NET::ERR_CERT_COMMON_NAME_INVALID になる場合の対応より、サーバ証明書にX509v3 Subject Alternative Nameを含むようにしないといけない

  6. オプションを追加してサーバ証明書を再度生成する

    $ docker run -e SSL_SUBJECT="foo.example.jp" -e SSL_DNS="foo.example.jp" -v /tmp/certs:/certs  paulczar/omgwtfssl
    ----------------------------
    | OMGWTFSSL Cert Generator |
    ----------------------------
    
    --> Certificate Authority
    ====> Using existing CA Key ca-key.pem
    ====> Using existing CA Certificate ca.pem
    ====> Generating new config file openssl.cnf
    ====> Generating new SSL KEY key.pem
    Generating RSA private key, 2048 bit long modulus
    .........................................................+++
    ...................................................+++
    e is 65537 (0x10001)
    ====> Generating new SSL CSR key.csr
    ====> Generating new SSL CERT cert.pem
    Signature ok
    subject=/CN=foo.example.jp
    Getting CA Private Key
    ====> Complete
    keys can be found in volume mapped to /certs
    
    ====> Output results as YAML
    ---
    ca_key: |
      -----BEGIN RSA PRIVATE KEY-----
       :
       :
      -----END RSA PRIVATE KEY-----
    
    ca_crt: |
      -----BEGIN CERTIFICATE-----
       :
       :
       :
      -----END CERTIFICATE-----
    
    ssl_key: |
      -----BEGIN RSA PRIVATE KEY-----
       :
       :
       :
      -----END RSA PRIVATE KEY-----
    
    ssl_csr: |
      -----BEGIN CERTIFICATE REQUEST-----
       :
       :
       :
      -----END CERTIFICATE REQUEST-----
    
    ssl_crt: |
      -----BEGIN CERTIFICATE-----
       :
       :
       :
      -----END CERTIFICATE-----
    

    生成されたサーバ証明書を確認する

    $ openssl x509 -in /tmp/certs/cert.pem -text
    Certificate:
         :
         :
         :
            Issuer: CN = test-ca
         :
         :
         :
            Subject: CN = foo.example.jp
         :
         :
         :
            X509v3 extensions:
                X509v3 Basic Constraints:
                    CA:FALSE
                X509v3 Key Usage:
                    Digital Signature, Non Repudiation, Key Encipherment
                X509v3 Extended Key Usage:
                    TLS Web Client Authentication, TLS Web Server Authentication
                X509v3 Subject Alternative Name:
                    DNS:foo.example.jp, DNS:foo.example.jp
         :
         :
         :
    

    ※ paulczar/omgwtfsslが使用しているベースコンテナのalpineの仕様変更のためか、SSL_DNSオプションを追加しても「X509v3 Subject Alternative Name」が出力されなくなっています。paulczar/omgwtfsslからフォークされたstakater/ssl-certs-generator:1.0を使用すると出力されました(2019/05/09)

    $ docker run -e SSL_SUBJECT="foo.example.jp" -e SSL_DNS="foo.example.jp" -v /tmp/certs:/certs stakater/ssl-certs-generator:1.0
    
  7. 認証局の証明書と鍵が生成済みの場合、サーバ証明書への署名はそれを利用するので認証局証明書は再度インポートする必要はない
    ====> Using existing CA Key ca-key.pem
    ====> Using existing CA Certificate ca.pem
    
  8. 生成したサーバ証明書を上書きする
    $ sudo cp /tmp/certs/cert.pem /etc/ssl/certs/
    $ sudo cp /tmp/certs/key.pem /etc/ssl/private/
    $ sudo systemctl reload apache2
    
  9. Chromeでアクセスして確認

    証明書更新後にChromeでアクセス

Firefox SendをDockerで動かす

前提

  • OSはUbuntu 18.04
  • docker, docker-compose
  • サーバにホスト名send.example.jpでアクセスできるように設定済み

Dockerで動かす

  1. 設置先を作成
    $ sudo mkdir -p /var/www/firefox-send
    $ sudo chown $USER /var/www/firefox-send
    
  2. データ保存フォルダを作成
    $ mkdir /var/www/firefox-send/data
    $ chmod 777 /var/www/firefox-send/data
    
  3. Firefox Sendのコードをクローン
    $ cd /var/www/firefox-send
    $ git clone https://github.com/mozilla/send.git
    
  4. 最新版(記事時点で3.0.9)のタグに切り替える
    $ cd send
    $ git checkout refs/tags/v3.0.9
    
  5. Firefox Sendのマニュアルに従いセットアップ
    $ npm install
    $ npm run build
    
  6. docker-compose.yml編集
    • seleniumは不要なので削除
      @@ -10,10 +10,3 @@
             - REDIS_HOST=redis
         redis:
           image: redis:alpine
      -  selenium-firefox:
      -    image: b4handjr/selenium-firefox
      -    ports:
      -      - "${VNC_PORT:-5900}:5900"
      -    shm_size: 2g
      -    volumes:
      -      - .:/code
      
    • server/config.jsを参考に、最大ファイルサイズ(デフォルト1G)、最大ダウンロード可能回数(デフォルト5回)、最大ダウンロード有効期限(デフォルト1日)を
      それぞれ2G, 100回, 604800秒(7日)に変更する

      @@ -8,5 +8,8 @@
             - "1443:1443"
           environment:
             - REDIS_HOST=redis
      +      - ANON_MAX_FILE_SIZE=2147483648
      +      - ANON_MAX_DOWNLOADS=100
      +      - ANON_MAX_EXPIRE_SECONDS=604800
         redis:
           image: redis:alpine
      
    • アップロードされたファイルの保存先はデフォルトではos.tmpdir()とランダム文字列のフォルダ名で作成されるため、マウントできるように固定化し、ホストと接続する
      @@ -11,5 +11,8 @@
             - ANON_MAX_FILE_SIZE=2147483648
             - ANON_MAX_DOWNLOADS=100
             - ANON_MAX_EXPIRE_SECONDS=604800
      +      - FILE_DIR=/tmp/send
      +    volumes:
      +      - ../data:/tmp/send
         redis:
           image: redis:alpine
      

    最終形

    version: "3"
    services:
      web:
        build: .
        links:
          - redis
        ports:
          - "1443:1443"
        environment:
          - REDIS_HOST=redis
          - ANON_MAX_FILE_SIZE=2147483648
          - ANON_MAX_DOWNLOADS=100
          - ANON_MAX_EXPIRE_SECONDS=604800
          - FILE_DIR=/tmp/send
        volumes:
          - ../data:/tmp/send
      redis:
        image: redis:alpine
    
  7. コンテナ起動

    初回はイメージのダウンロード、イメージのビルドが行われたのち、起動する。

    $ docker-compose up
    Creating network "send_default" with the default driver
    Pulling redis (redis:alpine)...
    :
    :
    :
    Building web
    :
    :
    :
    Successfully built 6b7d96723980
    Successfully tagged send_web:latest
    WARNING: Image for service web was built because it did not already exist. To rebuild this image you must use `docker-compose build` or `docker-compose up --build`.
    Creating send_redis_1 ... done
    Creating send_web_1   ... done
    Attaching to send_redis_1, send_web_1
    redis_1  | 1:C 12 Apr 2019 06:19:49.928 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
    redis_1  | 1:C 12 Apr 2019 06:19:49.929 # Redis version=5.0.4, bits=64, commit=00000000, modified=0, pid=1, just started
    redis_1  | 1:C 12 Apr 2019 06:19:49.929 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
    redis_1  | 1:M 12 Apr 2019 06:19:49.931 * Running mode=standalone, port=6379.
    redis_1  | 1:M 12 Apr 2019 06:19:49.931 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
    redis_1  | 1:M 12 Apr 2019 06:19:49.931 # Server initialized
    redis_1  | 1:M 12 Apr 2019 06:19:49.931 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.
    redis_1  | 1:M 12 Apr 2019 06:19:49.931 # WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled.
    redis_1  | 1:M 12 Apr 2019 06:19:49.931 * Ready to accept connections
    

    正常起動が確認できれば、-dオプションをつけてバックグランド起動する。

  8. http://send.example.jp:1443 でアクセス確認

    Topページ

    1. ファイルのアップロードを確認

      セット
      UP中
      UP完了

    2. ファイルが保存されているのを確認

      $ ls -hl /var/www/firefox-send/data/
      total 101M
      -rw-r--r-- 1 10001 10001 101M Apr 12 07:04 1-cf9812b3fc33ef68
      
    3. ダウンロード確認

      UP後Top
      DL
      DL中
      DL後

Apacheのプロキシ連携

http://send.example.jp:1443 でアクセスしているのを、 http://send.example.jp でアクセスするためにApacheを使用する。

Apacheはインストール済みとする。(apt install apache2)

プロキシ設定は、node.js – WebSockets and Apache proxy : how to configure mod_proxy_wstunnel? – Stack Overflowを参考にした。

  1. サイト設定ファイルを追加
    $ sudo vi /etc/apache2/sites-available/999-send.conf
    

    内容

    <VirtualHost *:80>
        ServerName send.example.jp
    
        ProxyRequests Off
        ProxyVia Off
        ProxyPreserveHost On
    
        <Proxy *>
             Require all granted
        </Proxy>
    
        RewriteEngine On
    
        # websocket
        RewriteCond %{HTTP:Upgrade}  =websocket                     [NC]
        RewriteRule ^/api/ws(.*)     ws://localhost:1443/api/ws$1   [P,L]
    
        # rest
        ProxyPass /api http://localhost:1443/api
        ProxyPassReverse /api http://localhost:1443/api
    
        # static content
        ProxyPass / http://localhost:1443/
        ProxyPassReverse / http://localhost:1443/
    </VirtualHost>
    
  2. 必要なApacheモジュールの有効化(mod_rewrite,mod_proxy,mod_proxy_http,mod_proxy_wstunnel)
    $ sudo a2enmod rewrite proxy proxy_http proxy_wstunnel
    Enabling module rewrite.
    Enabling module proxy.
    Considering dependency proxy for proxy_http:
    Module proxy already enabled
    Enabling module proxy_http.
    Considering dependency proxy for proxy_wstunnel:
    Module proxy already enabled
    Enabling module proxy_wstunnel.
    To activate the new configuration, you need to run:
      systemctl restart apache2
    
  3. サイトの有効化
    $ sudo a2ensite 999-send
    Enabling site 999-send.
    To activate the new configuration, you need to run:
      systemctl reload apache2
    
  4. Apache再起動
    $ sudo systemctl restart apache2
    $ sudo systemctl status apache2
    ● apache2.service - The Apache HTTP Server
       Loaded: loaded (/lib/systemd/system/apache2.service; enabled; vendor preset: enabled)
      Drop-In: /lib/systemd/system/apache2.service.d
               └─apache2-systemd.conf
       Active: active (running) since Fri 2019-04-12 07:22:40 UTC; 6s ago
      Process: 8870 ExecStop=/usr/sbin/apachectl stop (code=exited, status=0/SUCCESS)
      Process: 8876 ExecStart=/usr/sbin/apachectl start (code=exited, status=0/SUCCESS)
     Main PID: 8892 (apache2)
        Tasks: 55 (limit: 4703)
       CGroup: /system.slice/apache2.service
               ├─8892 /usr/sbin/apache2 -k start
               ├─8893 /usr/sbin/apache2 -k start
               └─8894 /usr/sbin/apache2 -k start
    
    Apr 12 07:22:40 firefoxsend systemd[1]: Starting The Apache HTTP Server...
    Apr 12 07:22:40 firefoxsend systemd[1]: Started The Apache HTTP Server.
    
  5. http://send.example.jp でアクセスして確認

    HTTPで接続

SSL

実際に使う場合にはSSLを使用する必要がある。

docker-compose.ymlにNODE_ENV=productionを追加すると、./server/routes/upload.js, ./server/routes/ws.jsにある

const protocol = config.env === 'production' ? 'https' : req.protocol;
const url = `${protocol}://${req.get('host')}/download/${newId}/`;

において、ファイルのダウンロードURLのプロトコル部分がhttpsになる。

また、大きなファイルをアップロードしてhttpでアクセスすると「このブラウザーは、この大きさのファイルを復号化できません。」と表示されてしまう。
HTTP接続で1Gファイルをダウンロード

一方でhttpsで接続すると、問題なくダウンロードできる。
HTTPS接続で1Gファイルをダウンロード

設定サンプル

a2ensite default-ssl
a2enmod ssl

また999-send.confを以下のように変更する。(ポート番号の変更、証明書の指定)

<VirtualHost *:443>
    ServerName send.example.jp

    SSLEngine on
    SSLCertificateFile      /~~~/server.pem
    SSLCertificateKeyFile   /~~~/serverkey.pem

    ProxyRequests Off
    ProxyVia Off
    ProxyPreserveHost On

    <Proxy *>
         Require all granted
    </Proxy>

    RewriteEngine On

    # websocket
    RewriteCond %{HTTP:Upgrade}  =websocket                     [NC]
    RewriteRule ^/api/ws(.*)     ws://localhost:1443/api/ws$1   [P,L]

    # rest
    ProxyPass /api http://localhost:1443/api
    ProxyPassReverse /api http://localhost:1443/api

    # static content
    ProxyPass / http://localhost:1443/
    ProxyPassReverse / http://localhost:1443/

</VirtualHost>

MinIOとの連携

Firefox Sendは保存先にS3が使用できる。そこで、S3互換のオブジェクトストレージのMinIOを保存先として使用してみる。

MinIOもDockerコンテナが提供されているのでそれを使用する。

  1. MinIOの起動
    1. MinIO保存先フォルダを作成
    $ mkdir /var/www/firefox-send/minio
    $ chmod 777 /var/www/firefox-send/minio
    
    1. MinIOコンテナ起動
    $ cd /var/www/firefox-send
    $ docker run -p 9000:9000 -v $(pwd)/minio:/data -e "MINIO_ACCESS_KEY=MinIO" -e "MINIO_SECRET_KEY=MinIO123" minio/minio server /data
    

    MINIO_ACCESS_KEY,MINIO_SECRET_KEYは適当なものに変更する

    1. ブラウザで http://send.example.jp:9000 にアクセス

    MinIOにアクセス

    1. MINIO_ACCESS_KEY,MINIO_SECRET_KEYでログイン

    MinIOログイン後

    1. バケットの作成

    右下の+ボタンを押下してCreate bucketを選択し、適当なバケット名で作成する。ここではsendという名前を使用する。

    バケット作成
    バケット作成2
    バケット作成3

    1. ファイルのアップロード

    右下の+ボタンを押下してUpload fileを選択し、適当なファイルをアップロードする

    MinIOへファイルアップロード

    1. アップロードファイルの確認
    $ ls -al /var/www/firefox-send/minio
    total 16
    drwxrwxrwx 4 vagrant vagrant 4096 Apr 12 10:24 .
    drwxr-xr-x 5 vagrant root    4096 Apr 12 10:00 ..
    drwxr-xr-x 6 root    root    4096 Apr 12 10:24 .minio.sys
    drwxr-xr-x 2 root    root    4096 Apr 12 10:24 send
    
    $ ls -al /var/www/firefox-send/minio/send
    total 102408
    drwxr-xr-x 2 root    root         4096 Apr 12 10:24 .
    drwxrwxrwx 4 vagrant vagrant      4096 Apr 12 10:24 ..
    -rw-r--r-- 1 root    root    104857600 Apr 12 10:24 test_100M
    
  2. Firefox Sendと連携
    1. server/storage/s3.jsの編集

      Firefox SendでS3を使用する際、設定ファイルに書くのはS3のバケット名のみ。このままだとAWSに接続してしまうため、エンドポイントも指定できるようにコードを変更する。

      参考

      @@ -1,4 +1,15 @@
       const AWS = require('aws-sdk');
      +const config = {};
      +if (typeof process.env.AWS_S3_ENDPOINT !== 'undefined') {
      +  config['endpoint'] = process.env.AWS_S3_ENDPOINT;
      +}
      +if (typeof process.env.AWS_S3_USE_PATH_STYLE_ENDPOINT !== 'undefined') {
      +  config['s3ForcePathStyle'] = process.env.AWS_S3_USE_PATH_STYLE_ENDPOINT == 'true' ? true : false;
      +}
      +if (typeof process.env.AWS_S3_SIGNATURE_VERSION !== 'undefined') {
      +  config['signatureVersion'] = process.env.AWS_S3_SIGNATURE_VERSION;
      +}
      +AWS.config.update(config);
       const s3 = new AWS.S3();
      
       class S3Storage {
      
    2. docker-compose.ymlにMinIO接続設定を追加

      s3.jsへの変更で追加した、AWS_S3_ENDPOINT, AWS_S3_USE_PATH_STYLE_ENDPOINT,AWS_S3_SIGNATURE_VERSIONに加え、MinIOの認証情報(MINIO_ACCESS_KEY, MINIO_SECRET_KEY)をaws-sdkが認識する環境変数(AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY)を使ってセットする。

      @@ -11,8 +11,13 @@
             - ANON_MAX_FILE_SIZE=2147483648
             - ANON_MAX_DOWNLOADS=100
             - ANON_MAX_EXPIRE_SECONDS=604800
      -      - FILE_DIR=/tmp/send
             - NODE_ENV=production
      +      - S3_BUCKET=send
      +      - AWS_ACCESS_KEY_ID=MinIO
      +      - AWS_SECRET_ACCESS_KEY=MinIO123
      +      - AWS_S3_ENDPOINT=http://172.17.0.1:9000
      +      - AWS_S3_USE_PATH_STYLE_ENDPOINT=true
      +      - AWS_S3_SIGNATURE_VERSION=v4
           volumes:
             - ../data:/tmp/send
         redis:
      

      エンドポイントのホストはFirefox SendのWEBコンテナ(send_web_1)から見たMinIOコンテナ(hardcore_cray)になることに注意。localhostだとFirefox SendのWEBコンテナ自身を指すことになる。

      vagrant@firefoxsend:/var/www/firefox-send/send$ docker ps
      CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                    PORTS                    NAMES
      cd9ea929756e        send_web            "node server/bin/pro…"   7 minutes ago       Up 7 minutes              0.0.0.0:1443->1443/tcp   send_web_1
      b6a74fc54b0e        minio/minio         "/usr/bin/docker-ent…"   33 minutes ago      Up 33 minutes (healthy)   0.0.0.0:9000->9000/tcp   hardcore_cray
      b51bf27c6c54        redis:alpine        "docker-entrypoint.s…"   5 hours ago         Up 7 minutes              6379/tcp                 send_redis_1
      vagrant@firefoxsend:/var/www/firefox-send/send$ docker inspect hardcore_cray|grep Gateway
                  "Gateway": "172.17.0.1",
                  "IPv6Gateway": "",
                          "Gateway": "172.17.0.1",
                          "IPv6Gateway": "",
      

      MinIOコンテナのGatewayの値(172.17.0.1)を使用した。

    3. コンテナの再ビルド

      server/storage/s3.jsの変更をコンテナに反映するためビルドを行う

      docker-compose build
      
    4. コンテナ起動
      $ docker-compose restart
      Restarting send_web_1   ... done
      Restarting send_redis_1 ... done
      
    5. ファイルをアップロードしてMinIO側に保存されるか確認

      Send側
      MinIO側

      MinIO側の保存ディレクトリにファイルがあるか確認

      $ ls -lh /var/www/firefox-send/minio/send
      total 101M
      -rw-r--r-- 1 root root 101M Apr 15 06:35 1-ec820a4eb15f7917
      

      同じIDのファイルがアップロードされ、保存されている。

docker-composeに入れた版

ネットワークの指定が簡単になるのでMinIOもdocker-compose.ymlに入れてみた

参考
MinIO | Deploy MinIO on Docker Compose

version: "3"
services:
  web:
    build: .
    links:
      - redis
      - minio
    ports:
      - "1443:1443"
    environment:
      - REDIS_HOST=redis
      - ANON_MAX_FILE_SIZE=2147483648
      - ANON_MAX_DOWNLOADS=100
      - ANON_MAX_EXPIRE_SECONDS=604800
      - NODE_ENV=production
      - S3_BUCKET=send
      - AWS_ACCESS_KEY_ID=MinIO
      - AWS_SECRET_ACCESS_KEY=MinIO123
      - AWS_S3_ENDPOINT=http://minio:9000
      - AWS_S3_USE_PATH_STYLE_ENDPOINT=true
      - AWS_S3_SIGNATURE_VERSION=v4
  redis:
    image: redis:alpine
  minio:
    image: minio/minio
    ports:
      - "9000:9000"
    environment:
      - MINIO_ACCESS_KEY=MinIO
      - MINIO_SECRET_KEY=MinIO123
    volumes:
      - ../data:/data
    command: server /data

QNAP上のファイル名の文字化けを直す

文字コードをUTF8で運用しているQNAP NAS上に、文字コードがSJISの状態で日本語ファイル名のファイルがアップロードされたため、文字化けしたファイル名(フォルダ名)を修正する

準備

概要

文字化けを起こしたファイルのリネーム方法 – Qiitaに沿って修正します。

文字化け前のファイル名を知りたいため、iconvでのファイル名確認を追加しました。

  1. lsコマンドで対象ファイル(フォルダ)のinode番号を確認
  2. iconvコマンドで元のファイル名を確認
  3. findコマンドの-inumオプションで1で確認した番号でファイルを指定し、-execオプションを使いmvでリネーム

しかしQNAPのシェルでは、

  • iconvコマンドがない
  • findコマンドの-inumオプションない

という状況のため、解決法として、NASなどの組み込みデバイス向けソフトウェアリポジトリのEntwareを使い、findとiconvをインストールしそれを利用するようにします。

Entwareインストール

  1. EntwareのInstall on QNAP NASにアクセスし、this package for standard installationのリンクからQPKGファイルをダウンロード
  2. QNAPの管理画面を開く
  3. AppCenterを開き右上の歯車(設定)をクリック AppCenterを開いたとき
  4. 手動でインストールから参照を選択し、1でダウンロードしたQPKGファイルを指定する。インストールボタンを押下してインストール パッケージの手動インストール
  5. 正常にインストールするとマイアプリに表示される Entwareインストール後

findutilsとiconvをインストール

  1. QNAPにSSHで接続(文字コードはUTF-8)
  2. Entwareのパッケージ管理コマンドのopkgコマンドを使用してfindutilsとiconvをインストール
    [~] # opkg install findutils iconv
    

文字化け修正

  1. lsコマンドで対象ファイル(フォルダ)のinode番号を確認
        [/share/homes/sample] # ls -li
        50528266 lrwxrwxrwx    1 admin    administ        31 Jun  4 17:41 @Recycle -> /share/homes/@Recycle/sample/
        50528923 -rw-rw-rw-    1 sample   everyone       161 Jun  4 17:41 ??{??t?@?C????.txt
        

    文字化けファイル名のファイルのinode番号が50528923であることを確認

  2. iconvコマンドで元のファイル名を確認
        [/share/homes/sample] # ls -li|iconv -f SJIS -t UTF-8
        50528266 lrwxrwxrwx    1 admin    administ        31 Jun  4 17:41 @Recycle -> /share/homes/@Recycle/sample/
        50528923 -rw-rw-rw-    1 sample   everyone       161 Jun  4 17:41 日本語ファイル名.txt
        
  3. findコマンドの-inumオプションで1で確認した番号でファイルを指定し、-execオプションを使いmvでリネーム
            [/share/homes/sample] # find . -inum 50528923 -ok mv '{}' "日本語ファイル名.txt" \;
            < mv ... ./??{??t?@?C????.txt > ? y
        
  4. リネーム結果を確認
        [/share/homes/sample] # ls -li
        50528266 lrwxrwxrwx    1 admin    administ        31 Jun  4 17:41 @Recycle -> /share/homes/@Recycle/sample/
        50528923 -rw-rw-rw-    1 sample   everyone       161 Jun  4 17:41 日本語ファイル名.txt
        

リネームする対象が多いとき

  1. awkを使ってコマンド文字列を生成
    ls -il|iconv -f Shift_JIS -t UTF-8 |awk -v q=\'  '{print "yes\|find . -inum "$1" -ok mv "q"{}"q" "q$10q" \\;"}'
    

    出力結果

    yes|find . -inum 61801127 -ok mv '{}' '日本語ファイル名1.jpg' \;
    yes|find . -inum 61801128 -ok mv '{}' '日本語ファイル名2.jpg' \;
    yes|find . -inum 61801129 -ok mv '{}' '日本語ファイル名3.jpg' \;
    :
    :
    :
    :
    
  2. (生成されたコマンドに問題なければ)生成した文字列を実行していく

参考

package.jsonにあるversionを取得したい

コード中に自身のpackage.jsonにある値を取得するには、フィールド名の頭にnpm_package_をつけるて、process.envから参照できる。

バージョン番号(version)を取得したい場合:

console.log(process.env.npm_package_version);

パッケージで使う場合は、process.envは、読み込み元のpackage.jsonの値が使われるため、requireを使って直接package.jsonを読み込む。

p = require('package.json');
console.log(p.version);

Ubuntu 16.04でAndroid実機を接続

Ubuntu 16.04上でAndroidアプリ開発を行う中で、実機での動作を確認しようと思い、USBデバッグを有効にしたHuawei P8 liteをUSBで接続し、adb devicesを実行したところ、以下のようなレスポンスになった。

$ adb devices
List of devices attached
ARCxxxxxxxxxxxxx    no permissions (user in plugdev group; are your udev rules wrong?); see [http://developer.android.com/tools/device.html]

ハードウェア端末上でアプリを実行するを参照すると、udevにルールファイルを追加して、開発で使用する各種端末のUSB設定を追加する必要があるとのこと。

追加するルールは、udev – Android adb no permission – Ask Ubuntuの中にあったandroid-udev-rulesを使用してみた。

  1. udevルールの追加
    ~$ mkdir work
    ~$ cd work/
    ~/work$ git clone https://github.com/M0Rf30/android-udev-rules.git
    Cloning into 'android-udev-rules'...
    remote: Counting objects: 833, done.
    remote: Compressing objects: 100% (8/8), done.
    remote: Total 833 (delta 2), reused 1 (delta 0), pack-reused 825
    Receiving objects: 100% (833/833), 202.48 KiB | 285.00 KiB/s, done.
    Resolving deltas: 100% (324/324), done.
    Checking connectivity... done.
    ~/work$ cd android-udev-rules
    ~/work/android-udev-rules$ sudo cp -v 51-android.rules /etc/udev/rules.d/51-android.rules
    '51-android.rules' -> '/etc/udev/rules.d/51-android.rules'
    ~/work/android-udev-rules$ sudo chmod a+r /etc/udev/rules.d/51-android.rules
    
  2. グループの追加
    <br />~/work/android-udev-rules$ sudo groupadd adbusers
    ~/work/android-udev-rules$ sudo usermod -a -G adbusers $(whoami)
    

    ※今回は、android-udev-rulesの手順通り行ったが、ユーザーはplugdevグループに属しているので、51-android.rules中のGROUP="adbusers"GROUP="plugdev"に変更すれば、グループの追加は不要。

  3. udev再起動

    ~/work/android-udev-rules$ sudo udevadm control --reload-rules
    ~/work/android-udev-rules$ sudo service udev restart
    
  4. 実機接続

    USBで接続しているAndroid端末を一度外し、再度接続。Android Debug Bridge(adb)を再起動する。

    ~/work/android-udev-rules$ adb kill-server
    ~/work/android-udev-rules$ adb devices
    List of devices attached
    ARCxxxxxxxxxxxxx    device
    

PHPアプリケーションをHerokuにデプロイするときに”ERROR: Couldn’t parse ‘composer.lock’; it must be a valid lock”エラー

PHPアプリケーションをHerokuにデプロイしようと思い、

git push heroku master

としたところ

-----> PHP app detected
-----> Bootstrapping...
-----> Installing platform packages...
 ! ERROR: Couldn't parse 'composer.lock'; it must be a valid lock
  file generated by Composer. Run 'composer update', add/commit
  the change, then push again.
 ! Push rejected, failed to compile PHP app.
 ! Push failed

となりデプロイが失敗。

composer.lockは存在しており、指示通りcomposer updateをしてcomposer.jsoncomposer.lockを更新したうえでデプロイしても同じエラーが発生しました。

php – Heroku ERROR: Failed parsing composer.lock; it must be non-empty and valid JSON – Stack Overflow

より

composer self-updateでcomposer自体を更新し、composer updateしたうえで、デプロイすることでエラーが出なくなりデプロイできました。