2026年5月16日土曜日

ラズパイでNAS +α

 手持ちのラズパイ3B+をNASにし、DLNAサーバーとしても運用したい。

手持ちの5インチディスプレイを使用し、監視システム+α(時計とか天気とか)の表示をさせたい。chatGPTに相談しながら作成してみる。

1.サーバー監視

・ストレージ使用率(総容量、使用量、空き、〇%超で色変化)

・ディスク状態(温度、SMART異常の有無)

・ネットワーク状況(リアルタイム転送速度、接続クライアント数)

・CPU/メモリ使用率(RPi3の負荷監視)

・稼働時間(再起動忘れ防止)

2.+α

・現在時間、カレンダー

・天気情報

・接続中ユーザー表示

・最近のファイル更新ログ

・バックアップ状態

・フォトフレーム(NAS内の写真を表示)

・音楽再生情報(再生中の曲名)

・ニュースヘッドライン

3.状態異常の強調表示

・温度〇度以上の時、ストレージ使用率〇%以上、ネットワーク切断 など


構想:
まず、1のサーバー監視を作成し、順次+αのうちいずれかを表示できるように考えたい。
→python+FlaskでシンプルWeb表示


環境構築:

OS:Debialn13 (コードネームtrixie)
ラズベリーパイ3B+
KUMAN 5インチディスプレイ(800×480)

sambaとminidlnaの導入

①HDDの接続確認

$ lsblk
出力例)
NAME        MAJ:MIN RM  SIZE RO TYPE MOUNTPOINTS
sda            8:0    0  3.6T  0 disk
┗sda1        8:1    0  3.6T  0 part /media/pi

②HDDのファイル形式確認

$ sudo blkid
出力例)
/dev/sda1: LABEL="HD-ADU3" BLOCK_SIZE="512" UUID="XXXXXXXXX" TYPE="ntfs"
※UUIDは後で使います。ntfsでフォーマットされています。

③必要なソフトのインストール、まとめて実行

$ sudo apt update
$ sudo apt install samba minidlna ntfs-3g exfat-fuse -y

最新バージョン(2026.5.10時点)
samba is already the newest version (2:4.22.8+dfsg-0+deb13u1).
minidlna is already the newest version (1.3.3+dfsg-1.1+b1).
ntfs-3g is already the newest version (1:2022.10.3-5+deb13u1).
exfat-fuse is already the newest version (1.4.0-3+b2).

④HDDのマウント先を作る

$ sukdo mkdir -p /mnt/hdd

⑤HDDを手動でマウント

$ sudo mount -t ntfs-3g /dev/sda1 /mnt/hdd

下記のエラーが出る場合

Mount is denied because the NTFS volume is already exclusively opened.
The volume may be already mounted, or another software may use it which
could be identified for example by the help of the 'fuser' command.

すでに /dev/sda1がどこかに接続されているためにおこるエラー。接続先を調べるには
$ mount | grep sda1
表示例)
/dev/sda1 on /media/pi/HD-ADU3 type ntfs3 
→/media/piに接続されているから /mnt/hddには接続できないので、一旦外す。

※/media/pi は一時的な接続先となる。USBメモリの接続など。そのため、毎回必ず「/media/pi/HD-ADU3」となるとは限らない。サーバー用途などでは、「/mnt/hdd」だけではなく「/srv/storage」なども良い。

$ sudo umount /media/pi/HD-ADU3
表示例)
sda           8:0    0  3.6T  0 disk
┗sda1        8:1    0  3.6T  0 part
①の表示例、(┗sda1        8:1    0  3.6T  0 part /media/pi)と比べるとマウントポイントが消えていることが分かる。
再度手動でマウントする。
$ sudo mount -t ntfs-3g /dev/sda1 /mnt/hdd

⑥成功確認

$  ls /mnt/hdd
HDDの中のファイル一覧が見えれば成功

⑦自動マウント設定

UUIDを確認
$ sudo blkid
表示例)
/dev/sda1: UUID="340815D408159648"

fstab編集
$ sudo nano /etc/fstab

一番下に下記の一文を追加
UUID=上記のUUID /mnt/hdd ntfs-3g defaults,uid=1000,gid=1000,umask=000 0 0

