Raspbian Jessie Lite の SDカード 延命化

Raspberry Pi を サーバのように連続稼働させておくとなると、SD カードの寿命が気になってきます. ちょっと心配なので SD カードへの書き込みを減らして、少しでも延命させたいと思います.

作業環境

  • Raspbian Jessie Lite

Swap 領域 を 無効化する

Raspberry Pi 2 と 3 は メモリが 1GB あり、Zero と Zero W は 512MB あります. 十分な量とは言い難いかもしれませんが、こんな小さなデバイスに様々な機能を押し込めるよりも、機能を絞って乗っているメモリで間に合うようにするのも手です.
そのため、まずは Swap 領域を無効にして SD カードへの退避書き込みをなくしたいと思います.

まず、Swap で 確保されている量を確認します. 約 100MB Swap に 割り当てられてるようです.

1
2
3
4
5
pi@raspberrypi:~ $ free -h
total used free shared buffers cached
Mem: 923M 77M 846M 6.2M 6.9M 39M
-/+ buffers/cache: 30M 892M
Swap: 99M 0B 99M

続いて、現在使用している Swap 領域を無効化し、また Swap 領域のマネージャをパケージごと削除して完全に使用しないようにします.

1
2
3
pi@raspberrypi:~ $ sudo swapoff --all
pi@raspberrypi:~ $ sudo apt-get purge -y --auto-remove dphys-swapfile
pi@raspberrypi:~ $ sudo rm -fr /var/swap

テンポラリ領域 を RAM へ 配置する

テンポラリ領域 /tmp/var/tmp、そうしょっちゅう置かれている感じは無いのですが、わずかとはいえ SD カードへの書き込みは減らしたいところ. これらは自動的に削除されることが前提の場所なので、電源を切った際に消えても問題は無いでしょう.

/etc/fstab/tmp/var/tmp に それぞれ tmpfs で メモリ上のファイルシステム を割り当てます. エディタで /etc/fstab を 開き、下記の 2行を追加します. 追加後は sudo reboot して新しいファイルシステムで起動します.

1
2
3
4
5
6
7
pi@raspberrypi:~ $ sudo nano /etc/fstab
tmpfs /tmp tmpfs defaults,size=32m,noatime,mode=1777 0 0
tmpfs /var/tmp tmpfs defaults,size=16m,noatime,mode=1777 0 0
pi@raspberrypi:~ $ sudo rm -fr /tmp
pi@raspberrypi:~ $ sudo rm -fr /var/tmp
pi@raspberrypi:~ $ sudo reboot

rsyslog の ログ出力を抑制する

続いてログ出力を抑制します. Raspberry Pi の 用途にもよりますが、必要なログだけに絞り込むことで SD カードへの書き込みを減らすことができます.
/etc/rsyslog.conf を エディタで編集します. 編集箇所は #### RULES #### 以降で、下記のように先頭に # を 付けてコメントアウトします. 保存後は rsyslog を 再起動します.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
pi@raspberrypi:~ $ sudo nano /etc/rsyslog.conf
#daemon.* -/var/log/daemon.log
#kern.* -/var/log/kern.log
#lpr.* -/var/log/lpr.log
#mail.* -/var/log/mail.log
#user.* -/var/log/user.log
#mail.info -/var/log/mail.info
#mail.warn -/var/log/mail.warn
#mail.err /var/log/mail.err
#news.crit /var/log/news/news.crit
#news.err /var/log/news/news.err
#news.notice -/var/log/news/news.notice
#*.=debug;\
# auth,authpriv.none;\
# news.none;mail.none -/var/log/debug
pi@raspberrypi:~ $ sudo systemctl restart rsyslog

ログ出力ディレクトリ を RAM へ 配置する

ログ出力ディレクトリ を RAM へ 配置し SD カードへの書き込みを抑制します. なお、これをやると電源を切るとログがなくなってしまうので、必要なログがある場合は SD カード側に出すようにするか、定期的&シャットダウン時に退避する仕掛けが必要となります.

まずは /etc/fstab/var/logtmpfs で マウントするように設定します.

1
2
pi@raspberrypi:~ $ sudo nano /etc/fstab
tmpfs /var/log tmpfs defaults,size=32m,noatime,mode=0755 0 0

起動時に /var/log の ディレクトリ構成を復元するスクリプトを設置します. これを忘れるとエラーが発生したり、起動できないプロセスがあったりするので、忘れないようにします.
/etc/rc.localexit 0 の 手前に下記を追加します.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
pi@raspberrypi:~ $ sudo nano /etc/rc.local
mkdir -p /var/log/apt
mkdir -p /var/log/fsck
mkdir -p /var/log/ntpstats
mkdir -p /var/log/samba
chmod 750 /var/log/samba
chown ntp:ntp /var/log/ntpstats
chown root:adm /var/log/samba
touch /var/log/btmp
touch /var/log/lastlog
touch /var/log/wtmp
chmod 600 /var/log/btmp
chmod 664 /var/log/lastlog
chmod 664 /var/log/wtmp
chown root:utmp /var/log/btmp
chown root:utmp /var/log/lastlog
chown root:utmp /var/log/wtmp
exit 0

最後にリブートします.

1
2
pi@raspberrypi:~ $ sudo rm -fr /var/log
pi@raspberrypi:~ $ sudo reboot

ext4 ファイルシステム の ジャーナル を 無効化する

ファイルの変更ログの記録で SD カードへ書き込みが発生するので無効化し、抑制します. 本格的なサーバ運用でしたら必須の機能ですが、そうゆのはキチンとしたサーバ用 H/W で 稼働させるとして、ラズパイにはカジュアルな感じで動いてもらおうと思い止めます.

1
2
3
4
5
pi@raspberrypi:~ $ sudo umount /dev/mmcblk0p2
pi@raspberrypi:~ $ sudo tune2fs -O ^has_journal /dev/mmcblk0p2
tune2fs 1.42.12 (29-Aug-2014)
pi@raspberrypi:~ $ sudo reboot


とりあえず考えられそうな書き込み元を止めてみました. それでも Raspberry Pi 3 の ACT LED が かなり点滅しているので、何かしら読み書きしているのかなぁと.
本格的にやるなら監視用のツールでも入れるなり、読み込み専用のファイルシステムにするなりした方がよいのでしょうが、かなりの手間になってしまうので追々と.

