🌞

夏の熱暴走から自宅鯖を守る!スマホの在宅判定+Nature Remoで自動シャットダウンスクリプト

に公開

はじめに

春の陽気から段々と夏の気配を感じ始めた今日この頃。
家の室温も30℃に達してきたため、自分がいない間に自宅鯖が熱暴走しないよう、外出中かつ暑くなったら自動シャットダウンするようにしたいと思います。

登場人物

  • 自宅鯖(Rocky Linux 9)
    自動シャットダウンをするサーバーです。
    条件判断と条件達成で自動シャットダウンを実行するスクリプトを組み、それをcronで定期実行させます。

  • Nature Remo 3
    今回は、こいつで取得した家の温度を使っていこうと思います。
    データはAPIで取得します。

  • スマホ
    在宅かどうかの判断は、スマホが家にいるかどうかで判断していきます。

  • ルーター
    スマホが家にいるかどうかの判断材料(一部)を提供してもらいます。

前準備

アクセストークンを取得

まずはこちらのページからアクセストークンを取得してください。

アクセストークンを環境変数に設定

任意のディレクトリで環境変数を設定

# ここだけroot権限で実行してください。
touch /etc/.env
echo 'NATURE_REMO_ACCESS_TOKEN="取得したトークンを設定"' > /etc/.env

rootしか読み込めないよう権限を変更します。

sudo chmod 600 /etc/.env

ルーターの設定

ルーター側の設定で、スマホのIPアドレスを固定しておいてください。

スクリプト作成

保存先

/usr/local/bin/auto-shutdown.shを作成します。

作成したスクリプト
#!/bin/bash

#============================================
# 1.設定
#============================================
if [ -f "/etc/.env" ]; then
  source "/etc/.env"
else
  logger -t "auto-shutdown" "Error: 環境変数が見つかりません。"
  exit 1
fi

PHONE_IP="スマホのIPアドレス"          # スマホの固定IP
FLAG_FILE="/tmp/InHome.flag"        # 最後に在宅と判定した時刻の記録
ABSENT_COUNT_FILE="/tmp/absent.count"  # 連続不在カウント
OVER_TEMP=32
GRACE_MINUTES=30                  # 在宅フラグの猶予期間(分)
ABSENT_THRESHOLD=3                # 連続不在カウントの閾値

LOG_TAG="auto-shutdown"

#============================================
# 2.関数:在宅と判定した場合の処理
#============================================
mark_home() {
  touch "$FLAG_FILE"
  echo "0" > "$ABSENT_COUNT_FILE"
  logger -t "$LOG_TAG" "スマホは家にいます。 判定理由:($1)"
  exit 0
}

#============================================
# 3.Ping判定
#============================================
if ping -c 3 -W 2 "$PHONE_IP" > /dev/null 2>&1; then
  mark_home "ping"
fi

#============================================
# 4.ARPテーブル判定
#============================================
NEIGH_STATE=$(ip neigh show "$PHONE_IP" 2>/dev/null | awk '{print $NF}')

case "$NEIGH_STATE" in
  REACHABLE|DELAY|PROBE|STALE|PERMANENT|NOARP)
    mark_home "arp:$NEIGH_STATE"
    ;;
esac

#============================================
# 5.不在判定の閾値確認
#============================================

# 5-1. 猶予期間チェック
if [ -f "$FLAG_FILE" ]; then
  if find "$FLAG_FILE" -mmin -${GRACE_MINUTES} | grep -q "$FLAG_FILE"; then
    logger -t "$LOG_TAG" "スマホは不在ですが、経過時間が短いのでシャットダウンしませんでした。"
    exit 0
  fi
fi

# 5-2. 連続不在カウントを増加
ABSENT_COUNT=0
if [ -f "$ABSENT_COUNT_FILE" ]; then
  ABSENT_COUNT=$(cat "$ABSENT_COUNT_FILE")
fi
ABSENT_COUNT=$((ABSENT_COUNT + 1))
echo "$ABSENT_COUNT" > "$ABSENT_COUNT_FILE"

logger -t "$LOG_TAG" "スマホが不在です。カウント:$ABSENT_COUNT/$ABSENT_THRESHOLD"

# 5-3. 閾値に未達でシャットダウン処理をスキップ
if [ "$ABSENT_COUNT" -lt "$ABSENT_THRESHOLD" ]; then
  exit 0
fi

#============================================
# 6.Nature Remoから温度を取得
#============================================
REMO_JSON=$(curl -s --max-time 10 -H "Authorization: Bearer $NATURE_REMO_ACCESS_TOKEN" "https://api.nature.global/1/devices")

