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?

OpenWrt Banana Pi BPI-R4

Last updated at Posted at 2025-06-29

:flag_jp: Japanese article
OpenWrt_icon.png

はじめに

おススメ管理インターフェース

検証環境

  • ボード: Revision 1.1
  • OpenWrt: 24.10.2
  • クライアントPC: Windows1124H2
  • ビルド環境: Ubuntu Desktop 24.04.2 LTS

OpenWrt搭載Wi-Fi7デバイス比較

Wi-Fi7デバイス
  • SinoVoip Banana Pi BPI-R4
    価格: 4~6万円程度
    用途: オープンソースルーター開発向け
    CPU: MediaTek MT7988A (Filogic 880)
    メモリ: 4GB or 8GB DDR4
    ストレージ: MicroSD(TF) card or 8GB eMMC
    WAN: 1G ×1
    LAN: 1G ×3
    SFP: 10G ×2
    USB: USB 3.2 Type-A
    OS: OpenWrt

  • Linksys Velop WRT Pro 7
    価格: 5万5千~6万円程度
    用途: ホーム&ビジネス向け
    CPU: Qualcomm IPQ9554
    メモリ: 1GB DDR4
    ストレージ: 8GB eMMC
    WAN: 2.5G ×1
    LAN: 1G ×4
    OS: QSDK 19.07/カーネル 5.4.213(公式OpenWrtインストール不可)
    MLO: サポート

  • GL.iNet Flint 3 GL-BE9300
    https://www.gl-inet.com/campaign/gl-be9300-jp/
    価格: 1万5千~3万円程度
    用途: 家庭や小規模オフィス向け
    CPU: Qualcomm IPQ9554?
    メモリ: 1GB DDR4
    ストレージ: 8GB eMMC
    WAN: 2.5G ×1
    WAN/LAN: 2.5G ×1
    LAN: 2.5G ×4
    USB: USB 3.0 Type-A
    OS: QSDK 23.05/カーネル 5.4.213 (公式OpenWrtインストール可?)
    MLO: サポート

wiki-qsdk


OpenWrt

OpenWrt Project へようこそ

320.logo.png

仕様
The UCI system
LuCI web interface

スクリーンショット 2025-07-15 091642.jpg


デバイス情報

インストール

ファームウェア

MTK-SDK
カスタムファームウェア

リリースビルド
※含む: モデム、SFP、モデムパッケージなど

base-files ca-bundle dnsmasq dropbear firewall4 fitblk fstools kmod-crypto-hw-safexcel kmod-gpio-button-hotplug kmod-leds-gpio kmod-nft-offload kmod-phy-aquantia libc libgcc libustream-mbedtls logd mtd netifd nftables odhcp6c odhcpd-ipv6only opkg ppp ppp-mod-pppoe procd-ujail uboot-envtools uci uclient-fetch urandom-seed urngd wpad-basic-mbedtls kmod-hwmon-pwmfan kmod-i2c-mux-pca954x kmod-eeprom-at24 kmod-mt7996-firmware kmod-mt7996-233-firmware kmod-rtc-pcf8563 kmod-sfp kmod-usb3 e2fsprogs f2fsck mkf2fs mt7988-wo-firmware luci

snapshotビルド
※含む: モデム、SFP、モデムパッケージなど

apk-mbedtls base-files ca-bundle dnsmasq dropbear firewall4 fitblk fstools kmod-crypto-hw-safexcel kmod-gpio-button-hotplug kmod-leds-gpio kmod-nft-offload libc libgcc libustream-mbedtls logd mtd netifd nftables odhcp6c odhcpd-ipv6only ppp ppp-mod-pppoe procd-ujail uboot-envtools uci uclient-fetch urandom-seed urngd wpad-basic-mbedtls kmod-hwmon-pwmfan kmod-i2c-mux-pca954x kmod-eeprom-at24 kmod-mt7996-firmware kmod-mt7996-233-firmware kmod-rtc-pcf8563 kmod-sfp kmod-usb3 e2fsprogs f2fsck mkf2fs mt7988-wo-firmware luci

初回起動時に実行されるスクリプト (uci-defaults)
※高速ローミング構成 (usteer + 802.11r/k/v)

# Beware! This script will be in /rom/etc/uci-defaults/ as part of the image.
# Uncomment lines to apply:
#
root_password="12345678"
lan_ip_address="192.168.1.1"
#
# pppoe_username=""
# pppoe_password=""
#
enable_led="1"
#
enable_ttyd="1"
#
wlan_name="ばなな"
wlan_password="12345678"
usteer_country="JP"
usteer_mobility_domain="1234"
usteer_bands="2g 5g 6g"
usteer_htmodes="HE20 HE80 HE160"
usteer_txpowers="10 15 ''"
usteer_channels="1 auto auto"
usteer_nasids="ap1-2g ap1-5g ap1-6g"
usteer_snr="30 20 15"

# log potential errors
exec >/tmp/setup.log 2>&1

if [ -n "$root_password" ]; then
  (echo "$root_password"; sleep 1; echo "$root_password") | passwd > /dev/null
fi

# Configure LAN
# More options: https://openwrt.org/docs/guide-user/base-system/basic-networking
if [ -n "$lan_ip_address" ]; then
  uci set network.lan.ipaddr="$lan_ip_address"
  uci commit network
fi

# Configure PPPoE
# More options: https://openwrt.org/docs/guide-user/network/wan/wan_interface_protocols#protocol_pppoe_ppp_over_ethernet
if [ -n "$pppoe_username" -a "$pppoe_password" ]; then
  uci set network.wan.proto=pppoe
  uci set network.wan.username="$pppoe_username"
  uci set network.wan.password="$pppoe_password"
  uci commit network
fi

# Configure LED indicators
if [ -n "$enable_led" ]; then
  # Configure WAN LED
  uci add system led
  uci set system.@led[-1].name='wan'
  uci set system.@led[-1].sysfs='green
  :status'
  uci set system.@led[-1].trigger='netdev'
  uci set system.@led[-1].dev='wan'
  uci set system.@led[-1].mode='link tx rx'
  
  # Configure LAN LED
  uci add system led
  uci set system.@led[-1].name='br-lan'
  uci set system.@led[-1].sysfs='blue:wps'
  uci set system.@led[-1].trigger='netdev'
  uci set system.@led[-1].dev='br-lan'
  uci set system.@led[-1].mode='link tx rx'
  
  uci commit system
fi

# Configure ttyd
if [ -n "$enable_ttyd" ]; then
  if ! uci -q get ttyd.@ttyd[0]; then
    uci add ttyd ttyd
  fi
  uci set ttyd.@ttyd[0].interface='@lan'
  uci set ttyd.@ttyd[0].command='/bin/login -f root'
  uci set ttyd.@ttyd[0].ipv6='1'
  uci commit ttyd
fi

# Configure usteer with multi-band WiFi
if [ -n "$wlan_name" -a -n "$wlan_password" -a ${#wlan_password} -ge 8 ]; then
  
  cp /etc/config/wireless /etc/config/wireless.usteer.bak
  
  NUM_IFACES=$(grep -c "^config wifi-device" /etc/config/wireless)
  i=0
  while [ $i -lt $NUM_IFACES ]; do
    iface="default_radio${i}"
    radio="radio${i}"
    band=$(echo $usteer_bands | awk -v n=$((i+1)) '{print $n}')
    htmode=$(echo $usteer_htmodes | awk -v n=$((i+1)) '{print $n}')
    txpower=$(echo $usteer_txpowers | awk -v n=$((i+1)) '{print $n}')
    nasid=$(echo $usteer_nasids | awk -v n=$((i+1)) '{print $n}')
    min_snr=$(echo $usteer_snr | awk -v n=$((i+1)) '{print $n}')
    channel=$(echo $usteer_channels | awk -v n=$((i+1)) '{print $n}')
    
    uci set wireless.$radio.band="$band"
    uci set wireless.$radio.channel="$channel"
    uci set wireless.$radio.htmode="$htmode"
    uci set wireless.$radio.country="$usteer_country"
    [ -n "$txpower" ] && uci set wireless.$radio.txpower="$txpower"
    uci set wireless.$radio.disabled='0'
    
    uci set wireless.$iface.device="$radio"
    uci set wireless.$iface.network='lan'
    uci set wireless.$iface.mode='ap'
    uci set wireless.$iface.ssid="$wlan_name"
    uci set wireless.$iface.encryption='sae'
    uci set wireless.$iface.key="$wlan_password"
    uci set wireless.$iface.isolate='1'
    uci set wireless.$iface.ocv='1'
    uci set wireless.$iface.ieee80211r='1'
    uci set wireless.$iface.mobility_domain="$usteer_mobility_domain"
    uci set wireless.$iface.ft_over_ds='1'
    uci set wireless.$iface.nasid="$nasid"
    uci set wireless.$iface.usteer_min_snr="$min_snr"
    uci set wireless.$iface.ieee80211k='1'
    uci set wireless.$iface.ieee80211v='1'
    uci set wireless.$iface.disabled='0'
    
    [ "$band" = "5g" ] && uci set wireless.$iface.background_radar='1' && uci set wireless.$iface.ft_psk_generate_local='1'
    
    i=$((i+1))
  done
  
  uci set usteer.@usteer[0].band_steering='1'
  uci set usteer.@usteer[0].load_balancing='1'
  uci set usteer.@usteer[0].sta_block_timeout='300'
  uci set usteer.@usteer[0].min_snr='20'
  uci set usteer.@usteer[0].max_snr='80'
  uci set usteer.@usteer[0].signal_diff_threshold='10'
  uci commit wireless
  uci commit usteer
  
  /etc/init.d/usteer enable
  wifi reload
fi

# Configure LED indicators
if [ -n "$enable_led_config" ]; then
  # Configure WAN LED
  if [ -n "$wan_led_name" -a -n "$wan_led_sysfs" ]; then
    uci add system led
    uci set system.@led[-1].name="$wan_led_name"
    uci set system.@led[-1].sysfs="$wan_led_sysfs"
    uci set system.@led[-1].trigger='netdev'
    uci set system.@led[-1].dev='wan'
    uci set system.@led[-1].mode='link tx rx'
  fi

echo "All done!"

SDカードイメージライター

SDカードサイズの拡張

SDカード初期化

※管理者として実行

diskpart
list disk
select disk 
clean

SDカード拡張
  • 用意するもの:
    SDカード ×2枚 ※サイズが異なっても可
    SDカードリーダー
  • 作業工程
    両方のSDカードに同じイメージを書き込み
    サブのSDカードでデバイス起動
    メインのSDカードをリーダーに差してUSB接続
    fdiskでパーティション7を容量拡張
    SDカードを入れ替え (完了)

Type UUID: CAE9BE83-B15F-49CC-863F-081B744A2D93
Partition UUID: 5452574F-2211-4433-5566-778899AABB07

  • 手順
PKGS="f2fs-tools fdisk gdisk kmod-usb-storage kmod-usb-storage-uas parted"
command -v opkg && opkg update && opkg install $PKGS
command -v apk  && apk update  && apk add $PKGS
partprobe /dev/sda
fdisk /dev/sda
d
7
n
7
未入力
未入力
t
7
CAE9BE83-B15F-49CC-863F-081B744A2D93
x
u
7
5452574F-2211-4433-5566-778899AABB07
n
7
production
r
w
  • 断電しメインのSDカードをデバイスに入替挿入 ※USBカードリーダーを抜く

ビルド

Linuxライブディストリビューション (設定永続化仕様)

USB M.2 SSD エンクロージャー

ElecGear NV2242A
51yxNrsSvhL.AC_SL1200.jpg


Live Linux

Ubuntu


RAMディスク作成
  • クライアント確認
free -h
df -h
  • RAMディスク自動マウント設定
    /etc/fstab 自動追記(要8GB以上: 動的サイズ)
    ※推奨メモリ: 16GB以上
#!/bin/bash
sudo cp /etc/fstab /etc/fstab.bak.$(date +%Y%m%d%H%M%S)
free -m | awk '
/^Mem:/ {
    total_mb = $2
    if (total_mb <= 8000) exit 0
    size_gb = int(total_mb / 2 / 1024)
    if (size_gb > 32) size_gb = 32
    if (size_gb < 1) size_gb = 1
    print "tmpfs /mnt/ramdisk tmpfs defaults,size=" size_gb "G 0 0"
}
' > /tmp/ramdisk_fstab
sudo sed -i '/^tmpfs \/mnt\/ramdisk tmpfs /d' /etc/fstab
if [ -s /tmp/ramdisk_fstab ]; then
    sudo tee -a /etc/fstab < /tmp/ramdisk_fstab
fi
rm -f /tmp/ramdisk_fstab
sudo mkdir -p /mnt/ramdisk
sudo umount /mnt/ramdisk 2>/dev/null
sudo mount -a
df -h /mnt/ramdisk
grep '/mnt/ramdisk' /etc/fstab
echo "RAMディスク設定完了" 

  • Windowsの時刻がUTCになる問題対応
sudo timedatectl set-local-rtc 1
timedatectl
# RTC in local TZ: yes

起動可能なUSBドライブ作成
  • Rufus
    rufus-**p.exe
    ※Persistent partition size: 8GB以上推奨 (永続化設定)

カスタムイメージ

openwrt_bpi-r4_mtk_builder

openwrt_bpi-r4_mtk_builder


bpi-r4-openwrt-builder

bpi-r4-openwrt-builder

Ubuntu

  • ビルド用パッケージインストール
sudo apt update && sudo apt install -y build-essential libncurses-dev zlib1g-dev gawk git gettext libssl-dev xsltproc rsync python3 python3-setuptools python3-pip unzip file wget qemu-utils device-tree-compiler swig libzstd-dev libtool flex bison patch subversion mercurial libfuse-dev libglib2.0-dev libelf-dev libmpc-dev libmpfr-dev libgmp-dev autoconf automake pkg-config libpython3-dev libusb-dev libusb-1.0-0-dev libffi-dev liblzma-dev perl
  • GitHubリポジトリクローン及びビルド実行
cd /mnt/ramdisk # RAMディスク利用

git clone https://github.com/fatal1101/bpi-r4-openwrt-builder.git
cd bpi-r4-openwrt-builder

# 最新のタグ名を取得し、そのタグにチェックアウトする
LATEST_TAG=$(git describe --tags $(git rev-list --tags --max-count=1))
git checkout ${LATEST_TAG}

# ビルドスクリプトの実行
./bpi-r4-openwrt-builder.sh
  • トラブル対応
バージョン: 2025-07-07 無理だな。。。
# Disable crypto-eip package due to build issues
sed -i 's/CONFIG_PACKAGE_crypto-eip=y/# CONFIG_PACKAGE_crypto-eip is not set/' mtk-openwrt-feeds/autobuild/unified/filogic/24.10/defconfig

# Disable pce package if it causes issues
sed -i 's/CONFIG_PACKAGE_pce=y/# CONFIG_PACKAGE_pce is not set/' mtk-openwrt-feeds/autobuild/unified/filogic/24.10/defconfig

cd /mnt/ramdisk/bpi-r4-openwrt-builder
./bpi-r4-openwrt-builder.sh

OpenMPTCProuter

OpenMPTCProuter


チューニング

ネットワークパフォーマンス

ツール
  • メモリ・バッファ設定
    net.core.rmem_max/wmem_max: ソケット受信/送信バッファの最大サイズ
    tcp_rmem/tcp_wmem: TCP受信/送信バッファサイズ(最小/デフォルト/最大)
    初期値: rmem_max/wmem_max = 212KB (212,992 bytes)
    推奨値: 12MB以上(4GBメモリなら16MB推奨)
    設定値: 16MB (16,777,216 bytes)

  • TCP最適化
    tcp_fastopen: TCP Fast Open(接続高速化)
    tcp_keepalive_probes: 接続維持のプローブ回数
    初期値: tcp_wmem最大値 = 4MB (4,194,304), tcp_rmem最大値 = 6MB (6,291,456)
    推奨値: 16MB以上
    設定値: 16MB (16,777,216)

  • TCP設定
    tcp_congestion_control: 輻輳制御アルゴリズム
    初期値: cubic, fastopen=1, keepalive_probes=9
    推奨値: westwood, fastopen=3, keepalive_probes=3
    設定値: cubic, fastopen=3, keepalive_probes=3(westwoodは利用不可)

  • コネクション追跡
    nf_conntrack_max: 同時接続数の上限
    netdev_max_backlog: ネットワークデバイスのキューサイズ
    somaxconn: リスニングキューの最大サイズ
    初期値: nf_conntrack_max=65,536, netdev_max_backlog=1,000, somaxconn=4,096
    推奨値: 131,072接続以上
    設定値: nf_conntrack_max=262,144, netdev_max_backlog=5,000, somaxconn=16,384

  • 設定

#!/bin/sh

TMP=/tmp/aios
mkdir -p "$TMP"

cat > "$TMP/dynamic-network-optimizer.sh" << 'SCRIPT_END'
#!/bin/sh
set -e

CONFIG_FILE="/etc/sysctl.d/99-network-optimization-auto.conf"

RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
MAGENTA='\033[0;35m'
NC='\033[0m'

log_info() { printf "${BLUE}[INFO]${NC} %s\n" "$1"; }
log_success() { printf "${GREEN}[OK]${NC} %s\n" "$1"; }
log_warning() { printf "${YELLOW}[WARN]${NC} %s\n" "$1"; }
log_error() { printf "${RED}[ERROR]${NC} %s\n" "$1"; }
log_highlight() { printf "${CYAN}[HIGHLIGHT]${NC} %s\n" "$1"; }

detect_memory() {
    local mem_kb=$(grep MemTotal /proc/meminfo | awk '{print $2}')
    printf '%s\n' "$((mem_kb / 1024))"
}

detect_cpu_cores() {
    grep -c ^processor /proc/cpuinfo
}

get_current_connections() {
    if [ -f /proc/sys/net/netfilter/nf_conntrack_count ]; then
        cat /proc/sys/net/netfilter/nf_conntrack_count
    else
        printf '%s\n' "0"
    fi
}

get_best_congestion_control() {
    local available
    available=$(cat /proc/sys/net/ipv4/tcp_available_congestion_control)
    for algo in bbr cubic reno; do
        if printf '%s\n' "$available" | grep -q "$algo"; then
            printf '%s\n' "$algo"
            return
        fi
    done
    printf '%s\n' "$(printf '%s\n' "$available" | awk '{print $1}')"
}

# Get current value dynamically - this is the key fix
get_current_value() {
    local setting="$1"
    case "$setting" in
        "rmem_max")
            cat /proc/sys/net/core/rmem_max 2>/dev/null || printf "%s" "unknown"
            ;;
        "wmem_max")
            cat /proc/sys/net/core/wmem_max 2>/dev/null || printf "%s" "unknown"
            ;;
        "tcp_rmem")
            cat /proc/sys/net/ipv4/tcp_rmem 2>/dev/null | tr -s '\t' ' ' | sed 's/^[[:space:]]*//' | sed 's/[[:space:]]*$//' || printf "%s" "unknown"
            ;;
        "tcp_wmem")
            cat /proc/sys/net/ipv4/tcp_wmem 2>/dev/null | tr -s '\t' ' ' | sed 's/^[[:space:]]*//' | sed 's/[[:space:]]*$//' || printf "%s" "unknown"
            ;;
        "tcp_congestion_control")
            cat /proc/sys/net/ipv4/tcp_congestion_control 2>/dev/null || printf "%s" "unknown"
            ;;
        "nf_conntrack_max")
            cat /proc/sys/net/netfilter/nf_conntrack_max 2>/dev/null || printf "%s" "unknown"
            ;;
        "netdev_max_backlog")
            cat /proc/sys/net/core/netdev_max_backlog 2>/dev/null || printf "%s" "unknown"
            ;;
        "somaxconn")
            cat /proc/sys/net/core/somaxconn 2>/dev/null || printf "%s" "unknown"
            ;;
        "active_connections")
            get_current_connections
            ;;
        *)
            printf "%s" "unknown"
            ;;
    esac
}

