Skip to content

OpenWrt系统使用

约 4430 字大约 15 分钟

R2SOpenWrt

2025-04-13

前言

闲言少叙,总而言之就是又折腾了 R2S,把之前用的原本 ImmortalWrt 换成了 YAOF 的 OpenWrt 系统,原因是集成的插件多,好用省心。按照惯例,记录下本次折腾过程中碰到的麻烦事。

1. 固件刷入

这里选择的固件版本是23.05.5-sfs,因为这是最后一个支持 docker 的版本。为什么选择这个,自己动手安装不香吗?不香,有点麻烦,因为这个固件用的源中的软件和 ImmortalWrt 用的相比有挺多没有的。当然可以替换源,或者动手去网上找各种luci-*-all.ipk的插件上传安装,但是说了麻烦,做减法比做加法容易,既然有集成好的用就是了,一味的追求系统版本没意义,只要用起来稳定,让人省心,那就是好的。

固件刷写注意事项:

  • 刷入前:进行扩容,保证给软件安装预留充足空间,不用太多,增加 1G 足以。扩容方式
  • 刷入中:跟以前一样;
  • 刷入后:启动进入系统后,不要启动 docker,磁盘分个区给 docker,这里我分了6G 空间足够用了。然后挂载分区到/opt/docker

未提及的操作照旧即可。

顺便提一下这个固件内置的软件源:

src/gz openwrt_core https://mirrors.pku.edu.cn/openwrt/releases/23.05.5/targets/rockchip/armv8/packages
src/gz openwrt_base https://mirrors.pku.edu.cn/openwrt/releases/23.05.5/packages/aarch64_generic/base
src/gz openwrt_luci https://mirrors.pku.edu.cn/openwrt/releases/23.05.5/packages/aarch64_generic/luci
src/gz openwrt_packages https://mirrors.pku.edu.cn/openwrt/releases/23.05.5/packages/aarch64_generic/packages
src/gz openwrt_routing https://mirrors.pku.edu.cn/openwrt/releases/23.05.5/packages/aarch64_generic/routing
src/gz openwrt_telephony https://mirrors.pku.edu.cn/openwrt/releases/23.05.5/packages/aarch64_generic/telephony

如果要替换为 ImmortalWrt 的软件源,只需要按照上述格式,把地址换成https://downloads.immortalwrt.org/。但是换源之后可能会有意想不到的问题,比如原固件的插件使用的依赖在原来的源中找得到,但是在新源可能找不到或者版本不可用。操作有风险,换源需谨慎。

Tip:sfs 的固件能够重置,当系统无法正常启动时,不要立马刷机,先尝试断电后插拔 TF 卡,然后重新上电长按reset键,之后观察sys指示灯的变化。当红灯快速闪烁表示正在重置中,慢闪表示系统正常启动中,长亮表示系统正常运行中。重置固件后虽然需要重新设置系统,但是 TF 卡上存储的文件不会丢失。

2. 安装插件

2.1 Tailscale

Tailscale的作用就不必多说了,内网穿透神器。与之齐名的还有Zerotier,为啥不用这个?因为这个设置起来费老鼻子劲了,很折腾,而且免费计划只支持最多3个网络➕10个设备,按说差不多够用了,但是感觉捉襟见肘。但Tailscale的安装就非常方便,并且免费计划支持3个用户➕100个设备,大气多了,用不完,根本用不完😄。

在安装插件前,先到软件包中安装iptables-nft,这个是必要的依赖。之后上luci-app-tailscale下载最新版就完事,记得下载的是luci-app-tailscale_*_all.ipk,然后上传安装即可,启动后会自动设置好上网接口以及防火墙。入口在 OpenWrt 系统的服务下面。

Tailscale的使用也很简单,登录了相同账号的设备就能互相访问。这里有个关键的地方,当安装了Tailscale的软路由为主路由模式时,在 OpenWrt 的 Tailscale 的高级设置下把暴露子网配置好,这样内网中的所有设备即使没有安装Tailscale也可以被访问到,在外网的设备只需要打开Tailscale就能像在内网中一样访问。

注意:每个设备登录后是有时效的,默认有效期 3 个月,可以在控制台调整。如果你想不失效,就在控制台选中设备后点击Disable key expiry

再就是,当你想要他人加入你的tailnet,有三种选择:

  1. 登录到 Tailscale 的控制台配置一个Auth keys,他人使用你这个 Auth keys 登录,即可将他的设备加入到你的tailnet中。优点是,不用泄露你的密码;缺点是,Auth keys 最大有效期为 90 天。
  2. 他人正常登录其账号,你邀请对方的账号到你的tailnet中。邀请成功后,需要对方重新登录其账号,此时就能看到有若干选项,要求其选择一个tailnet加入,那么选择你的即可。优点是,无有有效期限制;缺点是,占用一个用户的名额。
  3. 他人正常登录其账号,你在 Tailscale 的控制台选择一个设备分享给对方,对方接受要求后,你的设备就出现在了对方的tailnet中。