GitHub の Private Repository から デプロイキーを使って Read-only clone を 許可する

GitHub の Private Repository に 置いてあるソース を clone させて使いたいケースがあります. そんな場合はデプロイキーを使うことで Read-only な clone を 許せます.

作業環境

  • Windows 7
  • GitHub

背景

今回は Slack の ボット開発で使っているリポジトリから、稼働環境 で clone して実行したいというものになります. Slack の ボット開発では Node.js を 使っています. Node.js は JavaScript なのでリポジトリから clone して直接実行することができ、とても手軽です.
Public Repository でしたら直接ダウンロードできるので特に問題は無いのですが、Private Repository の場合はアクセスするための権限が必要となります.
通常、開発者は自分のアカウントがあり自前の SSH Key を 登録しています. とはいえ稼働環境で、この開発者のキーを使うわけにはいきません. 開発者がアクセスできる全ての権限を稼働環境に置いてしまうことになります.
そこでリポジトリごとに Read-only で clone できる方法が必要となります.

※ 多くの場合は CI と 組み合わせて、自動ビルド・テストからのデプロイを実現しているかと思いますが、今回は簡易ボットの運用なので CI 無しの、clone - 直実行 で 行っているものになります.

SSH Key の 生成

ssh-keygen は Microsoft が 開発している Win32-OpenSSH を 使いました. 生成する鍵の種類と強度は Raspbian Jessie Lite の SSH/公開鍵認証 設定 での調査に則り ed25519 に したいと思います.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
c:\> ssh-keygen -t ed25519 -C ""
Generating public/private ed25519 key pair.
Enter file in which to save the key (C:\Users\[username]/.ssh/id_ed25519):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in C:\Users\[username]/.ssh/id_ed25519.
Your public key has been saved in C:\Users\[username]/.ssh/id_ed25519.pub.
The key fingerprint is:
SHA256:T69X1O58qiKv7Sp/aPJTI+PpzUdOZDO+EcD5ldc8ON8
The key's randomart image is:
+--[ED25519 256]--+
| . . .o.|
| + oo.+|
| o .ooo|
| B ..E|
| S .+ = . |
| ooo.= . .|
| . *.=.+ o |
| o B+o.= +|
| B*OB+...XX|
+----[SHA256]-----+

GitHub の リポジトリ に Deploy Key を 追加

Deploy Key を 登録するリポジトリの設定 https://github.com/[username]/[repository]/settings/keys に アクセスします. ウェブからは [Settings] - [Deploy keys] を たどります.
画面から [Add deploy key] ボタンをクリックします.

Deploy Key 追加画面が表示されるので、[Title] に この鍵のタイトルを付けます. [Key] に id_ed25519.pub の 内容(※ id_ed25519(.pub なし) ではないことに注意)を貼り付けます.
今回は Read-only の Deploy Key にするため、[Allow write access] は チェックせず、[Add key] を クリックします.

GitHub に Deploy Key が 追加されました. Read-only が ついているところがポイントになります.

Deploy Key の 利用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
user@host:/tmp$ mkdir ~/.ssh
user@host:/tmp$ chmod 700 ~/.ssh
user@host:/tmp$ mv /mnt/id_ed25519 ~/.ssh
user@host:/tmp$ chmod 600 ~/.ssh/id_ed25519
user@host:/tmp$ echo -e "Host github.com\n\tStrictHostKeyChecking no\n" >> ~/.ssh/config
user@host:/tmp$ git clone git@github.com:[username]/[repository].git
Cloning into '[repository]'...
Warning: Permanently added 'github.com,192.30.253.112' (RSA) to the list of known hosts.
remote: Counting objects: 216, done.
remote: Compressing objects: 100% (95/95), done.
remote: Total 216 (delta 56), reused 0 (delta 0), pack-reused 121
Receiving objects: 100% (216/216), 54.22 KiB | 0 bytes/s, done.
Resolving deltas: 100% (123/123), done.
Checking connectivity... done.
user@host:/tmp$ ls -l
total 8
drwxr-xr-x. 6 user user 4096 Mar 13 04:53 [repository]

まずは生成した Deploy Key の Private Key id_ed25519 を 使えるようにします. 今回は /mnt/id_ed25519 に 配置しておいたとして、ホームディレクトリに .ssh ディレクトリを作成して、Private Key を 移動します. パーミッションが正しく設定できていないと鍵が読み込めない場合があるので chmod を 忘れないようにします.

続いて、~/.ssh/config に github.com への接続はホストキーの確認をしない設定します. これを行わない場合 ~/.ssh/known_hosts に github.com の ホストキーが登録されていない場合、以下のようにホストキーの確認を求められ入力待ちとなります. Shell Script などで 自動化する場合に都合が悪いのでホストキーの確認をしないようにしています.

1
2
3
4
5
user@host:/tmp$ git clone git@github.com:[username]/[repository].git
Cloning into '[repository]'...
The authenticity of host 'github.com (192.30.253.113)' can't be established.
RSA key fingerprint is 16:27:ac:a5:76:28:2d:36:63:1b:56:4d:eb:df:a6:48.
Are you sure you want to continue connecting (yes/no)?

後は git clone git@github.com:[username]/[repository].git で clone できるので、Node.js の 実行をするなり自由にできます.

ホントに Read-only なの?

1
2
3
4
5
6
user@host:/tmp/[repository]$ git push origin master
ERROR: The key you are authenticating with has been marked as read only.
fatal: Could not read from remote repository.
Please make sure you have the correct access rights
and the repository exists.

リポジトリに Push したところ marked as read only と言ってエラーが返るので Read-only に なっているようです.


これで Private Repository でも 稼働環境で git clone で ソースを取得して実行できるようになりました. スクリプト言語などで CI を 組み合わせずに簡易的に動作させる場合は、Deploy Key を 使うことで、ユーザ追加などをせずにアクセス権を絞って clone できるので便利ですね.
本当は CI を 使って、ちゃんと自動テストをしてからデプロイしたほうが良いのですが、お遊びのボットで多少壊れてても許されるような場合には使っていきたいところです.