calculate_buffer_sizes() {
    local mem_mb=$1
    local cores=$2
    
    if [ "$mem_mb" -ge 3072 ]; then
        rmem_max=16777216; wmem_max=16777216
        tcp_rmem="4096 262144 16777216"; tcp_wmem="4096 262144 16777216"
        conntrack_max=262144; netdev_backlog=5000; somaxconn=16384
    elif [ "$mem_mb" -ge 1536 ]; then
        rmem_max=8388608; wmem_max=8388608
        tcp_rmem="4096 131072 8388608"; tcp_wmem="4096 131072 8388608"
        conntrack_max=131072; netdev_backlog=2500; somaxconn=8192
    elif [ "$mem_mb" -ge 512 ]; then
        rmem_max=4194304; wmem_max=4194304
        tcp_rmem="4096 65536 4194304"; tcp_wmem="4096 65536 4194304"
        conntrack_max=65536; netdev_backlog=1000; somaxconn=4096
    else
        rmem_max=1048576; wmem_max=1048576
        tcp_rmem="4096 32768 1048576"; tcp_wmem="4096 32768 1048576"
        conntrack_max=32768; netdev_backlog=500; somaxconn=2048
    fi
    
    if [ "$cores" -gt 4 ]; then
        netdev_backlog=$((netdev_backlog * 2))
        somaxconn=$((somaxconn * 2))
    elif [ "$cores" -gt 2 ]; then
        netdev_backlog=$((netdev_backlog + netdev_backlog / 2))
        somaxconn=$((somaxconn + somaxconn / 2))
    fi
}

format_bytes() {
    local bytes=$1
    if [ "$bytes" = "unknown" ]; then
        printf '%s' "unknown"
        return
    fi
    if [ "$bytes" -ge 1048576 ]; then
        printf '%s' "$((bytes / 1048576))MB"
    elif [ "$bytes" -ge 1024 ]; then
        printf '%s' "$((bytes / 1024))KB"
    else
        printf '%s' "${bytes}B"
    fi
}

values_equal() {
    local current="$1"
    local new="$2"
    
    if [ "$current" = "unknown" ] || [ "$new" = "unknown" ]; then
        return 1
    fi
    
    local norm_current=$(printf '%s\n' "$current" | tr -s ' \t' ' ' | sed 's/^[[:space:]]*//' | sed 's/[[:space:]]*$//')
    local norm_new=$(printf '%s\n' "$new" | tr -s ' \t' ' ' | sed 's/^[[:space:]]*//' | sed 's/[[:space:]]*$//')
    
    [ "$norm_current" = "$norm_new" ]
}

show_comparison() {
    printf '\n'
    printf "BEFORE vs AFTER COMPARISON\n"
    printf '\n'

    local max_setting=7
    local max_value=6
    local status_width=11

    local single_settings="rmem_max wmem_max tcp_congestion_control nf_conntrack_max netdev_max_backlog somaxconn"

    for setting in $single_settings; do
        [ ${#setting} -gt $max_setting ] && max_setting=${#setting}
    done

    local tcp_items="tcp_rmem (min) tcp_rmem (default) tcp_rmem (max) tcp_wmem (min) tcp_wmem (default) tcp_wmem (max)"
    for item in $tcp_items; do
        [ ${#item} -gt $max_setting ] && max_setting=${#item}
    done

    for setting in $single_settings; do
        local current_value=$(get_current_value "$setting")
        local new_value
        case "$setting" in
            "rmem_max") new_value="$rmem_max" ;;
            "wmem_max") new_value="$wmem_max" ;;
            "tcp_congestion_control") new_value="$best_congestion" ;;
            "nf_conntrack_max") new_value="$conntrack_max" ;;
            "netdev_max_backlog") new_value="$netdev_backlog" ;;
            "somaxconn") new_value="$somaxconn" ;;
        esac
        local display_current display_new
        case "$setting" in
            "rmem_max"|"wmem_max")
                display_current="$(format_bytes "$current_value")"
                display_new="$(format_bytes "$new_value")"
                ;;
            *)
                display_current="$current_value"
                display_new="$new_value"
                ;;
        esac
        [ ${#display_current} -gt $max_value ] && max_value=${#display_current}
        [ ${#display_new}     -gt $max_value ] && max_value=${#display_new}
    done

    for proto in rmem tcp_wmem; do
        local cur=$(get_current_value "${proto/tcp_/tcp_}")
        local new=$(eval echo "\$tcp_${proto/tcp_}")
        if [ "$cur" != "unknown" ]; then
            for val in $(echo "$cur") $(echo "$new"); do
                [ ${#val} -gt $max_value ] && max_value=${#val}
            done
        fi
    done

    max_setting=$((max_setting + 1))
    max_value=$((max_value + 1))

    local sep_setting sep_value sep_status
    sep_setting=$(printf '%*s' "$max_setting" '' | tr ' ' '-')
    sep_value=$(printf '%*s' "$max_value" '' | tr ' ' '-')
    sep_status=$(printf '%*s' "$status_width" '' | tr ' ' '-')

    printf "%-${max_setting}s| %-${max_value}s| %-${max_value}s| %-${status_width}s\n" "Setting" "Before" "After" "Status"

    printf "%s+-%s+-%s+-%s\n" "$sep_setting" "$sep_value" "$sep_value" "$sep_status"

    print_row() {
        local setting="$1" current_val="$2" new_val="$3" status="$4"
        printf "%-${max_setting}s| %-${max_value}s| %-${max_value}s| %s\n" "$setting" "$current_val" "$new_val" "$status"
    }

    for setting in $single_settings; do
        local current_value=$(get_current_value "$setting")
        local new_value status
        case "$setting" in
            "rmem_max") new_value="$rmem_max" ;;
            "wmem_max") new_value="$wmem_max" ;;
            "tcp_congestion_control") new_value="$best_congestion" ;;
            "nf_conntrack_max") new_value="$conntrack_max" ;;
            "netdev_max_backlog") new_value="$netdev_backlog" ;;
            "somaxconn") new_value="$somaxconn" ;;
        esac
        local display_current display_new
        case "$setting" in
            "rmem_max"|"wmem_max")
                display_current="$(format_bytes "$current_value")"
                display_new="$(format_bytes "$new_value")"
                ;;
            *)
                display_current="$current_value"
                display_new="$new_value"
                ;;
        esac
        if values_equal "$current_value" "$new_value"; then
            status="Same"
        else
            status="Changed"
            if [ "$current_value" != "unknown" ] && [ "$new_value" != "unknown" ]; then
                case "$setting" in
                    "rmem_max"|"wmem_max"|"netdev_max_backlog"|"somaxconn"|"nf_conntrack_max")
                        if [ "$new_value" -gt "$current_value" ] 2>/dev/null; then
                            status="↑ Increased"
                        elif [ "$new_value" -lt "$current_value" ] 2>/dev/null; then
                            status="↓ Optimized"
                        fi
                        ;;
                esac
            fi
        fi
        print_row "$setting" "$display_current" "$display_new" "$status"
    done

    print_tcp_rows() {
        local name="$1" cur="$2" new="$3"
        if [ "$cur" = "unknown" ]; then
            print_row "${name} (min)"     "unknown" "$(echo "$new" | awk '{print $1}')" "Unknown"
            print_row "${name} (default)" "unknown" "$(echo "$new" | awk '{print $2}')" "Unknown"
            print_row "${name} (max)"     "unknown" "$(echo "$new" | awk '{print $3}')" "Unknown"
            return
        fi
        local c1 c2 c3 n1 n2 n3 s1 s2 s3
        c1=$(echo "$cur" | awk '{print $1}');   n1=$(echo "$new" | awk '{print $1}')
        c2=$(echo "$cur" | awk '{print $2}');   n2=$(echo "$new" | awk '{print $2}')
        c3=$(echo "$cur" | awk '{print $3}');   n3=$(echo "$new" | awk '{print $3}')
        s1="Same"; s2="Same"; s3="Same"
        [ "$c1" != "$n1" ] && s1="Changed"
        [ "$c2" != "$n2" ] && s2="Changed"
        [ "$c3" != "$n3" ] && s3="Changed"
        [ "$n1" -gt "$c1" ] 2>/dev/null && s1="↑ Increased"
        [ "$n1" -lt "$c1" ] 2>/dev/null && s1="↓ Decreased"
        [ "$n2" -gt "$c2" ] 2>/dev/null && s2="↑ Increased"
        [ "$n2" -lt "$c2" ] 2>/dev/null && s2="↓ Decreased"
        [ "$n3" -gt "$c3" ] 2>/dev/null && s3="↑ Increased"
        [ "$n3" -lt "$c3" ] 2>/dev/null && s3="↓ Decreased"
        print_row "${name} (min)"     "$c1" "$n1" "$s1"
        print_row "${name} (default)" "$c2" "$n2" "$s2"
        print_row "${name} (max)"     "$c3" "$n3" "$s3"
    }

    print_tcp_rows "tcp_rmem" "$(get_current_value tcp_rmem)" "$tcp_rmem"
    print_tcp_rows "tcp_wmem" "$(get_current_value tcp_wmem)" "$tcp_wmem"

    printf "%s+-%s+-%s+-%s\n" "$sep_setting" "$sep_value" "$sep_value" "$sep_status"
    print_row "active_connections" "$(get_current_connections)" "$(get_current_connections)" "Same"

    printf '\n'
    printf "Legend:\n"
    printf "  Same        - No change needed\n"
    printf "  ↑ Increased - Value will be increased for better performance\n"
    printf "  ↓ Optimized - Value will be reduced for memory optimization\n"
    printf "  Changed     - Value will be modified\n"
    printf '\n'
}

