0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Raspberry Pi 5 で無課金、モニター不要、自動で会話を記録するスマートスピーカーを作成

Last updated at Posted at 2025-01-18

大学の講義でICT実践なるものがあり、スマートスピーカーを作ったのでそのプロセスを記録しておこうと思います。

systemdでの自動実行やOpenJTalkのインストールなどを行ったため、linux周りについての知識も身についたと思います。

本記事ではpythonコードの解説はほぼ行わず、

環境構築やライブラリのインストール、systemdの設定などをメインに扱っています。

pythonのコードについてはgithubのコードを見ればおおよそわかります。

GPIO_○○の記述を全削除すれば、その処理過程や内容はかなりわかりやすくなるはずです。

主な目次

何をつくるの?

スクリーンショット 2025-01-18 175309.png

Geminiとfaster-whisperを使って、無課金のスマートスピーカーを作ります。

もともとChatGPTと通信したかったのですが、ChatGPT apiのクレジット購入ができませんでした。
そのためGeminiの無料枠を利用します。

ちなみに、ChatGPT apiの無料クレジット付与については2025年1月17日現在、すでに終了しています。

ChatGPT apiの使用を想定していたため、入力内容をDeepLで英訳してからAPIに送信したりする機能もあります。
(元々ChatGPTと通信する場合にトークン数をなるべく節約したかった)

そのため、ChatGPTと通信する処理については中途半端に終わっています。

(ちゃんと通信が出来ていることは確認したが、応答の読み上げやGPTに渡す会話履歴の機能などについては動作未確認)

スマートスピーカーとの会話はスプレッドシートに記録され、設定したメールアドレスに送信されます。

全体の処理内容はこんな感じ。

スクリーンショット 2025-01-18 172002.png

今回作成したプログラムはすべてgithubに公開されています。

GPTに聞いたらライセンスを書いておけと言われたのでひとまずMITライセンスにしておきます。

これをダウンロードして、お手元のRaspberryPi 5 で実行できるようにします。

参考元

使用機器

Raspberry Pi 5 8GBモデル

USBスピーカー: サンワサプライ MM-SPU218K

USBマイク: MillSO

実行環境

Raspberry Pi OS 64bit

python 3.9.21

pyenvを利用して、仮想環境上ですべてのプログラムを実行しています。

1.環境構築

pyenvのインストール、パス設定

brewの方が良いと聞きますが、自分の場合curlで行いました。

GPTにお願いして出てきたコマンドをそのまま実行してます。

curl https://pyenv.run | bash

ホームディレクトリ直下にある.bashrcファイルに以下の内容を末尾に追加し、pyenvのパスを通します。

.bashsrc
# 他のいろんな記述...

export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init --path)"
eval "$(pyenv init -)"

.bashrcの変更内容を反映

source ~/.bashrc

これでpyenvコマンドが使えるようになります。

以下、pythonのインストールです。

pyenv install --listで利用可能なpythonのバージョンを確認できます。

今回はpython 3.9.21をインストールしていきます。

pyenv install 3.9.21

インストールが終わったら、今回使用する仮想環境を構築していきます。

RaspGPTを仮想環境の名前とします。

pyenv virtualenv 3.9.21 RaspGPT

これで仮想環境を構築することができます。
次は仮想環境を有効にします。

pyenv activate RaspGPT

これで仮想環境に入れます。

2.必要なライブラリのインストール

sudo apt install 編

忙しい方向けに。まとめた記述はこれ

sudo apt install portaudio19-dev
sudo apt-get install open-jtalk
sudo apt-get install open-jtalk-mecab-naist-jdic hts-voice-nitech-jp-atr503-m001

pyaudioのインストールに失敗しないようにインストール

sudo apt install portaudio19-dev

OpenJTalkのインストール

sudo apt-get install open-jtalk
sudo apt-get install open-jtalk-mecab-naist-jdic hts-voice-nitech-jp-atr503-m001

