openwrt 作为一款基于 Linux 的嵌入式系统,很多功能特性其实与 Linux 一样,软件开发的难点在于解决交叉编译时出现的问题,为了新入门的开发人员更快的上手,特在此汇总一些最常用的开发说明,当然你也可以直接访问 openwrt 官方网站获取更全面的开发人员指南。openwrt Project: Developer guide
遵纪守法
虽然软件、网络等技术产物是虚拟的,但最终是作用于现实生活中,与大众的日常生活息息相关,所以必然要接受各种法律法规的管控,各类技术人员首先需要学习并牢记当地的法律法规,以免误入歧途,玩火自焚。
在中国工作和生活的技术人员请学习如下法律法规:
《中华人民共和国网络安全法》、《计算机信息网络国际联网安全保护管理办法》、《计算机软件保护条例》、《中华人民共和国计算机信息系统安全保护条例》
LUCI 界面
openwrt 的界面其实就是网页界面,默认是由 uhttpd 服务器承载,之所以叫做 LUCI ,因为这是使用 Lua 脚本编写的控制界面,全称 Lua Unified Configuration Interface,当然目前已经不再使用 Lua 脚本了,从 openwrt 19.07.4 版开始,界面已经切换为使用 JavaScript 脚本来编写,其拥有更便利的页面控件,页面自由度也大大提高,因为脚本交由客户端运行,页面流畅度自然也比 Lua 界面高出不少。
下面仅介绍 openwrt 的 JavaScript 脚本界面规范。
LUCI 网页界面的意义:各类程序的配置文件直接手动配置也是完全没问题的,但提供网页界面可以更直观的管理各项配置参数,并且可以通过界面对输入的参数进行一些限制,防止因手误输入错误的参数,具备一定程度的防呆功能,这对于复杂的各类软件功能是很有帮助的。
完整的界面文件结构
以源代码目录的文件结构为例,一个基本的界面程序应当具备如下所示的目录文件结构。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
<pre class="inline:true lang:bash class:language-bash decode:1 " >openwrt ┕feeds ┕luci ┕applications ┕luci-app-name # 界面程序的主目录 ┕htdocs ┊ ┕luci-static ┊ ┕resources ┊ ┕view ┊ ┕name.js # JavaScript 脚本界面文件。 ┕po ┊ ┕zh_Hans # 此目录名称对应简体中文。 ┊ ┕name.po # 界面语言翻译文件。 ┕root ┊ ┕etc ┊ ┊ ┕uci-defaults ┊ ┊ ┕luci-app-name # 软件安装完毕后,默认执行的脚本(一次性脚本),可选。 ┊ ┕usr ┊ ┕share ┊ ┕luci ┊ ┊ ┕menu.d ┊ ┊ ┕luci-app-name.json # 界面菜单,在系统菜单中的名称、顺序等。 ┊ ┕rpcd ┊ ┕acl.d ┊ ┕luci-app-name.json # 权限控制文件,管控界面能执行的各类操作。 ┕Makefile # 编译文件。 |
界面程序的 Makefile 编写指南
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
<pre class="inline:true lang:bash class:language-bash decode:1 " ># # Copyright (C) 2020 OpenWrt.org # # This is free software, licensed under the GNU General Public License v2. # See /LICENSE for more information. # 注释信息,可选。 # 所有的可选项,不需要时可以直接不写。 # 加载相关规则文件,必需。 include $(TOPDIR)/rules.mk # 在 OpenWrt 编译菜单中显示的标题,必需 LUCI_TITLE:=My package - LuCI interface # 依赖关系,可选 LUCI_DEPENDS:=+luci-mod-admin-full # 是否要限制硬件平台,可选 LUCI_PKGARCH:=all # 版本号,可选 PKG_VERSION:=1.0 # 修订版本号,可选 PKG_RELEASE:=1 # 标记日期,可选 PKG_DATE:=20201130 # 作者信息,可选 PKG_MAINTAINER:=OpenWrt-Life [email protected]> # 软件许可信息,可选 PKG_LICENSE:=Apache-2.0 # 加载相关规则文件,必需。 include ../../luci.mk # 下面一行是 Luci 界面专用调用标识,必需,如果缺失会导致不会被加入 OpenWrt 的编译菜单中。 # call BuildPackage - OpenWrt buildroot signature |
JavaScript 脚本界面
至于如何编写完整的 JavaScript 脚本界面,不在本指南范围内,你应该先去学会通用的 JavaScript 编写知识,这里只额外说明 openwrt 平台上特有的一些接口或注意事项。
以源代码中已有的 Samba4 的 JavaScript 脚本界面为例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 |
<pre class="inline:true lang:javascript class:language-javascript decode:1 " >'use strict'; //使用紧凑格式,编译时会自动压缩此脚本。 'require view'; 'require fs'; 'require form'; 'require tools.widgets as widgets'; //上面这些都是声明的接口调用,OpenWrt 下有效。 return view.extend({ load: function() { return Promise.all([ L.resolveDefault(fs.stat('/sbin/block'), null), //读取文件状态。 L.resolveDefault(fs.stat('/etc/config/fstab'), null), L.resolveDefault(fs.stat('/usr/sbin/nmbd'), {}), L.resolveDefault(fs.stat('/usr/sbin/samba'), {}), L.resolveDefault(fs.stat('/usr/sbin/winbindd'), {}), L.resolveDefault(fs.exec('/usr/sbin/smbd', ['-V']), null), //执行命令,获取版本号。 ]); }, render: function(stats) { var m, s, o, v; v = ''; m = new form.Map('samba4', _('Network Shares-samba4')); //关联配置文件/etc/config/samba4,括号内为页面标题名称。 if (stats[5] && stats[5].code === 0) { v = stats[5].stdout.trim(); } s = m.section(form.TypedSection, 'samba', 'Samba ' + v); //配置名为 samba 的子配置节点,此处也为页面说明,这里仅用于显示 samba 的版本号了。 s.anonymous = true; //隐藏配置文件中的 Section 节点名称。 s.tab('general', _('General Settings')); //子菜单,选项卡界面。 s.tab('template', _('Edit Template')); s.taboption('general', form.Flag, 'enable', _('Enable')); //复选框选项,此选项位于'general'子菜单下。 s.taboption('general', widgets.NetworkSelect, 'interface', _('Interface'), //这是菜单名称 _('Listen only on the given interface or, if unspecified, on lan'));//这是菜单注释,详细说明。 o = s.taboption('general', form.Value, 'workgroup', _('Workgroup')); o.placeholder = 'WORKGROUP'; //占位符,用于提示用户应该输入什么样的字符。 o = s.taboption('general', form.Value, 'description', _('Description')); o.placeholder = 'Samba4 on OpenWrt'; s.taboption('general', form.Flag, 'enable_extra_tuning', _('Enable extra Tuning'), _('Enable some community driven tuning parameters, that may improve write speeds and better operation via WiFi.')); s.taboption('general', form.Flag, 'allow_legacy_protocols', _('Allow legacy protocols'), _('Allow connection using smb(v1) protocol.')); s.taboption('general', form.Flag, 'disable_async_io', _('Force synchronous I/O'), _('On lower-end devices may increase speeds, by forceing synchronous I/O instead of the default asynchronous.')); s.taboption('general', form.Flag, 'macos', _('Enable macOS compatible shares'), _('Enables Apple's AAPL extension globally and adds macOS compatibility options to all shares.')); o = s.taboption('general', form.Value, 'nice', _('Scheduling priority'), _('Set the scheduling priority of the spawned process.')); o.datatype = 'range(-20,19)'; //限制此输入框的格式,只允许输入-20至19的数字。 o.default = '0'; //此选项的默认值。 o.rmempty = false; //是否允许为空值。false则表示否,不允许为空值。 if (stats[2].type === 'file') { s.taboption('general', form.Flag, 'disable_netbios', _('Disable Netbios')) } //符合判断条件才会显示出来的菜单。 if (stats[3].type === 'file') { s.taboption('general', form.Flag, 'disable_ad_dc', _('Disable Active Directory Domain Controller')) } if (stats[4].type === 'file') { s.taboption('general', form.Flag, 'disable_winbind', _('Disable Winbind')) } o = s.taboption('template', form.TextValue, '_tmpl', _(''), _("This is the content of the file '/etc/samba/smb.conf.template' from which your samba configuration will be generated. " + "Values enclosed by pipe symbols ('|') should not be changed. They get their values from the 'General Settings' tab.")); //这句话太长,不方便阅读,可以切断,用 + 号连接,这样程序仍然会认为这是一句话。 o.rows = 20; //行高 o.cfgvalue = function(section_id) { return fs.trimmed('/etc/samba/smb.conf.template'); }; //读取指定的文件。 o.write = function(section_id, formvalue) { return fs.write('/etc/samba/smb.conf.template', formvalue.trim().replace(/rn/g, 'n') + 'n'); }; //写入数据到指定的文件。 s = m.section(form.TableSection, 'sambashare', _('Shared Directories'), _('Please add directories to share. Each directory refers to a folder on a mounted device.'));//配置名为 sambashare 的子配置节点 s.anonymous = true; s.addremove = true; //允许添加或删除此配置节点 s.option(form.Value, 'name', _('Name')); o = s.option(form.Value, 'path', _('Path')); if (stats[0] && stats[1]) { o.titleref = L.url('admin', 'system', 'mounts'); } o = s.option(form.Flag, 'browseable', _('Browse-able')); o.enabled = 'yes'; //使复选框选项使用指定的参数,勾选则写入参数 yes o.disabled = 'no'; //使复选框选项使用指定的参数,不勾选则写入参数 no o.default = 'yes'; o = s.option(form.Flag, 'read_only', _('Read-only')); o.enabled = 'yes'; o.disabled = 'no'; o.default = 'no'; // smb.conf default is 'yes' o.rmempty = false; s.option(form.Flag, 'force_root', _('Force Root')); o = s.option(form.Value, 'users', _('Allowed users')); o.rmempty = true; o = s.option(form.Flag, 'guest_ok', _('Allow guests')); o.enabled = 'yes'; o.disabled = 'no'; o.default = 'yes'; // smb.conf default is 'no' o.rmempty = false; o = s.option(form.Flag, 'guest_only', _('Guests only')); o.enabled = 'yes'; o.disabled = 'no'; o.default = 'no'; o = s.option(form.Flag, 'inherit_owner', _('Inherit owner')); o.enabled = 'yes'; o.disabled = 'no'; o.default = 'no'; o = s.option(form.Value, 'create_mask', _('Create mask')); o.maxlength = 4; //限制字符长度,4表示最多只允许4个英文字符长度。 o.default = '0666'; // smb.conf default is '0744' o.placeholder = '0666'; o.rmempty = false; o = s.option(form.Value, 'dir_mask', _('Directory mask')); o.maxlength = 4; o.default = '0777'; // smb.conf default is '0755' o.placeholder = '0777'; o.rmempty = false; o = s.option(form.Value, 'vfs_objects', _('Vfs objects')); o.rmempty = true; s.option(form.Flag, 'timemachine', _('Apple Time-machine share')); o = s.option(form.Value, 'timemachine_maxsize', _('Time-machine size in GB')); o.rmempty = true; o.maxlength = 5; return m.render(); } }); |
界面脚本与配置文件是对应关系,将关联的配置文件 /etc/config/samba4 内容贴出来,两相对照才能更准确的理解各个参数的意义。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<pre class="inline:true lang:bash class:language-bash decode:1 " > config samba option name 'OpenWrt' option workgroup 'WORKGROUP' option description 'Samba on OpenWrt' option charset 'UTF-8' option enable '1' option disable_ad_dc '1' option disable_winbind '1' option interface 'lan' option nice '0' config sambashare option name 'sda' option path '/mnt/sda3' option read_only 'no' option force_root '1' option guest_ok 'yes' option create_mask '0666' option dir_mask '0777' |
通过上面的实例,你应该已经初步知晓了, openwrt 当前的 JavaScript 脚本界面的运行规则了,下面介绍一些常用的其它脚本规则。
提示:源码目录自带开发文档,请查阅 cd openwrt/feeds/luci/docs
获取网络接口的选项
1 2 3 4 |
<pre class="inline:true lang:javascript class:language-javascript decode:1 " >'require tools.widgets as widgets';//申明调用接口 //获取接口的网络名称,例如 lan wan wan6 s.option('general', widgets.NetworkSelect, 'interface', _('Interface')); |
1 2 3 4 |
<pre class="inline:true lang:javascript class:language-javascript decode:1 " >'require tools.widgets as widgets';//申明调用接口 //获取接口的设备名称,例如 eth0 br-lan s.option('general', widgets.DeviceSelect, 'interface', _('Interface name')); |
获取系统用户列表
1 2 3 4 |
<pre class="inline:true lang:javascript class:language-javascript decode:1 " >'require tools.widgets as widgets';//申明调用接口 //获取操作系统内的用户列表。 s.option('general', widgets.UserSelect, 'user', _('Run daemon as user')); |
配置一个下拉列表框,只允许用户选择预设的参数。
1 2 3 4 5 6 7 8 9 10 |
<pre class="inline:true lang:javascript class:language-javascript decode:1 " >'require form';//申明调用接口 o = s.option(form.ListValue, 'leasetime', _('Lease time')); o.value('1h', _('One hour')); //下拉列表框将显示预设的选项名称,此名称可以使用翻译文件进行转换。 o.value('2h'); //此选项将直接显示为 2h o.value('1d', _('One day')); o.value('7d', _('A week')); o.rmempty = true;//允许此选项为空值,即允许此选项不存在。 |
配置一个下拉列表框,允许用户选择预设的参数,也允许用户自行输入参数。
1 2 3 4 5 6 7 8 9 10 |
<pre class="inline:true lang:javascript class:language-javascript decode:1 " >'require form';//申明调用接口 o = s.option(form.Value, 'leasetime', _('Lease time')); o.value('1h', _('One hour')); //下拉列表框将显示预设的选项名称,此名称可以使用翻译文件进行转换。 o.value('2h'); //此选项将直接显示为 2h o.value('1d', _('One day')); o.value('7d', _('A week')); o.rmempty = true;//允许此选项为空值,即允许此选项不存在。 |
配置一个动态列表,允许自由添加删除多个参数,展示一个实例样板。
1 2 3 4 5 6 7 8 |
<pre class="inline:true lang:javascript class:language-javascript decode:1 " >'require form';//申明调用接口 o = s.taboption('general', form.DynamicList, 'address', _('Static address'), _('List of domains to force to an IP address')); o.optional = true;//表示此选项为可选属性。 o.placeholder = '/openwrt.xyz/192.168.9.1'; //占位符,用于提示用户应该输入什么样的字符。 |
配置含多个子菜单的选项卡界面
1 2 3 4 5 6 7 8 |
<pre class="inline:true lang:javascript class:language-javascript decode:1 " >'require form';//申明调用接口 o = s.tab("general", _("General Settings")); o = s.tab("advanced", _('Advanced Settings')); o = s.taboption('general', form.Flag, "enabled", _("Enabled")); o = s.taboption('advanced', form.Flag, "enable", _("Enable")); |
配置选项关联另外的某个选项,当关联的选项为指定的值时才显示。
1 2 |
<pre class="inline:true lang:javascript class:language-javascript decode:1 " >o.depends("advanced", "1"); //关联选项 advanced 参数为 1 时,此选项才会显示。 |
限制选项参数格式的常用类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<pre class="inline:true lang:javascript class:language-javascript decode:1 " >o.datatype = 'string';//允许任意字符组合。(此为默认值,即如果不指定数据类型就默认是这个。) o.datatype = 'uinteger';//只允许输入正整数。 o.datatype = 'range(-20,19)';//只允许输入范围内的数值。(只允许输入-20至19的数字) o.datatype = 'list(string)';//限制此选项的格式为列表值,一般用于配合动态列表。 o.datatype = 'directory';//只允许输入路径格式,且此目录必须已经存在。 o.datatype = 'file';//只允许输入文件路径,且此文件必须已经存在。 o.datatype = 'hostname';//限制参数为主机名。 o.datatype = 'host';//限制参数为网站域名。 o.datatype = 'port';//只允许输入端口号。 o.datatype = 'portrange';//限制参数为端口范围,即允许的书写格式如: 1025-65535 o.datatype = 'ipaddr';//限制参数为ip地址。 o.datatype = 'ip4addr';//限制参数为ipv4地址。 o.datatype = 'ip6addr';//限制参数为ipv6地址。 o.datatype = 'ipaddrport';//限制参数为ip地址加端口,书写格式如:127.0.0.1:80 o.datatype = 'or(ipaddr,hostname)';//限制参数为IP地址或者主机名。 o.datatype = 'max(1024)';//限制参数最大值。 o.datatype = 'min(60)';//限制参数最小值。 |
界面语言翻译文件
在对应的语言目录下创建 po 翻译文件即可。
1 2 3 |
<pre class="inline:true lang:bash class:language-bash decode:1 " ># 使用命令快速创建 po 翻译文件示例。 cd openwrt/feeds/luci ./build/i18n-scan.pl applications/luci-app-samba4/ > applications/luci-app-samba4/po/zh_Hans/samba4.po |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
<pre class="inline:true lang:bash class:language-bash decode:1 " ># # OpenWrt-Life [email protected]>, 2020. # # msgid 为源语言文字。 # msgstr 为翻译后的文字。 # 这是可选注释,标记这个词句在源代码 JavaScript 脚本中的第几行。 #: applications/luci-app-samba4/htdocs/luci-static/resources/view/samba4.js:97 msgid "Allow guests" msgstr "允许匿名用户" # 对于比较长的语句,为了方便阅读源码,可以断句书写,见下面的实例。 # 但需要注意,不允许出现连续两个空格,否则会导致翻译失效。 #: applications/luci-app-samba4/htdocs/luci-static/resources/view/samba4.js:46 msgid "" "Enables Apple's AAPL extension globally and adds macOS compatibility options " "to all shares." msgstr "全局启用 Apple 的 AAPL 扩展,并为所有共享添加 macOS 兼容性选项。" #: applications/luci-app-samba4/htdocs/luci-static/resources/view/samba4.js:63 msgid "" "This is the content of the file '/etc/samba/smb.conf.template' from which " "your samba configuration will be generated. Values enclosed by pipe symbols " "('|') should not be changed. They get their values from the 'General " "Settings' tab." msgstr "" "这是将从其上生成 samba 配置的文件“/etc/samba/smb.conf.template”的内容。由管道" "符(“|”)包围的值不应更改。它们将从“常规设置”标签中获取其值。" # 翻译文件的冲突优先级。 # 当同一个源词句被翻译为不同的词句,则最后安装的会优先显示于所有界面。 # 例如单词 “Enable”,有 A、B、C 软件界面都含有这个源词语,但翻译词语却各不相同。 # 例如 A 翻译为“启用”,B 翻译为“打开”,C 翻译为“开启” # A 安装后,“Enable”显示为“启用” # B 安装后,所有界面的“Enable”将显示为“打开” # C 安装后,所有界面的“Enable”将显示为“开启” # 所以为了避免出现这种翻译混乱,请各位开发者用词准确且专业规范。 # 翻译文件的复用。 # 当一个界面所含的源词句与系统中已安装的翻译文件相同, # 则即使这个界面不提供任何翻译文件,界面的相关词句仍然能显示已有的翻译词句。 |
默认执行脚本
在 uci-defaults 目录下可以放置脚本文件,用于在 ipk 安装完毕后自动执行命令,一般用于为软件预配置运行环境等,当然也可以不用它。
1 2 3 4 5 6 7 |
<pre class="inline:true lang:bash class:language-bash decode:1 " >#!/bin/sh # 表示重载 rpcd 脚本,用于重新读取 ACL 权限规则文件。 /etc/init.d/rpcd reload # 表示执行完毕后退出此脚本。 exit 0 |
界面菜单
在 menu.d 目录下的 json 文件为软件的界面菜单文件,用于控制软件在系统菜单中的名称、顺序等。
下面用几个实例介绍 json 界面菜单文件的书写规范。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<pre class="inline:true lang:json class:language-json decode:1 " >{ "admin/services/samba4": { //表示位于系统菜单的“服务”下。 "title": "Network Shares-samba4", //菜单标题,可以由翻译文件转换。 "action": { "type": "view", //类型view,表示默认的调用格式。 "path": "samba4" // JavaScript 脚本的路径,此处表示文件在 view 目录下,不用写后缀名。 }, "depends": { //关联依赖项。 "acl": [ "luci-app-samba4" ], //权限控制文件的名称,不用写后缀名。 "uci": { "samba4": true } //表示关联 uci 配置文件 samba4,当配置文件存在才能显示界面菜单。(/etc/config/samba4) } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<pre class="inline:true lang:json class:language-json decode:1 " >{ "admin/network/sqm": { //表示位于系统菜单的“网络”下。 "title": "SQM QoS", "order": 59,//菜单排序,数字越大排序越靠后,如果与其它界面数值相同,则以字母顺序分先后。 "action": { "type": "view", "path": "network/sqm" // JavaScript 脚本的路径,此处表示文件在 view/network 目录下。 }, "depends": { "acl": [ "luci-app-sqm" ] } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
<pre class="inline:true lang:json class:language-json decode:1 " >{ "admin/troubleshooting": { //表示一级菜单。 "title": "Troubleshooting", "order": 80, "action": { "type": "firstchild" //表示此菜单不存在则自动创建。 } }, "admin/troubleshooting/packet_capture": { //表示位于系统菜单的“troubleshooting”下。 "title": "Packet Capture", "order": 1, "action": { "type": "view", "path": "packet_capture/tcpdump" }, "depends" : { "acl": [ "luci-app-packet-capture" ], "uci": { "packet_capture": true }, "fs": { "/usr/libexec/packet_capture": "executable", //表示需要读取到这些文件存在且可执行。 "/usr/libexec/packet_capture_start": "executable", "/usr/libexec/packet_capture_stop": "executable" } } } } |
权限控制文件
openwrt 为 JavaScript 脚本界面引入的权限控制机制,用于管制脚本界面能执行哪些系统操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
<pre class="inline:true lang:json class:language-json decode:1 " >{ "luci-app-samba4": { //这个权限控制文件的名称。 "description": "Grant access to LuCI app samba4", //描述,可由翻译文件转换。 "read": { //允许读取操作。 "file": { //表示关联对象为文件 "/etc/samba/smb.conf.template": [ "read" ], //表示允许读取这个文件。 "/usr/sbin/smbd": [ "exec" ] //表示允许执行此文件,只能读取数据。 }, "uci": [ "samba4" ] //表示允许读取uci配置文件 samba4(/etc/config/samba4) }, "write": { //允许写入操作。 "file": { "/etc/samba/smb.conf.template": [ "write" ] //允许写入这个文件。 }, "uci": [ "samba4" ] //表示允许写入uci配置文件 samba4 } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
<pre class="inline:true lang:json class:language-json decode:1 " >{ "luci-app-sqm": { "description": "Grant UCI access for luci-app-sqm", "read": { "file": { "/var/run/sqm/available_qdiscs": [ "list" ], //表示在这个目录下执行 list 命令。 "/usr/lib/sqm/*.qos.help": [ "read" ] }, "uci": [ "sqm" ], "ubus": { //表示允许 ubus 命令调用 "file": [ "read", "list" ], //允许 file 项目调用 read 和 list 命令。 "luci": [ "setInitAction" ] //允许界面调用 setInitAction } }, "write": { "uci": [ "sqm" ] } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
<pre class="inline:true lang:json class:language-json decode:1 " >{ "luci-app-packet-capture": { "description": "Grant access to tcpdump ubus object", "read": { "cgi-io": [ "download", "exec" ], //允许执行的 cgi-io 操作。 "ubus": { "tcpdump": [ "*" ], //允许通过 ubus 调用 tcpdump 执行任意命令。 "luci": [ "getProcessList" ] //允许获取 luci 流程列表。 }, "uci": [ "packet_capture", "system" ], "file": { "/tmp/capture.pcap": [ "read" ] } }, "write": { "uci": [ "packet_capture" ], "file": { "/usr/libexec/packet_capture_start": [ "exec" ], //允许执行此文件,并可写入数据。 "/usr/libexec/packet_capture_stop": [ "exec" ], "/usr/libexec/packet_capture": [ "exec" ], "/tmp/capture.pcap": [ "write" ] //允许写入此文件。 } } } } |
界面调试技巧
开发软件界面的时候,经常需要查看界面改动后的实际效果,难道必须要编译为 ipk 文件再安装吗?并不是,通过上面的学习,你应该了解到界面文件其实都是纯文本状态,编译过程只是压缩打包而已,并不是为了编译成二进制文件,所以只需要把文件放入对应的系统目录,然后刷新浏览器页面,或者清空浏览器缓存即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<pre class="inline:true lang:bash class:language-bash decode:1 " ># 以 samba4 为例,将相关文件放入对应的系统目录即可。 # JavaScript 脚本界面 /www/luci-static/resources/view/samba4.js # UCI 配置文件 /etc/config/samba4 # 界面菜单文件 /usr/share/luci/menu.d/luci-app-samba4.json # 权限控制文件 /usr/share/rpcd/acl.d/luci-app-samba4.json # 无法直接显示 po 翻译文件,需要转换为 lmo /usr/lib/lua/luci/i18n/samba4.zh-cn.lmo |
openwrt Procd 系统初始化和守护程序管理
在 openwrt 上要让一个程序正常运行,自然也需要一个启动脚本来提供服务,不然就只能使用命令行操控了,openwrt 下提供了一款类似于 systemd 的进程管理守护程序,称之为 Procd,其提供了非常强大的脚本功能,对于程序运行大有助益。
openwrt官网关于 Procd脚本的说明:openwrt Project: procd init script parameters
以程序 memcached 为例,演示传统启动脚本和 Procd 脚本的区别。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
<pre class="inline:true lang:bash class:language-bash decode:1 " >#!/bin/sh /etc/rc.common # Copyright (C) 2010-2011 OpenWrt.org # 这是传统启动脚本实例。 # 开机自启动时的顺序,1-99,数字越大,启动越晚。 START=80 start_instance () { local section="$1" config_get user "$section" 'user' # 获取配置文件参数 config_get maxconn "$section" 'maxconn' config_get listen "$section" 'listen' config_get port "$section" 'port' config_get memory "$section" 'memory' config_get options "$section" 'options' service_start /usr/bin/memcached -d -u $user -c $maxconn -l $listen -p $port -m $memory $options # 启动参数 } start() { config_load 'memcached' config_foreach start_instance 'memcached' } stop() { service_stop /usr/bin/memcached } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
<pre class="inline:true lang:bash class:language-bash decode:1 " >#!/bin/sh /etc/rc.common # Copyright (C) 2010-2011 OpenWrt.org # 这是 Procd 脚本的实例。 START=80 STOP=10 # 使用 PROCD 的标识,必需。 USE_PROCD=1 PROG=/usr/bin/memcached start_instance () { local section="$1" config_get user "$section" 'user' # 获取配置文件参数 config_get maxconn "$section" 'maxconn' config_get listen "$section" 'listen' config_get port "$section" 'port' config_get memory "$section" 'memory' config_get options "$section" 'options' config_get_bool enable "$section" enable 0 [ "$enable" -gt 0 ] || return 1 # 获取 UCI 配置文件中的 enable 参数,以判断是否应该启动。 procd_open_instance procd_set_param command "$PROG" # 程序可执行文件路径。 procd_append_param command -u $user # 附加参数 procd_append_param command -c $maxconn procd_append_param command -l $listen procd_append_param command -p $port procd_append_param command -m $memory procd_append_param command $options procd_set_param respawn # 守护进程,当进程出现故障未响应时自动重载。 procd_set_param stdout 1 # 将输出信息转发至系统日志。 procd_set_param stderr 1 # 将错误日志转发至系统日志。 procd_close_instance } start_service() { config_load 'memcached' config_foreach start_instance 'memcached' } service_triggers() { procd_add_reload_trigger "memcached" # 监控 UCI 配置文件,当文件发生变化时自动重载程序。 # 例如通过界面勾选启用,再点击保存并应用后,则程序就能自动运行。 } |
搭建 openwrt 本地软件源
开发软件过程中,免不了要经常进行实机测试,难道要搭建一个 HTTP 服务器来提供安装源吗?并不需要,经验丰富的开发者应该已经想到了,那就是直接使用 openwrt 自带的 uhttpd 来作为 HTTP 服务器,并不需要任何额外设置,只需要将软件源目录软链接至 www 目录即可。
首先通过 FTP 或其它各种方式,将软件源目录上传至路由器磁盘空间。

然后使用软链接命令,将软件源目录链接至 www 目录即可。
1 |
<pre class="inline:true lang:bash class:language-bash decode:1 " >ln -s /mnt/sda3/soft /www/soft |
此时可以打开浏览器测试,看是否能成功访问。

接着再修改软件包的 “OPKG 配置” 里的软件源地址。

然后就可以开始使用了。

文章来源于互联网:openwrt达人教程之开发人员入门指南