Botkit が 自動切断されなくなった、みたい

Botkit で Slack の ボットを作成した際に、ボットがいつの間にか止まっているということがあり、ワークアラウンドのコードで対処していました.
2017年3月現在 の 最新 v0.5.1 に したところ、自動停止しないようになったようです.

作業環境

  • Slack
  • Node.js 6.9.1 LTS
  • Botkit 0.5.1

稼働状況を確認

コネクションの接続/切断時にコンソール・ログを出力するだけのボットを設置し放置してみました.

1
2
3
4
5
6
7
8
9
'use strict';
const Botkit = require('botkit');
const controller = Botkit.slackbot();
controller.spawn({
token: process.env.bot_access_token
}).startRTM((err, bot, payload) => { console.log(`start: ${new Date()}`) });
controller.on('rtm_close', (bot, err) => { console.log(`close: ${new Date()}`) });

稼働時のログは下記となります. v0.5.0 から Botkit の バージョンがログ出力されるようになったみたいです.

1
2
3
4
5
6
7
Initializing Botkit v0.5.1
info: ** No persistent storage method specified! Data may be lost when process shuts down.
info: ** Setting up custom handlers for processing Slack messages
info: ** API CALL: https://slack.com/api/rtm.start
notice: ** BOT ID: bot ...attempting to connect to RTM!
notice: RTM websocket opened
start: Fri Mar 03 2017 13:15:21 GMT+0000 (UTC)

開始日時のログ start: Fri Mar 03 2017 13:15:21 GMT+0000 (UTC) が 出ている通り、3月3日 に 稼働をさせて、このポストを書いている時点 3月10日まで rtm_close イベントは呼ばれておらず、また Slack 上でもボットがオンラインであることを確認しています. このボットを仕掛けたのは発言の少ない Slack チームだったので、この期間はボットの連続稼働実験のために完全に会話をしない状態を作り出すことができました. そのため会話に関することは無通信だったと考えてよいはずです.
前回の v0.4.2 では 6時間ぐらいで自動切断されていたのに対して、今回は 7日間は稼働しているので自動切断されなくなったと考えてよさそうです.

Botkit v0.4.2 から v0.5.1 までの更新

何時、何処で自動切断されなくなったのか Change Log を 確認したところ、v0.4.4 で “Make stale connection detection configurable PR #505“ を マージしています.
このタイトルからは、コネクションの自動切断の改善には思えないのですが、他にはコネクションに関する話はなさそうなので、これが気になります.

また コードの比較 からも、コネクションに関係しそうなのは lib/Slackbot_worker.js 周りの変更だと思われます.

これらに関連しているであろう Pull Request #505 を 確認したいと思います.
確認したところ… いきなり出てきた THRASHLIFE に 圧倒、なかなか凄いプルリクです.

それはさておき、肝心の内容ですが、どうもコネクションを維持するための Ping/Pong の 処理が正しく行えていないことについて改善のプルリクに思われます. ただプルリクの書き出で “A LOT of work that was maxing out CPU” と CPU 負荷をかけたケースであることや、最後 jonchurch さん の コメントで “weird edge case” と あるので、今回のように無通信が続いたことによる自動切断とは関連があるとは思えないのですが “the node process wasn’t processing the pong response” が どうしても気になります.

v0.4.2 を 使っていた時も、ほぼ暇なボットで会話の通信も少なかったので CPU 負荷はかかってなかったとは思うので、説明からすると関係はなさそうではあるのですが、他のコード変更が影響しているとは思えないですし. なんだろう…

diff を 確認したいと思います.

pingIntervalId = setInterval((...), 5000)pingTimeoutId = setTimeout((...), 5000); に 変わったところが大きい違いでしょうか. setInterval による繰り返しから、setTimeout の 遅延処理にして処理内で setTimeout を 再呼び出しに変わっています. プルリクでも “replace the use of an interval with a timeout” と言っているので、そう変更したのでしょう.

はたして、この変更が自動切断を防止してくれるようになったのでしょうか… わからない orz

関連する記事の更新


結局 よくわからない、という結論で何とも情けないところではありますが、とりあえず自動切断されなくなり、ワークアラウンドも削除できるので良しとしよう…

最近インストールした Raspbian Jessie Lite が SSH 接続できない?

Crystal Signal Pi が 届いた ので、さっそく Raspbian を セットアップし、Crystal Signal Pi の ソフトウェアを入れて発光色を変えてみたいと思ったところ、まさかの SSH ログインができないというトラブルがあり、意外とはまったのでメモしておきます.

作業環境

  • Windows 7
  • Raspbian Jessie Lite (Release date: 2017-03-02)

トラブルの状況

新規に Raspbian Jessie Lite を SD カードに焼いて(いや、SD だから焼かないですがイメージとして…)、Crystal Signal Pi を 搭載した Raspberry Pi 3 Model B を 起動しました.
そして初期設定のために SSH を したところ、どうしてもつながらないという状況が発生.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
c:\Temp> ssh pi@raspberrypi.local
ssh: connect to host raspberrypi.local port 22: Unknown error
c:\Temp> ssh -v pi@raspberrypi.local
OpenSSH_7.3p1 Microsoft_Win32_port_with_VS, OpenSSL 1.0.2d 9 Jul 2015
debug1: Connecting to raspberrypi.local [ea13::2f1:0d1:adXX:dab%10] port 22.
debug1: socket:444, io:000000000030EE60, fd:3
debug1: finish_connect - ERROR: async io completed with error: 107, io:000000000030EE60
debug1: connect to address ea13::2f1:0d1:adXX:dab%10 port 22: Unknown error
debug1: close - io:000000000030EE60, type:1, fd:3, table_index:3
debug1: Connecting to raspberrypi.local [192.168.0.1XX] port 22.
debug1: socket:404, io:000000000030EE60, fd:3
debug1: finish_connect - ERROR: async io completed with error: 107, io:000000000030EE60
debug1: connect to address 192.168.0.1XX port 22: Unknown error
debug1: close - io:000000000030EE60, type:1, fd:3, table_index:3
ssh: connect to host raspberrypi.local port 22: Unknown error