英語ならこれもある。(今回は使わなかった。)

sudo apt install espeak-ng espeak-ng-data
espeak-ng "Hello, this is a test of the eSpeak-ng engine."

特にportaudio19-devは、古い方の名前(たしかportaudio-devだったか……?)がよく出てくるので、注意です。

他にも色々インストールはしましたが、そのへんはエラーが出たら適宜インストールをお願いします。

ChatGPTに投げたり、ググれば出てくるはずです。

OpenJTalkの準備

OpenJTalkとは、
先程のコマンドを実行すれば、
/usr/share/hts-voice/nitech-jp-atr503-m001/nitech_jp_atr503_m001.htsvoice
というファイルがダウンロードされます。

これが音声モデルです。
また、
/var/lib/mecab/dic/open-jtalk/naist-jdicという辞書がダウンロードされます。

デフォルト音声モデルでも良いのですが、せっかくなので他のモデルもダウンロードします。

調べたところ初音ミクモデルなんかも有志の方が作られているとか。

ですが、今回はいろんなところで挙げられている「MEI」というhtsファイルをダウンロードします。

ここからダウンロードできるので、ダウンロードしたら解凍して、

MMDAgent_Example-1.8/Voice/mei

にある.htsファイルを、

/usr/share/hts-voice/

以下に移動させます。

githubに上げたコードはデフォルトでMEIのボイスを設定してあるので注意してください。

pip install 編

これをreqirements.txtに保存します。

reqirements.txt
ctranslate2
faster-whisper
openai
gpiozero
requests
pyaudio
pyttsx3
google-generativeai

次のコマンドを実行します。

pip install -r reqirements.txt

これで必要なものはまとめてインストールできます。

覚えておくと便利です。

ディレクトリはこんな配置になっています。

スクリーンショット 2025-01-18 172522.png

3.Main.py実行のために必要な設定

ソースコードはgithubに上げているので、そこからダウンロードしてください。

以降はホームディレクトリ直下にクローンしたものと仮定して、すなわち

~/RaspGPT

の形でダウンロードされているという前提で説明を進めます

GPIOと回路周り

こんな感じの回路を想定しています。

スクリーンショット 2025-01-18 172510.png

faster-whisper-smallのダウンロード

cd ~/RaspGPT/modules
git clone https://huggingface.co/Systran/faster-whisper-small

git LFSの設定は別にいらないです。

ファイルサイズの関係上、「model.bin」をダウンロードできなかったりするので、

から手動でダウンロードし、modules/faster-whisper-smallのmodel.binに上書きします。

mediumモデルやlargeモデルも同じやりかたでダウンロードできます。

Main.pyにapiキーを設定。

自分で取得をお願いします。
Main.pyを開くとmain関数内冒頭のほうにAPIキーを格納するための変数があるのでそこに記述をお願いします。

GAS_URLは後ほど。

Main.py
main():
    # ....
    
    API_KEY_DEEPL = "hogehoge_DEEPL"
    API_KEY_GPT = None
    API_KEY_GEMINI = "hogehoge_GEMINI"
    GAS_URL = "hogehoge_GAS_URL"
    # ....

USBマイクの設定

modules/Record.pyでは、録音を行う際にUSBデバイスのインデックスが必要。
そのために使用するマイクのデバイス名を調べます。

マイクを抜き差ししながら

lsusb

とするとマイクのデバイス名が大体わかります。自分の場合は

Bus 004 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 003 Device 002: ID 1a81:1004 Holtek Semiconductor, Inc. Wireless Dongle 2.4 GHZ HT82D40REW
Bus 003 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 001 Device 002: ID 1b3f:5580 Generalplus Technology Inc. SF-558
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

こんな感じで表示されて、抜き差しの結果

Bus 001 Device 002: ID 1b3f:5580 Generalplus Technology Inc. SF-558

SF-558というのがマイクの名前だとわかりましたので、Main.pyINPUT_DEVICE_INDEX

