OpenWrt 使用

[toc]

交叉编译工具链

musl 工具链 https://musl.cc/

ipv6

ipv6 需要开启防火墙 wan 的 546 udp 入站

如果需要配置多个隔离的 lan 口的话,IPv6 分配长度至少设置为运营商分配 PD 前缀 +4,同时每个 lan 口设置不同的 ipv6 前缀,这样子在 ipv6 层面才是划分为不同的子网,不然仅会有一个 lan 口能获取到 ipv6 地址

第三方源

src/gz dllkids https://op.dllkids.xyz/packages/aarch64_generic/
src/gz openwrt_kiddin9 https://dl.openwrt.ai/latest/packages/aarch64_generic/kiddin9

由于这两个源都没有切换到 apk, 25.12以后的版本可以看后文自行编译

魔法

HelloWorld 的包名叫 luci-app-ssr-plus

自行编译组件

安装编译环境

参考 https://openwrt.org/docs/guide-developer/toolchain/install-buildsystem

以 Ubuntu 为例

apt install build-essential git gawk rsync unzip \
    libncurses-dev swig python3-dev python3-setuptools \
    python3-pyelftools curl

下载 SDK

https://downloads.openwrt.org/releases/

我的是 25.12 的 nanopi-r5c 下载链接为 https://downloads.openwrt.org/releases/25.12.3/targets/rockchip/armv8/openwrt-sdk-25.12.3-rockchip-armv8_gcc-14.3.0_musl.Linux-x86_64.tar.zst

添加第三方软件

rm -rf package/helloworld
git clone --depth=1 https://github.com/fw876/helloworld.git package/helloworld

rm -rf package/openclash
git clone --depth=1 https://github.com/vernesong/OpenClash.git package/openclash

rm -rf package/passwall-packages
git clone --depth=1 https://github.com/Openwrt-Passwall/openwrt-passwall-packages.git package/passwall-packages

rm -rf package/passwall
git clone --depth=1 https://github.com/Openwrt-Passwall/openwrt-passwall.git package/passwall

rm -rf package/passwall2
git clone --depth=1 https://github.com/Openwrt-Passwall/openwrt-passwall2.git package/passwall2

编译

更新 feeds

./scripts/feeds update -a
./scripts/feeds install -a

配置

make menuconfig

Global Build Settings 取消勾选好几个 “Select all xxx”, 不然会勾选所有包,编译巨慢

LuCI > 3. Applications 中勾选并配置自己添加的包

编译

make defconfig # 自动勾选依赖
make package/luci-app-ssr-plus/compile package/luci-app-openclash/compile package/luci-app-passwall/compile package/luci-app-passwall2/compile -j $(nproc)

做成本地 apk 源

不用单独每个包签名,只签名索引文件也是可以正常使用 apk add 安装的

生成索引

方法1 使用构建脚本

make package/index

方法2 手动生成签名索引

适合把包全部拷贝在一起,手动生成一个完整的索引