verify_applied_settings() {
    printf '\n'
    log_info "=== Post-Application Verification ==="
    
    local verification_failed=0
    
    verify_setting() {
        local setting_name="$1"
        local expected_value="$2"
        local actual_value="$3"
        local display_name="$4"
        
        if values_equal "$actual_value" "$expected_value"; then
            log_success "$display_name: $(format_bytes "$actual_value" 2>/dev/null || printf "%s" "$actual_value")"
        else
            log_warning "$display_name: $actual_value (expected: $expected_value)"
            verification_failed=1
        fi
    }
    
    # Verify each setting using the same dynamic fetch method
    verify_setting "rmem_max" "$rmem_max" "$(get_current_value "rmem_max")" "rmem_max"
    verify_setting "wmem_max" "$wmem_max" "$(get_current_value "wmem_max")" "wmem_max"
    verify_setting "netdev_max_backlog" "$netdev_backlog" "$(get_current_value "netdev_max_backlog")" "netdev_max_backlog"
    verify_setting "somaxconn" "$somaxconn" "$(get_current_value "somaxconn")" "somaxconn"
    verify_setting "tcp_rmem" "$tcp_rmem" "$(get_current_value "tcp_rmem")" "tcp_rmem"
    verify_setting "tcp_wmem" "$tcp_wmem" "$(get_current_value "tcp_wmem")" "tcp_wmem"
    
    # Overall result
    if [ "$verification_failed" -eq 0 ]; then
        log_success "All settings verified successfully!"
    else
        log_warning "Some settings may not have been applied correctly"
        printf '\n'
        log_info "This may be due to:"
        printf "  - Kernel module not loaded (e.g., nf_conntrack)\n"
        printf "  - Insufficient permissions\n"
        printf "  - Kernel version compatibility\n"
        printf "  - Hardware limitations\n"
        printf '\n'
        log_info "Running 'dmesg | tail' might provide more information"
    fi
}

show_performance_impact() {
    printf "Performance Impact Summary\n"
    printf '\n'

    mem_mb=$(detect_memory)
    cores=$(detect_cpu_cores)

    printf "System Profile: %dMB RAM, %d CPU cores\n" "$mem_mb" "$cores"
    printf '\n'

    changes_found=0
    improvements=""

    # Check for actual improvements by comparing current vs new values
    current_rmem=$(get_current_value rmem_max)
    current_wmem=$(get_current_value wmem_max)
    current_backlog=$(get_current_value netdev_max_backlog)
    current_somaxconn=$(get_current_value somaxconn)

    if ! values_equal "$current_rmem" "$rmem_max" && [ "$current_rmem" != unknown ] && [ "$current_rmem" -gt 0 ] 2>/dev/null; then
        rmem_impr=$(( (rmem_max - current_rmem) * 100 / current_rmem ))
        if [ "$rmem_impr" -gt 0 ]; then
            improvements="${improvements}Receive buffer: +${rmem_impr}% increase -> Better download performance\n"
            changes_found=1
        elif [ "$rmem_impr" -lt 0 ]; then
            improvements="${improvements}Receive buffer: ${rmem_impr}% decrease -> Memory optimized\n"
            changes_found=1
        fi
    fi

    if ! values_equal "$current_wmem" "$wmem_max" && [ "$current_wmem" != unknown ] && [ "$current_wmem" -gt 0 ] 2>/dev/null; then
        wmem_impr=$(( (wmem_max - current_wmem) * 100 / current_wmem ))
        if [ "$wmem_impr" -gt 0 ]; then
            improvements="${improvements}Send buffer: +${wmem_impr}% increase -> Better upload performance\n"
            changes_found=1
        elif [ "$wmem_impr" -lt 0 ]; then
            improvements="${improvements}Send buffer: ${wmem_impr}% decrease -> Memory optimized\n"
            changes_found=1
        fi
    fi

    if ! values_equal "$current_backlog" "$netdev_backlog" && [ "$current_backlog" != unknown ] && [ "$current_backlog" -gt 0 ] 2>/dev/null; then
        backlog_impr=$(( (netdev_backlog - current_backlog) * 100 / current_backlog ))
        if [ "$backlog_impr" -gt 0 ]; then
            improvements="${improvements}Network backlog: +${backlog_impr}% increase -> Better packet processing\n"
            changes_found=1
        fi
    fi

    if ! values_equal "$current_somaxconn" "$somaxconn" && [ "$current_somaxconn" != unknown ] && [ "$current_somaxconn" -gt 0 ] 2>/dev/null; then
        connq_impr=$(( (somaxconn - current_somaxconn) * 100 / current_somaxconn ))
        if [ "$connq_impr" -gt 0 ]; then
            improvements="${improvements}Connection queue: +${connq_impr}% increase -> More concurrent connections\n"
            changes_found=1
        fi
    fi

    if [ "$changes_found" -eq 1 ]; then
        printf "Performance Improvements:\n"
        printf "%b" "$improvements"
    else
        printf "System is already optimally configured for your hardware\n"
        printf "No significant changes needed\n"
    fi

    printf '\n'
    printf "Expected Benefits:\n"
    printf "  - Reduced packet drops under high load\n"
    printf "  - Better throughput for large file transfers\n"
    printf "  - Improved responsiveness for multiple connections\n"
    printf "  - Optimized memory usage for your hardware\n"
    printf '\n'
}

remove_optimizer() {
    printf "Network Optimizer Removal Tool\n"
    printf "==============================\n"
    
    if [ ! -f "$CONFIG_FILE" ]; then
        log_info "No optimization config found"
        return 0
    fi

    printf "Do you want to remove the existing optimization config? (y/N): "
    read -r confirm_remove
    if [ "$confirm_remove" != "y" ] && [ "$confirm_remove" != "Y" ]; then
        log_info "Removal cancelled by user"
        return 0
    fi

    local backup_file=""
    for backup in "${CONFIG_FILE}.backup."*; do
        [ -f "$backup" ] && { backup_file="$backup"; break; }
    done

    if [ -n "$backup_file" ]; then
        log_info "Restoring from backup: $backup_file"
        cp "$backup_file" "$CONFIG_FILE"
        sysctl -p "$CONFIG_FILE" >/dev/null 2>&1
        log_success "Original settings restored"
    else
        log_info "No backup found – removing config file"
        rm "$CONFIG_FILE"
        log_success "Config file removed"
        log_highlight "Reboot required to return to system defaults"
    fi
}

optimizer_main() {
    printf "Dynamic Network Performance Optimizer\n"
    printf "=====================================\n"
  
    if [ -f "$CONFIG_FILE" ]; then
        log_warning "Existing config detected: $CONFIG_FILE"
        remove_optimizer
        exit 0
    fi
    
    printf '\n'
    log_info "=== System Analysis ==="
    local mem_mb=$(detect_memory)
    local cores=$(detect_cpu_cores)
    local best_congestion=$(get_best_congestion_control)
    
    log_info "Detected RAM: ${mem_mb}MB"
    log_info "Detected CPU cores: $cores"
    log_info "Best congestion control: $best_congestion"
    
    calculate_buffer_sizes "$mem_mb" "$cores"
    
    show_comparison
    
    show_performance_impact
    
    printf "Do you want to apply these changes? (y/N): "
    read -r confirm
    
    if [ "$confirm" != "y" ] && [ "$confirm" != "Y" ]; then
        log_info "Operation cancelled by user"
        exit 0
    fi
    
    if [ -f "$CONFIG_FILE" ]; then
        local backup_file="${CONFIG_FILE}.backup.$(date +%Y%m%d_%H%M%S)"
        cp "$CONFIG_FILE" "$backup_file"
        log_info "Backed up existing config to: $backup_file"
    fi
    
    log_info "Creating configuration: $CONFIG_FILE"
    cat > "$CONFIG_FILE" << CONFIG_EOF
net.core.rmem_max = $rmem_max
net.core.wmem_max = $wmem_max
net.ipv4.tcp_rmem = $tcp_rmem
net.ipv4.tcp_wmem = $tcp_wmem
net.ipv4.tcp_congestion_control = $best_congestion
net.ipv4.tcp_fastopen = 3
net.ipv4.tcp_keepalive_probes = 3
net.netfilter.nf_conntrack_max = $conntrack_max
net.core.netdev_max_backlog = $netdev_backlog
net.core.somaxconn = $somaxconn
CONFIG_EOF

    printf '\n'
    log_info "Applying configuration"
    if sysctl -p "$CONFIG_FILE" >/dev/null 2>&1; then
        log_success "Configuration applied successfully"
    else
        log_warning "Some settings may have failed (check kernel support)"
    fi
    
    verify_applied_settings
    
    printf '\n'
    log_success "Network optimization completed!"
    log_highlight "Reboot recommended for full effect and persistent changes"
    
    printf '\n'
    log_info "Current memory usage:"
    free -h
}

if [ "$0" = "${0#*/}" ] || [ "${0##*/}" = "$(basename "$0")" ]; then
    optimizer_main "$@"
fi
SCRIPT_END
chmod +x "$TMP/dynamic-network-optimizer.sh"
sh "$TMP/dynamic-network-optimizer.sh"


Wi-Fi

ドライバー

BPI-R4-MT76-OPENWRT-V21.02


wget -O /tmp/mt7996_eeprom_233_2i5i6i.bin 'https://github.com/openwrt/mt76/raw/refs/heads/master/firmware/mt7996/mt7996_eeprom_233_2i5i6i.bin'
FW_DIR="/lib/firmware/mediatek/mt7996"
if [ -f "$FW_DIR/mt7996_eeprom_233_2i5i6i.bin" ]; then
  mv "$FW_DIR/mt7996_eeprom_233_2i5i6i.bin" "$FW_DIR/mt7996_eeprom_233_2i5i6i.bin.bak"
fi
mv /tmp/mt7996_eeprom_233_2i5i6i.bin "$FW_DIR/"

wget -O /tmp/mt7996_eeprom_233.bin 'https://github.com/openwrt/mt76/raw/refs/heads/master/firmware/mt7996/mt7996_eeprom_233.bin'
FW_DIR="/lib/firmware/mediatek/mt7996"
if [ -f "$FW_DIR/mt7996_eeprom_233.bin" ]; then
  mv "$FW_DIR/mt7996_eeprom_233.bin" "$FW_DIR/mt7996_eeprom_233.bin.bak"
fi
mv /tmp/mt7996_eeprom_233.bin "$FW_DIR/"

バックグラウンドレーダー
uci set wireless.default_radio1.background_radar=1
# uci set wireless.default_radio2.background_radar=1
uci commit wireless
wifi reload

ワイヤレスオフロード有効化 (スナップショット)
  • SDカード用設定(RAMを2GB以下に制限)
if ! fw_printenv -n bootargs | grep -q 'mem=2048M'; then
  new_bootargs="$(fw_printenv -n bootargs) mem=2048M"
  fw_setenv bootargs "$new_bootargs"
fi
grep -q '^options mt7996e wed_enable=Y sr_scene_detect=Y' /etc/modules.conf || echo 'options mt7996e wed_enable=Y sr_scene_detect=Y' >> /etc/modules.conf
echo "再起動してください"
# reboot
  • 復元
new_bootargs=$(fw_printenv -n bootargs | sed 's/mem=2048M//g') 
fw_setenv bootargs $new_bootargs
sed -i '/^options mt7915e wed_enable=Y$/d' /etc/modules.conf
sed -i '/^options mt7996e wed_enable=Y sr_scene_detect=Y$/d' /etc/modules.conf
echo "再起動してください"
# reboot

Wi-Fi 7

MLO (スナップショット)

MAC80211_MT76_Programming_Guide_V4.10.pdf

  • MLO設定
#!/bin/sh

SSID='ばなな'
WPA_KEY='password'
COUNTRY='JP'
MLD_ID='42'

BANDS="2g 5g 6g"
CHANNELS="11 auto auto"
HTMODES="EHT40 EHT160 EHT320"

cp /etc/config/wireless /etc/config/wireless.mlo.bak
rm /etc/config/wireless
wifi config

NUM_RADIOS=$(echo $BANDS | wc -w)

i=0
while [ $i -lt $NUM_RADIOS ]; do
    radio="radio${i}"
    iface="default_radio${i}"

    band=$(echo $BANDS | awk -v n=$((i+1)) '{print $n}')
    channel=$(echo $CHANNELS | awk -v n=$((i+1)) '{print $n}')
    htmode=$(echo $HTMODES | awk -v n=$((i+1)) '{print $n}')
    mlo_link_id="$i"

    uci set wireless.$radio.disabled='0'
    uci set wireless.$radio.band="$band"
    uci set wireless.$radio.channel="$channel"
    uci set wireless.$radio.htmode="$htmode"
    uci set wireless.$radio.country="$COUNTRY"
    uci set wireless.$radio.mlo='1'
    uci set wireless.$radio.mld_id="$MLD_ID"

    ([ "$band" = "5g" ] || [ "$band" = "6g" ]) && uci set wireless.$radio.background_radar='1'

    uci set wireless.$iface.disabled='0'
    uci set wireless.$iface.device="$radio"
    uci set wireless.$iface.network='lan'
    uci set wireless.$iface.mode='ap'
    uci set wireless.$iface.ssid="$SSID"
    uci set wireless.$iface.encryption='sae'
    uci set wireless.$iface.key="$WPA_KEY"
    uci set wireless.$iface.mlo_link_id="$mlo_link_id"

    i=$((i+1))
done
uci set wireless.default_radio1.background_radar=1
uci commit wireless
wifi reload
  • 復元
cp /etc/config/wireless.mlo.bak /etc/config/wireless
uci commit wireless
wifi reload

Wi-Fi 6

高速ローミング構成 (usteer + 802.11r/k/v)

wifi.png

usteerとバンドステアリングの設置

#!/bin/sh
SSID='ばなな'
WIFI_KEY='password'
COUNTRY='JP'
MOBILITY_DOMAIN='1234'
BANDS="2g 5g 6g"
HTMODES="HE20 HE80 HE160"
TXPOWERS="10 15 ''"
CHANNELS="1 auto auto"
NASIDS="ap1-2g ap1-5g ap1-6g"
SNR="30 20 15"

opkg list-installed | grep -q "luci-app-usteer" || { opkg update && opkg install luci-app-usteer; }
cp /etc/config/wireless /etc/config/wireless.usteer.bak
rm /etc/config/wireless
wifi config