デバッグ・ログを出してみましたが私のレベルではよくわからず… SSH の デバッグログに出ている IP が 取れているのは ルーター側でも確認できていますし、Ping も 届いていました. ネットワーク的には正しく接続できているようです.

これまでのラズパイは?

久々にインストールから作業したので、だいぶ間が空いてましたが当時使っていた古いイメージ 2016-09-23-raspbian-jessie-lite.img が 残っていたので、こちらを新規で使ったところ SSH 接続できました. あれ?

ということは、これまでのバージョンアップの中で何かが変わったのかもしれません. ということで、ようやくリリースノートをあたります. (もっと早く見ようよ、自分 orz)
以下 http://downloads.raspberrypi.org/raspbian/release_notes.txt より抜粋.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
2017-03-02:
* Updated kernel and firmware (final Pi Zero W support)
2017-02-16:
* Chromium browser updated to version 56
* Adobe Flash Player updated to version 24.0.0.221
* RealVNC Server and Viewer updated to version 6.0.2 (RealVNC Connect)
* Sonic Pi updated to version 2.11
* Node-RED updated to version 0.15.3
* Scratch updated to version 120117
* Detection of SSH enabled with default password moved into PAM
* Updated desktop GL driver to support use of fake KMS option
* Raspberry Pi Configuration and raspi-config allow setting of fixed HDMI resolution
* raspi-config allows enabling of serial hardware independent of serial terminal
* Updates to kernel and firmware
* Various minor bug fixes and usability and appearance tweaks
2017-01-11:
* Re-release of the 2016-11-25 image with a FAT32-formatted boot partition
2016-11-25:
* SSH disabled by default; can be enabled by creating a file with name "ssh" in boot partition
* Prompt for password change at boot when SSH enabled with default password unchanged
* Adobe Flash Player included
* Updates to hardware video acceleration in Chromium browser
* Greeter now uses background image from last set in Appearance Settings rather than pi user
* Updated version of Scratch
* Rastrack option removed from raspi-config and Raspberry Pi Configuration
* Ability to disable graphical boot splash screen added to raspi-config and Raspberry Pi Configuration
* Appearance Settings dialog made tabbed to work better on small screens
* Raspberry Pi Configuration now requires current password to change password
* Various small bug fixes
* Updated firmware and kernel

ありました! ちょうど、これまで使っていた 2016-09-23 の 次のリリース 2016-11-25 で変わったようです.
SSH は デフォルトで起動しないようになっていて、ブート・パーティション に “ssh” というファイルを置くことで有効にできるようです. これによって、接続ができなかったのですね…

1
2
2016-11-25:
* SSH disabled by default; can be enabled by creating a file with name "ssh" in boot partition

これからのセットアップ

セットアップについては、これまで通り SD カードをフォーマットして、イメージを書き込むところまでは同じになります. 最後に “ssh” というファイルを置くという手順が増えました.

Windows からは、エクスプローラー で SD カード の ドライブを開いて、”ssh” というファイルを作ります.

作るファイルのもとは何でもよく拡張子を削除して作るだけになります. 今回はビットマップ イメージを選び、最初から入力されていた 新しいビットマップ イメージ.bmp を 消して ssh としました. “拡張子を変更すると、ファイルが使えなくなる可能性があります。” と 警告表示されますが、今回は特に問題ないので [はい] を クリックして進めます.

“ssh” というファイルが置かれました. これで完了、後は起動するだけです.

関連する記事の更新


ファイルを置いたら何時もと変わりなくアクセスできるようになりました. SSH 接続できないと各種設定ができないように思いますが、どうして SSH を デフォルトで無効にしたのだろう…

Crystal Signal Pi が 届いた!

Raspberry Pi に 約 7cm ぐらいの 四角柱 を 立て、マルチカラー の LED を で光らせることができるモジュール Crystal Signal Pi が 届き、ようやく組み立てができました. 本来はサーバなどの監視を行い、警告をわかりやすく表示してくれるものですが、いろいろな使い方に挑戦してみたいと思います. まずは、早速組み立てから.

作業環境

  • Raspberry Pi 3 Model B
  • Crystal Signal Pi

Crystal Signal Pi とは?

Crystal Signal Pi は、Raspberry Pi に 約 7cm ぐらいの 四角柱 を 立て、マルチカラー の LED を で光らせることができるモジュールです.
クラウド・ファンディングで購入者を募集されていることを知って、興味を持ち応募しました. クラウド・ファンディングの仕組みを使っていたとはいえ、製造は確定していたので普通に購入する形です.

光で監視するソリューション“ を キーワードに、2016年11月 の オープンソースカンファレンス 2016 Tokyo/Fall で デモをされたようです.
その際の資料はこちら、でしょうか. → “あらゆるイベントを可視化する! RaspberryPiで作るLED警告灯ソリューション

Crystal Signal Pi 到着 & 組立

開発元のインフィニットループさんの封筒で届きました. 写真では宛名部分を折り返していますが、 2つ購入で A4 の 封筒半分ちょっとぐらいです.

中身は緩衝材でしっかり保護されています.

1つ 1つ パッケージングされています. 丸穴をあければお店に そのまま吊るせる感じです. こんな感じで手軽に購入できるようになってほしいですね.

パッケージの中身は以下になります. 丁寧に部品ごとにも袋に入っています.

  • 説明書など書類 3枚
  • 基盤 1枚
  • ケースのアクリル板 2枚 と ネジ、滑り止め
  • LED で 発光する 四角柱 と ゴムバンド

説明書があるので、そう迷わず組立できました. 今回は Raspberry Pi 3 Model B を 使いました. なお組み立ての説明書 は PDF で 公開 されています. PDF は カラーなのでイメージがつかみやすいかもしれません.
まずケースのアクリルにネジとスペーサー入れ、ラズパイを載せ、続いてラズパイとCrystal Signal Pi 基盤の間用のスペーサーで留めます. ネジ穴が切ってあるのでクルクルして簡単に仮留めできます.

続いて Crystal Signal Pi の 基盤を載せます. GPIO に合わせたソケットが取り付けられているので、そのまま差し込みます.

最後にカバーのアクリル板をかぶせますが、四角柱を通してからカバーを載せます. 後は しっかり、ねじ止めして完成!

いざ、点灯!