上面三种方式的不同点:

  • 第 1 种,是他的设备加入到你的网络中,会占用你的设备限额,默认可以访问你账号下的所有设备,包括暴露出的子网,你也可以访问他的设备,可以编写ACL控制权限。用的是你的账号;
  • 第 2 种,是他的设备加入到你的网络中,会占用你的设备限额,并且会占用你的用户限额,默认可以访问你账号下的所有设备,包括暴露出的子网,你也可以访问他的设备,可以编写ACL控制权限。用的是他的账号;
  • 第 3 种,是你的设备加入他的网络中,会占用他的设备限额,他只能访问你分享的这个设备,即使此设备下有子网他也不能访问,但你分享的这个设备并不能访问他的网络,由于他并不在你的网络中,所以你无需也不能编写ACL控制权限。用的是他账号;

熟悉以上区别,选择合适的分享策略。

注意:Tailscale 通过 IPv6 直连速度就比较快,能保证正常使用,但如果以 IPv4 连接,若当前没有公网且网络 NAT 层数过多,则无法直连,就会走中转服务器,你懂的,那就非常慢了,慢到无法使用。
所以,一种方式就是家庭内网拨号时获取 IPv6 地址,运营商一般会下发,如果没有那就没得玩,而手机通过蜂窝数据上网一般也都是有 IPv6 的,两端都是 IPv6 就可以直连;另一种方式就是自建 Derp 中继服务器,但那就需要有一台具有公网 IP 的服务器,一般是买云服务器,要钱的方案不在白嫖党的选择范围。

2.2 Navidrome

官网,这是一个可以提供音乐分享的服务,我们可以在软路由上运行它来搭建自己的音乐私服,配合内网穿透后就可以无限制听音乐了。
注意:这仅仅是一个可以提供音乐分享的服务,它本身是没有任何音乐的,所有的音乐内容还是得自己去搞定,一劳永逸。

下载插件luci-app-navidrome并上传安装,入口在 OpenWrt 系统的服务下面。
安装完成后先不要急着启动,去磁盘管理分一个区用来存放音乐。分多大看个人情况,我没有其他需求,把剩余所有空间都分出来了。分区完后进行挂载,挂载路径随意,我这里是/opt/navidrome
在启动插件之前,设置好插件的各种目录,并确保这些目录都存在,否则启动会失败,之后正常启动浏览器访问操作即可。

2.3 Samba

Samba 协议可以用来在设备之间共享文件,Windows 和 Linux 系统都支持,不过需要做一些设置才能启用。

Linux

  1. 安装 Samba

    在软件包中搜索下载shadow-useradd luci-app-samba4 luci-i18n-samba4-zh-cn ,若已安装则忽略。安装完成后,入口在服务->网络共享

  2. 创建 Samba 用户

    useradd samba # 添加名为 samba 的用户
    smbpasswd -a samba # 为用户 samba 创建 smb 服务的密码
    mkdir -p /opt/navidrome/music # 创建一个目录用于共享(已存在则忽略)
    # 使用户 samba 获得目录权限。这一步很重要,否则当使用 Samba 客户端连接时会提示:没有权限。
    chown -R samba:samba /opt/navidrome/music # 或者执行 chmod -R 777 /opt/navidrome/music
    
    # 如果创建的 samba 用户不想用了
    smbpasswd -x samba # 删除用户 samba 的密码
    userdel samba # 删除用户
    
    # 相关命令
    service samba4 start # 启动服务
    service samba4 stop # 停止服务
    service samba4 restart # 重启服务
    service samba4 status # 服务状态
    testparm -v # 配置文件检查
  3. 设置共享

    服务->网络共享常规设置下,接口选择lan允许旧协议可以选择性勾选,默认不用。
    共享目录下需要设置的选项:

    名称路径可浏览允许用户创建权限掩码目录权限掩码
    music/opt/navidrome/musicsamba06660777

    保存并应用,然后用命令重启 Samba 服务。

    注意:当使用 ios 或 macos 进行 samba 交互时,会导致上传使用,此时需要勾选启用 macOS 兼容共享就能正常上传了,但又会引发其他可能的问题,如在快捷指令中列出 samba 服务上的目录,就无数据返回,对此,我的解决方法是用下面 Alist 提供的接口请求即可。

Windows

打开控制面板,在程序和功能->启用或关闭 Windows 功能中,勾选SMB 1.0/CIFS 文件共享支持SMB 直通
启用完成后,按Win+E打开文件资源管理器,在地址栏输入\\192.168.0.1双击music目录后输入前面创建的账户/密码即可。

