Raspberry Pi  OS 定期タスクの登録作業を簡略化する

### Raspberry Pi OS で 定期タスクの登録作業を簡略化するには

Raspberry Pi 端末にインストールされた Raspberry Pi OS で定期的に実行したいタスクがある場合、通常はログインユーザーが **```crontab -e```** で定期的に実行したいシェルスクリプトなどを登録しますが、定期タスクの数が増えれば端末上での登録作業も大変です。

さらに登録した定期タスクが一発で正しく動くようにするのも同様です。特に Raspberry Pi 端末が本番運用を想定している場合であればなおさらです。

今回は開発用 PC (Ubuntu OS) 上で定期タスクとスクリプトを作成し、動作検証済みの cron ファイルを Raspberry Pi 端末にインストールするシェルスクリプトを紹介します。

### 1 環境

#### 1-1 ターゲット端末

Raspberry Pi 4 ModelB
+ OS: Raspberry Pi OS (bookworm)
開発用 PC から SSHでログインできる状態になっていること

```bash
raspi@raspi:~ $ lsb_release -a
No LSB modules are available.
Distributor ID:	Debian
Description:	Debian GNU/Linux 12 (bookworm)
Release:	12
Codename:	bookworm

# アーキテクチャ
raspi@raspi:~ $ hostnamectl
 Static hostname: raspi
       Icon name: computer
      Machine ID: 8d34417287e64420a7fce2aaf05079f2
         Boot ID: f7bf314abf294661b3a27710fd03cc95
Operating System: Debian GNU/Linux 12 (bookworm)  
          Kernel: Linux 6.6.51+rpt-rpi-v8
    Architecture: arm64
```

+ cron 実行ユーザ: raspi

```bash
raspi@raspi:~ $ echo $USER
raspi
```

+ 想定する定期タスク
    + データベースへのデータ登録バッチ処理
    ※バッチ処理時にログファイルを出力
    + ログファイルの削除処理

#### 1-2 開発用 PC

+ OS: Ubuntu 22.04

```bash
i$ lsb_release -a
No LSB modules are available.
Distributor ID:	Ubuntu
Description:	Ubuntu 22.04.5 LTS
Release:	22.04
Codename:	jammy
```
+ cron 実行ユーザ: yukio

```bash
$ echo $USER
yukio
```


### 2 定期タスクのファイル作成

#### 2-1 開発用 PC で登録・作成

コマンド **crontab** を実行しログインユーザ用の cron ファイルを作成する

```bash
$ crontab -e
# Edit this file to introduce tasks to be run by cron.
#
# Each task to run has to be defined through a single line
# indicating with different fields when the task will be run
# and what command to run for the task
#
# To define the time you can provide concrete values for
# minute (m), hour (h), day of month (dom), month (mon),
# and day of week (dow) or use '*' in these fields (for 'any').
#
# Notice that tasks will be started based on the cron's system
# daemon's notion of time and timezones.
#
# Output of the crontab jobs (including errors) is sent through
# email to the user the crontab file belongs to (unless redirected).
#
# For example, you can run a backup of all your user accounts
# at 5 a.m every week with:
# 0 5 * * 1 tar -zcf /var/backups/home.tgz /home/
#
# For more information see the manual pages of crontab(5) and cron(8)
#
# m h  dom mon dow   command
```

末尾に２つのタスクを登録し、```:wq``` で登録タスクを保存します。

+ データベースにデータを登録するシェルスクリプトの呼び出し
毎日 9時に実行
+ 所定日付の古いログファイルを削除するシェルスクリプトの呼び出し
毎日 9時30に実行
 

```bash
# m h  dom mon dow   command
00 09 * * * /home/yukio/bin/scp_data_and_batch_insert.sh
30 09 * * * /home/yukio/bin/delete_before_days_log.sh 1
```

参考までにログファイル削除スクリプトを下記に示します。