def main():
    # ...
    
    INPUT_DEVICE_INDEX = 'SF-558'
    # ...

と書き換えます。

ハードウェア名がかぶってたりすると不具合を起こす可能性があるので注意をお願いします。

不具合が起きた場合pyaudio側で認識されているデバイス名をフルで入力すれば確実なので、

~/RaspGPT
python modules/Record.py

としてデバイス名の一覧を取得し、コピペすればうまく行くはずです。(多分エラーが出ますが)

Google SpreadSheetの設定

Google SpreadSheetにアクセス。適当な名前のスプレッドシートを作り、

1行目、A列から順に Input | TranslatedInput | Response | DateTime

と記述します。

記述したら、 「拡張機能」>「AppsScript」

として、
以下のGASを記述します。

メールアドレスについては自分で記述をお願いします。

RaspGPTdataRecord
function doPost(e) {
  //data = {"Input":"test", "TranslatedInput":"test", "Response":"test"}
  const timestamp = new Date();
  const mailAddress = "<任意のメールアドレス>";
  const mailSubject = `RaspGPT@${timestamp}`;

  try {
    // 受信データをパース
    Logger.log(e)
    const data = JSON.parse(e.postData.contents);

    // スプレッドシートを取得
    const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("シート1");
    Logger.log(sheet)

    // スプレッドシートのカラム名を取得(1行目がカラム名と仮定)
    const headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0];
    Logger.log(headers)

    // データのキーを確認して不一致がある場合はエラーを返す
    const dataKeys = Object.keys(data);
    const invalidKeys = dataKeys.filter(key => !headers.includes(key));
    if (invalidKeys.length > 0) {
      throw new Error(`Invalid keys detected: ${invalidKeys.join(", ")}. Allowed keys are: ${headers.join(", ")}.`);
    }

    // データを追加する行を作成
    const row = headers.map(header => (header in data ? data[header] : "")); // マッチするデータがあれば値を、それ以外は空白に
    row[row.length-1] = timestamp; // 最後の列にタイムスタンプを追加


    // 新しい行をスプレッドシートに追記
    sheet.appendRow(row);

    // JSONの内容を改行区切りで文字列に直す
    const mailMassage = row.join("\n\n");

    // メールを送信
    eMailSend(mailAddress, mailSubject, mailMassage);

    // 成功レスポンスを返す
    return ContentService.createTextOutput(
      JSON.stringify({ status: "success", message: "Data appended successfully." })
    ).setMimeType(ContentService.MimeType.JSON);

  } catch (error) {
    // エラーレスポンスを返す
    Logger.log(error)
    return ContentService.createTextOutput(
      JSON.stringify({ status: "error", message: error.message+" __mail send error__ " })
    ).setMimeType(ContentService.MimeType.JSON);
  }
}

function eMailSend(mailAddress, mailSubject, mailMassage) {
  try {
    // メール送信
    GmailApp.sendEmail(mailAddress, mailSubject, mailMassage);
  } catch (error) {
    Logger.log(error)
    // エラー時のレスポンスを返す
    return ContentService.createTextOutput(
      JSON.stringify({ status: "error", message: error.message })
    ).setMimeType(ContentService.MimeType.JSON);
  }
}

記述したら、ウェブアプリケーションとしてデプロイします。

アクセスできるユーザーは「全員」 とします。

デプロイしたら、GASを実行するためのURLが表示されるので、それをコピー。

Main.pyの GAS_URL に貼り付けます。

これで、SpreadSheet.pyから先程記述したGASを実行し、スプレッドシートへの記録とメール送信を行うことができます。

一応、ここまでの設定を行えば

cd ~/RaspGPT
python Main.py

とするだけで実行はできるはずです。

4.systemdを使った自動実行とその他の実装

ここからはsystemdを使って機能を追加していきます。

RaspberryPi5では、/etc/rc.localを使った自動起動ができないと思われます。