NUM_IFACES=$(grep -c "^config wifi-device" /etc/config/wireless)
i=0
while [ $i -lt $NUM_IFACES ]; do
    iface="default_radio${i}"
    radio="radio${i}"
    band=$(echo $BANDS | awk -v n=$((i+1)) '{print $n}')
    htmode=$(echo $HTMODES | awk -v n=$((i+1)) '{print $n}')
    txpower=$(echo $TXPOWERS | awk -v n=$((i+1)) '{print $n}')
    nasid=$(echo $NASIDS | awk -v n=$((i+1)) '{print $n}')
    min_snr=$(echo $SNR | awk -v n=$((i+1)) '{print $n}')
    channel=$(echo $CHANNELS | awk -v n=$((i+1)) '{print $n}')
    
    uci set wireless.$radio.band="$band"
    uci set wireless.$radio.channel="$channel"
    uci set wireless.$radio.htmode="$htmode"
    uci set wireless.$radio.country="$COUNTRY"
    [ -n "$txpower" ] && uci set wireless.$radio.txpower="$txpower"
    uci set wireless.$radio.disabled='0'
    
    uci set wireless.$iface.device="$radio"
    uci set wireless.$iface.network='lan'
    uci set wireless.$iface.mode='ap'
    uci set wireless.$iface.ssid="$SSID"
    uci set wireless.$iface.encryption='sae'
    uci set wireless.$iface.key="$WIFI_KEY"
    uci set wireless.$iface.isolate='1'
    uci set wireless.$iface.ocv='1'
    uci set wireless.$iface.ieee80211r='1'
    uci set wireless.$iface.mobility_domain="$MOBILITY_DOMAIN"
    uci set wireless.$iface.ft_over_ds='1'
    uci set wireless.$iface.nasid="$nasid"
    uci set wireless.$iface.usteer_min_snr="$min_snr"
    uci set wireless.$iface.ieee80211k='1'
    uci set wireless.$iface.ieee80211v='1'
    uci set wireless.$iface.disabled='0'
    
    # DFS (BPI-R4固有設定)
    [ "$band" = "5g" ] && uci set wireless.$iface.background_radar='1' && uci set wireless.$iface.ft_psk_generate_local='1'
    
    i=$((i+1))
done

uci set usteer.@usteer[0].band_steering='1'
uci set usteer.@usteer[0].load_balancing='1'
uci set usteer.@usteer[0].sta_block_timeout='300'
uci set usteer.@usteer[0].min_snr='20'
uci set usteer.@usteer[0].max_snr='80'
uci set usteer.@usteer[0].signal_diff_threshold='10'
uci set wireless.default_radio1.background_radar=1

uci commit
/etc/init.d/usteer enable
/etc/init.d/usteer start
wifi reload
  • 復元
#!/bin/sh
/etc/init.d/usteer stop
/etc/init.d/usteer disable

cp /etc/config/wireless.usteer.bak /etc/config/wireless

rm -f /etc/config/usteer

opkg remove luci-app-usteer usteer

uci commit wireless
wifi reload

パーツ

ACアダプター

UCB Type-C PD 20V
  • 純正ACアダプター: 12V/5.2A or 19V 3.2A

  • USB-C回路図
    CH224Kは20V/3.25A(65W)プロファイルを固定で要求する
    9d71e1f22c5df37ed962d51aa307a6b95d715893.jpeg

  • PD 20V: 65Wアダプター
    PC-VP-BP143: 20V/3.25A,15V/3A,9V/3A,5V/3A
    EC-AC8565BK: 20V/3.25A,15V/3A,12V/3A,9V/3A,5V/3A


ファン

BPI-R4用ファン確認

汎用Thermal Sysfsドライバの使い方

  • ハードウェアモニタ(hwmon)デバイス一覧
ls -l /sys/class/hwmon/
  • ファンPWM値の現在値確認
cat /sys/class/hwmon/hwmon1/pwm1
  • ファンPWM制御の状態確認
cat /sys/class/hwmon/hwmon1/pwm1_enable
  • サーマルゾーン(温度トリップポイント)の種類確認
cat /sys/class/thermal/thermal_zone0/trip_point_0_type # クリティカル: 100%
cat /sys/class/thermal/thermal_zone0/trip_point_1_type # ホット
cat /sys/class/thermal/thermal_zone0/trip_point_2_type # アクティブ: 50%
cat /sys/class/thermal/thermal_zone0/trip_point_3_type # アクティブ: 30%
cat /sys/class/thermal/thermal_zone0/trip_point_4_type # アクティブ: 0%
  • サーマルゾーン(温度トリップポイント)の温度設定確認
cat /sys/class/thermal/thermal_zone0/trip_point_0_temp # 臨界: 125℃
cat /sys/class/thermal/thermal_zone0/trip_point_1_temp # 高: 120℃
cat /sys/class/thermal/thermal_zone0/trip_point_2_temp # 高: 115℃
cat /sys/class/thermal/thermal_zone0/trip_point_3_temp # 中: 85℃
cat /sys/class/thermal/thermal_zone0/trip_point_4_temp # 低: 40℃

BPI-R4用ファンスクリプト
  • pwmfan
# /etc/init.d/pwmfan
cat <<'EOF' > /etc/init.d/pwmfan
#!/bin/sh /etc/rc.common

START=99
PIDFILE=/var/run/pwmfan.pid

start() {
    if [ -f $PIDFILE ]; then
        PID=$(cat $PIDFILE 2>/dev/null)
        if [ -n "$PID" ] && kill -0 "$PID" 2>/dev/null; then
            echo "Already running"
            return 1
        else
            rm -f $PIDFILE
        fi
    fi
    /usr/bin/pwmfan-loop &
    echo $! > $PIDFILE
}

stop() {
    if [ -f $PIDFILE ]; then
        PID=$(cat $PIDFILE 2>/dev/null)
        if [ -n "$PID" ] && kill -0 "$PID" 2>/dev/null; then
            kill $PID
        fi
        rm -f $PIDFILE
    fi
}

restart() {
    stop
    sleep 1
    start
}
EOF

chmod 755 /etc/init.d/pwmfan

# /usr/bin/pwmfan-loop
cat <<'EOF' > /usr/bin/pwmfan-loop
#!/bin/sh

CONFIG=pwmfan
SECTION="pwmfan"

get_temp() {
    local temp
    temp=$(cat /sys/class/thermal/thermal_zone0/temp 2>/dev/null)
    [ -z "$temp" ] && temp=0
    echo $((temp / 1000))
}

set_pwm() {
    local pwm="$1"
    echo 1 > /sys/class/hwmon/hwmon1/pwm1_enable
    echo $pwm > /sys/class/hwmon/hwmon1/pwm1
}

read_config() {
    INTERVAL=$(uci get ${CONFIG}.@${SECTION}[0].interval 2>/dev/null)
    [ -z "$INTERVAL" ] && INTERVAL=30
    TRIP_TEMP=$(uci get ${CONFIG}.@${SECTION}[0].trip_temp 2>/dev/null)
    TRIP_PWM=$(uci get ${CONFIG}.@${SECTION}[0].trip_pwm 2>/dev/null)
    MINPWM=$(uci get ${CONFIG}.@${SECTION}[0].minpwm 2>/dev/null)
    [ -z "$MINPWM" ] && MINPWM=100
    MAXPWM=$(uci get ${CONFIG}.@${SECTION}[0].maxpwm 2>/dev/null)
    [ -z "$MAXPWM" ] && MAXPWM=255

    if [ -z "$TRIP_TEMP" ] || [ -z "$TRIP_PWM" ]; then
        exit 1
    fi
    case "$INTERVAL" in
        ''|*[!0-9]*) INTERVAL=30 ;;
    esac
}

main_loop() {
    read_config
    local temp pwm i t p last_pwm=0
    set -- $TRIP_TEMP
    local trip_count=$#
    while true; do
        temp=$(get_temp)
        pwm=$MINPWM
        set -- $TRIP_TEMP
        local j=1
        for t in $@; do
            set -- $TRIP_PWM
            p=$(eval "echo \$$j")
            [ -z "$p" ] && p=$MINPWM
            [ "$temp" -ge "$t" ] && pwm=$p
            j=$((j+1))
        done
        [ "$pwm" -lt "$MINPWM" ] && pwm=$MINPWM
        [ "$pwm" -gt "$MAXPWM" ] && pwm=$MAXPWM
        if [ "$pwm" != "$last_pwm" ]; then
            set_pwm "$pwm"
            last_pwm=$pwm
        fi
        sleep "$INTERVAL"
    done
}

main_loop
EOF

chmod 755 /usr/bin/pwmfan-loop

touch /etc/config/pwmfan
uci -q delete pwmfan.@pwmfan[0]
uci -q commit pwmfan
uci -q add pwmfan pwmfan
uci -q set pwmfan.@pwmfan[-1].interval='20'
uci -q set pwmfan.@pwmfan[-1].minpwm='80'
uci -q set pwmfan.@pwmfan[-1].maxpwm='255'
uci -q set pwmfan.@pwmfan[-1].trip_temp='60 70'
uci -q set pwmfan.@pwmfan[-1].trip_pwm='128 255'
uci -q commit pwmfan
/etc/init.d/pwmfan enable
/etc/init.d/pwmfan restart

# /usr/bin/fan-status
cat <<'EOF' > /usr/bin/fan-status
#!/bin/sh

TEMP_RAW=$(cat /sys/class/thermal/thermal_zone0/temp 2>/dev/null)
TEMP_C=$(awk "BEGIN {printf \"%.1f\", $TEMP_RAW/1000}")
PWM=$(cat /sys/class/hwmon/hwmon1/pwm1 2>/dev/null)
PWM_PERCENT=$((PWM * 100 / 255))
echo "========================="
echo " Fan & Temperature Status"
echo "-------------------------"
echo " Temperature : ${TEMP_C} °C"
echo " Fan PWM     : ${PWM} (${PWM_PERCENT} %)"
echo "========================="
EOF

chmod 755 /usr/bin/fan-status
fan-status
  • 監視
while true; do
    fan-status
    sleep 20
done
  • リムーブ
/etc/init.d/pwmfan stop
/etc/init.d/pwmfan disable

rm -f /etc/init.d/pwmfan
rm -f /usr/bin/pwmfan-loop
rm -f /usr/bin/fan-status

uci -q delete pwmfan.@pwmfan[0]
uci -q commit pwmfan

[ ! -s /etc/config/pwmfan ] && rm -f /etc/config/pwmfan

モデム

SIMカードトレイ
  • SIMサイズ: nanoSIM
  • SIMトレイ: 第1世代: iPhone5/5S/SE用

※SIMを入れずトレイを挿入すると壊れます!
iphone5_simcardtray.jpg


Fibocom FM350-GL (14c3:4d75)

FM350 AT Commands User Manual

  • 動作環境
    Revision: 1.1
    OpenWrt: 24.10.2
    ModemManager: 1.22.0
    kmod-mtk-t7xx: 6.6.93-r1
    14C3:4D75: 81600.0000.00.29.18.16_DO
    モード: PCIe
    SIMスロット: SIM1
    キャリア: docomo

  • MHF4コネクタ (アンテナ端子)
    FM350.png
    M: プライマリ/送信 (全利用)
    M1: 4×4 MIMO
    M2: 4×4 MIMO
    D/G: ダイバーシティ/受信 (全利用)

  • 既知の問題
    モデムデバイスは、完全に断電してから通電させないと、認識しない
    ※M.2スロットの3.3 V電源ラインはカーネルから制御できず、PCIeリセットピンも露出していない
    PCIeモードでは、ATコマンドは送信出来ない
    SIM1の青色LEDは、通信が確立しないと点灯しない
    SIMトレイがスムースに挿入出来ない場合、SIMが認識しない事がある (ケースネジを緩め要調整)

  • モデム設定

#!/bin/sh

WWAN="wwan"
APN="spmode.ne.jp"
ALLOWEDAUTH="chap"
ALLOWEDMODE="5G" # SPモードの場合必須
METRIC="100"
IPTYPE="ipv4v6"
LOGLEVEL="ERR"
IP6ASSIGN="64"
MTU="1500"
SIGNALRATE="120"
INIT_EPSBEARER="default" # SPモードの場合必須
AUTO="0"

cp /etc/config/network /etc/config/network.wwan.bak
cp /etc/config/firewall /etc/config/firewall.wwan.bak

PKGS="kmod-mtk-t7xx modemmanager-rpcd luci-proto-modemmanager pciutils mbim-utils ubus"
command -v opkg && opkg update && opkg install $PKGS
command -v apk && apk update && apk add $PKGS

WWAN_IDX=$(uci show network | grep "name='wwan0'" | sed -n "s/network.@device

\[\([0-9]\+\)\]

.*/\1/p")
if [ -n "$WWAN_IDX" ]; then
  uci set network.@device[$WWAN_IDX].ipv6='1'
  [ -n "$MTU" ] && uci set network.@device[$WWAN_IDX].mtu="${MTU}"
  [ -n "$MTU" ] && uci set network.@device[$WWAN_IDX].mtu6="${MTU}"
else
  uci add network device
  uci set network.@device[-1].name='wwan0'
  uci set network.@device[-1].ipv6='1'
  [ -n "$MTU" ] && uci set network.@device[-1].mtu="${MTU}"
  [ -n "$MTU" ] && uci set network.@device[-1].mtu6="${MTU}"
fi

uci set network.${WWAN}=interface
uci set network.${WWAN}.proto='modemmanager'
uci set network.${WWAN}.apn="${APN}"
uci set network.${WWAN}.allowedauth="${ALLOWEDAUTH}"
uci set network.${WWAN}.allowedmode="${ALLOWEDMODE}"
uci set network.${WWAN}.iptype="${IPTYPE}"
uci set network.${WWAN}.loglevel="${LOGLEVEL}"
uci set network.${WWAN}.metric="${METRIC}"
uci set network.${WWAN}.force_link='1'
uci set network.${WWAN}.ip6assign="${IP6ASSIGN}"
[ -n "$MTU" ] && uci set network.${WWAN}.mtu="${MTU}"
[ -n "$SIGNALRATE" ] && uci set network.${WWAN}.signalrate="${SIGNALRATE}"
uci set network.${WWAN}.auto="${AUTO}"
uci set network.${WWAN}.init_epsbearer="${INIT_EPSBEARER}"

[ -z "$(uci get firewall.@zone[1].network 2>/dev/null | grep -w 'wwan')" ] && uci add_list firewall.@zone[1].network='wwan'

uci commit network
uci commit firewall

/etc/init.d/modemmanager stop
sleep 2
/etc/init.d/modemmanager start
sleep 3

ubus call network reload
sleep 2
ifup wwan

  • モデムリセット
    ※テスト中 (動作不安定: セーフモードに入るリスク有)
cat > /etc/modem_power.sh << 'EOF'
#!/bin/sh

pci_reset() {
    local pci_path="/sys/bus/pci/devices/0003:01:00.0"
    
    if [ -f "$pci_path/reset" ]; then
        echo 1 > "$pci_path/reset"
        return 0
    else
        return 1
    fi
}

driver_reset() {   
    echo "0003:01:00.0" > /sys/bus/pci/drivers/mtk_t7xx/unbind 2>/dev/null
    sleep 2
    
    echo "0003:01:00.0" > /sys/bus/pci/drivers/mtk_t7xx/bind 2>/dev/null
    sleep 3
}

modem_reset() {
    /etc/init.d/modemmanager stop
    sleep 2
    
    if ! pci_reset; then
        driver_reset
    fi
    
    sleep 3
    
    /etc/init.d/modemmanager start
    sleep 5
}

check_status() {
    lspci | grep -i mediatek | grep 4d75
    
    ls -la /dev/wwan0* 2>/dev/null || echo "なし"
    
    mmcli -L 2>/dev/null || echo "モデムなし"
    
    if mmcli -L 2>/dev/null | grep -q "Modem"; then
        mmcli -m 0 2>/dev/null | grep -E "(状態|State|オペレータ|Operator|信号|Signal)" || true
    fi
    
    dmesg | grep -i -E "(t7xx|mtk|modem)" | tail -5
}

case "$1" in
    reset)
        modem_reset
        ;;
    status)
        check_status
        ;;
    *)
        echo "使用法: $0 {reset|status}"
        ;;