如此一来,就可能很方便的上传音乐了,搭配内网穿透,可以随时随地的操作。

2.4 AList

可以使用 AList 将 smb 转接为 webdav 服务,这是最简单、方便且功能强大的方式。在软件包下搜索luci-i18n-alist-zh-cn安装即可。之后的使用方式这里不多说,参考文档即可。
值得注意的是,AList 完全可以用过 api 的方式来操作,通过 http 的方式来请求,但默认情况下guest用户是禁用的,这就导致我们所有的 api 操作都需要鉴权。有的时候仅仅是查看目录,此时可以将guest用户启用即可,即使不给于任何权限,也是可以查看的。

2.5 SVN

有时候需要一个文件版本控制系统,即方便在多台主机上管理文件,也方便文件回溯。Git 协议的服务对于 R2S 来说可能比较占资源,不过 SVN 是非常轻量的。
系统->软件包中搜索subversion,可以看到三个包:subversion-clientsubversion-libssubversion-server。其中 libs 包是其他两个的依赖包,如果只是作为服务端使用,安装subversion-server即可,会自动安装subversion-libs依赖包。安装完成后安装如下步骤来初始化:

  1. 创建 SVN 仓库

    mkdir -p /srv/svn
    cd /srv/svn
    # 创建一个名为 repos 的仓库
    svnadmin create repos

    执行完后,目录结构类似:

    repos

    conf# 配置文件(如 passwd、authz)

    authz# 用户权限

    passwd# 账号密码

    svnserve.conf# 服务器配置

    db# 版本历史、文件内容、元数据都在这里!

    format

    hooks# 钩子脚本(如 post-commit)

    locks# 锁文件

    README.txt

  2. 配置 SVN 用户和权限

    修改repos/conf/svnserve.conf启用身份验证:

    svnserve.conf
    [general]
    anon-access = none
    auth-access = write
    password-db = passwd
    authz-db = authz
    realm = R2S Repos

    修改repos/conf/passwd,添加用户名密码:

    passwd
    [users]
    admin = admin
    b560m = 123456
    winmi = 123456

    修改repos/conf/authz,配置权限:

    authz
    [groups]
    # 定义了两个组(名称随意),为组赋予了成员
    admin = admin
    dev = b560m,winmi
    
    [/]
    # * 表示匿名,* = r 表示匿名可以读(r)到 / 下的仓库
    # 如果不想匿名看到所有就 * =  ,不给任何权限
    * = r
    @admin = rw
    
    # 因为仓库为 repos,所以一定要是 repos:<子路径>
    [repos:/sub-store]
    @dev = rw
    
    [repos:/proxy-server]
    @dev = rw
    
    [repos:/assist-tool]
    @dev = rw

    通过这种配置,可以在启动一个 svn 服务的情况下,管理多个子目录,每个子目录即表示一个代码库。

  3. 重启 SVN 服务

    修改/etc/config/subversion的内容:

    subversion
    config subversion
         option path     '/srv/svn'
         option port     '3690'

    注意,服务启动的路径在/srv/svn,实际创建的仓库应该在其子目录下,然后重启 SVN 服务service subversion restart

  4. 客户端连接

    注意,直接在服务器上的仓库下创建子目录是无效的,那不会被 svn 管理,正确的做法是由管理员先拉取整个仓库到本地,然后在本地创建好目录结构:

    svn-repos

    assist-tool

    trunk# 主干

    braches# 分支

    tags# 版本快照

    proxy-server

    sub-store

    # 管理员拉取整个仓库
    svn checkout svn://192.168.0.1/repos --username admin -- password admin
    # 开发者拉取子目录
    svn checkout svn://192.168.0.1/repos/sub-store --username winmi -- password 123456

    之后可以add/commit/update/log等常规操作了。

注意:上传到 SVN 的文件都在db目录中,但不是原样存储,而是被压缩、编码、分块成数据库形式。如你想导出完整版本库的内容为一个普通文件夹,可以用:

svn export svn://192.168.0.1/repos
# 或者使用 dump,可以导出整个仓库,包含提交记录。用于崩溃恢复或服务器迁移
svnadmin dump /srv/svn/repos > ~/svn-repos.dump
# 然后在恢复的服务器上创建仓库
svnadmin create repos
# 接着恢复 dump
svnadmin load /srv/svn/repos < ~/svn-repos.dump

SVN 与 Git 不同点:

  • svn 不像 git 那样通过一个.gitignore文件就可以忽略多个、多级的文件/目录,svn 只能手动设置忽略单个文件/目录,每次忽略其实是设置目录的属性,因此设置忽略的操作也是要提交的。
  • svn 不像 git 在本地删除了跟踪的文件后提交即可同步删除远程文件,svn 必须要使用svn delete命令删除文件然后提交才可以。