また、今回は

  • Main.pyがUSB接続デバイスを取り扱う
  • pyenvの仮想環境からプログラムを自動で実行する

という2点があるため、一部bashファイルの記述やserviceファイルの記述内容にも注意が必要です。

これから、以下の機能の実装手順を解説していきます。

  1. プログラム(Main.py)の自動実行
  2. 特定のSSIDに対するwifi自動接続
  3. シャットダウンボタンの実装

共通手順

以下のコードを実行して、start_raspgpt.sh, wifi_reconnect.sh, button_shutdown.sh すべてに実行権限を付与します。

これがないとsystemdからバッシュファイルを実行できません。

~/RaspGPT/
chmod +x *.sh

あとで使うので、自分のユーザーidを確認してください。

id

一番最初にでてくるuidという番号を使います。

もし登録しているユーザが複数なら、

logname

で自分のユーザー名を確認して、どれがidの出力結果から自分のidを特定してください。

以下、それぞれの機能について説明します。

1. プログラム(Main.py)の自動実行

start_raspgpt.shの編集

まず、start_raspgpt.shを開いて、***で囲ってある部分を編集します。

start_raspgpt.sh
#!/bin/bash

# pyenv の環境変数とパスを設定
export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"

# pyenv を初期化
eval "$(pyenv init --path)"
eval "$(pyenv init -)"
eval "$(pyenv virtualenv-init -)"

# スピーカーの音量調整
amixer sset Master 80%

# 指定の仮想環境を有効化
pyenv shell RaspGPT

# スクリプト実行ディレクトリに移動
cd /home/***自分のユーザー名***/RaspGPT

# Python スクリプトを実行
python Main.py

raspgpt.serviceの編集

_serviceディレクトリ以下にserviceファイルが格納されているので、これを編集します。

cd ~/RaspGPT/_service
sudo nano raspgpt.service

この記述内容のうち、***で囲ってある箇所を編集してください。

raspgpt.service
[Unit]
Description=Raspgpt Auto Start Service
After=network.target

[Service]
Type=simple
User=***自分のユーザー名***
WorkingDirectory=/home/***自分のユーザー名***/RaspGPT
ExecStart=/home/***自分のユーザー名***/RaspGPT/start_raspgpt.sh
Environment="XDG_RUNTIME_DIR"=/run/user/***自分のユーザーid***
Restart=always
RestartSec=7

[Install]
WantedBy=multi-user.target

記述したら、Ctrl + Sで保存し、Ctrl + Xでエディタを閉じて、

~/RaspGPT/_service/
sudo cp raspgpt.service /etc/systemd/system/raspgpt.service
sudo systemctl daemon-reload
sudo systemctl enable raspgpt.service
sudo systemctl start raspgpt.service

としてやれば、systemdがラズパイ起動時に毎回

/home/***自分のユーザー名***/RaspGPT/start_raspgpt.sh

を実行し、その結果Main.pyが実行されるようになります。

もしこれでMain.pyがうまく動かない場合は、

sudo journalctl -u raspgpt.service

としてやればサービスのログを確認できるのでここから原因を探りましょう。

2. と 3. のおおまかな流れはstart_raspgpt.serviceと一緒なので、同じ箇所は説明を省きます。

2. 特定のSSIDに対するwifi自動接続

wifiが切れたりしても勝手に再接続を試みるバッシュファイル。

時々このバッシュファイルが動いていてもGUI側に接続するか否かのウィンドウが表示されることがある。

なんとなく、wifiのSSIDを変えると次回起動時に聞いてくる印象です。

実際に使う場合はそこに気をつければ大丈夫。

wifi_reconnect.shの編集

SSIDとネットワークインターフェースを記述

wifi_reconnect.sh
#!/bin/bash

# 再接続を試みるSSIDのリスト(必要に応じて追加・変更)
TARGET_SSIDS=("***SSIDを設定***")