```delete_before_days_log.sh
#!/bin/bash

# 指定した日付前のファイルをすべて削除する
# $1: 7
find_cmd=$(which find)

# アプリケーションログディレクトリ: ~/logs/servertools
APPLOG_DIR="$HOME/logs/servertools"
# ログディレクトリ: ~/Documents/webriverside
DOCS_BASE="$HOME/Documents/webriverside"
# エラーログディレクトリ
ERRLOG_DIR="$DOCS_BASE/error_logs"
# CSVディレクトリ
CSV_DIR="$DOCS_BASE/csv"

$find_cmd $APPLOG_DIR -name "*.log" -mtime "+${1}" -delete
$find_cmd $ERRLOG_DIR -name "*.log" -mtime "+${1}" -delete
$find_cmd $CSV_DIR -name "*.csv" -mtime "+${1}" -delete
```


#### 2-2 開発用PCで実行確認

開発用 PC 上で定期タスクを実行しジャーナルログで実行結果を確認します。

(1) 最初のバッチ処理実行

```bash
yukio@Dell-T7500:~$ journalctl -u cron.service \
 --since="2024-11-25 08:55:00" --until="2024-11-25 09:05:00" \
 --no-hostname -o short-iso
2024-11-25T09:00:01+0900 CRON[4690]: pam_unix(cron:session): session opened for user yukio(uid=1000) by (uid=0)
2024-11-25T09:00:01+0900 CRON[4691]: (yukio) CMD (/home/yukio/bin/scp_data_and_batch_insert.sh)
2024-11-25T09:00:05+0900 CRON[4690]: (CRON) info (No MTA installed, discarding output)
2024-11-25T09:00:05+0900 CRON[4690]: pam_unix(cron:session): session closed for user yukio
```

(2) 最後のバッチ処理実行

```bash
yukio@Dell-T7500:~$ journalctl -u cron.service \
 --since="2024-11-25 09:25:00" --until="2024-11-25 09:35:00" \
 --no-hostname -o short-iso
2024-11-25T09:30:02+0900 CRON[5611]: pam_unix(cron:session): session opened for user root(uid=0) by (uid=0)
2024-11-25T09:30:02+0900 CRON[5612]: pam_unix(cron:session): session opened for user yukio(uid=1000) by (uid=0)
2024-11-25T09:30:02+0900 CRON[5614]: (yukio) CMD (/home/yukio/bin/delete_before_days_log.sh 7)
2024-11-25T09:30:02+0900 CRON[5611]: pam_unix(cron:session): session closed for user root
2024-11-25T09:30:02+0900 CRON[5612]: pam_unix(cron:session): session closed for user yukio
```


#### 2-3 ターゲット端末用の cron ファイル作成

(1) 開発用 PCの cron 実行ユーザーをターゲット端末の cron 実行ユーザーに変換する
※1 コメント行が削除されタスク行のみになります。
※2 コメント行も含める場合は **```| grep yukio \```** 部分を除外します。

```bash
$ sudo cat /var/spool/cron/crontabs/yukio \
 | grep yukio \
 | sed 's/yukio/raspi/g' \
 | tee ~/work/crontab/raspi