上記のUUID:このHDDを使う
/mnt/hdd:ここにHDDを接続する
ntfs-3g:NTFSを読み書きできるドライバを使う
defaults:Linux標準設定。(読み書き可能、自動マウント、実行許可、デバイス許可など)
uid=1000:ユーザー番号1000の所有物にする。ラズパイではpiがUID1000。
gid=1000:グループ番号。通常piのグループ。
umask=000:全員フルアクセスOK。セキュリティ的には自宅内限定。
0:dumpという古いバックアップ機能、現代ではほぼ使わない。
0:fsckという起動チェック設定。LinuxではNTFSは完全修復できないので0でOK。

つまり、「UUIDが〇〇のNTFS HDDを、起動時に /mnt/hddへ接続し、piユーザーが自由に読み書きできるようにする。」という意味。

⑧Samba設定(Windows共有)

設定ファイル編集
$ sudo nano /etc/samba/smb.conf

一番下に下記を追加
[HDD]
path = /mnt/hdd
browseable = yes
writeable = yes
create mask = 0777
directory mask = 0777
public = yes
guest ok = yes

⑨Samba再起動

$ sudo systemctl restart smbd
$ sudo systemctl enable smbd

⑩Windowsからアクセス

Windowsから
\\raspberrypi(ホストネーム、IPアドレス)
を開く。

※Windowsの「ネットワークの資格情報」にてはじかれてしまう場合は、
「Raspberry Piに存在するLinuxユーザー」、「Sambaに登録されたユーザー」、「Windowsで入力するユーザー」が一致している必要がある。
RaspberryPi側で $ whoami →例)pi
Sambaユーザー登録
$ sudo smbpasswd -a pi
表示例)
New SMB password:
Retype new password:

ここで設定したパスワードが、Windowsから接続するときに使うパスワードです。

※ネットワークドライブとして割り当てると後々アクセスが楽になる。

⑪MiniDLNA設定

$ sodo nano /etc/minidlna.conf

以下を探して変更
media_dir=/mnt/hdd    メディアフォルダ
friendly_name=RPi3    名前
inotify=yes                        自動監視ON

⑫MiniDLNA再起動

$ sudo systemctl restart minidlna
$ sudo systemctl enable minidlna

⑬データベース再構築(初回)

$ sudo minidlnad -R

⑭動作確認(Windows)

エクスプローラーで\\RPi3 

⑮トラブル時の確認コマンド