# チェックするネットワークインターフェース名(例: wlan0)
WIFI_INTERFACE="***ネットワークインターフェースを設定***"

# 接続確認と再接続試行のループ
while true; do
    # 現在の接続状態をチェック
    if nmcli -t -f DEVICE,STATE device status | grep "^${WIFI_INTERFACE}:connected" > /dev/null; then
        echo "Wi‑Fiは接続済みです。"
	break
    else
        echo "Wi‑Fi接続がありません。再接続を試みます..."

        # 各SSIDに対して再接続を試みる
        for ssid in "${TARGET_SSIDS[@]}"; do
            echo "SSID: $ssid への接続を試行中..."
            nmcli device wifi connect "$ssid" ifname "$WIFI_INTERFACE"
            # 接続に成功したらループを抜ける
            if nmcli -t -f DEVICE,STATE device status | grep "^${WIFI_INTERFACE}:connected" > /dev/null; then
                echo "$ssid に接続しました。"
                break
            fi
        done
    fi

    # 一定間隔をおいて再チェック(例: 60秒)
    sleep 3
done

wifi_reconnect.serviceの編集

cd ~/RaspGPT/_service
sudo nano wifi_reconnect.service

ユーザー名を記述

wifi_reconnect.service
[Unit]
Description=Wi-Fi Reconnect Service
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
ExecStart= /home/***ユーザー名***/RaspGPT/wifi_reconnect.sh
Restart=on-failure
User=***ユーザー名***
WorkingDirectory=/home/***ユーザー名***/RaspGPT

[Install]
WantedBy=multi-user.target

以下のコマンドを実行

~/RaspGPT/_service/
sudo cp wifi_reconnect.service /etc/systemd/system/wifi_reconnect.service
sudo systemctl daemon-reload
sudo systemctl enable wifi_reconnect.service
sudo systemctl start wifi_reconnect.service

NetworkManagerのディスパッチャディレクトリを編集

ネットワークの状態が変化した時に自動で実行される。

以下のコマンドを実行。

sudo nano /etc/NetworkManager/dispatcher.d/99-restart-wifi-reconnect

次の内容を記述

99-restart-wifi-reconnect
#!/bin/bash

INTERFACE="$1"
STATUS="$2"

# 特定のWi-Fiインターフェースと切断イベントを検知
if [ "$INTERFACE" = "wlan0" ] && [ "$STATUS" = "down" ]; then
    systemctl restart wifi_reconnect.service
fi

3. シャットダウンボタンの実装

RaspberryPi5にデフォルトで付いている電源ボタンを長押しした場合、正常終了しているのか怪しかったためシャットダウンボタンも一応実装。

ボタンが押されるとコマンド

sudo shutdown -h now

が実行される。

3番ピンと6番ピンをつないでシャットダウンボタンを作る記事もあったが、

RaspberryPi5でこの機能は排除されたと思われる。

button_shutdown.shの編集

ユーザー名を記述

button_shutdown.sh
#!/bin/bash

# pyenv の環境変数とパスを設定
export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"

# pyenv を初期化
eval "$(pyenv init --path)"
eval "$(pyenv init -)"
eval "$(pyenv virtualenv-init -)"

# 指定の仮想環境を有効化
pyenv shell RaspGPT

# スクリプト実行ディレクトリに移動
cd /home/***自分のユーザー名***/RaspGPT

# Python スクリプトを実行
python button_shutdown.py

button_shutdown.serviceの編集

cd ~/RaspGPT/_service
sudo nano button_shutdown.service

ユーザー名を記述

button_shutdown.service
[Unit]
Description=Button Triggered Shutdown
After=multi-user.target