Crystal Signal Pi の ソフトウェアが用意されていますが、インストールしなくても通電すると、緑とオレンジに交互に点滅した後、緑で光り続けます.
明るいところでは四角柱の側面部分は光がとおっている感じで発光感は強くないです.

天頂部は強く光っており斜めから見てもかなり眩しいです.

明るさ調整するにはソフトウェアが必要ですね. ソフトウェアを入れていないので、こちらも当然ですが、オレンジ色の丸ボタンも反応しません. また、shutdown した後も光続けています.


ラズパイで簡単な状態通知をできる Crystal Signal Pi、面白いプロダクトだと思います. API も 用意されているのでアイデア次第でいろいろな使い方ができそう.

2017年 3月現在、初期ロットのクラウド・ファンディングが終わったところで、次回ロットについては、オフィシャル・サイトでメール登録することで連絡をもらえるとのことです. 思った以上にすごくよかったので、さっそくメール登録しました!

Hexo の フッター に ソーシャル・アイコン を 設置

Hexo の デフォルト・テーマ Landscape に Twitter の 設定を追加した話では、ソーシャル・アイコンが追加されると早とちりをしてしまいました. 改めて思うと、ソーシャル・アイコンは、やはり どこかに配置しておきたいなぁと思うので、設置してみます.

作業環境

  • Hexo 3.2
  • Disqus
  • Twitter

設置場所 の 検討

トップのタイトル・カバーやサイド・バーに配置されているケースが多いように感じます. そういった場所に配置しようかと思ったのですが、現在のレイアウトから手ごろな配置が浮かばなかったので、今回はフッターのコピーライトの横あたりにひっそりと置いておくことにします.

アイコン集 の 検討

アイコン、アイコンはいろいろあるので悩みます. 素敵なデザインのサイトから頂戴しようかと思いましたが、まずは Font Awesome を 使って設定したいと思います.

Font Awesome は 各種アイコンをフォントとして用意してくれています. フォントなので大きさを変えても崩れませんし、色を変更することもできます. 単色の色設定にはなってしまいますがシンプルなテイストでまとめるとも考えられますし、とても使いやすいツールです.

ライセンスは、こちら で 2017年3月現在 の Version 4.7.0 では、フォント は SIL OFL 1.1、CSS などのコードは MIT と、今回のようなブログに設定するにはフリーで使わせていただけます. ただし企業や組織のブランド系のアイコンについては、注意書きがあるので確認およびそれに従った運用が必要です. Font Awesome の ライセンス を ご確認ください.

Hexo Landscape は /themes/landscape/source/css/fonts に Font Awesome の フォントが入っています. そのため新たに追加しなくても使えるのが嬉しいところです.

Hexo Landscape に 設置、も?

設置場所、アイコン集も決まったので Hexo Landscape に 配置していきます.
フッターに配置することにしたので、編集するファイルは /themes/landscape/layout/_partial/footer.ejs に なります. コピーライトの部分は以下のコードになっており &copy; 行 の <br> の 前に置くことでコピーライト表示に並べて行けそうです.

1
2
3
4
<div id="footer-info" class="inner">
&copy; <%= date(new Date(), 'YYYY') %> <%= config.author || config.title %><br>
<%= __('powered_by') %> <a href="http://hexo.io/" target="_blank">Hexo</a>
</div>

続いて、Font Awesome の アイコン ですが、こちらは Icons ページ から利用したいアイコンをクリックし、表示されたコードをコピペすることで簡単に利用することができます. たとえば Font Awesome の アイコン は <i class="fa fa-font-awesome" aria-hidden="true"></i> です.

とりあえず GitHub と Twitter を 配置するとして、以下のようなコードを置きました.
必要なソーシャルアイコンのタグを調べて配置して終了!

1
2
3
4
5
6
7
<div id="footer-info" class="inner">
&copy; <%= date(new Date(), 'YYYY') %> <%= config.author || config.title %>
<a href="https://github.com/[username]" target="_blank" title="GitHub"><i class="fa fa-github" aria-hidden="true"></i></a>
<a href="https://twitter.com/[username]" target="_blank" title="Twitter"><i class="fa fa-twitter" aria-hidden="true"></i></a>
<br>
<%= __('powered_by') %> <a href="http://hexo.io/" target="_blank">Hexo</a>
</div>

いざ表示!と、行きたかったのですが、残念ながら表示されませんでした…
コードは追加されており、配置されている場所こそ気にはなるもののレンダリングもされているようです. しかし、<i> の スタイルシート が user agent stylesheetfont-style: italic; に なっています.

なんと、Hexo Landscape には Font Awesome の フォント は 入っているものの、CSS が 入っていない… 読み込んでいないではなく、物理的にファイルもない のでした. そのため、Font Awesome の タグ ではアイコンが入ってくれないという事象が発生したものになります.

Unicode 指定 による CSS で 設置

タグ による指定できないので、フォントから直接 Unicode で 指定する方法を取りたいと思います.
Unicode による指定は、CSS で font-family に Font Awesome の フォントを指定し、content に Unicode で 使用する文字を指定します. Unicode は Icons ページ に コードが書かれているので、そちらを指定します. たとえば Font Awesome の アイコン は f2b4 なのでエスケープを入れて content: "\f2b4"; と なります.

CSS の クラス定義は、ちょうどソーシャルへシェアするためのアイコンのクラス定義があるので、それに習って作成したいと思います.
ファイルは /themes/landscape/source/css/_partial/article.styl で、$article-share-link.article-share-twitter などがソーシャル・シェアのアイコンになります. こちらをまねて $article-link.article-link-twitter.article-link-github を 作成しました.
フォント・カラー を 直前のコピーライトと合わせ、文字をやや大きくしています. Unicode は Twitter が f099 で、GitHub が f09b なので、それぞれ content に 指定しています.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$article-link
color: #999 !important
&:before
font-size: 24px
font-family: font-icon
text-align: center
&:hover
color: #fff !important
text-decoration: none !important
.article-link-twitter
@extend $article-link
&:before
content: "\f099"
.article-link-github
@extend $article-link
&:before
content: "\f09b"

/themes/landscape/layout/_partial/footer.ejs は 先ほどのリンクを作る <a> タグ に クラス定義をあてるだけになります.