$ systemctl status smbd  Samba状態
表示例)
pi@RPi3:~ $ systemctl status smbd
● smbd.service - Samba SMB Daemon
     Loaded: loaded (/usr/lib/systemd/system/smbd.se>
     Active: active (running) since Sun 2026-05-10 2>
 Invocation: 8ab259a2c8db44a2a2b87a54ba54d3b6
       Docs: man:smbd(8)
             man:samba(7)
             man:smb.conf(5)
   Main PID: 11111 (smbd)
     Status: "smbd: ready to serve connections..."
      Tasks: 4 (limit: 751)
        CPU: 7.385s
     CGroup: /system.slice/smbd.service
             tq11111 /usr/sbin/smbd --foreground --n>
             tq11114 "smbd: notifyd" .
             tq11115 "smbd: cleanupd "
             mq11583 "smbd: client [240b:13:7e1:7600>

$ systemctl status minidlna   DLNA状態
表示例)
pi@RPi3:~ $ systemctl status minidlna
● minidlna.service - MiniDLNA lightweight DLNA/UPnP->
     Loaded: loaded (/usr/lib/systemd/system/minidln>
     Active: active (running) since Sun 2026-05-10 2>
 Invocation: bea131b5f08645419921f4a3d9b183c8
       Docs: man:minidlnad(1)
             man:minidlna.conf(5)
   Main PID: 11666 (minidlnad)
      Tasks: 2 (limit: 751)
        CPU: 4min 53.080s
     CGroup: /system.slice/minidlna.service
             tq11666 /usr/sbin/minidlnad -f /etc/min>
             mq11673 /usr/sbin/minidlnad -f /etc/min>


Python+Flaskで表示画面作成

1.必要なパッケージをインストールする

$ sudo apt update
$ sudo apt install -y python3-flask python3-psutil smartmontools
$ sudo apt install -y mpd mpc  ※音楽表示をする場合

②作業フォルダ作成

$ mkdir -p ~/nas-monitor/templates
$ mkdir -p ~/nas-monitor/static

フォルダ構成
/home/pi/nas-monitor/
├── app.py
├── templates/
│     └── index.html
└── static/
    └── style.css

③Pythonコード作成(Flask本体)

$ nano ~/nas-monitor/app.py

from flask import Flask, render_template, jsonify
import psutil
import shutil
import subprocess
import time
from datetime import datetime

app = Flask(__name__)

# ===== 設定 =====

# HDD名
DISK = "/dev/sdb"

# 使用しているネットワークIF
# Wi-Fiなら wlan0
# 有線なら eth0
NETWORK_IF = "wlan0"

# マウント先
MOUNT_POINT = "/mnt/hdd"

# ==================

# ネットワーク速度計算用
last_bytes = 0
last_time = time.time()

def get_cpu_temp():

    try:

        temp = subprocess.check_output(
            ["vcgencmd", "measure_temp"]
        ).decode()

        return temp.replace("temp=", "").replace("'C\n", "")

    except:

        return "不明"


def get_disk_temp():

    try:

        output = subprocess.check_output(
            [
                "sudo",
                "smartctl",
                "-A",
                "-d",
                "sat",
                DISK
            ]
        ).decode()

        for line in output.splitlines():

            if "Temperature_Celsius" in line:

                return line.split()[-1] + "°C"

            if "Airflow_Temperature_Cel" in line:

                return line.split()[-1] + "°C"

        return "不明"

    except Exception as e:

        return "取得失敗"


def get_smart_status():

    try:

        output = subprocess.check_output(
            [
                "sudo",
                "smartctl",
                "-H",
                "-d",
                "sat",
                DISK
            ]
        ).decode()

        if "PASSED" in output:

            return "正常"

        else:

            return "異常"

    except:

        return "取得失敗"


def get_now_playing():

    try:

        song = subprocess.check_output(
            ["mpc", "current"]
        ).decode().strip()

        if song == "":

            return "なし"

        return song

    except:

        return "なし"


def get_network_speed():

    global last_bytes
    global last_time

    try:

        counters = psutil.net_io_counters(pernic=True)

        if NETWORK_IF not in counters:

            return 0

        net = counters[NETWORK_IF]

        current_bytes = net.bytes_recv + net.bytes_sent

        current_time = time.time()

        speed = (
            current_bytes - last_bytes
        ) / (
            current_time - last_time
        )

        last_bytes = current_bytes
        last_time = current_time

        # MB/s
        return round(speed / 1024 / 1024, 2)

    except:

        return 0


@app.route("/")
def index():

    return render_template("index.html")


@app.route("/status")
def status():

    # CPU
    cpu = psutil.cpu_percent()

    # RAM
    ram = psutil.virtual_memory().percent

    # ストレージ
    total, used, free = shutil.disk_usage(MOUNT_POINT)

    storage_percent = round(
        (used / total) * 100,
        1
    )

    total_tb = round(
        total / (1024**4),
        1
    )

    # 温度
    cpu_temp = get_cpu_temp()

    disk_temp = get_disk_temp()

    # SMART
    smart = get_smart_status()

    # ネットワーク
    network_speed = get_network_speed()

    # 時刻
    now = datetime.now().strftime("%H:%M:%S")

    # 音楽
    music = get_now_playing()

    return jsonify({

        "cpu": cpu,

        "ram": ram,

        "storage_percent": storage_percent,

        "total_tb": total_tb,

        "cpu_temp": cpu_temp,

        "disk_temp": disk_temp,

        "smart": smart,

        "network_speed": network_speed,

        "time": now,

        "music": music
    })


if __name__ == "__main__":

    app.run(
        host="0.0.0.0",
        port=5000
    )

④HTML作成

$ nano ~/nas-monitor/templates/index.html

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>NAS Monitor</title>
    <link rel="stylesheet" href="/static/style.css">
</head>
<body>
<div class="container">
    <div class="top">
        <div class="left">
            <h2>ストレージ使用率:<span id="storage"></span>%</h2>
            <p>( <span id="total"></span>TB)</p>
        </div>
        <div class="right">
            <p>システム温度:<span id="cpu_temp"></span>°C</p>
            <p>ディスク温度:<span id="disk_temp"></span></p>
            <p>SMART:<span id="smart"></span></p>
        </div>
    </div>
    <div class="middle">
        <div>
            <p>ネットワーク転送速度:<span id="network"></span> MB/s</p>
        </div>
        <div>
            <p>CPU使用率:<span id="cpu"></span>%</p>
            <p>RAM使用率:<span id="ram"></span>%</p>
        </div>
    </div>
    <div class="clock">
        <p>現在時間</p>
        <div id="time">00:00:00</div>
    </div>
    <div class="music">
        <p>現在再生中の音楽:<span id="music"></span></p>
    </div>
</div>
<script>
async function updateStatus() {
    const response = await fetch("/status");
    const data = await response.json();
    document.getElementById("storage").innerText =
        data.storage_percent;
    document.getElementById("total").innerText =
        data.total_gb;
    document.getElementById("cpu_temp").innerText =
        data.cpu_temp;
    document.getElementById("disk_temp").innerText =
        data.disk_temp;
    document.getElementById("smart").innerText =
        data.smart;
    document.getElementById("network").innerText =
        data.network_speed;
    document.getElementById("cpu").innerText =
        data.cpu;
    document.getElementById("ram").innerText =
        data.ram;
    document.getElementById("time").innerText =
        data.time;
    document.getElementById("music").innerText =
        data.music;
}
setInterval(updateStatus, 1000);
updateStatus();
</script>
</body>
</html>

⑤CSS作成

$ nano ~/nas-monitor/static/style.css

body {
    background: #efefef;
    font-family: sans-serif;
    margin: 0;
    padding: 10px;
    width: 800px;
    height: 480px;
    overflow: hidden;
}
.container {
    display: flex;
    flex-direction: column;
    height: 100%;
}
.top {
    display: flex;
    justify-content: space-between;
}
.middle {
    margin-top: 40px;
    display: flex;
    justify-content: space-between;
}
.clock {
    margin-top: 40px;
    text-align: center;
}
#time {
    font-size: 80px;
    letter-spacing: 10px;
}
.music {
    margin-top: 30px;
    font-size: 20px;
}

