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 网页界面的意义:各类程序的配置文件直接手动配置也是完全没问题的,但提供网页界面可以更直观的管理各项配置参数,并且可以通过界面对输入的参数进行一些限制,防止因手误输入错误的参数,具备一定程度的防呆功能,这对于复杂的各类软件功能是很有帮助的。
完整的界面文件结构
以源代码目录的文件结构为例,一个基本的界面程序应当具备如下所示的目录文件结构。
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 编写指南
#
# 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 脚本界面为例。
'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(/\r\n/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 内容贴出来,两相对照才能更准确的理解各个参数的意义。
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
获取网络接口的选项
'require tools.widgets as widgets';//申明调用接口
//获取接口的网络名称,例如 lan wan wan6
s.option('general', widgets.NetworkSelect, 'interface', _('Interface'));
'require tools.widgets as widgets';//申明调用接口
//获取接口的设备名称,例如 eth0 br-lan
s.option('general', widgets.DeviceSelect, 'interface', _('Interface name'));
获取系统用户列表
'require tools.widgets as widgets';//申明调用接口
//获取操作系统内的用户列表。
s.option('general', widgets.UserSelect, 'user', _('Run daemon as user'));
配置一个下拉列表框,只允许用户选择预设的参数。
'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;//允许此选项为空值,即允许此选项不存在。
配置一个下拉列表框,允许用户选择预设的参数,也允许用户自行输入参数。
'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;//允许此选项为空值,即允许此选项不存在。
配置一个动态列表,允许自由添加删除多个参数,展示一个实例样板。
'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';
//占位符,用于提示用户应该输入什么样的字符。
配置含多个子菜单的选项卡界面
'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"));
配置选项关联另外的某个选项,当关联的选项为指定的值时才显示。
o.depends("advanced", "1");
//关联选项 advanced 参数为 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 翻译文件即可。
# 使用命令快速创建 po 翻译文件示例。
cd openwrt/feeds/luci
# 先创建翻译模板文件。
mkdir -p applications/luci-app-samba4/po/templates
./build/i18n-scan.pl applications/luci-app-samba4/ > applications/luci-app-samba4/po/templates/samba4.pot
# 然后根据需要创建对应的翻译语言文件。
mkdir -p applications/luci-app-samba4/po/zh_Hans
./build/i18n-scan.pl applications/luci-app-samba4/ > applications/luci-app-samba4/po/zh_Hans/samba4.po
# 当你对界面进行了词句的修改后,可以使用命令更新已有的 po 翻译文件。
cd openwrt/feeds/luci
./build/i18n-scan.pl applications/luci-app-samba4/ > applications/luci-app-samba4/po/templates/samba4.pot
./build/i18n-update.pl applications/luci-app-samba4/po
# 当你对大量 Luci 界面修改了词句后,可以使用脚本批量更新已有的翻译文件。
cd openwrt/feeds/luci
./build/i18n-sync.sh
#
# OpenWrt-Life <[email protected]>, 2020.
#
# 指定字符集的文件头项。(必需)
msgid ""
msgstr "Content-Type: text/plain; charset=UTF-8\n"
# 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 安装完毕后自动执行命令,一般用于为软件预配置运行环境等,当然也可以不用它。
#!/bin/sh
# 表示重载 rpcd 脚本,用于重新读取 ACL 权限规则文件。
/etc/init.d/rpcd reload
# 表示执行完毕后退出此脚本。
exit 0
界面菜单
在 menu.d 目录下的 json 文件为软件的界面菜单文件,用于控制软件在系统菜单中的名称、顺序等。
下面用几个实例介绍 json 界面菜单文件的书写规范。
{
"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)
}
}
}
{
"admin/network/sqm": {
//表示位于系统菜单的“网络”下。
"title": "SQM QoS",
"order": 59,//菜单排序,数字越大排序越靠后,如果与其它界面数值相同,则以字母顺序分先后。
"action": {
"type": "view",
"path": "network/sqm"
// JavaScript 脚本的路径,此处表示文件在 view/network 目录下。
},
"depends": {
"acl": [ "luci-app-sqm" ]
}
}
}
{
"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 脚本界面引入的权限控制机制,用于管制脚本界面能执行哪些系统操作。
{
"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
}
}
}
{
"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" ]
}
}
}
{
"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 文件再安装吗?并不是,通过上面的学习,你应该了解到界面文件其实都是纯文本状态,编译过程只是压缩打包而已,并不是为了编译成二进制文件,所以只需要把文件放入对应的系统目录,然后刷新浏览器页面,或者清空浏览器缓存即可。
# 以 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 脚本的区别。
#!/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
}
#!/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 目录即可。
ln -s /mnt/sda3/soft /www/soft
此时可以打开浏览器测试,看是否能成功访问。
接着再修改软件包的 “OPKG 配置” 里的软件源地址。
然后就可以开始使用了。
评论(0)