esac
EOF
chmod +x /etc/modem_power.sh
  • モデムリセット
sh /etc/modem_power.sh reset
  • 復元
cp /etc/config/network.wwan.bak /etc/config/network
cp /etc/config/firewall.wwan.bak /etc/config/firewall
uci commit network
uci commit firewall
# reboot
  • デバイス確認
lspci
ls -la /dev/ | grep -E "(cdc|wwan)"
  • ネットワークインターフェース確認
ip link show
  • ベンダーID、プロダクトID確認
ls /sys/class/net/wwan0/device/
cat /sys/class/net/wwan0/device/../../../modalias
cat /sys/class/net/wwan0/device/../../../uevent
  • モデム確認
ubus call modemmanager info
ubus call modemmanager dump
mmcli -L
mmcli -m 0
  • 使用可能なすべてのオプション
mbimcli --help-all
  • バージョン確認
mbimcli --version
  • FCCロック確認
mbimcli -d /dev/wwan0mbim0 --intel-query-fcc-lock
mbimcli -d /dev/wwan0mbim0 --set-radio-state=on
  • FCCロック解除(必要な場合)
mbimcli -d /dev/wwan0mbim0 --intel-set-fcc-unlock

/etc/ModemManager/fcc.unlock/
dispatcher-fcc-unlock

MODEM="14c3" # 例
mkdir -p /etc/ModemManager/fcc.unlock/
chmod 644 /etc/ModemManager/fcc.unlock/$MODEM

cat <<'EOF' > /etc/ModemManager/fcc.unlock/$MODEM
# <ここにdispatcher-fcc-unlockの該当ソースを貼る>
EOF

/etc/init.d/modemmanager restart
  • モデム有効化(enable)
mmcli -m 0 -e
  • ラジオ有効化
mbimcli -d /dev/wwan0mbim0 --set-radio-state=on
  • ラジオ状態確認
mbimcli -d /dev/wwan0mbim0 --query-radio-state
  • デバイス機能確認
mbimcli -d /dev/wwan0mbim0 --query-device-caps
  • SIM状態確認
mbimcli -d /dev/wwan0mbim0 --query-subscriber-ready-status
  • ネットワーク登録状態確認
mbimcli -d /dev/wwan0mbim0 --query-registration-state
  • 信号強度確認
mmcli -m 0 --signal-get
mbimcli -d /dev/wwan0mbim0 --query-signal-state
  • APN設定確認
mbimcli -d /dev/wwan0mbim0 --query-provisioned-contexts
  • 接続IP情報
mbimcli -d /dev/wwan0mbim0  -p --query-ip-configuration=0
  • モデムの詳細状態確認
mmcli -m 0 --output-keyvalue
  • モデムの詳細情報
root@bpi-r4:~# mmcli -m 0
  -----------------------------------
  General   |                   path: /org/freedesktop/ModemManager1/Modem/0
            |              device id: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
  -----------------------------------
  Hardware  |           manufacturer: generic
            |                  model: MBIM [14C3:4D75]
            |      firmware revision: 81600.0000.00.29.18.16_DO
            |                         C43
            |           h/w revision: V1.0.6
            |              supported: gsm-umts, lte, 5gnr
            |                current: gsm-umts, lte, 5gnr
            |           equipment id: XXXXXXXXXXXXXXXX
  -----------------------------------
  System    |                 device: /sys/devices/platform/soc/11280000.pcie/pci0003:00/0003:00:00.0/0003:01:00.0
            |                physdev: /sys/devices/platform/soc/11280000.pcie/pci0003:00/0003:00:00.0/0003:01:00.0
            |                drivers: mtk_t7xx
            |                 plugin: generic
            |           primary port: wwan0mbim0
            |                  ports: wwan0 (net), wwan0at0 (at), wwan0mbim0 (mbim)
  -----------------------------------
  Numbers   |                    own: XXXXXXXXXXXX
  -----------------------------------
  Status    |                   lock: sim-pin2
            |         unlock retries: sim-pin2 (3)
            |                  state: connected
            |            power state: on
            |            access tech: lte, 5gnr
            |         signal quality: 48% (cached)
  -----------------------------------
  Modes     |              supported: allowed: 3g; preferred: none
            |                         allowed: 4g; preferred: none
            |                         allowed: 3g, 4g; preferred: none
            |                         allowed: 5g; preferred: none
            |                         allowed: 3g, 5g; preferred: none
            |                         allowed: 4g, 5g; preferred: none
            |                         allowed: 3g, 4g, 5g; preferred: none
            |                current: allowed: 5g; preferred: none
  -----------------------------------
  IP        |              supported: ipv4, ipv6, ipv4v6
  -----------------------------------
  3GPP      |                   imei: XXXXXXXXXXXXXXXX
            |          enabled locks: fixed-dialing
            |            operator id: 44010
            |          operator name: NTT DOCOMO
            |           registration: home
            |   packet service state: attached
  -----------------------------------
  3GPP EPS  |   ue mode of operation: csps-2
            |    initial bearer path: /org/freedesktop/ModemManager1/Bearer/4
            |     initial bearer apn: spmode.ne.jp
            | initial bearer ip type: ipv4v6
  -----------------------------------
  3GPP 5GNR |              mico mode: disabled
  -----------------------------------
  SIM       |       primary sim path: /org/freedesktop/ModemManager1/SIM/0
            |         sim slot paths: slot 1: /org/freedesktop/ModemManager1/SIM/0 (active)
            |                         slot 2: /org/freedesktop/ModemManager1/SIM/1
  -----------------------------------
  Bearer    |                  paths: /org/freedesktop/ModemManager1/Bearer/5
root@bpi-r4:~# 

スクリーンショット 2025-07-03 002228.jpg
Telit LN940 よりは速いが、5Gの速度にしては遅すぎる

docomo.png
iPhoneよりは速かった(笑)


SFP

SFP 10G
PKGS="ethtool-full"
command -v opkg && opkg update && opkg install $PKGS
command -v apk  && apk update  && apk add $PKGS

RTC

RTC有効化
PKGS="i2c-tools"
command -v opkg && opkg update && opkg install $PKGS
command -v apk  && apk update  && apk add $PKGS

fw_setenv bootconf_extra mt7988a-bananapi-bpi-r4-rtc

デバイス用のOpenWrtカスタムファームウェアをダウンロード

デバイスアクセス

デバイスアクセス(UCI)

パワーシェルでアクセス

PowerShellの開始

  • キー入力:Win+x > a > はい

UCI(SSH)アクセス

powershell:初期設定用
ssh -o StrictHostKeyChecking=no -oHostKeyAlgorithms=+ssh-rsa root@192.168.1.1
  • root@192.168.1.1's password:初期値:パスワード無し
SSHログイン出来ない場合:exclamation:
  • %USERPROFILE%\.ssh\known_hosts ※Windows隠しファイル
powershell
Clear-Content .ssh\known_hosts -Force 

OpenSSHのインストールが無い場合:exclamation: - 機能の確認 ※Windows 10 Fall Creators Update(1709)以降標準搭載 ```powershell:powershell Get-WindowsCapability -Online | Where-Object Name -like 'OpenSSH*' ``` - 機能のインストール ```powershell:powershell Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0 ``` ---

LuCi

ブラウザーでアクセス

初期設定

パスワード

初期値から変更
passwd

Changing password for root
New password:

passwd:入力モード
任意のパスワード

Retype password:

passwd:入力モード
再入力

passwd: password for root changed by root

exit

ホストネーム

ホストネーム(openwrt)を変更
#!/bin/sh
HOSTNAME='openwrt' # デバイス名
uci set system.@system[0].hostname=${HOSTNAME}
uci commit system
/etc/init.d/system reload

タイムゾーン

タイムゾーンを変更
#!/bin/sh
TIMEZONE='JST-9'
ZONENAME='Asia/Tokyo'
uci set system.@system[0].timezone=${TIMEZONE}
uci set system.@system[0].zonename=${ZONENAME}
uci commit system
/etc/init.d/sysntpd restart

NTP

NTPサーバーを変更
#!/bin/sh
POOL='jp'
uci delete system.ntp.server
uci add_list system.ntp.server=0.${POOL}.pool.ntp.org
uci add_list system.ntp.server=1.${POOL}.ppl.ntp.org
uci add_list system.ntp.server=2.${POOL}.pool.ntp.org 
uci add_list system.ntp.server=3.${POOL}.pool.ntp.org
uci set system.ntp.enable_server='1'
uci set system.ntp.use_dhcp='0'
uci set system.ntp.interface='lan'
uci commit system
/etc/init.d/sysntpd restart

SSH

WANからのアクセスを遮断
uci set dropbear.@dropbear[0].Interface='lan'
uci commit dropbear

LED

ネットワークアクセスで点灯点滅
#! /bin/sh
uci add system led
uci set system.@led[-1].name='wan'
uci set system.@led[-1].sysfs='green:status'
uci set system.@led[-1].trigger='netdev'
uci set system.@led[-1].dev='wan'
uci set system.@led[-1].mode='link tx rx'
uci add system led
uci set system.@led[-1].name='br-lan'
uci set system.@led[-1].sysfs='blue:wps'
uci set system.@led[-1].trigger='netdev'
uci set system.@led[-1].dev='br-lan'
uci set system.@led[-1].mode='link tx rx'
uci commit system
/etc/init.d/led reload

パッケージ

母国語サポート

LuCiの言語パッケージをインストール
  • 対応言語検索
#! /bin/sh
PATTERN="luci-i18n-base*"
command -v opkg && opkg update && opkg list $PATTERN
command -v apk  && apk update  && apk search $PATTERN
  • 設定
#! /bin/sh
I18N="ja"
PKGS="luci-i18n-base-${I18N} luci-i18n-firewall-${I18N}"
command -v opkg && opkg update && opkg install $PKGS
command -v apk && apk update && apk add $PKGS

UCI(TTYD)

ブラウザーでUCIにアクセス
#! /bin/sh
I18N="ja"
PKGS="luci-i18n-ttyd-${I18N}"
command -v opkg && opkg update && opkg install $PKGS
command -v apk && apk update && apk add $PKGS
uci set ttyd.@ttyd[0].ipv6='1'
uci set ttyd.@ttyd[0].command='/bin/login -f root' #自動ログイン
uci commit ttyd
/etc/init.d/rpcd restart

ファイラー

ブラウザでファイルにアクセス
mkdir -p /tmp/aios && wget -q -O /tmp/aios/install-filebrowser.sh https://raw.githubusercontent.com/site-u2023/aios/main/install-filebrowser.sh && sh /tmp/aios/install-filebrowser.sh
# ポート変更
uci set filebrowser.config.port=9090
# ルートディレクトリ変更  
uci set filebrowser.config.root=/mnt/storage
# ユーザーネーム及びパスワード変更
uci set filebrowser.config.username='root'
uci set filebrowser.config.password='password'
uci commit filebrowser
# 設定確認
uci show filebrowser
# サービス再起動
service filebrowser restart
# 既存DBをリセットしてUCI値で上書きしたいとき
rm /etc/filebrowser/filebrowser.db
service filebrowser restart
  • 使用方法
    filebrowser_main.sh: 自動判定 (インストールまたはリムーブ)
    filebrowser_main.sh install: インストール
    filebrowser_main.sh remove: リムーブ
    filebrowser_main.sh status: 状態確認

WinSCPでファイルにアクセス
#! /bin/sh
PKGS="openssh-sftp-server"
command -v opkg && opkg update && opkg install $PKGS
command -v apk  && apk update  && apk add $PKGS

クライアント設定(Windows)

  • 手動インストール
  • 自動インストール
    • キー入力:Win+x > a > はい
    • 最新版ソフトウェアのインストール