1
2
3
4
5
6
7
<div id="footer-info" class="inner">
&copy; <%= date(new Date(), 'YYYY') %> <%= config.author || config.title %>
<a href="https://twitter.com/[username]" class="article-link-twitter" target="_blank" title="Twitter"></a>
<a href="https://github.com/[username]" class="article-link-github" target="_blank" title="GitHub"></a>
<br>
<%= __('powered_by') %> <a href="http://hexo.io/" target="_blank">Hexo</a>
</div>

いざ、表示!


無事表示することができました. Font Awesome は よく利用させていただいています. 絵心やデザイン・センスががない身としてはとても助かっています. ありがとうございます!
普段はタグで指定しているので Font Awesome の CSS が 無いケースでの設定は初めてでしたが、ちょうど ソーシャル・シェア の 定義があり流用できたので助かりました.
テーマへの手入れが増えてきたので、そろそろテーマを切り替えて本格的にやっていきたいのですが、なかなか手が出せず、今しばらくは Hexo Landscape に ちょくちょく手を入れつつお世話になりそうです.

Hexo に コメント欄 の Disqus を 設置

Twitter の アカウントを開設した し、ちょっと寄り道しましたが、いよいよコメント欄 Disqus を 設置したいと思います.

作業環境

  • Hexo 3.2
  • Disqus
  • Twitter

Disqus の アカウント作成

Disqus の Signup ページ https://disqus.com/profile/signup へ アクセスします.

今回は Twitter の 認証連携でサインアップするので Twitter アイコン を クリックします.

Twitter の サイト へ 遷移するので、Twitter へ サインインして連携を許可します. 各種権限の許可ができない場合は Twitter 連携を諦めて直接サインアップします.

許可すると Disqus の サイトへ戻ります. 名前、メールアドレス、パスワードの入力をします. 名前 は Twitter からひいてくれたようですが、メアド、パスワードは入力が必須でした… この辺を入力したくない(特にパスワード)から Twitter で 認証したのに、意味なかった. orz

Disqus サイト の 開設

サインアップが完了すると、そのまま Disqus サイト の 開設が始まります.
ブログに設置するためにアカウントを作成したので、そのままサイトの開設に進みます.

ここでの「サイト」は Disqus に 開設するサイトのようで、ブログサイトとは関係ないようなので注意が必要です. (ブログサイトから連携する先の Disqus サイト と いったイメージでしょうか)

2つの選択肢が示されます. 今回はブログにコメント欄を設置するので [I want to install Disqus on my site] を クリックします.

基本設定の入力画面が表示されます.
[Website Name] は ユーザ名 に あたるものを入力します. ここで入力した文字列から生成された Your unique disqus URL の 先頭部分 が ブログから連携する Disqus の サイト の Short Name になります. 個々のブログやウェブサイトの名前でないことに注意が必要です.
ここで 表示された “Your unique disqus URL” = Short Name を ひかえておきます.
Category や Language は お好みで選択します.

無事、Disqus サイトの開設ができました.
[Got it. Let’s get started!] を クリックし、続いてブログ・サイトの連携を作成に進みます.

ブログ・サイト の 連携設定

自分のサイトで使っているサービスやプロダクトを選択する画面が表示されます.
Hexo は ラインナップされていないため、汎用 の [I don’t see my platform listed] を クリックします.

コードによるセットアップ方法について解説がありますが、Hexo の デフォルト・テーマ Landscape は 設定 1つで対応できるので、勉強がてら眺めつつ一番下の [Configure] を クリックします.

ブログ・サイトの設定を入力する画面が表示されます. ここは自サイトの情報になります.
[Website Name] は、ブログ の サイト名になります. コメント欄のタイトルに表示されます. [Website URL] は、ブログ の URL を 入力します. [Category]、[Description]、[Language] は お好みで.

無事、セットアップが完了しました!

Hexo に Disqus を 設定

Hexo の デフォルト・テンプレート Landscape の after-footer.ejsarticle.ejsconfig.disqus_shortname の 有無を判定し、コメント欄を出力するかが制御されています.

この config. の Prefix が 曲者で、Hexo の 設定 に disqus_shortname が 必要でした.
テーマに関する設定項目と思っていたので、Landscape の /themes/landscape/_config.yml に 設定をしたところ、どうしても表示されずはまりました. 同じファイル名ではありますが、Hexo の /_config.yml に 以下のように設定します.

1
disqus_shortname: [shortname]

ここで、Disqus の サイトで開設した Web サイト の Short Name の 値 を 設定します.
ローカルで確認する際に、もしかしたら正しく表示されないケースがあるので、その場合は hexo clean してから再生成すると表示されます.


Disqus アカウントのサインアップから、Disqus サイトの開設、ブログ連携 と 一連の作業で流れて行ったので、作業している時に今何をしているかちょっとわからないところがあり、迷ってしまいました… 最近 Disqus の ウェブサイトが変わったのか、自分のやり方が違ったのか、参考情報もなくて悩みつつでしたが、改めて文章で起こしてみると、3つのパートだったことが分かりました. 勉強になりました.

Hexo に Twitter の アカウント を 設定

Twitter の アカウントを開設した ので、いよいよコメント欄 Disqus の 設置!と、行きたいところですが、ちょっと寄り道. Hexo の デフォルト・テーマ Landscape に Twitter の 設定項目があるので設定したいと思います.

作業環境

  • Hexo 3.2
  • Twitter

Landscape の Twitter 設定項目

デフォルト・テーマ Landscape の 設定ファイルは /themes/landscape/_config.yml に なります. 以下に抜粋したとおり twitter: が あります.

1
2
3
4
5
6
7
# Miscellaneous
google_analytics:
favicon: /favicon.png
twitter:
google_plus:
fb_admins:
fb_app_id:

設定値 と 効果 は?

Landscape の ドキュメント Configuration – hexo-theme-landscape によると、”twitter - Twiiter ID” と あっさりしすぎてて、何のことか、また何を入れるかわかりません. 困った…

情報がないか探してみると、Configuration – hexo-theme-bootstrap-blog
に “twitter_id - Twitter ID of the author (ie. @c_g_martin)” が ありました.