⑥Flask起動

$ cd ~/nas-monitor
$ python3 app.py

表示例)
 * Running on all addresses (0.0.0.0)
 * Running on http://127.0.0.1:5000
 * Running on http://192.168.10.131:5000

ラズパイからなら、ブラウザにhttp://127.0.0.1:5000と入力
LAN内のWindows機からはhttp://192.168.10.131:5000と入力すると表示される。

⑦sudoers設定

$ sudo visudo

最後に以下の1文を追記する。

pi ALL=(ALL) NOPASSWD: /usr/sbin/smartctl

⑧自動起動設定

サービスに登録する。

$ sudo nano /etc/systemd/system/nas-monitor.service

[Unit]
Description=NAS Monitor
After=network.target

[Service]
User=pi
WorkingDirectory=/home/pi/nas-monitor
ExecStart=/usr/bin/python3 /home/pi/nas-monitor/app.py
Restart=always

[Install]
WantedBy=multi-user.target

上記自作のサービスを有効にする。

$ sudo systemctl daemon-reload
$ sudo systemctl enable nas-monitor
$ sudo systemctl start nas-monitor

⑨フルスクリーン表示

$ sudo apt install -y chromium
$ mkdir -p ~/.config/lxsession/LXDE
$ nano ~/.config/lxsession/LXDE/autostart

@xset s off
@xset -dpms
@xset s noblank

@unclutter -idle 0

@chromium
--kiosk
--app=http://localhost:5000
--no-first-run
--disable-infobars
--disable-session-crashed-bubble
--disable-features=TranslateUI
--disable-gpu
--incognito
--noerrdialogs


表示されない場合、LXDEが起動していない可能性もある。
必要なGUIを追加する。
$ sudo apt update
$ sudo apt install -y lxde-core lightdm xserver-xorg xinit openbox
$ sudo nano /etc/lightdm/lightdm.conf

[Seat:*]の下に以下を追加。
autologin-user=pi
autologin-user-timeout=0
user-session=LXDE
#autologin-session=rpd-labwc

でもTrixie は:labwc(Wayland)が標準採用なんだよね。。。