# API応答に失敗した場合はシャットダウンしない
if [ -z "$REMO_JSON" ]; then
  logger -t "$LOG_TAG" "Nature Remoからの応答がありません。シャットダウン処理をスキップします。"
  exit 0
fi

TEMP=$(echo "$REMO_JSON" | jq -r ".[0] | .newest_events.te.val")

# 温度取得失敗(空 or null)の処理
if [ -z "$TEMP" ] || [ "$TEMP" = "null" ]; then
  logger -t "$LOG_TAG" "室温の取得に失敗しました。シャットダウン処理をスキップします。"
  exit 0
fi

#============================================
# 7.温度判定とシャットダウン
#============================================
if [ "$(echo "$TEMP >= $OVER_TEMP" | bc)" -eq 1 ]; then
  logger -t "$LOG_TAG" "不在および高温を検知しました。シャットダウンを実行します。"
  /sbin/shutdown -h now
else
  logger -t "$LOG_TAG" "スマホは不在ですが、室温が低いためシャットダウンしませんでした。"
fi

スクリプト解説

1. 設定

if [ -f "/etc/.env" ]; then
  source "/etc/.env"
else
  logger -t "auto-shutdown" "Error: 環境変数が見つかりません。"
  exit 1
fi

PHONE_IP="スマホのIPアドレス"          # スマホの固定IP
FLAG_FILE="/tmp/InHome.flag"        # 最後に在宅と判定した時刻の記録
ABSENT_COUNT_FILE="/tmp/absent.count"  # 連続不在カウント
OVER_TEMP=32
GRACE_MINUTES=30                  # 在宅フラグの猶予期間(分)
ABSENT_THRESHOLD=3                # 連続不在カウントの閾値

LOG_TAG="auto-shutdown"
  • if文
    前準備で用意したファイルの存在を確認後、環境変数を展開します。
  • 変数設定
    スクリプト内で使用する各変数を設定しています。
変数名 内容
PHONE_IP スマホのIPアドレス
FLAG_FILE 在宅フラグ用ファイル
ABSENT_COUNT_FILE 不在判定のカウント用ファイル
OVER_TEMP 室温の閾値(今回は32℃)
GRACE_MINUTES 不在判定の猶予時間
ABSENT_THRESHOLD 不在カウントの閾値

2.関数:在宅と判定した場合の処理

mark_home() {
  touch "$FLAG_FILE"
  echo "0" > "$ABSENT_COUNT_FILE"
  logger -t "$LOG_TAG" "スマホは家にいます。 判定理由:($1)"
  exit 0
}

mark_homeという関数を作成します。
関数にしておくことにより、在宅判定後の処理をこいつに一任します。
やっていることは、

・在宅判定用のファイルの更新
・在宅判定のカウントのリセット
・ログへの在宅判定の記載

となっております。

3.Ping判定

if ping -c 3 -W 2 "$PHONE_IP" > /dev/null 2>&1; then
  mark_home "ping"
fi

まずは、スマホへのPingで在宅の判定を行います。
Pingを3回打って、返答があれば在宅と判定され、在宅処理を行います。

4.ARPテーブル判定

NEIGH_STATE=$(ip neigh show "$PHONE_IP" 2>/dev/null | awk '{print $NF}')

case "$NEIGH_STATE" in
  REACHABLE|DELAY|PROBE|STALE|PERMANENT|NOARP)
    mark_home "arp:$NEIGH_STATE"
    ;;
esac

スマホがルーターに繋がっているのに、何かしらの不具合等でPingの反応がなかった場合に備えての追加判定です。
ARPテーブルを確認し、IPアドレスとMacアドレスの紐づけ状況を確認します。

  • NEIGH_STATE変数の設定
    ip neigh showコマンドで、ARPテーブル上におけるスマホのステータスを取得します。
    上記コマンドだけだと、以下のような表示になります。
IPアドレス dev デバイス名 lladdr Macアドレス ステータス

ここから、 awk '{print $NF}'によって、最終列のステータスだけ取り出します。

  • case文
    ステータスが、REACHABLE DELAY PROBE STALE PERMANENT NOARPのいずれかの場合、在宅と判定され、在宅処理を行います。
各ステータスの意味
ステータス 意味
REACHABLE 通信可能
STALE 最近通信したが、今は少し時間が経っている
DELAY / PROBE 通信確認を試みている最中
PERMANENT / NOARP 通常は出ない特殊なステータス
※想定外のシャットダウン防止で、念のため含めています。