00 09 * * * /home/raspi/bin/scp_data_and_batch_insert.sh
30 09 * * * /home/raspi/bin/delete_before_days_log.sh 1
```

(2) ターゲット端末にコピー

```bash
$ scp -r ~/work/crontab/ raspi@192.168.0.24:~/
raspi                                 100%  113   145.6KB/s   00:00    
```


### 3 ターゲット端末の登録作業

ターゲット端末にSSHでログインします。

#### 3-1 手作業でインストール

(1) cron ファイルを手作業で所定のディレクトリにインストールする
※すべてのコマンド実行に sudo 権限が必要
+ ユーザの cron ファイルを所定の格納ディレクトリにコピー
+ ファイルの所有者 (**raspi**) とグループ (**crontab**) を変更
+ ファイルモードを変更 (**600**)

```bash
raspi@raspi:~ $ sudo cp ~/work/crontab/$USER /var/spool/cron/crontabs/
raspi@raspi:~ $ sudo chown $USER:crontab /var/spool/cron/crontabs/$USER
raspi@raspi:~ $ sudo chmod 600 /var/spool/cron/crontabs/$USER
raspi@raspi:~ $ sudo ls -l --time-style long-iso /var/spool/cron/crontabs/ | grep -v total
-rw------- 1 raspi crontab 1203 2024-11-25 17:33 raspi
```

(2) 登録作業の確認

**```crontab -l```** を実行しエラーなく以下のように出力されれば登録完了です

```bash
raspi@raspi:~ $ crontab -l
00 09 * * * /home/raspi/bin/scp_data_and_batch_insert.sh
30 09 * * * /home/raspi/bin/delete_before_days_log.sh 1
```

#### 3-2 タスクの実行確認

翌日の所定時刻に定期タスクが実行されたかをジャーナルログで確認します。

(1) 最初のバッチ処理実行

```bash
raspi@raspi:~ $ journalctl -u cron.service \
 --since="2024-11-26 08:55:00" --until="2024-11-26 09:05:00" \
 --no-hostname -o short-iso
2024-11-26T09:00:01+0900 CRON[1706]: pam_unix(cron:session): session opened for user raspi(uid=1000) by (uid=0)
2024-11-26T09:00:01+0900 CRON[1707]: (raspi) CMD (/home/raspi/bin/scp_data_and_batch_insert.sh)
2024-11-26T09:00:06+0900 CRON[1706]: (CRON) info (No MTA installed, discarding output)
2024-11-26T09:00:06+0900 CRON[1706]: pam_unix(cron:session): session closed for user raspi
```

(2) 最初のバッチ処理で出力されたログファイルを確認する 

(3) 最後のバッチ処理実行

```bash
raspi@raspi:~ $ journalctl -u cron.service \
 --since="2024-11-26 09:25:00" --until="2024-11-26 09:35:00" \
 --no-hostname -o short-iso
2024-11-26T09:30:01+0900 CRON[2589]: pam_unix(cron:session): session opened for user raspi(uid=1000) by (uid=0)
2024-11-26T09:30:01+0900 CRON[2590]: (raspi) CMD (/home/raspi/bin/delete_before_days_log.sh 1)
2024-11-26T09:30:01+0900 CRON[2589]: pam_unix(cron:session): session closed for user raspi
```

(4) 所定の古い日付のログファイルが削除されているか確認する


(5) 手作業で実行したコマンド入力をスクリプト化する

```shell
# cron: ~/bin/scp_data_and_batch_insert.sh every days
echo $my_passwd | { sudo --stdin cp ~/work/crontab/$USER /var/spool/cron/crontabs/
  sudo chown $USER:crontab /var/spool/cron/crontabs/$USER
  sudo chmod 600 /var/spool/cron/crontabs/$USER
}
```

(6) アプリケーションのインストールスクリプトに組み込む

下記投稿で紹介したアプリケーションのインストールスクリプトを例とします。

https://qiita.com/pipito-yukio/items/ff48e158e080c450a7c8

最新の docker (Community edition) パッケージを OS にインストールし PostgreSQLコンテナを起動するサービスを登録するインストールスクリプトになります。

+ アプリケーションインストーラーの処理内容
    + 古い docker 関連パッケージ削除
    + 安定版の docker 関連パッケージインストール
    + ログインユーザーに docker 実行権限を付与
    + Python仮想環境の作成とライブラリのインストール
    + PostgreSQL コンテナ起動サービスの登録処理
    + 定期タスクのインストール処理

```1_inst_libraries.sh
#!/bin/bash

# execute before export my_passwd=xxxxxx
# https://docs.docker.com/engine/install/debian/
#   Install Docker Engine on Raspberry pi OS based Debian Bookworm 12 (stable)
# Uninstall old versions
echo $my_passwd | {
for pkg in docker.io docker-doc docker-compose podman-docker containerd runc
do
   sudo --stdin apt-get -y remove $pkg
done
}