powershell:クライアントPC
$psVersion = $PSVersionTable.PSVersion.Major
$LINKS = Invoke-WebRequest "https://winscp.net/eng/download.php"
$LINKS_VERSION = $LINKS.Links | Where-Object {$_.href -like "*WinSCP-*-Setup.exe*"} | Select-Object -ExpandProperty href
$VERSION = ($LINKS_VERSION -split '/')[-2] -replace "WinSCP-([0-9]+\.[0-9]+\.[0-9]+).*", '$1'
Write-Host "Version to install: $VERSION"
$downloadUrl = "https://jaist.dl.sourceforge.net/project/winscp/WinSCP/$VERSION/WinSCP-$VERSION-Setup.exe?viasf=1"
Write-Host "Downloading from: $downloadUrl"
$ONAMAE = (whoami).Split('\')[1]
$destinationPath = "C:\Users\$ONAMAE\Downloads\WinSCP-$VERSION-Setup.exe"
Invoke-WebRequest -Uri $downloadUrl -OutFile $destinationPath
Write-Host "Installing WinSCP..."
Start-Process -FilePath $destinationPath -ArgumentList "/VERYSILENT /NORESTART" -Wait
Invoke-Expression "C:\Users\$ONAMAE\AppData\Local\Programs\WinSCP\WinSCP.exe"
  • 警告 > 強制的に貼り付け

  • WinSCP設定

    • セッション
      • ホスト名:192.168.1.1
      • ユーザー名:root
      • パスワード:設定したパスワード
      • ログインをクリック

CPU負荷分散

インストール
#! /bin/sh
PKGS="irqbalance"
command -v opkg && opkg update && opkg install $PKGS
command -v apk  && apk update  && apk add $PKGS
uci set irqbalance.irqbalance=irqbalance
uci set irqbalance.irqbalance.enabled='1'
uci commit irqbalance
/etc/init.d/irqbalance start

ネットワーク統計インターフェイス

インストール
#! /bin/sh
I18N="ja"
PKGS="luci-i18n-statistics-${I18N}"
command -v opkg && opkg update && opkg install $PKGS
command -v apk && apk update && apk add $PKGS
/etc/init.d/collectd enable
/etc/init.d/rpcd restart
  • プラグイン検索
#! /bin/sh
PATTERN="collectd-mod\*"
command -v opkg && opkg update && opkg list $PATTERN
command -v apk  && apk update  && apk search $PATTERN

追加テーマ

インストール
#! /bin/sh
PKGS="luci-theme-openwrt luci-theme-material"
command -v opkg && opkg update && opkg install $PKGS
command -v apk  && apk update  && apk add $PKGS

AdGuard Home

インストール

オフィシャル版
OpenWrt版

  • インストール及びリムーブ
#!/bin/sh

TMP=/tmp/aios
mkdir -p "$TMP"
cat <<'EOF' > "$TMP"/standalone-blocker-adguardhome.sh
#!/bin/sh

# OpenWrt 19.07+ configuration
# Reference: https://openwrt.org/docs/guide-user/services/dns/adguard-home
#            https://github.com/AdguardTeam/AdGuardHome
# This script file can be used standalone.

SCRIPT_VERSION="2025.08.26-00-00"

# set -ex

REQUIRED_MEM="50" # unit: MB
REQUIRED_FLASH="100" # unit: MB
LAN="${LAN:-br-lan}"
DNS_PORT="${DNS_PORT:-53}"
DNS_BACKUP_PORT="${DNS_BACKUP_PORT:-54}"

NET_ADDR=""
NET_ADDR6_LIST=""
SERVICE_NAME=""
INSTALL_MODE=""
ARCH=""
AGH=""
PACKAGE_MANAGER=""
FAMILY_TYPE=""

check_system() {
  if /etc/AdGuardHome/AdGuardHome --version >/dev/null 2>&1 || /usr/bin/AdGuardHome --version >/dev/null 2>&1; then
    printf "\033[1;33mAdGuard Home is already installed. Exiting.\033[0m\n"
    remove_adguardhome
    exit 0
  fi
  
  printf "\033[1;34mChecking system requirements\033[0m\n"
  
  LAN="$(ubus call network.interface.lan status 2>/dev/null | jsonfilter -e '@.l3_device')"
  if [ -z "$LAN" ]; then
    printf "\033[1;31mLAN interface not found. Aborting.\033[0m\n"
    exit 1
  fi
  
  if command -v opkg >/dev/null 2>&1; then
    PACKAGE_MANAGER="opkg"
  elif command -v apk >/dev/null 2>&1; then
    PACKAGE_MANAGER="apk"
  else
    printf "\033[1;31mNo supported package manager (apk or opkg) found.\033[0m\n"
    printf "\033[1;31mThis script is designed for OpenWrt systems only.\033[0m\n"
    exit 1
  fi
  
  MEM_TOTAL_KB=$(awk '/^MemTotal:/ {print $2}' /proc/meminfo)
  MEM_FREE_KB=$(awk '/^MemAvailable:/ {print $2}' /proc/meminfo)
  BUFFERS_KB=$(awk '/^Buffers:/ {print $2}' /proc/meminfo)
  CACHED_KB=$(awk '/^Cached:/ {print $2}' /proc/meminfo)
  if [ -n "$MEM_FREE_KB" ]; then
    MEM_FREE_MB=$((MEM_FREE_KB / 1024))
  else
    MEM_FREE_MB=$(((BUFFERS_KB + CACHED_KB) / 1024))
  fi
  MEM_TOTAL_MB=$((MEM_TOTAL_KB / 1024))
  DF_OUT=$(df -k / | awk 'NR==2 {print $2, $4}')
  FLASH_TOTAL_KB=$(printf '%s\n' "$DF_OUT" | awk '{print $1}')
  FLASH_FREE_KB=$(printf '%s\n' "$DF_OUT" | awk '{print $2}')
  FLASH_FREE_MB=$((FLASH_FREE_KB / 1024))
  FLASH_TOTAL_MB=$((FLASH_TOTAL_KB / 1024))
  
  if [ "$MEM_FREE_MB" -lt "$REQUIRED_MEM" ]; then
    mem_col="1;31"
  else
    mem_col="1;32"
  fi
  if [ "$FLASH_FREE_MB" -lt "$REQUIRED_FLASH" ]; then
    flash_col="1;31"
  else
    flash_col="1;32"
  fi
  
  printf "Memory: Free \033[%sm%s MB\033[0m / Total %s MB\n" \
    "$mem_col" "$MEM_FREE_MB" "$MEM_TOTAL_MB"
  printf "Flash: Free \033[%sm%s MB\033[0m / Total %s MB\n" \
    "$flash_col" "$FLASH_FREE_MB" "$FLASH_TOTAL_MB"
  printf "LAN interface: %s\n" "$LAN"
  printf "Package manager: %s\n" "$PACKAGE_MANAGER"
  
  if [ "$MEM_FREE_MB" -lt "$REQUIRED_MEM" ]; then
    printf "\033[1;31mError: Insufficient memory. At least %sMB RAM is required.\033[0m\n" \
      "$REQUIRED_MEM"
    exit 1
  fi
  if [ "$FLASH_FREE_MB" -lt "$REQUIRED_FLASH" ]; then
    printf "\033[1;31mError: Insufficient flash storage. At least %sMB free space is required.\033[0m\n" \
      "$REQUIRED_FLASH"
    exit 1
  fi
}

install_prompt() {
  printf "\033[1;34mSystem resources are sufficient for AdGuard Home installation. Proceeding with setup.\033[0m\n"

  if [ -n "$1" ]; then
    case "$1" in
      official) INSTALL_MODE="official"; return ;;
      openwrt) INSTALL_MODE="openwrt"; return ;;
      remove) remove_adguardhome ;;
      exit) exit 0 ;;
      *) printf "\033[1;31mWarning: Unrecognized argument '$1'. Proceeding with interactive prompt.\033[0m\n" ;;
    esac
  fi

  while true; do
    printf "[1] Install OpenWrt package\n"
    printf "[2] Install Official binary\n"
    printf "[0] Exit\n"
    printf "Please select (1, 2 or 0): "
    read -r choice

    case "$choice" in
      1|openwrt) INSTALL_MODE="openwrt"; break ;;
      2|official) INSTALL_MODE="official"; break ;;
      0|exit)
        printf "\033[1;33mInstallation cancelled.\033[0m\n"
        exit 0
        ;;
      *) printf "\033[1;31mInvalid choice '$choice'. Please enter 1, 2, or 0.\033[0m\n" ;;
    esac
  done
}

install_cacertificates() {
  case "$PACKAGE_MANAGER" in
    apk)
      apk update >/dev/null 2>&1
      apk add ca-certificates >/dev/null 2>&1
      ;;
    opkg)
      opkg update --verbosity=0 >/dev/null 2>&1
      opkg install --verbosity=0 ca-bundle >/dev/null 2>&1
      ;;
  esac
}

install_openwrt() {
  printf "Installing adguardhome (OpenWrt package)\n"
  
  case "$PACKAGE_MANAGER" in
    apk)
      PKG_VER=$(apk search adguardhome | grep "^adguardhome-" | sed 's/^adguardhome-//' | sed 's/-r[0-9]*$//')
      if [ -n "$PKG_VER" ]; then
        apk add adguardhome || {
          printf "\033[1;31mNetwork error during apk add. Aborting.\033[0m\n"
          exit 1
        }
        printf "\033[1;32madguardhome %s has been installed\033[0m\n" "$PKG_VER"
      else
        printf "\033[1;31mPackage 'adguardhome' not found in apk repository, falling back to official\033[0m\n"
        install_official
      fi
      ;;
    opkg)
      PKG_VER=$(opkg list | grep "^adguardhome " | awk '{print $3}')
      if [ -n "$PKG_VER" ]; then
        opkg install --verbosity=0 adguardhome || {
          printf "\033[1;31mNetwork error during opkg install. Aborting.\033[0m\n"
          exit 1
        }
        printf "\033[1;32madguardhome %s has been installed\033[0m\n" "$PKG_VER"
      else
        printf "\033[1;31mPackage 'adguardhome' not found in opkg repository, falling back to official\033[0m\n"
        install_official
      fi
      ;;
  esac
  
  SERVICE_NAME="adguardhome"
}

install_official() {
  CA="--no-check-certificate"
  URL="https://api.github.com/repos/AdguardTeam/AdGuardHome/releases/latest"
  VER=$( { wget -q -O - "$URL" || wget -q "$CA" -O - "$URL"; } | jsonfilter -e '@.tag_name' )
  [ -n "$VER" ] || { printf "\033[1;31mError: Failed to get AdGuardHome version from GitHub API.\033[0m\n"; exit 1; }
  
  mkdir -p /etc/AdGuardHome
  case "$(uname -m)" in
    aarch64|arm64) ARCH=arm64 ;;
    armv7l)        ARCH=armv7 ;;
    armv6l)        ARCH=armv6 ;;
    armv5l)        ARCH=armv5 ;;
    x86_64|amd64)  ARCH=amd64 ;;
    i386|i686)     ARCH=386 ;;
    mips)          ARCH=mipsle ;;
    mips64)        ARCH=mips64le ;;
    *) printf "Unsupported arch: %s\n" "$(uname -m)"; exit 1 ;;
  esac
  TAR="AdGuardHome_linux_${ARCH}.tar.gz"
  URL2="https://github.com/AdguardTeam/AdGuardHome/releases/download/${VER}/${TAR}"
  DEST="/etc/AdGuardHome/${TAR}"
  
  printf "Downloading AdGuardHome (official binary)\n"
  if ! { wget -q -O "$DEST" "$URL2" || wget -q "$CA" -O "$DEST" "$URL2"; }; then
    printf '\033[1;31mDownload failed. Please check network connection.\033[0m\n'
    exit 1
  fi
  printf "\033[1;32mAdGuardHome %s has been downloaded\033[0m\n" "$VER"
  
  tar -C /etc/ -xzf "/etc/AdGuardHome/${TAR}"
  rm "/etc/AdGuardHome/${TAR}"
  chmod +x /etc/AdGuardHome/AdGuardHome
  SERVICE_NAME="AdGuardHome"
  
  /etc/AdGuardHome/AdGuardHome -s install >/dev/null 2>&1 || {
    printf "\033[1;31mInitialization failed. Check AdGuardHome.yaml and port availability.\033[0m\n"
    exit 1
  }
  chmod 700 /etc/"$SERVICE_NAME"
}

get_iface_addrs() {
  local flag=0
  
  if ip -4 -o addr show dev "$LAN" scope global | grep -q 'inet '; then
    NET_ADDR=$(ip -4 -o addr show dev "$LAN" scope global | awk 'NR==1{sub(/\/.*/,"",$4); print $4}')
    flag=$((flag | 1))
  else
    printf "\033[1;33mWarning: No IPv4 address on %s\033[0m\n" "$LAN"
  fi

  if ip -6 -o addr show dev "$LAN" scope global | grep -q 'inet6 '; then
    NET_ADDR6_LIST=$(ip -6 -o addr show dev "$LAN" scope global | grep -v temporary | awk 'match($4,/^(2|fd|fc)/){sub(/\/.*/,"",$4); print $4;}')
    flag=$((flag | 2))
  else
    printf "\033[1;33mWarning: No IPv6 address on %s\033[0m\n" "$LAN"
  fi

  case $flag in
    3) FAMILY_TYPE=any  ;;
    1) FAMILY_TYPE=ipv4 ;;
    2) FAMILY_TYPE=ipv6 ;;
    *) FAMILY_TYPE=""   ;;
  esac
}

common_config() {
  cp /etc/config/network  /etc/config/network.adguard.bak
  cp /etc/config/dhcp     /etc/config/dhcp.adguard.bak
  cp /etc/config/firewall /etc/config/firewall.adguard.bak
  uci set dhcp.@dnsmasq[0].noresolv="1"
  uci set dhcp.@dnsmasq[0].cachesize="0"
  uci set dhcp.@dnsmasq[0].rebind_protection='0'
  # uci set dhcp.@dnsmasq[0].rebind_protection='1'  # keep enabled
  # uci set dhcp.@dnsmasq[0].rebind_localhost='1'   # protect localhost
  # uci add_list dhcp.@dnsmasq[0].rebind_domain='lan'  # allow internal domain
  uci set dhcp.@dnsmasq[0].port="${DNS_BACKUP_PORT}"
  uci set dhcp.@dnsmasq[0].domain="lan"
  uci set dhcp.@dnsmasq[0].local="/lan/"
  uci set dhcp.@dnsmasq[0].expandhosts="1"
  uci -q del dhcp.@dnsmasq[0].server || true
  uci add_list dhcp.@dnsmasq[0].server="127.0.0.1#${DNS_PORT}"
  uci add_list dhcp.@dnsmasq[0].server="::1#${DNS_PORT}"
  uci -q del dhcp.lan.dhcp_option || true
  uci add_list dhcp.lan.dhcp_option="6,${NET_ADDR}"
  uci -q del dhcp.lan.dhcp_option6 || true
  if [ -n "$NET_ADDR6_LIST" ]; then
    for ip in $NET_ADDR6_LIST; do
      uci add_list dhcp.lan.dhcp_option6="option6:dns=[${ip}]"
    done
  fi
  uci commit dhcp
  /etc/init.d/dnsmasq restart || {
    printf "\033[1;31mFailed to restart dnsmasq\033[0m\n"
    printf "\033[1;31mTo remove AdGuard Home, run:\033[0m\n"
    printf "\033[1;31msh %s/standalone-blocker-adguardhome.sh remove_adguardhome auto\033[0m\n" "$TMP"
    exit 1
  }
  /etc/init.d/odhcpd restart || {
    printf "\033[1;31mFailed to restart odhcpd\033[0m\n"
    printf "\033[1;31mTo remove AdGuard Home, run:\033[0m\n"
    printf "\033[1;31msh %s/standalone-blocker-adguardhome.sh remove_adguardhome auto\033[0m\n" "$TMP"
    exit 1
  }
  /etc/init.d/"$SERVICE_NAME" enable
  /etc/init.d/"$SERVICE_NAME" start
  
  printf "Router IPv4: %s\n" "$NET_ADDR"
  if [ -z "$NET_ADDR6_LIST" ]; then
    printf "\033[1;33mRouter IPv6: none found\033[0m\n"
  else
    printf "Router IPv6: %s\n" "$(echo "$NET_ADDR6_LIST" | tr '\n' ' ')"
  fi
  
  printf "\033[1;32mSystem configuration completed\033[0m\n"
}

