これから暑くなっていくので、CPU温度を監視して、指定温度になったらクロックを抑える監視モニターを作ってみました。

出来るだけホスト環境を汚さないようにLXDコンテナを作成して行うようにしています。
スクリプトが自動で行うこと
- コンテナ名の確認 — 作成するLXDコンテナ名を対話的に入力
- 前提条件チェック — LXD、lm-sensors、curl が無ければインストール
- ホスト側ファイル生成 —
throttle.sh、web_server.py、systemd service ファイルを/opt/script/cpu/に配置 - LXDコンテナ作成 — Ubuntu 26.04(非特権)+
/sys読み取り専用マウント + 共有ディレクトリ(ホストへのポート転送は無し) - コンテナ内セットアップ — Python3 + Webサーバ + Tailscale インストール・有効化 + systemd サービス設定
- ホスト側systemd有効化 — 再起動時に自動起動
- 動作確認 — コンテナ状態、コンテナ内Webサーバ応答、Tailscale状態、サービス状態を表示
- コンテナ内シェルに入る — 最後に
lxc exec <コンテナ名> -- bashを実行した状態で終了。ここで手動でTailscaleの認証を行う
設定値
| 変数 | デフォルト | 説明 |
|---|---|---|
CONTAINER_NAME | cpu | LXDコンテナ名(実行時に対話入力でも変更可) |
WEB_PORT | 3336 | Webダッシュボードのポート(コンテナ内) |
THRESHOLD | 80 | 温度閾値の初期値(℃)※後からWebで変更可能 |
THROTTLE_FREQ | 1800000 | 制限時のクロック(kHz) |
構成
ホスト
├── /opt/script/cpu/throttle.sh ← 温度監視・クロック制限(実際の書き換えはここ)
├── /opt/script/cpu/web_server.py ← Webダッシュボード(コンテナへコピーされる)
├── /opt/script/cpu/threshold.conf ← 温度閾値(Webから変更可能)
├── /opt/script/cpu/throttle.log ← ログ
└── /opt/script/cpu/cpu-throttle.service ← systemd設定(ホスト側)
LXDコンテナ(非特権, Ubuntu 26.04)
├── /host/sys/ ← ホストの/sys(読み取り専用マウント)
├── /opt/script/cpu/ ← 共有ディレクトリ(ホストと同一パス)
├── Tailscale ← MagicDNS経由のアクセス用(手動認証)
└── Webサーバ (port 3336, ホストへのポート転送なし)
インストール
実行するとまずコンテナ名の入力を求められます(Enterでデフォルトの cpu を使用)。
sudo mkdir -p /opt/script
sudo chown $USER:$USER /opt/script
chmod u+rwx /opt/script
cd /opt/script
nano install-cpucheck1.sh
sudo bash install-cpucheck1.sh
#!/usr/bin/env bash
# ============================================================================
# CPU温度監視環境 セットアップスクリプト
# LXDコンテナ (Ubuntu 26.04) + Webダッシュボード + CPUスロットル制御
# Tailscale経由でアクセス(ホスト側ポート転送なし)
# ============================================================================
set -euo pipefail
# --- 設定(デフォルト値) ---
CONTAINER_NAME="cpu"
WEB_PORT=3336
THRESHOLD=80
THROTTLE_FREQ=1800000 # 制限時クロック (kHz)
INSTALL_DIR="/opt/script/cpu"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# --- カラー出力 ---
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m'
info() { echo -e "${CYAN}[INFO]${NC} $*"; }
ok() { echo -e "${GREEN}[OK]${NC} $*"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
err() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
# --- コンテナ名の確認 ---
prompt_container_name() {
echo ""
read -p "作成するLXDコンテナ名を入力してください [デフォルト: ${CONTAINER_NAME}]: " input_name
if [[ -n "$input_name" ]]; then
CONTAINER_NAME="$input_name"
fi
ok "コンテナ名: ${CONTAINER_NAME}"
}
# --- 前提チェック ---
check_prereqs() {
info "前提条件をチェック中..."
if [[ $EUID -ne 0 ]]; then
err "sudo で実行してください: sudo $0"
exit 1
fi
# LXD
if ! command -v lxd &>/dev/null && ! command -v lxc &>/dev/null; then
warn "LXD がインストールされていません。インストールします..."
if command -v snap &>/dev/null; then
snap install lxd
lxd init --auto
else
err "snap が見つかりません。手動でLXDをインストールしてください。"
exit 1
fi
fi
ok "LXD: $(lxd --version 2>/dev/null || snap list lxd 2>/dev/null | awk 'NR==2{print $2}')"
# lm-sensors
if ! command -v sensors &>/dev/null; then
warn "lm-sensors がインストールされていません。インストールします..."
apt-get install -y lm-sensors
sensors-detect --auto 2>/dev/null || true
fi
ok "lm-sensors: $(sensors --version 2>/dev/null | head -1)"
# curl
if ! command -v curl &>/dev/null; then
apt-get install -y curl
fi
ok "curl: $(curl --version | head -1)"
echo ""
}
# --- ファイル生成 ---
generate_throttle_script() {
mkdir -p "$INSTALL_DIR"
cat > "$INSTALL_DIR/throttle.sh" << 'THROTTLE_EOF'
#!/usr/bin/env bash
set -euo pipefail
DEFAULT_THRESHOLD=__THRESHOLD__
THROTTLE_FREQ=__THROTTLE_FREQ__
INTERVAL=2
LOG_FILE="__INSTALL_DIR__/throttle.log"
CTRL_FILE="__INSTALL_DIR__/throttle_ctrl"
THRESHOLD_FILE="__INSTALL_DIR__/threshold.conf"
log() {
local msg="[$(date '+%Y-%m-%d %H:%M:%S')] $1"
echo "$msg"
echo "$msg" >> "$LOG_FILE"
}
get_threshold() {
if [[ -f "$THRESHOLD_FILE" ]]; then
local v
v=$(cat "$THRESHOLD_FILE" 2>/dev/null | tr -d '[:space:]')
if [[ "$v" =~ ^[0-9]+$ ]]; then
echo "$v"
return
fi
fi
echo "$DEFAULT_THRESHOLD"
}
get_temp() {
sensors 2>/dev/null | awk '/Package id 0/ {gsub(/[^0-9.]/, "", $3); printf "%d", $3}'
}
get_hw_max_freq() {
cat /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq
}
get_cur_max_freq() {
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq
}
get_cpu_count() {
ls -d /sys/devices/system/cpu/cpu[0-9]* | wc -l
}
set_all_max_freq() {
local freq=$1
local count
count=$(get_cpu_count)
for ((i = 0; i < count; i++)); do
echo "$freq" > "/sys/devices/system/cpu/cpu${i}/cpufreq/scaling_max_freq"
done
}
do_throttle() {
local cur hw
cur=$(get_cur_max_freq)
hw=$(get_hw_max_freq)
if [[ "$cur" -eq "$hw" ]]; then
log "手動制限: ${THROTTLE_FREQ} kHz に設定"
set_all_max_freq "$THROTTLE_FREQ"
fi
}
do_restore() {
local hw
hw=$(get_hw_max_freq)
log "手動復帰: ${hw} kHz に設定"
set_all_max_freq "$hw"
}
check_ctrl() {
if [[ -f "$CTRL_FILE" ]]; then
local cmd
cmd=$(cat "$CTRL_FILE" 2>/dev/null || true)
rm -f "$CTRL_FILE"
case "$cmd" in
throttle) do_throttle ;;
restore) do_restore ;;
esac
fi
}
if [[ $EUID -ne 0 ]]; then
echo "エラー: sudo で実行してください" >&2
exit 1
fi
# 初回起動時に閾値ファイルが無ければデフォルト値で作成
if [[ ! -f "$THRESHOLD_FILE" ]]; then
echo "$DEFAULT_THRESHOLD" > "$THRESHOLD_FILE"
fi
HW_MAX=$(get_hw_max_freq)
CPU_COUNT=$(get_cpu_count)
THROTTLED=false
LAST_THRESHOLD=$(get_threshold)
log "=== CPU温度監視スクリプト開始 ==="
log "CPU数: ${CPU_COUNT}, 最大クロック: ${HW_MAX} kHz"
log "制限クロック: ${THROTTLE_FREQ} kHz, 初期閾値: ${LAST_THRESHOLD}℃"
trap 'log "スクリプト停止"; exit 0' INT TERM
while true; do
check_ctrl
THRESHOLD=$(get_threshold)
if [[ "$THRESHOLD" != "$LAST_THRESHOLD" ]]; then
log "閾値変更を検知: ${LAST_THRESHOLD}℃ → ${THRESHOLD}℃"
LAST_THRESHOLD="$THRESHOLD"
fi
TEMP=$(get_temp)
if [[ -z "$TEMP" ]]; then
log "警告: 温度の取得に失敗"
sleep "$INTERVAL"
continue
fi
if [[ "$THROTTLED" == false && "$TEMP" -ge "$THRESHOLD" ]]; then
log "温度 ${TEMP}℃ >= ${THRESHOLD}℃ → 制限開始 (${THROTTLE_FREQ} kHz)"
set_all_max_freq "$THROTTLE_FREQ"
THROTTLED=true
elif [[ "$THROTTLED" == true && "$TEMP" -lt "$THRESHOLD" ]]; then
log "温度 ${TEMP}℃ < ${THRESHOLD}℃ → 復帰 (${HW_MAX} kHz)"
set_all_max_freq "$HW_MAX"
THROTTLED=false
fi
sleep "$INTERVAL"
done
THROTTLE_EOF
# プレースホルダー置換
sed -i "s|__THRESHOLD__|${THRESHOLD}|g" "$INSTALL_DIR/throttle.sh"
sed -i "s|__THROTTLE_FREQ__|${THROTTLE_FREQ}|g" "$INSTALL_DIR/throttle.sh"
sed -i "s|__INSTALL_DIR__|${INSTALL_DIR}|g" "$INSTALL_DIR/throttle.sh"
chmod +x "$INSTALL_DIR/throttle.sh"
ok "throttle.sh を生成: $INSTALL_DIR/throttle.sh"
}
generate_web_server() {
cat > "$INSTALL_DIR/web_server.py" << 'WEB_EOF'
#!/usr/bin/env python3
"""CPU温度監視 Webダッシュボード"""
import http.server, time
from pathlib import Path
from urllib.parse import urlparse, parse_qs
PORT = __WEB_PORT__
SYS_ROOT = Path("/host/sys")
SHARED_DIR = Path("__INSTALL_DIR__")
CTRL_FILE = SHARED_DIR / "throttle_ctrl"
THRESHOLD_FILE = SHARED_DIR / "threshold.conf"
DEFAULT_THRESHOLD = __THRESHOLD__
def get_threshold():
try:
v = THRESHOLD_FILE.read_text().strip()
return int(v)
except Exception:
return DEFAULT_THRESHOLD
def set_threshold(v):
THRESHOLD_FILE.write_text(f"{v}\n")
def get_cpu_freq():
result = {}
cpu_base = SYS_ROOT / "devices" / "system" / "cpu"
if not cpu_base.exists(): return result
for d in sorted(cpu_base.iterdir()):
if not d.name.startswith("cpu"): continue
cp = d / "cpufreq"
if not cp.exists(): continue
try:
result[d.name] = {
"cur": int((cp/"scaling_cur_freq").read_text().strip())//1000,
"max": int((cp/"scaling_max_freq").read_text().strip())//1000,
"hw": int((cp/"cpuinfo_max_freq").read_text().strip())//1000,
}
except: pass
return result
def get_temps():
temps = {}
hb = SYS_ROOT / "class" / "hwmon"
if not hb.exists(): return temps
for hw in sorted(hb.iterdir()):
nf = hw / "name"
if not nf.exists(): continue
nm = nf.read_text().strip()
if nm not in ("coretemp","acpitz"): continue
for f in sorted(hw.iterdir()):
if not (f.name.startswith("temp") and f.name.endswith("_input")): continue
try:
v = int(f.read_text().strip())/1000
lf = hw / f.name.replace("_input","_label")
lb = lf.read_text().strip() if lf.exists() else f.name
temps[lb] = round(v,1)
except: pass
return temps
def get_state():
lf = SHARED_DIR / "throttle.log"
if not lf.exists(): return False
try:
for l in reversed(lf.read_text().strip().split("\n")):
if "制限開始" in l: return True
if "復帰" in l: return False
except: pass
return False
def get_logs(n=8):
lf = SHARED_DIR / "throttle.log"
if not lf.exists(): return "---"
try: return "\n".join(lf.read_text().strip().split("\n")[-n:])
except: return "---"
def build():
ci = get_cpu_freq(); tp = get_temps(); th = get_state(); lg = get_logs()
cur_threshold = get_threshold()
cr = ""
for nm,inf in ci.items():
p = int(inf["cur"]/inf["hw"]*100) if inf["hw"]>0 else 0
c = "#4caf50" if p<60 else "#ff9800" if p<85 else "#f44336"
cr += f'<tr><td>{nm}</td><td>{inf["cur"]} MHz</td><td>{inf["max"]} MHz</td><td><div class="bb"><div class="b" style="width:{p}%;background:{c}"></div></div></td></tr>'
tr = ""
for lb,v in tp.items():
c = "#4caf50" if v<60 else "#ff9800" if v<80 else "#f44336"
tr += f'<tr><td>{lb}</td><td style="color:{c};font-weight:bold">{v}°C</td></tr>'
st = "THROTTLED" if th else "NORMAL"
sc = "#f44336" if th else "#4caf50"
bl = "Restore Full Speed" if th else "Throttle Now"
ba = "restore" if th else "throttle"
bc = "#4caf50" if th else "#f44336"
return f'''<!DOCTYPE html><html lang="ja"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>CPU Monitor</title><meta http-equiv="refresh" content="10"><style>
*{{margin:0;padding:0;box-sizing:border-box}}body{{font-family:-apple-system,BlinkMacSystemFont,sans-serif;background:#0f0f1a;color:#ccc;padding:20px}}
h1{{text-align:center;margin-bottom:16px;color:#00d4ff;font-size:1.4em}}
.st{{text-align:center;font-size:1.1em;margin-bottom:16px;padding:8px;border-radius:6px;background:{sc}22;color:{sc}}}
.bt{{display:block;margin:0 auto 16px;padding:8px 24px;border:none;border-radius:6px;background:{bc};color:#fff;font-size:1em;cursor:pointer}}
.bt:hover{{opacity:.85}}.g{{display:grid;grid-template-columns:1fr 1fr;gap:16px;max-width:860px;margin:0 auto}}
.c{{background:#161625;border-radius:10px;padding:16px;border:1px solid #252540}}
.c h2{{font-size:.9em;margin-bottom:10px;color:#00d4ff;border-bottom:1px solid #252540;padding-bottom:6px}}
table{{width:100%;border-collapse:collapse}}th,td{{padding:6px 10px;text-align:left;font-size:.85em}}th{{color:#666;font-weight:normal}}
.bb{{background:#252540;border-radius:3px;height:18px;width:100%}}.b{{height:100%;border-radius:3px;transition:width .5s}}
.lb{{background:#0a0a14;border-radius:8px;padding:12px;font-family:monospace;font-size:.75em;white-space:pre-wrap;max-height:180px;overflow-y:auto;margin-top:16px;max-width:860px;margin-left:auto;margin-right:auto;border:1px solid #252540}}
.lt{{text-align:center;color:#666;margin-top:16px;margin-bottom:4px;font-size:.85em}}
.th{{max-width:860px;margin:0 auto 16px;text-align:center}}
.th input{{width:70px;padding:6px;border-radius:4px;border:1px solid #252540;background:#0a0a14;color:#ccc;text-align:center;font-size:1em}}
.th button{{padding:6px 16px;border:none;border-radius:4px;background:#00d4ff;color:#0f0f1a;font-weight:bold;cursor:pointer;margin-left:6px}}
@media(max-width:600px){{.g{{grid-template-columns:1fr}}}}</style></head><body>
<h1>CPU Temperature Monitor</h1>
<div class="st">{st}</div>
<button class="bt" onclick="fetch('/api/{ba}').then(()=>location.reload())">{bl}</button>
<div class="th">閾値: <input type="number" id="th_input" value="{cur_threshold}" min="30" max="100">℃
<button onclick="setThreshold()">適用</button></div>
<div class="g"><div class="c"><h2>Temperature</h2><table>{tr}</table></div>
<div class="c"><h2>CPU Frequency</h2><table><tr><th>Core</th><th>Current</th><th>Limit</th><th></th></tr>{cr}</table></div></div>
<div class="lt">Throttle Log</div><div class="lb">{lg}</div>
<script>
function setThreshold() {{
const v = document.getElementById('th_input').value;
fetch('/api/set_threshold?value=' + encodeURIComponent(v)).then(()=>location.reload());
}}
</script>
</body></html>'''
class H(http.server.BaseHTTPRequestHandler):
def do_GET(self):
parsed = urlparse(self.path)
p = parsed.path
if p=="/api/throttle":
CTRL_FILE.write_text("throttle\n")
self.send_response(200); self.send_header("Content-Type","application/json"); self.end_headers()
self.wfile.write(b'{"ok":true}')
elif p=="/api/restore":
CTRL_FILE.write_text("restore\n")
self.send_response(200); self.send_header("Content-Type","application/json"); self.end_headers()
self.wfile.write(b'{"ok":true}')
elif p=="/api/set_threshold":
qs = parse_qs(parsed.query)
try:
v = int(qs.get("value", [""])[0])
if 0 < v < 150:
set_threshold(v)
ok = True
else:
ok = False
except Exception:
ok = False
self.send_response(200); self.send_header("Content-Type","application/json"); self.end_headers()
self.wfile.write(f'{{"ok":{str(ok).lower()}}}'.encode())
else:
self.send_response(200); self.send_header("Content-Type","text/html;charset=utf-8"); self.end_headers()
self.wfile.write(build().encode())
def log_message(self,*a): pass
if __name__=="__main__":
s=http.server.HTTPServer(("0.0.0.0",PORT),H)
print(f"CPU Monitor on http://0.0.0.0:{PORT}"); s.serve_forever()
WEB_EOF
sed -i "s|__WEB_PORT__|${WEB_PORT}|g" "$INSTALL_DIR/web_server.py"
sed -i "s|__INSTALL_DIR__|${INSTALL_DIR}|g" "$INSTALL_DIR/web_server.py"
sed -i "s|__THRESHOLD__|${THRESHOLD}|g" "$INSTALL_DIR/web_server.py"
ok "web_server.py を生成: $INSTALL_DIR/web_server.py"
}
generate_service_file() {
cat > "$INSTALL_DIR/cpu-throttle.service" << SVC_EOF
[Unit]
Description=CPU Temperature Throttle Service
After=multi-user.target
[Service]
Type=simple
ExecStart=${INSTALL_DIR}/throttle.sh
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
SVC_EOF
ok "cpu-throttle.service を生成: $INSTALL_DIR/cpu-throttle.service"
}
# --- LXDコンテナ setup ---
setup_container() {
info "LXDコンテナ '${CONTAINER_NAME}' をセットアップ中..."
# 既存コンテナがあれば確認
if lxc info "$CONTAINER_NAME" &>/dev/null; then
warn "コンテナ '${CONTAINER_NAME}' は既に存在します"
read -p "削除して再作成しますか? (y/N): " confirm
if [[ "$confirm" =~ ^[yY]$ ]]; then
lxc stop "$CONTAINER_NAME" 2>/dev/null || true
lxc delete "$CONTAINER_NAME" --force
ok "既存コンテナを削除しました"
else
info "既存コンテナを使用します"
return 0
fi
fi
# コンテナ作成(Ubuntu 26.04)
info "Ubuntu 26.04 コンテナを作成中..."
lxc init ubuntu:26.04 "$CONTAINER_NAME"
# 共有ディレクトリ・sysfs読み取り専用マウントのみ設定
# 実際のクロック書き換えはホスト側のcpu-throttle.serviceが行うため、
# コンテナに特権(security.privileged)は不要。
mkdir -p "$INSTALL_DIR"
chmod 777 "$INSTALL_DIR"
lxc config device add "$CONTAINER_NAME" sysfs disk source=/sys path=/host/sys readonly=true
lxc config device add "$CONTAINER_NAME" shared_dir disk source="$INSTALL_DIR" path="$INSTALL_DIR"
ok "コンテナ設定完了(非特権, /sys読み取り専用マウント, ホスト側ポート転送なし)"
# 起動
info "コンテナを起動中..."
lxc start "$CONTAINER_NAME"
sleep 8
# ネットワーク設定(DHCPが効かない場合のフォールバック)
lxc exec "$CONTAINER_NAME" -- bash -c "
ip addr add 10.65.26.100/24 dev eth0 2>/dev/null || true
ip link set eth0 up
ip route add default via 10.65.26.1 2>/dev/null || true
echo 'nameserver 8.8.8.8' > /etc/resolv.conf
echo 'nameserver 8.8.4.4' >> /etc/resolv.conf
" 2>/dev/null || true
# Python + Tailscaleセットアップ
info "コンテナ内でパッケージをインストール中..."
lxc exec "$CONTAINER_NAME" -- bash -c "
apt-get update -qq 2>/dev/null
apt-get install -y -qq python3 curl 2>/dev/null
" 2>/dev/null || true
info "コンテナ内にTailscaleをインストール中..."
lxc exec "$CONTAINER_NAME" -- bash -c "
curl -fsSL https://tailscale.com/install.sh | sh
systemctl enable --now tailscaled
" 2>/dev/null || warn "Tailscaleのインストールに失敗した可能性があります。手動で確認してください。"
ok "コンテナセットアップ完了"
}
# --- コンテナ内サービス setup ---
setup_container_service() {
info "コンテナ内systemdサービスを設定中..."
lxc exec "$CONTAINER_NAME" -- bash -c "
cat > /etc/systemd/system/cpu-monitor.service << 'EOF'
[Unit]
Description=CPU Monitor Web Server
After=network.target
[Service]
Type=simple
WorkingDirectory=${INSTALL_DIR}
ExecStart=/usr/bin/python3 ${INSTALL_DIR}/web_server.py
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable cpu-monitor.service
" 2>/dev/null
# Webサーバ起動
lxc exec "$CONTAINER_NAME" -- systemctl start cpu-monitor.service 2>/dev/null || \
lxc exec "$CONTAINER_NAME" -- bash -c "cd ${INSTALL_DIR} && nohup python3 web_server.py > /tmp/web.log 2>&1 &" 2>/dev/null
ok "コンテナ内Webサーバ起動完了"
}
# --- ホスト側systemd setup ---
setup_host_service() {
info "ホスト側systemdサービスを設定中..."
cp "$INSTALL_DIR/cpu-throttle.service" /etc/systemd/system/cpu-throttle.service
systemctl daemon-reload
systemctl enable cpu-throttle.service
ok "ホスト側throttleサービス有効化完了"
warn "サービスを開始するには: sudo systemctl start cpu-throttle"
}
# --- 動作確認 ---
verify() {
info "動作確認中..."
echo ""
echo -e "${CYAN}=== コンテナ状態 ===${NC}"
lxc list "$CONTAINER_NAME" --format csv 2>/dev/null | head -1
echo ""
echo -e "${CYAN}=== Webサーバ(コンテナ内) ===${NC}"
if lxc exec "$CONTAINER_NAME" -- curl -s --connect-timeout 3 "http://localhost:${WEB_PORT}" >/dev/null 2>&1; then
ok "コンテナ内 http://localhost:${WEB_PORT} でアクセス可能"
else
warn "Webサーバにまだアクセスできません(起動待ちの可能性があります)"
fi
echo ""
echo -e "${CYAN}=== Tailscale状態 ===${NC}"
lxc exec "$CONTAINER_NAME" -- tailscale status 2>/dev/null || warn "Tailscale未認証(手動で 'sudo tailscale up' を実行してください)"
echo ""
echo -e "${CYAN}=== サービス状態 ===${NC}"
systemctl is-enabled cpu-throttle.service 2>/dev/null && ok "cpu-throttle.service (host): enabled" || warn "cpu-throttle.service (host): 未設定"
lxc exec "$CONTAINER_NAME" -- systemctl is-enabled cpu-monitor.service 2>/dev/null && ok "cpu-monitor.service (container): enabled" || warn "cpu-monitor.service (container): 未設定"
echo ""
}
# --- メイン ---
main() {
echo ""
echo -e "${CYAN}╔══════════════════════════════════════════╗${NC}"
echo -e "${CYAN}║ CPU温度監視環境 セットアップ ║${NC}"
echo -e "${CYAN}╚══════════════════════════════════════════╝${NC}"
echo ""
prompt_container_name
check_prereqs
generate_throttle_script
generate_web_server
generate_service_file
setup_container
setup_container_service
setup_host_service
verify
echo ""
echo -e "${GREEN}╔══════════════════════════════════════════╗${NC}"
echo -e "${GREEN}║ セットアップ完了! ║${NC}"
echo -e "${GREEN}╚══════════════════════════════════════════╝${NC}"
echo ""
echo " ダッシュボード(コンテナ内ポート ${WEB_PORT})は"
echo " Tailscale MagicDNS経由でアクセスしてください。"
echo ""
echo " 1. これからコンテナ '${CONTAINER_NAME}' 内のシェルに入ります"
echo " 2. コンテナ内で以下を実行してTailscaleを認証してください:"
echo " sudo tailscale up"
echo " 3. 認証後、他のTailscaleデバイスから以下でアクセス可能です:"
echo " http://<コンテナのMagicDNS名>:${WEB_PORT}"
echo ""
echo " ホスト側の温度監視サービス開始:"
echo " sudo systemctl start cpu-throttle"
echo ""
info "コンテナ '${CONTAINER_NAME}' のシェルに入ります..."
lxc exec "$CONTAINER_NAME" -- bash
}
main "$@"
初回セットアップ後の手動操作(Tailscale認証)
セットアップ完了後、自動でコンテナ内シェルに入るので、そこで以下を実行。
tailscale up
表示されるURLでブラウザ認証を行うと、コンテナがTailscaleネットワークに参加します。認証後、コンテナのシェルからは exit で抜けられます。
また下記にもまとめていますが、ホスト側で監視サービスを起動します。
sudo systemctl start cpu-throttle
手動操作
bash
# 温度監視サービス開始(ホスト側)
sudo systemctl start cpu-throttle
# 温度監視サービス停止(ホスト側)
sudo systemctl stop cpu-throttle
# ダッシュボードアクセス(TailscaleのMagicDNS経由)
http://<コンテナのMagicDNS名>:3336
# コンテナに再度入る
lxc exec <コンテナ名> -- bash
閾値の変更
ダッシュボード上の入力欄に値を入力し「適用」ボタンを押すと、/opt/script/cpu/threshold.conf が更新され、次のホスト側監視ループ(最大2秒後)から新しい閾値が反映されます。
アンインストール
やることは以下の通りです。
- コンテナ名を入力(削除対象を明示的に確認)
- 削除内容を表示して最終確認(y/N)
- ホスト側
cpu-throttle.serviceを停止・無効化・ユニットファイル削除 - 念のため、制限がかかったままなら全コアのクロックを
cpuinfo_max_freq(ハード上限)に復帰 - LXDコンテナを stop → delete –force(Tailscaleの認証情報ごと消えます)
- 共有ディレクトリ
/opt/script/cpu(ログ・閾値設定含む)を削除
コンテナを消すとTailscaleのノード登録もローカル側からは消えますが、Tailscale管理コンソール側にデバイスが残ることがあるので、気になる場合は https://login.tailscale.com/admin/machines から手動で該当マシンを削除してください。
cd /opt/script
nano uninstall-cpucheck1.sh
sudo bash uninstall-cpucheck1.sh
#!/usr/bin/env bash
# ============================================================================
# CPU温度監視環境 アンインストールスクリプト
# ============================================================================
set -euo pipefail
INSTALL_DIR="/opt/script/cpu"
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m'
info() { echo -e "${CYAN}[INFO]${NC} $*"; }
ok() { echo -e "${GREEN}[OK]${NC} $*"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
err() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
if [[ $EUID -ne 0 ]]; then
err "sudo で実行してください: sudo $0"
exit 1
fi
echo ""
echo -e "${CYAN}╔══════════════════════════════════════════╗${NC}"
echo -e "${CYAN}║ CPU温度監視環境 アンインストール ║${NC}"
echo -e "${CYAN}╚══════════════════════════════════════════╝${NC}"
echo ""
# --- コンテナ名の確認 ---
read -p "削除するLXDコンテナ名を入力してください: " CONTAINER_NAME
if [[ -z "$CONTAINER_NAME" ]]; then
err "コンテナ名が入力されていません。中止します。"
exit 1
fi
echo ""
warn "以下を削除します:"
echo " - ホスト側サービス: cpu-throttle.service"
echo " - LXDコンテナ: ${CONTAINER_NAME}"
echo " - 共有ディレクトリ: ${INSTALL_DIR}(ログ・閾値設定含む)"
echo ""
read -p "本当に続行しますか? (y/N): " confirm
if [[ ! "$confirm" =~ ^[yY]$ ]]; then
info "中止しました。"
exit 0
fi
echo ""
# --- ホスト側 throttle サービス停止・削除 ---
info "ホスト側サービスを停止・削除中..."
if systemctl is-active --quiet cpu-throttle.service 2>/dev/null; then
systemctl stop cpu-throttle.service
fi
if systemctl is-enabled --quiet cpu-throttle.service 2>/dev/null; then
systemctl disable cpu-throttle.service
fi
if [[ -f /etc/systemd/system/cpu-throttle.service ]]; then
rm -f /etc/systemd/system/cpu-throttle.service
fi
systemctl daemon-reload
ok "cpu-throttle.service を削除しました"
# 念のため、制限がかかったままの場合は最大クロックに復帰させる
if [[ -f /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq ]]; then
HW_MAX=$(cat /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq)
COUNT=$(ls -d /sys/devices/system/cpu/cpu[0-9]* | wc -l)
for ((i = 0; i < COUNT; i++)); do
echo "$HW_MAX" > "/sys/devices/system/cpu/cpu${i}/cpufreq/scaling_max_freq" 2>/dev/null || true
done
ok "CPUクロック制限を解除しました(全コア ${HW_MAX} kHz に復帰)"
fi
# --- LXDコンテナ削除 ---
info "LXDコンテナを削除中..."
if lxc info "$CONTAINER_NAME" &>/dev/null; then
lxc stop "$CONTAINER_NAME" 2>/dev/null || true
lxc delete "$CONTAINER_NAME" --force
ok "コンテナ '${CONTAINER_NAME}' を削除しました"
else
warn "コンテナ '${CONTAINER_NAME}' は見つかりませんでした(既に削除済みの可能性)"
fi
# --- 共有ディレクトリ削除 ---
info "共有ディレクトリを削除中..."
if [[ -d "$INSTALL_DIR" ]]; then
rm -rf "$INSTALL_DIR"
ok "${INSTALL_DIR} を削除しました"
else
warn "${INSTALL_DIR} は見つかりませんでした"
fi
echo ""
echo -e "${GREEN}╔══════════════════════════════════════════╗${NC}"
echo -e "${GREEN}║ アンインストール完了 ║${NC}"
echo -e "${GREEN}╚══════════════════════════════════════════╝${NC}"
echo ""