echo $my_passwd | { sudo --stdin apt-get update
   sudo apt-get -y install ca-certificates curl python3-venv sqlite3 tree
}

# Dockerの公式GPG鍵を追加する
echo $my_passwd | {
   sudo --stdin curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
   sudo chmod a+r /etc/apt/keyrings/docker.asc
}
exit1=$?
if [ $exit1 -ne 0 ]; then
   exit $exit1
fi

# 安定版のリポジトリのセットアップ
echo $my_passwd | { echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  sudo --stdin tee /etc/apt/sources.list.d/docker.list > /dev/null
}

# Docker Engine のインストール: + docker-buildx-plugin 
echo $my_passwd | { sudo --stdin apt-get update
   sudo apt-get -y install docker-ce docker-ce-cli containerd.io \
   docker-buildx-plugin docker-compose-plugin
}
exit1=$?
if [ $exit1 -ne 0 ]; then
   exit $exit1
fi

# docker execute to $USER
echo $my_passwd | sudo --stdin gpasswd -a $USER docker

# Create Virtual Python environment.
if [ ! -d "$HOME/py_venv" ]; then
   mkdir py_venv
fi

cd py_venv
python3 -m venv py_psycopg2
. py_psycopg2/bin/activate
pip install -U pip
pip install -r ~/work/requirements.txt
exit1=$?
echo "Install requirements libraries into psycopg2 >> status=$exit1"
deactivate
if [ $exit1 -ne 0 ]; then
   exit $exit1
fi
cd ~/

# Enable user apps system services
echo $my_passwd | { sudo --stdin cp ~/work/etc/default/postgresql-docker /etc/default
  sudo cp ~/work/etc/systemd/system/postgresql-docker.service /etc/systemd/system
  sudo systemctl enable postgresql-docker.service
}

# ★ cron: ~/bin/scp_data_and_batch_insert.sh every days
echo $my_passwd | { sudo --stdin cp ~/work/crontab/$USER /var/spool/cron/crontabs/
  sudo chown $USER:crontab /var/spool/cron/crontabs/$USER
  sudo chmod 600 /var/spool/cron/crontabs/$USER
}

echo "Done, logout this terminqal."
```

### おまけ

Raspberry Pi zero W (Raspberry Pi OS Lite) に定期タスクをインストールした例を下記に示します。

+ 気象センサーからの気象データ (UDPパケット) をモニターしLEDに出力
+ 毎日、決まった時刻に天気予報サイトから天気情報を取得する
※黄色いボタンを押すと直近の時刻の天気情報をモニターに出力する

![weather_display_0_overview2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3583120/6be68af7-2e8f-f776-45ea-7c1fada2bc2d.jpeg)

天気予報情報の取得については下記Qiita投稿をご覧ください。

https://qiita.com/pipito-yukio/items/19663813b95377e33f4e

OS: Raspbian OS (**headless OS**)

```bash
pi@raspi-zero:~ $ hostnamectl
   Static hostname: raspi-zero
         Icon name: computer
        Machine ID: 26ab3c9d4cc142168890ec1fe2632b21
           Boot ID: 268918c88ed648a28bedb44485de27b1
  Operating System: Raspbian GNU/Linux 10 (buster)
            Kernel: Linux 5.10.63+
      Architecture: arm
```

cron に登録した定期タスク

+ 天気情報取得処理
※1 毎日: 5時10分, 10時10分, 12時10分, 14時10分, 16時10分
※2 天気情報取得ログファイル、OLEディスプレー表示ログファイルを出力
+ ログファイル削除処理
※1 毎日 23時30分に上記で出力されたログファイルを削除

```bash
pi@raspi-zero:~ $ crontab -l
10 5,10,12,14,16 * * * /home/pi/bin/get_weather_forecast.sh
30 23 * * * /home/pi/bin/remove_applogs.sh
```

(1) 天気情報取得スクリプト

天気予報サイトにHTTPクライアントを使ってご当地の天気情報を収集するpythonスクリプトを呼び出す。

```get_weather_forecast.sh
#!/bin/bash