common_config_firewall() {
  uci -q delete firewall.@include[0].reload
  uci commit firewall
  
  rule_name="adguardhome_dns_${DNS_PORT}"
  uci -q delete firewall."$rule_name" || true
  if [ -z "$FAMILY_TYPE" ]; then
    printf "\033[1;31mNo valid IP address family detected. Skipping firewall rule setup.\033[0m\n"
    return
  fi
  uci set firewall."$rule_name"=redirect
  uci set firewall."$rule_name".name="AdGuardHome DNS Redirect (${FAMILY_TYPE})"
  uci set firewall."$rule_name".family="$FAMILY_TYPE"
  uci set firewall."$rule_name".src="lan"
  uci set firewall."$rule_name".dest="lan"
  uci add_list firewall."$rule_name".proto="tcp"
  uci add_list firewall."$rule_name".proto="udp"
  uci set firewall."$rule_name".src_dport="$DNS_PORT"
  uci set firewall."$rule_name".dest_port="$DNS_PORT"
  uci set firewall."$rule_name".target="DNAT"
  uci commit firewall
  /etc/init.d/firewall restart || {
    printf "\033[1;31mFailed to restart firewall\033[0m\n"
    printf "\033[1;31mTo remove AdGuard Home, run:\033[0m\n"
    printf "\033[1;31msh %s/standalone-blocker-adguardhome.sh remove_adguardhome auto\033[0m\n" "$TMP"
    exit 1
  }
  
  printf "\033[1;32mFirewall configuration completed\033[0m\n"
}

remove_adguardhome() {
  local auto_confirm="$1"

  printf "\033[1;34mRemoving AdGuard Home\033[0m\n"

  if /etc/AdGuardHome/AdGuardHome --version >/dev/null 2>&1; then
    INSTALL_TYPE="official"; AGH="AdGuardHome"
  elif /usr/bin/AdGuardHome --version >/dev/null 2>&1; then
    INSTALL_TYPE="openwrt"; AGH="adguardhome"
  else
    printf "\033[1;31mAdGuard Home not found\033[0m\n"
    return 1
  fi

  printf "Found AdGuard Home (%s version)\n" "$INSTALL_TYPE"
  
  if [ "$auto_confirm" != "auto" ]; then
    printf "Do you want to remove it? (y/N): "
    read -r confirm
    case "$confirm" in
      [yY]*) ;;
      *) printf "\033[1;33mCancelled\033[0m\n"; return 0 ;;
    esac
  else
    printf "\033[1;33mAuto-removing due to installation error\033[0m\n"
  fi
  
  /etc/init.d/"${AGH}" stop    2>/dev/null || true
  /etc/init.d/"${AGH}" disable 2>/dev/null || true

  if [ "$INSTALL_TYPE" = "official" ]; then
    "/etc/${AGH}/${AGH}" -s uninstall 2>/dev/null || true
  else
    if command -v apk >/dev/null 2>&1; then
      apk del "$AGH" 2>/dev/null || true
    else
      opkg remove --verbosity=0 "$AGH" 2>/dev/null || true
    fi
  fi

  if [ -d "/etc/${AGH}" ] || [ -f "/etc/adguardhome.yaml" ]; then
    if [ "$auto_confirm" != "auto" ]; then
      printf "Do you want to delete the AdGuard Home configuration file(s)? (y/N): "
      read -r cfg
      case "$cfg" in
        [yY]*) rm -rf "/etc/${AGH}" /etc/adguardhome.yaml ;;
      esac
    else
      rm -rf "/etc/${AGH}" /etc/adguardhome.yaml
    fi
  fi

  for cfg in network dhcp firewall; do
    bak="/etc/config/${cfg}.adguard.bak"
    if [ -f "$bak" ]; then
      printf "\033[1;34mRestoring %s configuration\033[0m\n" "$cfg"
      cp "$bak" "/etc/config/${cfg}"
      rm -f "$bak"
    fi
  done

  uci commit network
  uci commit dhcp
  uci commit firewall

  /etc/init.d/dnsmasq restart  || { printf "\033[1;31mFailed to restart dnsmasq\033[0m\n"; exit 1; }
  /etc/init.d/odhcpd restart   || { printf "\033[1;31mFailed to restart odhcpd\033[0m\n"; exit 1; }
  /etc/init.d/firewall restart || { printf "\033[1;31mFailed to restart firewall\033[0m\n"; exit 1; }

  printf "\033[1;32mAdGuard Home has been removed successfully.\033[0m\n"

  if [ "$auto_confirm" != "auto" ]; then
    printf "\033[33mPress any key to reboot.\033[0m\n"
    read -r -n1 -s
    reboot
  else
    printf "\033[1;33mAuto-rebooting\033[0m\n"
    reboot
  fi

  exit 0
}

get_access() {
  local cfg port addr
  if [ -f "/etc/AdGuardHome/AdGuardHome.yaml" ]; then
    cfg="/etc/AdGuardHome/AdGuardHome.yaml"
  elif [ -f "/etc/adguardhome.yaml" ]; then
    cfg="/etc/adguardhome.yaml"
  fi
  if [ -n "$cfg" ]; then
    addr=$(awk '
      $1=="http:" {flag=1; next}
      flag && /^[[:space:]]*address:/ {print $2; exit}
    ' "$cfg" 2>/dev/null)
    port="${addr##*:}"
    [ -z "$port" ] && port=
  fi
  [ -z "$port" ] && port=3000

  if command -v qrencode >/dev/null 2>&1; then
    printf "\033[1;32mWeb interface IPv4: http://${NET_ADDR}:${port}/\033[0m\n"
    printf "http://${NET_ADDR}:${port}/\n" | qrencode -t UTF8 -v 3
    if [ -n "$NET_ADDR6_LIST" ]; then
      set -- $NET_ADDR6_LIST
      printf "\033[1;32mWeb interface IPv6: http://[%s]:%s/\033[0m\n" "$1" "$port"
      printf "http://[%s]:%s/\n" "$1" "$port" | qrencode -t UTF8 -v 3
    fi
  else
    printf "\033[1;32mWeb interface IPv4: http://${NET_ADDR}:${port}/\033[0m\n"
    if [ -n "$NET_ADDR6_LIST" ]; then
      set -- $NET_ADDR6_LIST
      printf "\033[1;32mWeb interface IPv6: http://[%s]:%s/\033[0m\n" "$1" "$port"
    fi
  fi
}

adguardhome_main() {
  check_system
  install_prompt "$@"
  install_cacertificates
  install_"$INSTALL_MODE"
  get_iface_addrs
  common_config
  common_config_firewall
  printf "\n\033[1;32mAdGuard Home installation and configuration completed successfully.\033[0m\n\n"
  get_access
}

adguardhome_main "$@"
EOF
chmod +x "$TMP"/standalone-blocker-adguardhome.sh

sh "$TMP"/standalone-blocker-adguardhome.sh



  • DNS BIND MODE
# Set default bind mode: "loopback" or "gua"
DNS_BIND_MODE="${DNS_BIND_MODE:-loopback}"

common_config() {
  # Option | DNS_BIND_MODE
  #
  # By setting DNS_BIND_MODE, you can switch dnsmasq’s upstream DNS between
  # “loopback” mode (127.0.0.1#53 / ::1#53) and
  # “gua” mode (router’s global IP#53).
  #
  # ・loopback (default)
  #   – dnsmasq → always resolves via local loopback to AdGuard Home
  #   – Ideal for environments where AdGuard Home is bound to localhost only
  #
  # ・gua
  #   – dnsmasq → resolves via the router’s global IPv4/IPv6 addresses
  #   – Useful for MAP-E/DS-Lite/WireGuard tunnels or
  #     direct DNS access from external clients
  #
  # Usage:
  #   export DNS_BIND_MODE="${DNS_BIND_MODE:-loopback}"

  printf "\033[1;34mBacking up configuration files\033[0m\n"
  cp /etc/config/network  /etc/config/network.adguard.bak
  cp /etc/config/dhcp     /etc/config/dhcp.adguard.bak
  cp /etc/config/firewall /etc/config/firewall.adguard.bak

  [ "$INSTALL_MODE" = "official" ] && {
    /etc/AdGuardHome/AdGuardHome -s install >/dev/null 2>&1 || {
      printf "\033[1;31mInitialization failed. Check AdGuardHome.yaml and port availability.\033[0m\n"
      exit 1
    }
  }

  chmod 700 /etc/"$SERVICE_NAME"
  /etc/init.d/"$SERVICE_NAME" enable
  /etc/init.d/"$SERVICE_NAME" start

  uci set dhcp.@dnsmasq[0].noresolv="1"
  uci set dhcp.@dnsmasq[0].cachesize="0"
  uci set dhcp.@dnsmasq[0].rebind_protection='0'

  # uci set dhcp.@dnsmasq[0].rebind_protection='1'  # keep enabled
  # uci set dhcp.@dnsmasq[0].rebind_localhost='1'   # protect localhost
  # uci add_list dhcp.@dnsmasq[0].rebind_domain='lan'  # allow internal domain

  uci set dhcp.@dnsmasq[0].port="54"
  uci set dhcp.@dnsmasq[0].domain="lan"
  uci set dhcp.@dnsmasq[0].local="/lan/"
  uci set dhcp.@dnsmasq[0].expandhosts="1"

  uci -q del dhcp.@dnsmasq[0].server || true
  if [ "$DNS_BIND_MODE" = "gua" ]; then
    uci add_list dhcp.@dnsmasq[0].server="${NET_ADDR}#53"
    for ip6 in $NET_ADDR6_LIST; do
      uci add_list dhcp.@dnsmasq[0].server="${ip6}#53"
    done
  else
    uci add_list dhcp.@dnsmasq[0].server='127.0.0.1#53'
    uci add_list dhcp.@dnsmasq[0].server='::1#53'
  fi

  uci -q del dhcp.lan.dhcp_option || true
  uci add_list dhcp.lan.dhcp_option="6,${NET_ADDR}"

  uci set dhcp.@dnsmasq[0].dns="::"
  uci -q del dhcp.lan.dhcp_option6 || true
  if [ -n "$NET_ADDR6_LIST" ]; then
    for ip6 in $NET_ADDR6_LIST; do
      uci add_list dhcp.lan.dhcp_option6="option6:dns=[${ip6}]"
    done
  fi

  uci commit dhcp

  /etc/init.d/dnsmasq restart || {
    printf "\033[1;31mFailed to restart dnsmasq\033[0m\n"
    printf "\033[1;31mCritical error: Auto-removing AdGuard Home and rebooting in 10 seconds (Ctrl+C to cancel)\033[0m\n"
    sleep 10
    remove_adguardhome "auto"
    reboot
    exit 1
  }

  /etc/init.d/odhcpd restart || {
    printf "\033[1;31mFailed to restart odhcpd\033[0m\n"
    printf "\033[1;31mCritical error: Auto-removing AdGuard Home and rebooting in 10 seconds (Ctrl+C to cancel)\033[0m\n"
    sleep 10
    remove_adguardhome "auto"
    reboot
    exit 1
  }

  printf "\033[1;32mRouter IPv4: %s\033[0m\n" "$NET_ADDR"

  if [ -z "$NET_ADDR6_LIST" ]; then
    printf "\033[1;33mRouter IPv6: none found\033[0m\n"
  else
    printf "Router IPv6: %s\n" "$NET_ADDR6_LIST"
  fi
}

Webmin

インストール
  • webminインストール
#!/bin/sh
set -e

## 1. 変数定義
INSTALL_DIR=/usr/libexec/webmin        # Webmin 本体設置先
CONFIG_DIR=/etc/webmin                # 設定ディレクトリ
LOG_DIR=/lib/webmin                   # ログ出力先
PID_DIR=/var/run/webmin               # pid 保存先
USER=admin                            # 管理ユーザー名
PASS=$(tr -dc A-Za-z0-9 </dev/urandom | head -c12)   # ランダムパスワード
PORT=10000                            # リッスンポート

## 2. 依存パッケージのインストール(SSL 有効化に perl-net-ssleay)
opkg update
BASE_PKGS="
  tar gzip unzip shared-mime-info openssl-util
  coreutils coreutils-install coreutils-stty coreutils-df
  make gawk fdisk patch diffutils binutils-objdump
  perl perlbase perlbase-bignum perlbase-commons perlbase-time
  perl-test-warn perl-test-harness
"
PERLBASE_PKGS="$(opkg list | awk '/^perlbase-/{print $1}')"
opkg install $BASE_PKGS $PERLBASE_PKGS --force-overwrite
echo "依存パッケージのインストールが完了しました。"

## 3. グループ「bin」の確認/追加
grep -q '^bin:' /etc/group || echo 'bin:x:2:' >> /etc/group

## 4. ディレクトリ作成
mkdir -p ${INSTALL_DIR} ${CONFIG_DIR} ${LOG_DIR} ${PID_DIR}

## 5. Webmin ダウンロード&展開
wget --no-check-certificate -O /tmp/webmin.tar.gz https://www.webmin.com/download/webmin-current.tar.gz
tar xzf /tmp/webmin.tar.gz -C ${INSTALL_DIR} --strip-components=1

## 6. setup.sh の無人化パッチ
#  atboot=1 → 0(xinetd 非依存化)
#  makeboot=1 行削除
sed -i -e 's/^atboot=1/atboot=0/' -e '/^makeboot=1/d' ${INSTALL_DIR}/setup.sh

## 7. setup.sh を非対話で実行
perl_bin=$(which perl || echo /usr/bin/perl)
os_name="Generic Linux"
os_ver=$(uname -r)
/usr/bin/env bash <<EOF | ${INSTALL_DIR}/setup.sh ${INSTALL_DIR}

${LOG_DIR}
${perl_bin}
${os_name}
${os_ver}
${PORT}
${USER}
${PASS}
${PASS}
n
EOF

## 8. SSL 無効化(SSLeay ライブラリなしでも動くように)
sed -i -e 's/^ssl=1/ssl=0/' ${CONFIG_DIR}/miniserv.conf

## 9. procd/rc.common ベースの init スクリプト作成
cat << 'EOI' > /etc/init.d/webmin
#!/bin/sh /etc/rc.common
START=90
STOP=10
USE_PROCD=1

PIDFILE=/var/run/webmin/miniserv.pid
COMMAND=/etc/webmin/.start-init

start_service() {
    procd_open_instance
    procd_set_param command ${COMMAND}
    procd_set_param pidfile ${PIDFILE}
    procd_set_param respawn
    procd_close_instance
}

stop_service() {
    /etc/webmin/.stop-init
}

reload_service() {
    /etc/webmin/.reload-init
}
EOI

chmod +x /etc/init.d/webmin

## 10. サービス有効化&起動
/etc/init.d/webmin enable
/etc/init.d/webmin start

## 完了メッセージ
echo "-------------------------------------------------"
echo "Webmin Installed!"
echo "URL:   http://$(ip addr show dev br-lan | grep -Po 'inet \K[\d.]+'):${PORT}/"
echo "User:  ${USER}"
echo "Pass:  ${PASS}"
echo "Log:   ${LOG_DIR}/miniserv.log"
echo "-------------------------------------------------"


  • リムーブ
service webmin stop
rm -rf /etc/init.d/webmin
rm -rf /etc/init.d/webmin.old
sed -i "/bin:x:2:/d" /etc/group
sed -i "/webmin/d" /etc/services
/etc/webmin/uninstall.sh

テスト

iperf3

インストール
#!/bin/sh

TMP=/tmp/aios
mkdir -p "$TMP"
cat > "$TMP"/iperf3_setup.sh << 'SCRIPT_END'
#!/bin/sh

# Usage: ./iperf3_setup.sh [start|stop|restart|enable|disable|status|interactive]

SERVICE_FILE="/etc/init.d/iperf3"
PIDFILE="/var/run/iperf3.pid"
LAN="br-lan"

RED='\033[1;31m'
GREEN='\033[1;32m'
YELLOW='\033[1;33m'
BLUE='\033[1;34m'
CYAN='\033[1;36m'
NC='\033[0m'

print_msg() { printf "${1}${2}${NC}\n"; }

get_iface_addrs() {
    local flag=0
    if ip -4 -o addr show dev "$LAN" scope global 2>/dev/null | grep -q 'inet '; then
        NET_ADDR=$(ip -4 -o addr show dev "$LAN" scope global | awk 'NR==1{sub(/\/.*/,"",$4); print $4}')
        flag=$((flag | 1))
    else
        printf "\033[1;33mWarning: No IPv4 address on %s\033[0m\n" "$LAN"
    fi
    
    if ip -6 -o addr show dev "$LAN" scope global 2>/dev/null | grep -q 'inet6 '; then
        NET_ADDR6_LIST=$(ip -6 -o addr show dev "$LAN" scope global | grep -v temporary | awk 'match($4,/^(2|fd|fc)/){sub(/\/.*/,"",$4); print $4;}')
        flag=$((flag | 2))
    else
        printf "\033[1;33mWarning: No IPv6 address on %s\033[0m\n" "$LAN"
    fi
    
    case $flag in
        3) FAMILY_TYPE=any ;;
        1) FAMILY_TYPE=ipv4 ;;
        2) FAMILY_TYPE=ipv6 ;;
        *) FAMILY_TYPE="" ;;
    esac
}