異なるテーマなので同じで大丈夫かと思いましたが、”The default Landscape Hexo theme was used as the starting point - Development“ と あるので信じて設定したところ、どうやら動作したようです.

なお、設定値は以下のように、アットマークを付けシングルクォートで囲む必要があります.

1
2
3
4
5
6
7
# Miscellaneous
google_analytics:
favicon: /favicon.png
twitter: '@username'
google_plus:
fb_admins:
fb_app_id:

さっそくローカルで確認したのですが、特に変化はなかったようです… あれ?
Twitter の アイコンが増えて、リンクしてくれたりするのかなぁぐらいに思っていたのですが、どうやら違うようです. では、何が設定されたのか…

Twitter Cards !

とりあえず生成されたサイトのソースを確認します. すると head に Twitter 関連のメタ情報が出力されていました.

この出力内容は Twitter Cards に 関するもので、Twitter に サイトのリンクをツイートされた際に、以下のようにサイトの画像サムネイルやタイトル、先頭分のサマリが表示されます.


Twitter Cards の 説明によると、”リッチメディアをツイートに添付してウェブサイトへのトラフィックを促進できます – Twitterカード — Twitter Developers” とのこと.
ただの文字列リンクより、サマリが表示されるので設定しておくに越したことはないので、このまま設定しておきたいと思います.

Twitter や GitHub の アカウントへのリンクを貼る機能と勝手に思っていましたが早とちりでした. しまった…
とはいえ、リンクは貼りたいので、どっかでテーマを変えるなり、カスタマイズしないとだなぁ~

Slack で プレミアムフライデー・ボット してみる

2017年2月24日 金曜日、それは “プレミアムフライデー” なる施策の開始日である. うん、ウチは関係ないらしいのだけど! とうことで、関係ないボット を Slack に 乗せてみた.

作業環境

  • Slack
  • Node.js 6.9.1 LTS
  • Botkit 0.4.9 0.5.1
  • node-cron 1.2.1
  • moment-timezone 0.5.10

プレミアムフライデーって、なにそれ?おいしいの?

まずもって関係ないので、よく知りません. 人によっては美味しいものであったり、おもしろい物であるようです.

経済産業省によりますと以下の施策だそうです.

個人が幸せや楽しさを感じられる体験(買物や家族との外食、観光等)や、そのための時間の創出を促すことで、
(1) 充実感・満足感を実感できる生活スタイルの変革への機会になる
(2) 地域等のコミュニティ機能強化や一体感の醸成につながる
(3)(単なる安売りではなく)デフレ的傾向を変えていくきっかけとなる
といった効果につなげていく取組です。

なんか漠然として、よくわかりませんが、こちらのサイト を サマると以下でしょうか.

  • 月末の金曜日は早く仕事を終え退社する
  • 早く退社した時間は、余暇として楽しむ
  • ちょっと豊かな月末金曜日を過ごしてハッピーになる

いいなぁ、ハッピ~. こちらもアバウトな感じでよくわからない.
とりあえず聞いたことある話からすると、以下のようです.

  • 月末の金曜日は 15時までに退社する
  • 退社後は食事や娯楽などを楽しむ
  • 消費が活性化され、楽しんだ人 も 景気 も ハッピー になる

なるほど. いいなぁ、ハッピ~.

ボットの仕様を考える

月末の金曜日は 15時に変えることを促す通知をするのが通常仕様とすることになりそうです. 一方で関係ない身としては 15時に帰れと言われても困りますので、何かしらの工夫が必要です.

いただきます! こちらのネタ!!
ということで、まずは usernameicon を プレミアムフライデーなアカウントにして 15時退社をアナウンス. 続いて通常ボット・アカウントに戻って上記ネタでリプライする、自作自演ボットにしたいと思います. これなら関係無い感が出てるボットになって、きっと朝から はっぴー な 気分になれ…

ボット実装

自動切断のワークアラウンド・コードを削除する更新をしました (2017年3月10日)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
'use strict';
const Botkit = require('botkit');
const cron = require('cron');
const http = require('http');
const moment = require('moment-timezone');
const controller = Botkit.slackbot();
moment.locale('ja');
moment.tz.setDefault('Asia/Tokyo');
controller.spawn({
token: process.env.bot_access_token
}).startRTM((err, bot, payload) => {
new cron.CronJob({ cronTime: '00 00 11 * * 5', timeZone: 'Asia/Tokyo', start: true, onTick: () => {
let now = moment();
if (now.month() != now.clone().add(7, 'days').month()) {
bot.say({
channel: 'random',
text: '今日、月末の金曜日は午後3時に退社して余暇を楽しもうという ' +
'<https://premium-friday.go.jp/#section_about|プレミアムフライデー> だよ! ' +
'退社時間が早まることで消費が活性化するし、働き方改革にもつながる施策なんだ. ' +
'さぁ~ みんな3時には帰って、買い物、食事や旅行などして普段よりも豊かな生活を送ろう!!',
username: 'プレミアムフライデー ボット',
icon_url: 'http://www.meti.go.jp/press/2016/12/20161212001/20161212001-a.jpg'
}, (err, response) => {
bot.say({
channel: 'random',
text: `<http://[YOUR_IMG_URL]/image.jpg?${moment().unix()}| >`
});
});
}
}});
});

Botkit や Moment.js の セットアップ系はいつも通りで、定時処理は毎度の new cron.CronJob() で ジョブを作成します.
今回は「月末の金曜日 11時」に 処理をしたいのですが、cronTime だけでの表現が浮かびませんでしたので、まずは「金曜日 11時」に 処理を起動するようにしました.
続いて現在日時を取得して、7日後が同じ月か now.month() != now.clone().add(7, 'days').month() で 判定することで月末の金曜日なのかを確認しています.

月末の金曜日だったら、まずは「プレミアムフライデー ボット」が 広報の通知をするために usernameicon_url を 変更してポストします.

「プレミアムフライデー ボット」が 発言した後に返事を返したいので、以下のように bot.say() の 中で再発言させています. bot.say() を 並べてしまうと、タイミングによっては通常ボットの返事が先に来てしまうことがありえるので、bot.say() の コールバックから発言する処理になります.

1
2
3
bot.say({ /* プレミアムフライデーの発言 */ }, (err, response) => {
bot.say( /* 通常ボットからの返事 */ );
});