[Service]
ExecStart=/home/***自分のユーザー名***/RaspGPT/button_shutdown.sh
Environment="XDG_RUNTIME_DIR"=/run/user/***ユーザーid***
Restart=always
RestartSec=7
User=***自分のユーザー名***

[Install]
WantedBy=multi-user.target

以下のコマンドを実行

~/RaspGPT/_service/
sudo cp button_shutdown.service /etc/systemd/system/button_shutdown.service
sudo systemctl daemon-reload
sudo systemctl enable button_shutdown.service
sudo systemctl start button_shutdown.service

これで完成(のはず)

以上の手順を踏めば、RaspberryPi起動時にMain.pyが実行され、そのほか諸々の機能も正常に実行されるはずです。

今回目指したこと

今回作るスマートスピーカーは、このような特徴があります。

  • ローカルで文字起こし
  • DeepLと通信して翻訳
  • Geminiと通信(何気にあんまり見かけない)
  • 送られてきた内容を逐次読み上げる(レスポンスの高速化)
  • スプレッドシートに内容を記録、メール送信
  • GPIOピンからのボタン入力で処理を行うため、モニター不要
  • シャットダウンボタンを実装することで安全に終了可能
  • ラズパイ起動時に自動でプログラムが起動(プログラムが終了してもすぐに再度実行される)
  • 指定したSSIDと接続できない場合、再接続を試みる

さっくり言うと

実用的なスマートスピーカーを目指して作りました。

かなり多機能になっているかと思われます。
尚の事、ChatGPT APIを使えなかったことが悔やまれます。

使い方メモ

自分でダウンロードしたhtsを使いたい

自分で追加したいモデルがあれば適宜.htsファイルをダウンロードして、

/usr/share/hts-voice/

に追加して、modulesディレクトリ内のVoReading.pyvoice_pathsを編集して、

RaspGPT/modules/VoReading.py
        # Set default paths
        voice_paths = {
            "DEFAULT": "/usr/share/hts-voice/nitech-jp-atr503-m001/nitech_jp_atr503_m001.htsvoice",
            "MEI_ANG": "/usr/share/hts-voice/mei_angry.htsvoice",
            "MEI_BAS": "/usr/share/hts-voice/mei_angry.htsvoice",
            "MEI_HAP": "/usr/share/hts-voice/mei_angry.htsvoice",
            "MEI_NOR": "/usr/share/hts-voice/mei_angry.htsvoice",
            "MEI_SAD": "/usr/share/hts-voice/mei_angry.htsvoice",
            "新しく追加する音声の名前": "usr/share/hts-voice/追加したhtsのファイル名"
        }

Main.py

RaspGPT/Main.py
    #=============================
    # OpenJTalkの設定
    #=============================

    # 話すスピード
    SPEED = 1.3

    # 声の種類
    VOICE_TYPE = "新しく追加する音声の名前"

    # 辞書のパス
    DIC_PATH = None

VOICE_TYPEを変更してください。これでモデルの変更が可能です。

おまけ

RaspberryPi 5で直接文字起こししたときの速度

faster-whisperINT8量子化モデルを使用しています。

smallモデルではbatch_size=3, beam_size=1

mediumモデルではbatch_size=1, beam_size=1

デフォルトで設定しています。

使用するモデルについては、

WHISPER_MODELを変更することで変えられます。

使ってみた感じ


faster-whisper-small

普通にスマートスピーカーとして使うならこちらが推奨。

短い音声(4〜8秒)ならほぼ同じ秒数で処理可能。(9秒前後のことが多い)

長い音声(48秒とか)ならそれより短い時間(28秒前後)で処理可能。

beam_sizeが大きいと逆に精度が落ちることもあり、なんともいえない。

デフォルトでは 1 に設定して、応答速度を上げている。

batch_sizeは何度か試した結果、3がベストだった。

ただし精度に不安あり。

そのため、Main.pyではinitial_promptを設定することで文字起こしの文脈が通りやすくなるよう調整している。

また、

  • Geminiの最新の応答内容を文ごとに分割し、ユーザが設定した数だけランダムにサンプリングして文章を記録
  • ユーザが最初に設定したWHISPER_MODEL_CONTEXT_DEFAULT に先程サンプリングした文章を加えて、それをinitial_promptに渡す

という処理を行っている。

これにより、会話の文脈にあった文字起こしをしやすくなるよう調整している。

が、それでもおかしな出力が返ってくることがある。


faster-whisper-medium

精度はsmallモデルよりも明らかに高い。

安定度が違う。

smallとmediumの間に超えられない壁があるんじゃないかと思うくらい差が歴然。

しかし、文字起こしにかかる時間が長い。

短い音声(4〜8秒)でも20秒以上かかることがザラ。

長い音声(48秒)なら70秒程度。

batch_sizebeam_size も調整したが無理。

応答が遅くてもいいやって人はこっちを使うのがいいかも。


結論
  • faster-whisper-smallがスマートスピーカーとして向いている
  • faster-whisper-mediumは応答が遅くても良い、正確性を重視したい場合に向いている

modules以下のpythonファイルからいじくれるので、興味がある方は試してみてください。

systemdのENVIRONMENT="XDG_RUNTIME_DIR"="/run/user/***自分のユーザーid***"とは何?

systemdは、システム環境でserviceファイル内で指定した内容を実行します。

この際ユーザーは指定されておらず、USBデバイスへのアクセスなどができません。

そのため、

Environment="XDG_RUNTIME_DIR"=/run/user/***自分のユーザーid***

としてXDG_RUNTIME_DIR/run/user/***自分のユーザーid***を指定してやることでserviceファイルの内容をどのユーザーとして実行するのかを指定しています。

エラーの原因がわからず調べていたところ、他の方がこの記述でうまくいったと報告していたので私も試してみたところうまくいきました。

よくわからなかったのでXDG_RUNTIME_DIRについてドキュメントを調べたところ、こんな内容が書かれていました。

以下ChatGPTの翻訳

pam_systemdのDescription部分の翻訳

pam_systemd は、ユーザーのセッションを systemd のログインマネージャである systemd-logind.service(8) に登録し、それによって systemd のコントロールグループ階層に統合します。

このモジュールは、ユーザーの JSON ユーザーレコード(定義されている場合)に基づき、セッションに対してさまざまなリソース管理やランタイムパラメータを適用します。


ログイン時の動作

pam_systemdsystemd-logind.service と連携して以下を実行します:

  1. ユーザーのランタイムディレクトリの作成:
  • /run/user/$UID がまだ存在しない場合、新しい tmpfs ファイルシステムとしてディレクトリを作成またはマウントし、クォータを適用します。その所有権はログインしたユーザーに変更されます。
  1. 環境変数 $XDG_SESSION_ID の初期化:
  • 監査機能が利用可能で、pam_loginuid.so がこのモジュールの前に実行されている場合、監査セッションID(/proc/self/sessionid)から初期化されます。そうでない場合、独立したセッションカウンタが使用されます。
  1. 新しいシステムスコープユニットの作成:
  • セッション用に新しい systemd スコープユニットが作成されます。
  • これがユーザーの最初の同時セッションである場合、自動的にユーザー専用のスライスユニットが user.slice 配下に作成され、その中にスコープが配置されます。
  • また、systemd のユーザーマネージャインスタンスを実行する user@.service のインスタンスが開始されます。
  1. ユーザー環境変数の設定:
  • ユーザーの JSON レコードに基づき、$TZ(タイムゾーン)、$EMAIL(メールアドレス)、$LANG(ロケール)の環境変数を設定します。
  • また、ユーザーレコードで明示的に設定された環境変数をインポートし、umasknice レベル、リソース制限を初期化します。

ログアウト時の動作

  1. プロセスの終了:
  • logind.conf(5) の設定で KillUserProcesses= が有効になっている場合、セッション内のすべてのプロセスが終了します。
  • ユーザーの最後の同時セッションが終了した場合、systemd のユーザーインスタンスとユーザーのスライスユニットも終了します。
  1. ランタイムディレクトリの削除:
  • ユーザーの最後の同時セッションが終了すると、/run/user/$UID とその内容も削除されます。

例外

  • システムが systemd を初期化システムとして起動していない場合、このモジュールは何もせず、すぐに PAM_SUCCESS を返します。

この仕組みによって、ユーザーごとのセッション管理、プロセスのクリーンアップ、リソースの分離が適切に行われ、システムの安定性とセキュリティが向上します。

ドキュメントのEnvironment $XDG_RUNTIME_DIRについて

ユーザープライベートで書き込み可能なディレクトリへのパスであり、マシン上でユーザーがログインしている間に関連付けられるものです。このディレクトリは、ユーザーが初めてログインしたときに自動的に作成され、ユーザーが最後にログアウトしたときに削除されます。

ユーザーが同時に2回ログインした場合、両方のセッションで同じ $XDG_RUNTIME_DIR とその内容が共有されます。一方、ユーザーが1回ログインし、その後ログアウトし、再びログインした場合、このディレクトリの内容はその間に消失します。ただし、アプリケーションはこの動作に依存せず、古いファイル(スタイルファイル)を扱えるようにする必要があります。

このディレクトリにセッションごとのプライベートデータを保存するには、ファイル名に $XDG_SESSION_ID の値を含めるべきです。このディレクトリは、AF_UNIXソケット、FIFO、PIDファイルなど、ランタイムファイルシステムオブジェクトに使用されるべきです。また、このディレクトリはローカルであることが保証され、オペレーティングシステムが提供する可能な限りのファイルシステム機能セットを提供します。

詳細については、XDG Base Directory Specification を参照してください。なお、現在のユーザーがセッションの元のユーザーでない場合、$XDG_RUNTIME_DIR は設定されません。

わからなかったのでさらに調べる

このサイトの $XDG_RUNTIME_DIR についての記述をChatGPTに翻訳させる。

$XDG_RUNTIME_DIR 関連の記述について要約

$XDG_RUNTIME_DIR は、ユーザー固有の一時的なランタイムファイルやその他のファイルオブジェクト(例: ソケット、名前付きパイプなど)を保存するためのベースディレクトリを定義する環境変数です。

主なポイント:

  • 所有権とアクセス権: このディレクトリはユーザー自身が所有し、ユーザーのみが読み書きアクセスできる必要があります。Unix のアクセスモードは 0700 に設定されるべきです。

  • ライフサイクル: ディレクトリの寿命はユーザーのログインセッションに関連付けられています。ユーザーが最初にログインしたときに作成され、完全にログアウトしたときに削除される必要があります。

  • 一貫性: ユーザーが複数回ログインした場合、同じディレクトリが指し示され、最初のログインから最後のログアウトまで存在し続ける必要があります。

  • 持続性: このディレクトリ内のファイルは、システムの再起動や完全なログアウト/ログインサイクルを超えて持続しない必要があります。

  • ローカルファイルシステム: ディレクトリはローカルファイルシステム上にあり、他のシステムと共有されない必要があります。また、オペレーティングシステムの標準に従った完全な機能を備えている必要があります。

これらの要件により、$XDG_RUNTIME_DIR はユーザーのランタイムデータの安全で一時的な保存場所として機能します。

/run/user/$UID がまだ存在しない場合、新しい tmpfs ファイルシステムとしてディレクトリを作成またはマウントし、クォータを適用します。その所有権はログインしたユーザーに変更されます。」

「なお、現在のユーザーがセッションの元のユーザーでない場合、$XDG_RUNTIME_DIR は設定されません。」

こうした記述から、systemd側でログインユーザーを設定するためにXDG_RUNTIME_DIRを参照していると解釈しています。

ちなみに、systemdのserviceファイル内では

Environment=TEST_VALUE="hogehoge"

と記述することで変数を代入できたりするとか。

詳しく知りたい方はこちらを参考にどうぞ

systemdのパラメータ(INSTALL, UNITなど)についてはこちらが参考になる

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?