当使用 svn 管理时,删除了多个文件如何批量提交呢?有两种方式:

方法一:使用如下命令:

svn status | grep '^!' | sed 's/^! *//' | while IFS= read -r file; do svn delete "$file"; done
svn commit -m "批量删除被本地手动删除的文件"

方法二:创建脚本sync-svn-delete.sh以及 VSCode 的 task 文件.vscode/tasks.json实现点击运行。

sync-svn-delete.sh
#!/bin/bash
# 自动同步本地被手动删除的文件到 SVN 仓库

cd trunk

echo "📁 正在扫描本地被删除的文件..."

deleted_files=$(svn status | grep '^!' | sed 's/^! *//')

if [ -z "$deleted_files" ]; then
    echo "✅ 没有检测到需要同步删除的文件。"
    exit 0
fi

echo "$deleted_files" | while IFS= read -r file; do
    echo "➖ 删除:$file"
    svn delete "$file"
done

echo "🚀 提交到 SVN..."
svn commit -m "同步删除本地手动删除的文件"
tasjs.json
{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "同步 SVN 删除文件",
            "type": "shell",
            "command": "${workspaceFolder}/.vscode/sync-svn-delete.sh",
            "problemMatcher": [],
            "options": {
                "shell": {
                    "executable": "C:\\Program Files\\Git\\bin\\bash.exe"
                }
            },
            "presentation": {
                "reveal": "always"
            },
            "group": {
                "kind": "build",
                "isDefault": true
            }
        }
    ]
}

当使用图形化客户端,比如 TortoiseSVN 检出时,默认会缓存首次输入的用户和密码,如果想要切换用户则需要删除C:\Users\<用户名>\AppData\Roaming\Subversion\auth(Windows)或~/.subversion/auth(Linux/MacOS),在下次操作 SVN 时就会要求输入用户/密码。

如果是用命令行操作可以:

svn checkout svn://192.168.0.1/repos --username admin --password admin --no-auth-cache --non-interactive --quiet

-no-auth-cache表示不缓存密码,--non-interactive避免交互式输入密码,适合脚本,--quiet表示静默的不输出详情。

为了维护仓库代码的安全,应该有定时任务每天自动导出整个仓库代码,脚本如下:

点击查看代码
# -*- coding: utf-8 -*-
import sys
from pathlib import Path
import shutil
import subprocess
from datetime import datetime
import os
import re
import zipfile

TMP = '/tmp/svn-bak' # 临时文件目录
BAK = '/srv/share/代码库/svn' # 备份文件目录
LIMIT = 5  # 最大备份数量

def init():
    # 删除临时目录(避免 checkout 失败)
    if Path(TMP).exists():
        shutil.rmtree(TMP)

    # 创建目录,包括中间目录
    Path(TMP).mkdir(parents=True, exist_ok=True)
    Path(BAK).mkdir(parents=True, exist_ok=True)


def zip(source_dir, zip_path):
    source_dir = os.path.abspath(source_dir)  # 确保是绝对路径
    with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
        for root, _, files in os.walk(source_dir):
            for file in files:
                abs_file_path = os.path.join(root, file)
                rel_path = os.path.relpath(abs_file_path, os.path.dirname(source_dir))
                zipf.write(abs_file_path, arcname=rel_path)

def main():
    print(f"时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}|svn repos 仓库备份", file=sys.stderr)
    cmd = [
        "svn", "export", "svn://127.0.0.1/repos", f"{TMP}/repos",
        "--username", "admin",
        "--password", "admin",
        "--non-interactive",
        "--no-auth-cache",
        "--quiet"
    ]
    result = subprocess.run(cmd,capture_output=True,text=True)
    if result.returncode != 0:
        print('checkout failed: ' + result.stderr, file=sys.stderr)
        sys.exit(-1)
    # 压缩备份
    zip(f"{TMP}/repos",f"{BAK}/reposbak-{datetime.now().strftime('%Y%m%d%H%M%S')}.zip")
    pattern = re.compile(r"reposbak-\d{14}\.zip")
    files = [f for f in os.listdir(BAK) if pattern.fullmatch(f)]
    # 如果压缩包数量超过限制
    if len(files) > LIMIT:
        # 保留最近的时间
        files.sort(reverse=True)
        # 其余全部删除
        for f in files[LIMIT:]:
            os.remove(os.path.join(BAK, f))
    # 删掉临时目录(因为不需要了)
    shutil.rmtree(TMP)

# 0 2 * * * /usr/bin/python /usr/local/bin/svnrepo-backup.py >> /var/log/svnrepo-backup.log 2>&1
if __name__ == '__main__':
    init()
    main()