get_lan_ip() {
    get_iface_addrs
    if [ -n "$NET_ADDR" ]; then
        printf "${NET_ADDR}"
    else
        printf "192.168.1.1"
    fi
}

check_package() {
    command -v iperf3 >/dev/null 2>&1 || {
        command -v apk >/dev/null 2>&1 && apk info iperf3 >/dev/null 2>&1
    } || {
        command -v opkg >/dev/null 2>&1 && opkg list-installed | grep -q "^iperf3 "
    }
}

check_service() { [ -f "$SERVICE_FILE" ]; }

install_package(){
  PKGS="iperf3"
  print_msg "$BLUE" "Installing $PKGS package"
  command -v opkg >/dev/null 2>&1 && opkg update >/dev/null 2>&1 && opkg install $PKGS && return 0
  command -v apk  >/dev/null 2>&1 && apk update >/dev/null 2>&1 && apk add $PKGS && return 0
  print_msg "$RED" "No supported package manager found (opkg/apk)"
  return 1
}

create_service() {
    local ip
    ip=$(get_lan_ip)
    print_msg "$BLUE" "Creating iperf3 service for $ip:5201"
    
    cat > "$SERVICE_FILE" << 'SERVICEEOF'
#!/bin/sh /etc/rc.common
START=95
STOP=01
USE_PROCD=1
PROG=/usr/bin/iperf3
pidfile=/var/run/iperf3.pid

start_service() {
    local ip
    ip=$(ip -4 -o addr show dev br-lan scope global 2>/dev/null | awk 'NR==1{sub(/\/.*/,"",$4); print $4}')
    [ -z "$ip" ] && ip="192.168.1.1"
    
    printf "Starting iperf3 server on %s:5201\n" "$ip"
    procd_open_instance
    procd_set_param command "$PROG" --server --daemon --pidfile "$pidfile" --bind "$ip"
    procd_set_param pidfile "$pidfile"
    procd_set_param respawn
    procd_close_instance
}

stop_service() {
    printf "Stopping iperf3 server\n"
    [ -f "$pidfile" ] && {
        pid=$(cat "$pidfile" 2>/dev/null)
        [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null && {
            kill "$pid" 2>/dev/null && printf "iperf3 server stopped\n" || {
                printf "Failed to stop iperf3 server (PID: %s)\n" "$pid"; return 1
            }
        }
        rm -f "$pidfile"
    }
}

status() {
    local ip pid
    ip=$(ip -4 -o addr show dev br-lan scope global 2>/dev/null | awk 'NR==1{sub(/\/.*/,"",$4); print $4}')
    [ -z "$ip" ] && ip="192.168.1.1"
    
    [ -f "$pidfile" ] && {
        pid=$(cat "$pidfile" 2>/dev/null)
        [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null && {
            printf "iperf3 server is running (PID: %s) on %s:5201\n" "$pid" "$ip"; return 0
        }
        printf "iperf3 server is not running (stale pidfile)\n"; rm -f "$pidfile"; return 1
    } || { printf "iperf3 server is not running\n"; return 1; }
}
SERVICEEOF
    
    chmod +x "$SERVICE_FILE"
    print_msg "$GREEN" "Service configured for $ip:5201"
}

install_iperf3() {
  print_msg "$BLUE" "Installing iperf3"
  command -v iperf3 >/dev/null 2>&1 || install_package iperf3 || { print_msg "$RED" "Package installation failed"; return 1; }
  [ -f "$SERVICE_FILE" ]   || create_service  || { print_msg "$RED" "Service creation failed";  return 1; }
  print_msg "$GREEN" "Installation completed"
  printf "${CYAN}service iperf3 [enable|start|stop|status]${NC}\n"
  printf "${CYAN}Test: iperf3 -c $(get_lan_ip) -t 10${NC}\n"
}

remove_iperf3() {
    local auto="$1"
    print_msg "$BLUE" "Removing iperf3"
    
    local pkg=false svc=false
    check_package && pkg=true
    check_service && svc=true
    
    [ "$pkg" = "false" ] && [ "$svc" = "false" ] && { print_msg "$RED" "iperf3 not found"; return 1; }
    
    [ "$auto" != "auto" ] && {
        printf "Remove iperf3? (y/N): "
        read -r confirm
        case "$confirm" in 
            [yY]*) ;; 
            *) print_msg "$YELLOW" "Cancelled"; return 0;; 
        esac
    }
    
    [ "$svc" = "true" ] && {
        print_msg "$BLUE" "Removing service"
        service iperf3 stop 2>/dev/null || true
        service iperf3 disable 2>/dev/null || true
        rm -f "$SERVICE_FILE" "$PIDFILE"
        print_msg "$GREEN" "Service removed"
    }
    
    [ "$pkg" = "true" ] && {
        print_msg "$BLUE" "Removing package"
        if command -v apk >/dev/null 2>&1; then
            apk del iperf3 && print_msg "$GREEN" "Package removed" || { print_msg "$RED" "Remove failed"; return 1; }
        elif command -v opkg >/dev/null 2>&1; then
            opkg remove iperf3 && print_msg "$GREEN" "Package removed" || { print_msg "$RED" "Remove failed"; return 1; }
        else
            print_msg "$YELLOW" "Cannot remove package - no package manager"
        fi
    }
    
    print_msg "$GREEN" "Removal completed"
}

show_usage() {
    printf "Usage: %s [command]\n\n" "$0"
    printf "Commands:\n"
    printf "  start       - Start iperf3 service\n"
    printf "  stop        - Stop iperf3 service\n"
    printf "  restart     - Restart iperf3 service\n"
    printf "  enable      - Enable service at boot\n"
    printf "  disable     - Disable service at boot\n"
    printf "  status      - Show service status\n"
    printf "  interactive - Interactive mode (default)\n"
    printf "  help        - Show this help\n\n"
    printf "Note: Install/Uninstall is determined automatically\n"
}

interactive_mode() {
    while true; do
        printf "\n${BLUE}iperf3 Management${NC}\n"

        check_package && pkg=true || pkg=false
        check_service && svc=true || svc=false
        if [ "$svc" = "true" ]; then
            service iperf3 status >/dev/null 2>&1 && running=true || running=false
        else
            running=false
        fi

        if [ "$pkg" = "false" ] || [ "$svc" = "false" ]; then
            printf "[1] Install iperf3\n"
            printf "[2] Exit\n"
            printf "Please select (1-2): "
            read -r choice

            case "$choice" in
                1)
                    install_iperf3
                    ;;
                2)
                    print_msg "$GREEN" "Goodbye"
                    break
                    ;;
                *)
                    printf "${RED}Invalid selection: %s${NC}\n" "$choice"
                    ;;
            esac

        else
            printf "[1] Start service     [5] Disable service\n"
            printf "[2] Stop service      [6] Show status\n"
            printf "[3] Restart service   [7] Remove iperf3\n"
            printf "[4] Enable service    [8] Exit\n"
            printf "Please select (1-8): "
            read -r choice

            case "$choice" in
                1) service iperf3 start;;
                2) service iperf3 stop;;
                3) service iperf3 restart;;
                4)
                    service iperf3 enable
                    print_msg "$GREEN" "Enabled at boot"
                    ;;
                5)
                    service iperf3 disable
                    print_msg "$GREEN" "Disabled at boot"
                    ;;
                6)
                    service iperf3 status
                    ;;
                7)
                    remove_iperf3
                    ;;
                8)
                    print_msg "$GREEN" "Goodbye"
                    break
                    ;;
                *)
                    printf "${RED}Invalid selection: %s${NC}\n" "$choice"
                    ;;
            esac
        fi

        if [ "$choice" != "2" ] && [ "$choice" != "8" ]; then
            printf "\n${CYAN}Press Enter to continue${NC}"
            read -r dummy 2>/dev/null || true
        fi
    done
}

iperf3_main() {
    case "${1:-interactive}" in
        start|stop|restart)
            check_service || {
                print_msg "$RED" "Service not configured. Run script to install."
                exit 1
            }
            service iperf3 "$1"
            ;;

        enable|disable)
            check_service || {
                print_msg "$RED" "Service not configured. Run script to install."
                exit 1
            }
            service iperf3 "$1"
            print_msg "$GREEN" "Service ${1}d at boot"
            ;;

        status)
            check_service || {
                print_msg "$RED" "Service not configured"
                exit 1
            }
            service iperf3 status
            printf "\n${CYAN}Server: $(get_lan_ip):5201${NC}\n"
            printf "${CYAN}Test: iperf3 -c $(get_lan_ip) -t 10${NC}\n"
            ;;

        interactive)
            interactive_mode
            ;;

        auto-toggle)
            if check_package || check_service; then
                remove_iperf3
            else
                install_iperf3
            fi
            ;;

        help|-h|--help)
            show_usage
            ;;

        *)
            print_msg "$RED" "Unknown command: $1"
            show_usage
            exit 1
            ;;
    esac
}

[ "${0##*/}" = "iperf3_setup.sh" ] && iperf3_main "$@"
SCRIPT_END

chmod +x "$TMP"/iperf3_setup.sh
printf "\n${GREEN}=== iperf3 Setup Script Created ===${NC}\n"
printf "${CYAN}Location: $TMP/iperf3_setup.sh${NC}\n"
printf "${YELLOW}Usage: $TMP/iperf3_setup.sh [start|stop|status|help]${NC}\n"
printf "${YELLOW}Interactive: $TMP/iperf3_setup.sh${NC}\n"

printf "\n${BLUE}Running script in interactive mode${NC}\n"
sh "$TMP"/iperf3_setup.sh
  • サーバー起動 (2回目以降)
/etc/init.d/iperf3 start
  • クライアントで実行
$iperf3 = Get-Command iperf3.exe -ErrorAction SilentlyContinue
if (-not $iperf3) {
    $iperf3 = Get-ChildItem -Path $PWD -Filter iperf3.exe -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1
    if ($iperf3) { $iperf3 = $iperf3.FullName }
    else {
        Write-Host "iperf3.exe が見つかりません。" -ForegroundColor Red
        exit 1
    }
} else {
    $iperf3 = $iperf3.Source
}

$server = (Get-DnsClientServerAddress -AddressFamily IPv4 | Select-Object -ExpandProperty ServerAddresses)[0]

& "$iperf3" -c $server -t 10
  • 追加テスト
& "$iperf3" -c $server -t 60 -P 16 -w 2M
  • リムーブ
sh /tmp/aios/iperf3_setup.sh auto-toggle

トラブル

既知の問題

2025年7月現在
  • Wi-FiはBEよりAXの方が安定的かつ速い
    ※AXは最速レベル (ただしノイズフロアは-80 dBmでちょっと非常用的)

初期化

ファクトリーリセット(初期化)

リセット
# 要注意
firstboot && reboot now

This will erase all settings and remove any installed packages. Are you sure? [N/y]

初期化:入力モード
y

デバイスリセットボタン
デバイスのリセットボタンを10秒押し続ける


あとがき

  • BPI-R3より、色々進化している

  • ハードウェアアクセレーターは有効

  • Wi-Fi7はまだまだ成熟していないが、逆にどんどん良くなるのが面白いかな

  • 購入について、AliExpressが一番安いが、アフターサービスを考えると、Amazonやエレファインの方が安心かもしれない

  • モデム動かすのに、丸1日掛かった。。。

  • Wi-Fi BEはまだ不安定だが、AXの完成度は高い
    ※BPI-R3より早いね (アップロードはWANチューニングの問題)
    ocn.png

  • BPI-BE1900 なんだか値が張りそうな気がするが

  • AdGuardHomeとWebminのメンテついでに掲載した

0
0
0

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?