. $HOME/py_venv/py37_pigpio/bin/activate
python $HOME/bin/pigpio/GetWeatherForecastFromTenkijp.py

deactivate
```

(2) ログファイル削除スクリプト

```remove_applogs.sh
#!/bin/bash

cd $HOME/logs/pigpio
rm -f application_*.log
rm -f display_*.log
cd ~
```

以下は実際に作成したアプリケーションのインストールスクリプト

+ インストール処理内容
    + アプリケーションが参照する環境変数をユーザの .bashrc に設定
    + システムパッケージのインストール
    python3-venv, sqlite3, pigpio, i2c-tools 等
    + Python仮想環境の作成とライブラリのインストール
    + SQLite3 データベースの生成と気象データ登録用テーブルの作成
    + 定期タスクの登録処理
    ※コメント行に ★印がある箇所
    + アプリケーションサービスの登録
        + UDPパケットモニター起動サービス
        + シャットダウンボタン押下時のシャットダウン開始サーピス 

```inst_main.sh
#!/bin/bash

# execute before export my_passwd=xxxxxx

# add application environ values to .bashrc
cat ~/work/add_env_in_bashrc.txt >> ~/.bashrc

echo $my_passwd | sudo --stdin apt update && sudo apt -y upgrade
# headless os not installed.
echo $my_passwd | sudo --stdin apt -y install python3-venv sqlite3 pigpio i2c-tools tree

# Create Virtual Python environment.
mkdir py_venv
cd py_venv
python3 -m venv py37_pigpio
. py37_pigpio/bin/activate
pip install -U pip
pip install -r ~/work/py_venv/requirements_pigpio.txt
deactivate
cd ~/

# load PATH_WEATHER_DB
. ~/work/add_env_in_bashrc.txt
# Create weather database and tables by SQLite3
echo "Database file: $PATH_WEATHER_DB"
sqlite3 $PATH_WEATHER_DB < ~/db/weather_db.sql

# ★ cron: remove application logs every days ★
echo $my_passwd | { sudo --stdin cp ~/work/crontab/$USER /var/spool/cron/crontabs/
  sudo chown $USER:crontab /var/spool/cron/crontabs/$USER
  sudo chmod 600 /var/spool/cron/crontabs/$USER
}

# Enable pigpiod.service
echo $my_passwd | sudo --stdin systemctl enable pigpiod.service

# Enable application services
echo $my_passwd | { sudo --stdin cp ~/work/etc/default/switch-to-poweroff /etc/default
  sudo cp ~/work/etc/default/udp-weather-mon /etc/default
  sudo cp ~/work/etc/systemd/system/switch-to-poweroff.service /etc/systemd/system
  sudo cp ~/work/etc/systemd/system/udp-weather-mon.service /etc/systemd/system
  sudo systemctl enable udp-weather-mon.service
  sudo systemctl enable switch-to-poweroff.service
}

echo "Done."
```

ここで紹介したスクリプトの概要とソースは下記 GitHub で公開しています。

[(pipito-yukio) ラズベリーパイによる家庭用気象データ監視システム](https://github.com/pipito-yukio/home_weather_sensors)

### 最後に

Raspberry Pi 端末では GPIO などにアクセスするような定期タスクは少ないと思っています。

また Raspberry Pi OS Lite にいたってはヘッドレス OS なので、端末上でシェルスクリプトとか Python スクリプトを開発するなどは現実的でないでしょう。

開発用 PC の OS として UbuntuOS を使うと、ほとんどのケースで Raspberry Pi 用の定期タスクとそれに関連するスクリプトを開発・テストすることが可能になります。