for feed in base luci packages; do
  ./staging_dir/host/bin/apk mkndx --allow-untrusted --sign-key private-key.pem -o bin/packages/aarch64_generic/$feed/packages.adb bin/packages/aarch64_generic/$feed/*
done

部署

将公钥 public-key.pem 改名后放在 /etc/apk/keys 目录下,注意文件名别和已有的相同

将生成索引的目录拷贝到路由器上,我这里是放在 /data/packages 目录下了

添加到本地源

for feed in base luci packages; do
  echo "file://data/packages/$feed/packages.adb" >> /etc/apk/repositories.d/local.list
done

DDNS

开机自启

将 /etc/init.d/ddns 中 boot 函数删除,或者在 boot 中调用 start 函数

阿里云

获取 AccessKey

打开 RAM 访问控制 https://ram.console.aliyun.com/overview

点击左侧 用户 -> 创建用户

创建用户时,勾选 “OpenAPI 调用访问”,记住 AccessKey,一次性的,页面关闭后就找不到了,一定要记住

创建完成后授予如下两个权限

01

配置阿里云解析

https://github.com/renndong/ddns-scripts-aliyun

文件来自上述链接,也可以直接手动放到对应目录下

/usr/lib/ddns/update_aliyun_com.sh

#!/bin/sh
#
#.Distributed under the terms of the GNU General Public License (GPL) version 2.0
# 2023 ren dong <lml256@foxmail.com>
#
# Aliyun DNS Documentation at https://help.aliyun.com/document_detail/29742.html
#
# This script is parsed by dynamic_dns_functions.sh inside send_update() function
#
# using following options from /etc/config/ddns
# option username - AccessKeyID generated by Aliyun
# option password - AccessKeySecret generated by Aliyun
# option domain   - "hostname@yourdomain.TLD" or "@yourdomain.TLD"
#
# variable __IP already defined with the ip-address to use for update
#

# check parameters
[ -z "$CURL" ] && [ -z "$CURL_SSL" ] && write_log 14 "Communication require cURL with SSL support. Please install"
[ -z "$username" ] && write_log 14 "Service section not configured correctly! Missing key as 'username'"
[ -z "$password" ] && write_log 14 "Service section not configured correctly! Missing secret as 'password'"

. /usr/share/libubox/jshn.sh

local __RR __HOST __DOMAIN __TYPE

# split __RR __DOMAIN from $domain
__RR=$(printf '%s' "$domain" | cut -d@ -f1)
__DOMAIN=$(printf '%s' "$domain" | cut -d@ -f2)

[ -z "$__RR" ] && {
    __RR="@" && __HOST="$__DOMAIN"
} || __HOST="$__RR.$__DOMAIN"

# set record type
[ "$use_ipv6" -eq 0 ] && __TYPE="A" || __TYPE="AAAA"

# set API URL base
__URLBASE="https://alidns.aliyuncs.com/?"

# encode params using RFC3986 rule
encode_url_component() {
    local __STR1 __STR2 __INDEX
    __STR1=$(printf -- '%s' "$1" | $CURL -Gso /dev/null -w '%{url_effective}' --data-urlencode @- "aliyun.com" | cut -d "?" -f 2)
    __STR2=""
    __INDEX=0
    # convert the two hex numbers after '%' to uppercase
    # we need uppercase hex, and use the above code is enough on other linux platform. but
    # the curl of openwrt is a little bit different to other versions, i dont know why
    while [ "$__INDEX" -lt ${#__STR1} ]; do
        if [ "${__STR1:$__INDEX:1}" = "%" ]; then
            __STR2="$__STR2$(printf -- '%s' "${__STR1:$__INDEX:3}" | tr [a-z] [A-Z])" && __INDEX=$((__INDEX + 3))
        else
            __STR2="$__STR2${__STR1:$__INDEX:1}" && __INDEX=$((__INDEX + 1))
        fi
    done
    printf -- '%s' "$__STR2"
}

do_request() {
    local __COMMON_PARAMS __CANONICALIZED_QUERY_STRING __STRING_TO_SIGN __SIGNATURE
    local __PROGRAM __HTTP_CODE __ERR
    __COMMON_PARAMS="Format=JSON
                  Version=2015-01-09
                  AccessKeyId=$username
                  SignatureMethod=HMAC-SHA1
                  SignatureVersion=1.0
                  Timestamp=$(encode_url_component "$(date -u +"%Y-%m-%dT%H:%M:%SZ")")
                  SignatureNonce=$(head /dev/urandom | tr -dc '0123456789' | head -c16)"

    # build canonicalized query string, notice we use ascii order when sorting
    __CANONICALIZED_QUERY_STRING="$(printf -- '%s' "$__COMMON_PARAMS $*" | sed 's/\s\+/\n/g' | LC_COLLATE=C sort | xargs | sed 's/\s/\&/g')"

    # calculate signature
    __STRING_TO_SIGN="GET&$(encode_url_component "/")&$(encode_url_component "$__CANONICALIZED_QUERY_STRING")"
    __SIGNATURE="$(printf -- '%s' "$__STRING_TO_SIGN" | openssl sha1 -binary -hmac "$password&" | openssl base64)"
    __SIGNATURE="Signature=$(encode_url_component "$__SIGNATURE")"

    __PROGRAM="$CURL -sSL -o $DATFILE --stderr $ERRFILE -w '%{http_code}' \"$__URLBASE$__CANONICALIZED_QUERY_STRING&$__SIGNATURE\""

    write_log 7 "Run command #> $__PROGRAM"
    __HTTP_CODE=$(eval "$__PROGRAM")
    __ERR=$?
    [ "$__ERR" -eq 0 ] && [ "$__HTTP_CODE" -eq 200 ] || {
        write_log 3 "Run command got error, curl err: $__ERR, http_code: $__HTTP_CODE"
        write_log 7 "DATFILE: $(cat "$DATFILE") ERRFILE $(cat "$ERRFILE")"
        return 1
    }
}

do_request "Action=DescribeSubDomainRecords" \
    "DomainName=$(encode_url_component "$__DOMAIN")" \
    "Type=$__TYPE" \
    "SubDomain=$(encode_url_component "$__HOST")" || return 1

# load record id and record value from the response
json_load_file "$DATFILE"
json_get_var __RECORD_COUNT TotalCount

# if no record found, report error
[ "$__RECORD_COUNT" -eq 0 ] && {
    write_log 7 "DNS record of $__HOST is not exist."
    return 1
}

# if multiple records are found, only use the first one
[ "$__RECORD_COUNT" -gt 1 ] && {
    write_log 4 "WARNING: found multiple records of $__HOST, only use the first one"
}

# select the first DNS record
json_select DomainRecords
json_select Record
json_select 1
# get the record id of the first DNS record
json_get_var __RECORD_ID RecordId
json_get_var __RECORD_VALUE Value

# dont update if the ip has not changed
[ "$__RECORD_VALUE" = "$__IP" ] && {
    write_log 7 "DNS record is up to date"
    return 0
}

do_request "Action=UpdateDomainRecord" \
    "RR=$(encode_url_component "$__RR")" \
    "RecordId=$__RECORD_ID" \
    "Type=$__TYPE" \
    "Value=$(encode_url_component "$__IP")" || return 1

return 0

/usr/share/ddns/default/aliyun.com.json

{
    "name": "aliyun.com",
    "ipv4": {
        "url": "update_aliyun_com.sh"
    },
    "ipv6": {
        "url": "update_aliyun_com.sh"
    }
}

Docker

Config

OpenWrt docker 默认用的配置文件是 /tmp/dockerd/daemon.json,/tmp 目录是在内存中,修改重启路由器后就失效

该配置文件是 /etc/init.d/dockerd 根据 /etc/config/dockerd 自动生成的,需要关闭 iptables,不然映射的端口局域网内也无法访问

config globals 'globals'
        option data_root '/opt/docker/'
        option log_level 'warn'
        option iptables '0'
        option auto_start '1'
        list registry_mirrors 'https://mirror.xxxx.com'

Storage Driver

https://docs.docker.com/storage/storagedriver/select-storage-driver/

默认 Storage Driver 是 vfs,这玩意有个大坑,每一层镜像都会全量保存,而不是增量保存,就会特别占用磁盘空间,在 /etc/init.d/dockerd 里面看了一下读取配置的代码,并不能设置 Storage Driver,我的做法是直接在 /etc/init.d/dockerd 内部启动 dockerd 的时候直接加参数 --storage-driver=fuse-overlayfs,这个需要安装 fuse-overlayfs

如果 docker 的目录是挂载的其他分区,如ext4,推荐使用 overlay2