通常ボットからの返事では、<http://[YOUR_IMG_URL]/image.jpg?${moment().unix()}| ><url| > の 書式で URL を 半角スペースに置き換えてポストする記法を使っています. これによって URL 無しで画像だけポストできます. ?${moment().unix()} は、Slack が 同じ URL だと 2回目以降は折りたたんでしまって、最初から画像が見えないので、実行時の Unix timestamp を つけることで URL を 変えて展開できるようにしています.
※ 画像は自前で用意して適当なところへ置いておきます.

実験!

cronTime を 調整して、いざ実験. ちゃんと表示されました.
とりあえず実験用にはネタ元のたツイートから画像を拝借させて頂きましたが、後は画像を自前のネタに変えて適当なとこにアップしてボットを設置するだけですね. 関係ない人々の月末の週末が楽しみです♪


今週末に迫ってきたプレミアムフライデー. 急に騒がしくなってくる中で関係ない身としては流れに乗れない寂しさから勢いボットを作って乗ってみました. 帰るころにはハッピーな人で溢れかえっているか、後の祭りか…
みなさま、よい週末を.

『知識ゼロから学ぶソフトウェアテスト』 あるいは 無残なるノーテスト

Java で プログラミングをしている場合に、テストケースの作成に JUnit を ベースにテストしているケースがあるかと思います. “Assert that X is Y.” の 考え方 を 基本に isnotnullValue などしてテストケースを書いていくよいライブラリと思うのですが…

テスト も カバレッジ も ちゃんと やってます!

それは昔々の事、とあるアプリケーションに携わった人のお話しで機能追加することから物語は始まりました. (いや物語らん話しのがよかた)

このアプリケーション、しっかりテストケースが作ることになっており、なんと C0 カバレッジ 90% 以上、C1 も 努力目標ながら高レベルでされているとのことでした. ちゃんとテストするフレンズなんだね!すごーい!(2017年2月現在 流行りのフレーズ)

と、聞いていたのも束の間、コードを触り始めると気になる謎の実装、不思議なテストケース…
「テストなってないじゃん?」いや、まぁ、「テストするとは」の定義は何かと自問自答するわけですが、テストケースがあるとか、カバレッジが取れてるとかでなくて、「何を検証したいのか」じゃないのかなぁと、考えたとのことです.
僭越ながら同感です. いたずらにカバレッジだけとっても、通過させるためだけの意味のないテストケースの実行コードなんてものがあふれることもあり得ます.

え? その “verify(x, never()).y()”…

さて、そんな中、聞かれたのが “verify(x, never()).y()” というコード. これだけ聞かれてもよくわからないのですが、たぶんモックでメソッドの呼び出し有無を判定したいと思われます.

そして出てきたのが以下のようなテストケース. (※ ポスト用に命名等を汎化させています)

1
2
3
4
5
6
public class DaoTest {
@Test public void testAdd() {
verify(dao, never()).findAll();
}
// ...(省略)
}

いゃ~! 見たくなかった、聞きたくなかった…
たぶん、Deta Access Object = DAO の テストケースと思われます.
きっと、新しいレコード を 追加 = add するのをテストしたかったのでしょう. 知らんけど.
verify()never() は モックを簡単にするためのライブラリから、メソッドの実行確認をしているのでしょう.
だけど、dao.add() 実行してないじゃん? あと findAll() しなくね?、どう考えても. (そもそも Dao の 処理を何も実行してないからアレだけど) White Box テストだから知ってるはずだし?

どうして、そうなったのか. 少し話を聞いたたらしいのですが、「テストって、何をテストをするんですか?」「どうやるんですか?」「できすか?やって見せてください!」…
えーっと、逆ギレ? DAO とかテストめんどくさいの分かるけど、単純 な Getter/Setter だけのテストだってできてないんですが (T_T

1
2
3
4
5
6
7
8
public class ModelTest {
@Test public void setParam() {
Model model = new Model();
model.set("あああ..."); // ※ すごい長い「あ」が続く
assertEquals("あああ...", model.get());
// ※ この後、同様のパターンで半角英数、記号、全半角の組み合わせ? などが続く
}
}

なお、C0 90% は、どっか別のテストケースで実行していたのが、たまたま通過していたものだったとか. 対となるテストケースの実行では無残なカバレッジ率とのこと… はあぁ 全部作り直しかよ~と、ご愁傷様です ( ̄人 ̄)
ひゃ~ こわい、こわい.

“assertTrue(true)” ?

そんな今となっては香ばしい昔話を黄泉がえらせたのが、Titter に 流れてきた衝撃のツイート.

“assertTrue(true)”… 何を言っているのかわかりませんでいた. いや TDD とかで、Fake it、ですよね~ やるやる~~www と…

いや、ヤバいです. どういう話しですか?
先の昔話も 今も テストの闇は深い… (“JUnit 目視” という、別系統のヤバいのも見た気が)

『知識ゼロから学ぶソフトウェアテスト』

テストで悩んだら『知識ゼロから学ぶソフトウェアテスト』を 読んでみるのがよいかと思った次第で、ちょうどアプリケーションのテストの不備に、”assertTrue(true)” という闇の呪文、半額セールが重なったので、勢い投稿を書いてしまいました!

こちらの書籍は JUnit とかの実装技術ではないのですが、「テストとは」という視点で書かれています. テストに対する考え方を学ぶことができるのではないでしょうか. いきなりテストケースを実装する前に、何を、どうやってテストするのかを知ってから、作るとよいかと思います.

私は改訂版の前の書籍を読んだことがありますが、テストケースはどう作るべきかの基礎をしっかり学ばせていただきました. 改訂版とのことで、どう変わったのかは改めて読みたいと思いますが、基本的な考え方を知るにはよいと思います.

ちょうど、翔泳社祭2017「ぼうけんキッズ」刊行記念eBOOK半額セール(2/22まで) | 翔泳社 で 電子ブックは半額で買えるので今はチャンスですね!

Amazon Kindle 版 は こちら https://amazon.jp/ebook/dp/B00HQ7S5CA です.


「テストって何をするんだろ」には『知識ゼロから学ぶソフトウェアテスト』も よいと思いますので、よかったら ご参考までに.