5.不在判定の閾値確認

# 5-1. 猶予期間チェック
if [ -f "$FLAG_FILE" ]; then
  if find "$FLAG_FILE" -mmin -${GRACE_MINUTES} | grep -q "$FLAG_FILE"; then
    logger -t "$LOG_TAG" "スマホは不在ですが、経過時間が短いのでシャットダウンしませんでした。"
    exit 0
  fi
fi

# 5-2. 連続不在カウントを増加
ABSENT_COUNT=0
if [ -f "$ABSENT_COUNT_FILE" ]; then
  ABSENT_COUNT=$(cat "$ABSENT_COUNT_FILE")
fi
ABSENT_COUNT=$((ABSENT_COUNT + 1))
echo "$ABSENT_COUNT" > "$ABSENT_COUNT_FILE"

logger -t "$LOG_TAG" "スマホが不在です。カウント:$ABSENT_COUNT/$ABSENT_THRESHOLD"

# 5-3. 閾値に未達でシャットダウン処理をスキップ
if [ "$ABSENT_COUNT" -lt "$ABSENT_THRESHOLD" ]; then
  exit 0
fi

在宅中の誤判定や、コンビニにちょっと行った際など急にシャットダウンされるのも困るので、複数回不在だった場合のみシャットダウンします。
ここの処理は、指定した回数の不在判定が行われているかの確認です。

5-1. 猶予期間チェック
フラグ用ファイルの更新時間をチェックしています。
更新時間が30分以内であれば、ログにその旨を記載し、そのまま処理を終了します。

5-2. 連続不在カウントを増加
5-1で更新時間が閾値をオーバーしていることが判明したら、不在判定としてカウントを追加しています。

5-3. 閾値に未達でシャットダウン処理をスキップ
5-2で追加したカウントが閾値を超えていないか確認します。
超えていなければ、そのまま処理を終了します。

6.Nature Remoから温度を取得

REMO_JSON=$(curl -s --max-time 10 -H "Authorization: Bearer $NATURE_REMO_ACCESS_TOKEN" "https://api.nature.global/1/devices")

# API応答に失敗した場合はシャットダウンしない
if [ -z "$REMO_JSON" ]; then
  logger -t "$LOG_TAG" "Nature Remoからの応答がありません。シャットダウン処理をスキップします。"
  exit 0
fi

TEMP=$(echo "$REMO_JSON" | jq -r ".[0] | .newest_events.te.val")

# 温度取得失敗(空 or null)の処理
if [ -z "$TEMP" ] || [ "$TEMP" = "null" ]; then
  logger -t "$LOG_TAG" "室温の取得に失敗しました。シャットダウン処理をスキップします。"
  exit 0
fi
  • REMO_JSON変数の設定
    REMO_JSON変数に、curlコマンドでAPIから取得した値を全て格納します。

  • API応答に失敗した場合はシャットダウンしない
    REMO_JSON変数に値を入力できなかった場合、APIサーバから応答がなかったとしてシャットダウン処理をスキップします。

  • TEMP変数の設定
    REMO_JSON変数から温度に該当する数値だけを抽出して、TEMP変数に格納します。

  • 温度取得失敗(空 or null)の処理
    TEMP変数が空(or null)の場合にシャットダウン処理をスキップします。

7.温度判定とシャットダウン

if [ "$(echo "$TEMP >= $OVER_TEMP" | bc)" -eq 1 ]; then
  logger -t "$LOG_TAG" "不在および高温を検知しました。シャットダウンを実行します。"
  /sbin/shutdown -h now
else
  logger -t "$LOG_TAG" "スマホは不在ですが、室温が低いためシャットダウンしませんでした。"
fi

TEMP変数の値が閾値(今回は32℃)に達していれば、シャットダウン処理を実行します。

cronへの登録

以下コマンドでcronに登録します。
今回作成したスクリプトを10分毎に実行するよう設定します。

sudo crontab -e
*/10 * * * * /usr/local/bin/auto-shutdown.sh

あとがき

今回は、外出中に室温が上昇して、サーバーが落ちるのを防ぐためのスクリプトを書いてみました。
まだ設定した閾値の32℃を超える季節ではありませんが、このスクリプトが無事起動してサーバーを守ってくれることを祈りつつ、適宜修正はしていこうと思います。

※この記事と同様の記事をQiita(https://qiita.com/KO_angels/items/b9a1d4cf1e2b68d6a3a2)にも挙げています。

Discussion