Compare commits
48 Commits
v0.6(depre
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b69fcf0bb2 | ||
|
|
87d28fb618 | ||
|
|
663ac6c90b | ||
|
|
10fedfded1 | ||
|
|
1720662f73 | ||
|
|
cb7dbba054 | ||
|
|
e9814cf85b | ||
|
|
73e98a682d | ||
|
|
1c02ace538 | ||
|
|
9067d3d3ab | ||
|
|
eb93cd47b7 | ||
|
|
fcd78c296c | ||
|
|
e1d107de12 | ||
|
|
1806a459d2 | ||
|
|
4ff18a1269 | ||
|
|
f9aa55ca4d | ||
|
|
ef40cb3051 | ||
|
|
0e65c8a059 | ||
|
|
75652f5e7d | ||
|
|
9803fe0ab4 | ||
|
|
d0c1f03ce5 | ||
|
|
63ddfa1ed0 | ||
|
|
36a70fa706 | ||
|
|
5b669fe718 | ||
|
|
55802f750f | ||
|
|
d94260e7ec | ||
|
|
8ad9c5a086 | ||
|
|
6c19812db5 | ||
|
|
dd46273f24 | ||
|
|
90b711f55e | ||
|
|
c20703d5be | ||
|
|
e0e13d4962 | ||
|
|
be3b7e7dd2 | ||
|
|
5e0729df31 | ||
|
|
8918f12ef6 | ||
|
|
13d8d06248 | ||
|
|
98d7b9d4f0 | ||
|
|
3f245bb410 | ||
|
|
b5a4429854 | ||
|
|
9364dabe0a | ||
|
|
be0912d7ae | ||
|
|
259f4cf122 | ||
|
|
170bd8ddd7 | ||
|
|
f92713818d | ||
|
|
ffc10b81e0 | ||
|
|
87022499e3 | ||
|
|
1ac7652e0a | ||
|
|
76acd939a9 |
88
README.md
@@ -1,48 +1,78 @@
|
||||
# Internet detector for OpenWrt.
|
||||
Internet-detector is an application for checking the availability of the Internet. Performs periodic connections to a known public host (8.8.8.8, 1.1.1.1) and determines the actual Internet availability.
|
||||
|
||||
**OpenWrt** >= 19.07.
|
||||
|
||||
**Dependences:** lua, luci-lib-nixio, libuci-lua.
|
||||
Internet-detector is an application for checking the availability of the Internet. Performs periodic connections to a known public host and determines the actual Internet availability.
|
||||
|
||||
**Features:**
|
||||
- It can run continuously as a system service or only in an open web interface.
|
||||
- Checking the availability of a host using ping or by connecting via TCP to a specified port.
|
||||
- Checking the availability of a host using ping (L3) or by connecting via TCP to a specified port (L4).
|
||||
- Testing URL accessibility via HTTP (L7) (curl).
|
||||
- LED indication of Internet availability.
|
||||

|
||||
- Performing actions when connecting and disconnecting the Internet (Restarting network, modem or device. Executing custom shell scripts).
|
||||
- Sending email notification when Internet access is restored.
|
||||
- The daemon is written entirely in Lua using the nixio library.
|
||||
- Performing actions when connecting and disconnecting the Internet: rebooting device, restarting network or modem (internet-detector-mod-modem-restart), executing custom shell scripts.
|
||||
- Sending email notification when Internet access is restored (internet-detector-mod-email).
|
||||
- Sending telegtam notification when Internet access is restored (internet-detector-mod-telegram).
|
||||
- The daemon is written entirely in Lua using the luaposix library.
|
||||
|
||||
## Installation notes
|
||||
**OpenWrt >= 21.02.**
|
||||
|
||||
**OpenWrt >= 21.02:**
|
||||
**Dependences:** lua, luaposix, libuci-lua.
|
||||
|
||||
**Recommended:** curl.
|
||||
|
||||
## Installation notes:
|
||||
|
||||
opkg update
|
||||
wget --no-check-certificate -O /tmp/internet-detector_0.6-0_all.ipk https://github.com/gSpotx2f/packages-openwrt/raw/master/current/internet-detector_0.6-0_all.ipk
|
||||
opkg install /tmp/internet-detector_0.6-0_all.ipk
|
||||
rm /tmp/internet-detector_0.6-0_all.ipk
|
||||
/etc/init.d/internet-detector start
|
||||
/etc/init.d/internet-detector enable
|
||||
wget --no-check-certificate -O /tmp/internet-detector_1.7.0-r1_all.ipk https://github.com/gSpotx2f/packages-openwrt/raw/master/current/internet-detector_1.7.0-r1_all.ipk
|
||||
opkg install /tmp/internet-detector_1.7.0-r1_all.ipk
|
||||
rm /tmp/internet-detector_1.7.0-r1_all.ipk
|
||||
service internet-detector start
|
||||
service internet-detector enable
|
||||
|
||||
wget --no-check-certificate -O /tmp/luci-app-internet-detector_0.6-1_all.ipk https://github.com/gSpotx2f/packages-openwrt/raw/master/current/luci-app-internet-detector_0.6-1_all.ipk
|
||||
opkg install /tmp/luci-app-internet-detector_0.6-1_all.ipk
|
||||
rm /tmp/luci-app-internet-detector_0.6-1_all.ipk
|
||||
/etc/init.d/rpcd restart
|
||||
|
||||
Email notification:
|
||||
|
||||
opkg install mailsend
|
||||
wget --no-check-certificate -O /tmp/luci-app-internet-detector_1.7.0-r1_all.ipk https://github.com/gSpotx2f/packages-openwrt/raw/master/current/luci-app-internet-detector_1.7.0-r1_all.ipk
|
||||
opkg install /tmp/luci-app-internet-detector_1.7.0-r1_all.ipk
|
||||
rm /tmp/luci-app-internet-detector_1.7.0-r1_all.ipk
|
||||
service rpcd restart
|
||||
|
||||
i18n-ru:
|
||||
|
||||
wget --no-check-certificate -O /tmp/luci-i18n-internet-detector-ru_0.6-1_all.ipk https://github.com/gSpotx2f/packages-openwrt/raw/master/current/luci-i18n-internet-detector-ru_0.6-1_all.ipk
|
||||
opkg install /tmp/luci-i18n-internet-detector-ru_0.6-1_all.ipk
|
||||
rm /tmp/luci-i18n-internet-detector-ru_0.6-1_all.ipk
|
||||
|
||||
**[OpenWrt 19.07](https://github.com/gSpotx2f/luci-app-internet-detector/tree/19.07)**
|
||||
wget --no-check-certificate -O /tmp/luci-i18n-internet-detector-ru_1.7.0-r1_all.ipk https://github.com/gSpotx2f/packages-openwrt/raw/master/current/luci-i18n-internet-detector-ru_1.7.0-r1_all.ipk
|
||||
opkg install /tmp/luci-i18n-internet-detector-ru_1.7.0-r1_all.ipk
|
||||
rm /tmp/luci-i18n-internet-detector-ru_1.7.0-r1_all.ipk
|
||||
|
||||
## Screenshots:
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
## Modem restart module (internet-detector-mod-modem-restart):
|
||||
|
||||
**Dependences:** modemmanager.
|
||||
|
||||
wget --no-check-certificate -O /tmp/internet-detector-mod-modem-restart_1.7.0-r1_all.ipk https://github.com/gSpotx2f/packages-openwrt/raw/master/current/internet-detector-mod-modem-restart_1.7.0-r1_all.ipk
|
||||
opkg install /tmp/internet-detector-mod-modem-restart_1.7.0-r1_all.ipk
|
||||
rm /tmp/internet-detector-mod-modem-restart_1.7.0-r1_all.ipk
|
||||
service internet-detector restart
|
||||
|
||||

|
||||
|
||||
## Email notification module (internet-detector-mod-email):
|
||||
|
||||
**Dependences:** mailsend.
|
||||
|
||||
wget --no-check-certificate -O /tmp/internet-detector-mod-email_1.7.0-r1_all.ipk https://github.com/gSpotx2f/packages-openwrt/raw/master/current/internet-detector-mod-email_1.7.0-r1_all.ipk
|
||||
opkg install /tmp/internet-detector-mod-email_1.7.0-r1_all.ipk
|
||||
rm /tmp/internet-detector-mod-email_1.7.0-r1_all.ipk
|
||||
service internet-detector restart
|
||||
|
||||

|
||||
|
||||
## Telegram notification module (internet-detector-mod-telegram):
|
||||
|
||||
**Dependences:** curl.
|
||||
|
||||
wget --no-check-certificate -O /tmp/internet-detector-mod-telegram_1.7.0-r1_all.ipk https://github.com/gSpotx2f/packages-openwrt/raw/master/current/internet-detector-mod-telegram_1.7.0-r1_all.ipk
|
||||
opkg install /tmp/internet-detector-mod-telegram_1.7.0-r1_all.ipk
|
||||
rm /tmp/internet-detector-mod-telegram_1.7.0-r1_all.ipk
|
||||
service internet-detector restart
|
||||
|
||||

|
||||
|
||||
41
internet-detector-mod-email/Makefile
Normal file
@@ -0,0 +1,41 @@
|
||||
#
|
||||
# (с) 2025 gSpot (https://github.com/gSpotx2f/luci-app-internet-detector)
|
||||
#
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=internet-detector-mod-email
|
||||
PKG_VERSION:=1.7.0
|
||||
PKG_RELEASE:=1
|
||||
PKG_MAINTAINER:=gSpot <https://github.com/gSpotx2f/luci-app-internet-detector>
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
define Package/$(PKG_NAME)
|
||||
SECTION:=net
|
||||
CATEGORY:=Network
|
||||
TITLE:=Email module for internet-detector
|
||||
URL:=https://github.com/gSpotx2f/luci-app-internet-detector
|
||||
PKGARCH:=all
|
||||
DEPENDS:=+internet-detector +mailsend
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/description
|
||||
Email support for internet-detector.
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/conffiles
|
||||
endef
|
||||
|
||||
define Build/Configure
|
||||
endef
|
||||
|
||||
define Build/Compile
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/install
|
||||
$(INSTALL_DIR) $(1)/usr/lib/lua/internet-detector/modules
|
||||
$(INSTALL_DATA) ./files/usr/lib/lua/internet-detector/modules/mod_email.lua $(1)/usr/lib/lua/internet-detector/modules/mod_email.lua
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,$(PKG_NAME)))
|
||||
@@ -0,0 +1,226 @@
|
||||
--[[
|
||||
Dependences:
|
||||
mailsend
|
||||
--]]
|
||||
local unistd = require("posix.unistd")
|
||||
|
||||
local Module = {
|
||||
name = "mod_email",
|
||||
runPrio = 60,
|
||||
config = {
|
||||
debug = false,
|
||||
},
|
||||
syslog = function(level, msg) return true end,
|
||||
debugOutput = function(msg) return true end,
|
||||
writeValue = function(filePath, str) return false end,
|
||||
readValue = function(filePath) return nil end,
|
||||
deadPeriod = 0,
|
||||
alivePeriod = 0,
|
||||
mode = 0, -- 0: connected, 1: disconnected, 2: both
|
||||
hostAlias = "OpenWrt",
|
||||
mta = "/usr/bin/mailsend",
|
||||
mtaConnectTimeout = 5,
|
||||
mtaReadTimeout = 5,
|
||||
mailRecipient = nil,
|
||||
mailSender = nil,
|
||||
mailUser = nil,
|
||||
mailPassword = nil,
|
||||
mailSmtp = nil,
|
||||
mailSmtpPort = nil,
|
||||
mailSecurity = "tls",
|
||||
msgTextPattern = "[%s] (%s) @ %s", -- Message (host, instance, message)
|
||||
msgSubPattern = "%s notification", -- Subject (host)
|
||||
msgConnectPattern = "Connected: %s",
|
||||
msgDisconnectPattern = "Disconnected: %s",
|
||||
msgSeparator = " | ",
|
||||
msgMaxItems = 50,
|
||||
msgSendAttempts = 3,
|
||||
msgSendTimeout = 5,
|
||||
status = nil,
|
||||
_enabled = false,
|
||||
_deadCounter = 0,
|
||||
_aliveCounter = 0,
|
||||
_msgSentDisconnect = true,
|
||||
_disconnected = true,
|
||||
_msgSentConnect = true,
|
||||
_connected = true,
|
||||
_msgBuffer = {},
|
||||
_msgSendCounter = 3,
|
||||
_msgTimeoutCounter = 5,
|
||||
}
|
||||
|
||||
function Module:init(t)
|
||||
if t.mode ~= nil then
|
||||
self.mode = tonumber(t.mode)
|
||||
end
|
||||
if t.dead_period ~= nil then
|
||||
self.deadPeriod = tonumber(t.dead_period)
|
||||
end
|
||||
if t.alive_period ~= nil then
|
||||
self.alivePeriod = tonumber(t.alive_period)
|
||||
end
|
||||
if t.host_alias then
|
||||
self.hostAlias = t.host_alias
|
||||
else
|
||||
self.hostAlias = self.config.hostname
|
||||
end
|
||||
|
||||
self.mailRecipient = t.mail_recipient
|
||||
self.mailSender = t.mail_sender
|
||||
self.mailUser = t.mail_user
|
||||
self.mailPassword = t.mail_password
|
||||
self.mailSmtp = t.mail_smtp
|
||||
self.mailSmtpPort = t.mail_smtp_port
|
||||
|
||||
if t.mail_security ~= nil then
|
||||
self.mailSecurity = t.mail_security
|
||||
end
|
||||
if tonumber(t.message_at_startup) == 1 then
|
||||
self._msgSentDisconnect = false
|
||||
self._disconnected = false
|
||||
self._msgSentConnect = false
|
||||
self._connected = false
|
||||
end
|
||||
|
||||
if unistd.access(self.mta, "x") then
|
||||
self._enabled = true
|
||||
else
|
||||
self._enabled = false
|
||||
self.syslog("err", string.format("%s: %s is not available", self.name, self.mta))
|
||||
end
|
||||
|
||||
if (not self.mailRecipient or
|
||||
not self.mailSender or
|
||||
not self.mailUser or
|
||||
not self.mailPassword or
|
||||
not self.mailSmtp or
|
||||
not self.mailSmtpPort) then
|
||||
self._enabled = false
|
||||
self.syslog("warning", string.format(
|
||||
"%s: Insufficient data to connect to the SMTP server", self.name))
|
||||
end
|
||||
|
||||
self._msgSendCounter = self.msgSendAttempts
|
||||
end
|
||||
|
||||
function Module:appendNotice(str)
|
||||
self._msgBuffer[#self._msgBuffer + 1] = str
|
||||
if #self._msgBuffer > self.msgMaxItems then
|
||||
local t = {}
|
||||
for i = #self._msgBuffer - self.msgMaxItems + 1, #self._msgBuffer do
|
||||
t[#t + 1] = self._msgBuffer[i]
|
||||
end
|
||||
self._msgBuffer = t
|
||||
end
|
||||
end
|
||||
|
||||
function Module:sendMessage(msg, textPattern)
|
||||
local retVal = 1
|
||||
local verboseArg = ""
|
||||
local emailMsg = string.format(
|
||||
textPattern, self.hostAlias, self.config.serviceConfig.instance, msg)
|
||||
|
||||
-- Debug
|
||||
if self.config.debug then
|
||||
verboseArg = " -v"
|
||||
end
|
||||
self.debugOutput(string.format("--- %s ---", self.name))
|
||||
|
||||
local securityArgs = "-starttls -auth-login"
|
||||
if self.mailSecurity == "ssl" then
|
||||
securityArgs = "-ssl -auth"
|
||||
end
|
||||
|
||||
local mtaCmd = string.format(
|
||||
'%s%s %s -smtp "%s" -port %s -ct %s -read-timeout %s -cs utf-8 -user "%s" -pass "%s" -f "%s" -t "%s" -sub "%s" -M "%s"',
|
||||
self.mta, verboseArg, securityArgs, self.mailSmtp, self.mailSmtpPort,
|
||||
self.mtaConnectTimeout, self.mtaReadTimeout,
|
||||
self.mailUser, self.mailPassword, self.mailSender, self.mailRecipient,
|
||||
string.format(self.msgSubPattern, self.hostAlias),
|
||||
emailMsg)
|
||||
|
||||
-- Debug
|
||||
self.debugOutput(string.format("%s: %s", self.name, mtaCmd))
|
||||
self.syslog("debug", string.format("%s: %s", self.name, mtaCmd))
|
||||
|
||||
retVal = os.execute(mtaCmd)
|
||||
if retVal == 0 then
|
||||
self.syslog("info", string.format(
|
||||
"%s: Message sent to %s", self.name, self.mailRecipient))
|
||||
self._msgBuffer = {}
|
||||
else
|
||||
self.syslog("err", string.format(
|
||||
"%s: An error occured while sending message", self.name))
|
||||
end
|
||||
|
||||
return retVal
|
||||
end
|
||||
|
||||
function Module:run(currentStatus, lastStatus, timeDiff, timeNow, inetChecked)
|
||||
if not self._enabled then
|
||||
return
|
||||
end
|
||||
|
||||
if currentStatus == 1 then
|
||||
self._aliveCounter = 0
|
||||
self._msgSentConnect = false
|
||||
|
||||
if not self._disconnected then
|
||||
self._disconnected = true
|
||||
self:appendNotice(string.format(
|
||||
self.msgDisconnectPattern, os.date("%Y.%m.%d %H:%M:%S", os.time())))
|
||||
end
|
||||
|
||||
if not self._msgSentDisconnect and (self.mode == 1 or self.mode == 2) then
|
||||
if self._deadCounter >= self.deadPeriod then
|
||||
self._msgSendCounter = 0
|
||||
self._msgSentDisconnect = true
|
||||
else
|
||||
self._deadCounter = self._deadCounter + timeDiff
|
||||
end
|
||||
end
|
||||
self._connected = false
|
||||
elseif currentStatus == 0 then
|
||||
self._deadCounter = 0
|
||||
self._msgSentDisconnect = false
|
||||
|
||||
if not self._connected then
|
||||
self._connected = true
|
||||
self:appendNotice(string.format(
|
||||
self.msgConnectPattern, os.date("%Y.%m.%d %H:%M:%S", os.time())))
|
||||
end
|
||||
|
||||
if not self._msgSentConnect and (self.mode == 0 or self.mode == 2) then
|
||||
if self._aliveCounter >= self.alivePeriod then
|
||||
self._msgSendCounter = 0
|
||||
self._msgSentConnect = true
|
||||
else
|
||||
self._aliveCounter = self._aliveCounter + timeDiff
|
||||
end
|
||||
end
|
||||
self._disconnected = false
|
||||
end
|
||||
|
||||
if self._msgSendCounter < self.msgSendAttempts then
|
||||
if self._msgTimeoutCounter >= self.msgSendTimeout then
|
||||
if #self._msgBuffer > 0 then
|
||||
if self:sendMessage(table.concat(self._msgBuffer, self.msgSeparator), self.msgTextPattern) == 0 then
|
||||
self._msgSendCounter = self.msgSendAttempts
|
||||
else
|
||||
self._msgSendCounter = self._msgSendCounter + 1
|
||||
end
|
||||
end
|
||||
self._msgTimeoutCounter = 0
|
||||
else
|
||||
self._msgTimeoutCounter = self._msgTimeoutCounter + timeDiff
|
||||
end
|
||||
else
|
||||
self._msgTimeoutCounter = self.msgSendTimeout
|
||||
end
|
||||
end
|
||||
|
||||
function Module:onExit()
|
||||
return true
|
||||
end
|
||||
|
||||
return Module
|
||||
41
internet-detector-mod-modem-restart/Makefile
Normal file
@@ -0,0 +1,41 @@
|
||||
#
|
||||
# (с) 2025 gSpot (https://github.com/gSpotx2f/luci-app-internet-detector)
|
||||
#
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=internet-detector-mod-modem-restart
|
||||
PKG_VERSION:=1.7.0
|
||||
PKG_RELEASE:=1
|
||||
PKG_MAINTAINER:=gSpot <https://github.com/gSpotx2f/luci-app-internet-detector>
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
define Package/$(PKG_NAME)
|
||||
SECTION:=net
|
||||
CATEGORY:=Network
|
||||
TITLE:=Modem restart module for internet-detector
|
||||
URL:=https://github.com/gSpotx2f/luci-app-internet-detector
|
||||
PKGARCH:=all
|
||||
DEPENDS:=+internet-detector +modemmanager
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/description
|
||||
Support modem restart for internet detector.
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/conffiles
|
||||
endef
|
||||
|
||||
define Build/Configure
|
||||
endef
|
||||
|
||||
define Build/Compile
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/install
|
||||
$(INSTALL_DIR) $(1)/usr/lib/lua/internet-detector/modules
|
||||
$(INSTALL_DATA) ./files/usr/lib/lua/internet-detector/modules/mod_modem_restart.lua $(1)/usr/lib/lua/internet-detector/modules/mod_modem_restart.lua
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,$(PKG_NAME)))
|
||||
@@ -0,0 +1,152 @@
|
||||
--[[
|
||||
Dependences:
|
||||
modemmanager
|
||||
--]]
|
||||
local unistd = require("posix.unistd")
|
||||
|
||||
local Module = {
|
||||
name = "mod_modem_restart",
|
||||
runPrio = 40,
|
||||
config = {},
|
||||
syslog = function(level, msg) return true end,
|
||||
debugOutput = function(msg) return true end,
|
||||
writeValue = function(filePath, str) return false end,
|
||||
readValue = function(filePath) return nil end,
|
||||
mmcli = "/usr/bin/mmcli",
|
||||
mmInit = "/etc/init.d/modemmanager",
|
||||
deadPeriod = 600,
|
||||
attempts = 1,
|
||||
attemptInterval = 15,
|
||||
ifaceTimeout = 0,
|
||||
iface = nil,
|
||||
anyBand = false,
|
||||
status = nil,
|
||||
_enabled = false,
|
||||
_attemptsCounter = 0,
|
||||
_attemptIntervalCounter = 0,
|
||||
_deadCounter = 0,
|
||||
_firstAttempt = true,
|
||||
_ifaceRestarting = false,
|
||||
_ifaceRestartCounter = 0,
|
||||
_disconnectedAtStartup = false,
|
||||
}
|
||||
|
||||
function Module:toggleIface(flag)
|
||||
if not self.iface then
|
||||
return
|
||||
end
|
||||
return os.execute(
|
||||
string.format("%s %s", (flag and "/sbin/ifup" or "/sbin/ifdown"), self.iface)
|
||||
)
|
||||
end
|
||||
|
||||
function Module:init(t)
|
||||
if t.dead_period ~= nil then
|
||||
self.deadPeriod = tonumber(t.dead_period)
|
||||
end
|
||||
if t.attempts ~= nil then
|
||||
self.attempts = tonumber(t.attempts)
|
||||
end
|
||||
if t.attempt_interval ~= nil then
|
||||
self.attemptInterval = tonumber(t.attempt_interval)
|
||||
end
|
||||
if t.iface ~= nil then
|
||||
self.iface = t.iface
|
||||
end
|
||||
if t.iface_timeout ~= nil then
|
||||
self.ifaceTimeout = tonumber(t.iface_timeout)
|
||||
end
|
||||
if t.any_band ~= nil then
|
||||
self.anyBand = (tonumber(t.any_band) ~= 0)
|
||||
end
|
||||
if tonumber(t.disconnected_at_startup) == 1 then
|
||||
self._disconnectedAtStartup = true
|
||||
end
|
||||
|
||||
if not unistd.access(self.mmcli, "x") then
|
||||
self.anyBand = false
|
||||
end
|
||||
|
||||
if (unistd.access(self.mmInit, "x")
|
||||
and os.execute(string.format("%s enabled", self.mmInit)) == 0) then
|
||||
self._enabled = true
|
||||
else
|
||||
self._enabled = false
|
||||
self.syslog("err", string.format(
|
||||
"%s: modemmanager service is not available", self.name))
|
||||
end
|
||||
end
|
||||
|
||||
function Module:restartMM()
|
||||
if os.execute(string.format("%s enabled", self.mmInit)) == 0 then
|
||||
if self.anyBand then
|
||||
self.syslog("info", string.format(
|
||||
"%s: resetting current-bands to 'any'", self.name))
|
||||
os.execute(string.format("%s -m any --set-current-bands=any", self.mmcli))
|
||||
end
|
||||
|
||||
self.syslog("info", string.format("%s: reconnecting modem", self.name))
|
||||
os.execute(string.format("%s restart", self.mmInit))
|
||||
|
||||
if self.iface then
|
||||
self.syslog("info", string.format(
|
||||
"%s: restarting network interface '%s'", self.name, self.iface))
|
||||
self:toggleIface(false)
|
||||
if self.ifaceTimeout < 1 then
|
||||
self:toggleIface(true)
|
||||
else
|
||||
self._ifaceRestarting = true
|
||||
end
|
||||
end
|
||||
else
|
||||
self.syslog("warning", string.format(
|
||||
"%s: modemmanager service is disabled", self.name))
|
||||
end
|
||||
if self.attempts > 0 then
|
||||
self._attemptsCounter = self._attemptsCounter + 1
|
||||
end
|
||||
end
|
||||
|
||||
function Module:run(currentStatus, lastStatus, timeDiff, timeNow, inetChecked)
|
||||
if not self._enabled then
|
||||
return
|
||||
end
|
||||
if self.iface and self._ifaceRestarting then
|
||||
if self._ifaceRestartCounter >= self.ifaceTimeout then
|
||||
self:toggleIface(true)
|
||||
self._ifaceRestarting = false
|
||||
self._ifaceRestartCounter = 0
|
||||
else
|
||||
self._ifaceRestartCounter = self._ifaceRestartCounter + timeDiff
|
||||
end
|
||||
else
|
||||
if currentStatus == 1 then
|
||||
if self._disconnectedAtStartup and self._deadCounter >= self.deadPeriod then
|
||||
if self.attempts == 0 or self._attemptsCounter < self.attempts then
|
||||
if self._firstAttempt or self._attemptIntervalCounter >= self.attemptInterval then
|
||||
self:restartMM()
|
||||
self._attemptIntervalCounter = 0
|
||||
self._firstAttempt = false
|
||||
else
|
||||
self._attemptIntervalCounter = self._attemptIntervalCounter + timeDiff
|
||||
end
|
||||
end
|
||||
else
|
||||
self._deadCounter = self._deadCounter + timeDiff
|
||||
end
|
||||
else
|
||||
self._attemptsCounter = 0
|
||||
self._attemptIntervalCounter = 0
|
||||
self._deadCounter = 0
|
||||
self._disconnectedAtStartup = true
|
||||
self._firstAttempt = true
|
||||
end
|
||||
self._ifaceRestartCounter = 0
|
||||
end
|
||||
end
|
||||
|
||||
function Module:onExit()
|
||||
return true
|
||||
end
|
||||
|
||||
return Module
|
||||
41
internet-detector-mod-telegram/Makefile
Normal file
@@ -0,0 +1,41 @@
|
||||
#
|
||||
# (с) 2025 gSpot (https://github.com/gSpotx2f/luci-app-internet-detector)
|
||||
#
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=internet-detector-mod-telegram
|
||||
PKG_VERSION:=1.7.0
|
||||
PKG_RELEASE:=1
|
||||
PKG_MAINTAINER:=gSpot <https://github.com/gSpotx2f/luci-app-internet-detector>
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
define Package/$(PKG_NAME)
|
||||
SECTION:=net
|
||||
CATEGORY:=Network
|
||||
TITLE:=Telegram messenger module for internet-detector
|
||||
URL:=https://github.com/gSpotx2f/luci-app-internet-detector
|
||||
PKGARCH:=all
|
||||
DEPENDS:=+internet-detector +curl
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/description
|
||||
Telegram messenger support for internet-detector.
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/conffiles
|
||||
endef
|
||||
|
||||
define Build/Configure
|
||||
endef
|
||||
|
||||
define Build/Compile
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/install
|
||||
$(INSTALL_DIR) $(1)/usr/lib/lua/internet-detector/modules
|
||||
$(INSTALL_DATA) ./files/usr/lib/lua/internet-detector/modules/mod_telegram.lua $(1)/usr/lib/lua/internet-detector/modules/mod_telegram.lua
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,$(PKG_NAME)))
|
||||
@@ -0,0 +1,271 @@
|
||||
--[[
|
||||
Dependences:
|
||||
curl
|
||||
--]]
|
||||
|
||||
local unistd = require("posix.unistd")
|
||||
|
||||
local Module = {
|
||||
name = "mod_telegram",
|
||||
runPrio = 70,
|
||||
syslog = function(level, msg) return true end,
|
||||
debugOutput = function(msg) return true end,
|
||||
writeValue = function(filePath, str) return false end,
|
||||
readValue = function(filePath) return nil end,
|
||||
deadPeriod = 0,
|
||||
alivePeriod = 0,
|
||||
mode = 0, -- 0: connected, 1: disconnected, 2: both
|
||||
hostAlias = "OpenWrt",
|
||||
connectTimeout = 5,
|
||||
tgAPIToken = nil,
|
||||
tgChatId = nil,
|
||||
tgMsgURLpattern = "https://api.telegram.org/bot%s/sendMessage?chat_id=%s&parse_mode=html&text=%s",
|
||||
msgTextPattern = "<strong>[%s] (%s)</strong> @ %s", -- Message (host, instance, message)
|
||||
msgConnectPattern = "Connected: %s",
|
||||
msgDisconnectPattern = "Disconnected: %s",
|
||||
msgSeparator = " | ",
|
||||
msgMaxItems = 50,
|
||||
msgSendAttempts = 3,
|
||||
msgSendTimeout = 5,
|
||||
curlExec = "/usr/bin/curl",
|
||||
curlParams = "-s --no-keepalive",
|
||||
status = nil,
|
||||
_enabled = false,
|
||||
_deadCounter = 0,
|
||||
_aliveCounter = 0,
|
||||
_msgSentDisconnect = true,
|
||||
_disconnected = true,
|
||||
_msgSentConnect = true,
|
||||
_connected = true,
|
||||
_msgBuffer = {},
|
||||
_msgSendCounter = 3,
|
||||
_msgTimeoutCounter = 5,
|
||||
}
|
||||
|
||||
local function prequire(package)
|
||||
local retVal, pkg = pcall(require, package)
|
||||
return retVal and pkg
|
||||
end
|
||||
|
||||
function Module:init(t)
|
||||
self._enabled = true
|
||||
if t.mode ~= nil then
|
||||
self.mode = tonumber(t.mode)
|
||||
end
|
||||
if t.dead_period ~= nil then
|
||||
self.deadPeriod = tonumber(t.dead_period)
|
||||
end
|
||||
if t.alive_period ~= nil then
|
||||
self.alivePeriod = tonumber(t.alive_period)
|
||||
end
|
||||
if t.host_alias then
|
||||
self.hostAlias = t.host_alias
|
||||
else
|
||||
self.hostAlias = self.config.hostname
|
||||
end
|
||||
if t.api_token ~= nil then
|
||||
self.tgAPIToken = t.api_token
|
||||
end
|
||||
if t.chat_id ~= nil then
|
||||
self.tgChatId = t.chat_id
|
||||
end
|
||||
if tonumber(t.message_at_startup) == 1 then
|
||||
self._msgSentDisconnect = false
|
||||
self._disconnected = false
|
||||
self._msgSentConnect = false
|
||||
self._connected = false
|
||||
end
|
||||
|
||||
if unistd.access(self.curlExec, "x") then
|
||||
self._enabled = true
|
||||
else
|
||||
self._enabled = false
|
||||
self.syslog("err", string.format("%s: %s is not available", self.name, self.curlExec))
|
||||
end
|
||||
if not self.tgAPIToken then
|
||||
self._enabled = false
|
||||
self.syslog("err", string.format("%s: Telegram bot API token not specified.", self.name))
|
||||
end
|
||||
if not self.tgChatId then
|
||||
self._enabled = false
|
||||
self.syslog("err", string.format("%s: Telegram chat ID not specified.", self.name))
|
||||
end
|
||||
|
||||
self._msgSendCounter = self.msgSendAttempts
|
||||
end
|
||||
|
||||
function Module:escape(str)
|
||||
local t = {}
|
||||
for i in str:gmatch(".") do
|
||||
if i:match("[^%w_]") then
|
||||
t[#t + 1] = "%" .. string.format("%x", string.byte(i))
|
||||
else
|
||||
t[#t + 1] = i
|
||||
end
|
||||
end
|
||||
return table.concat(t)
|
||||
end
|
||||
|
||||
function Module:appendNotice(str)
|
||||
self._msgBuffer[#self._msgBuffer + 1] = str
|
||||
if #self._msgBuffer > self.msgMaxItems then
|
||||
local t = {}
|
||||
for i = #self._msgBuffer - self.msgMaxItems + 1, #self._msgBuffer do
|
||||
t[#t + 1] = self._msgBuffer[i]
|
||||
end
|
||||
self._msgBuffer = t
|
||||
end
|
||||
end
|
||||
|
||||
function Module:httpRequest(url)
|
||||
local retCode = 1, data
|
||||
local fh = io.popen(string.format(
|
||||
'%s --connect-timeout %s %s "%s"; printf "\n$?";',
|
||||
self.curlExec,
|
||||
self.connectTimeout,
|
||||
self.curlParams,
|
||||
url
|
||||
), "r")
|
||||
if fh then
|
||||
data = fh:read("*a")
|
||||
fh:close()
|
||||
if data ~= nil then
|
||||
local s, e = data:find("[0-9]+\n?$")
|
||||
retCode = tonumber(data:sub(s))
|
||||
data = data:sub(0, s - 2)
|
||||
if not data or data == "" then
|
||||
data = nil
|
||||
end
|
||||
end
|
||||
else
|
||||
retCode = 1
|
||||
end
|
||||
return retCode, data
|
||||
end
|
||||
|
||||
function Module:parseResponse(str)
|
||||
local ok, errCode, desc
|
||||
ok = str:match('"ok":(%w+)')
|
||||
if ok == "false" then
|
||||
errCode = tonumber(str:match('"error_code":(%d+)'))
|
||||
if errCode then
|
||||
desc = str:match('"description":"([%w%s%p_]+)"')
|
||||
end
|
||||
end
|
||||
return ok, errCode, desc
|
||||
end
|
||||
|
||||
function Module:messageRequest(msg, textPattern)
|
||||
local retVal = 1
|
||||
local tgMsg = string.format(
|
||||
textPattern, self.hostAlias, self.config.serviceConfig.instance, msg)
|
||||
local url = string.format(
|
||||
self.tgMsgURLpattern, self.tgAPIToken, self.tgChatId, self:escape(tgMsg))
|
||||
|
||||
local ok, errCode, desc
|
||||
local retCode, data = self:httpRequest(url)
|
||||
if data then
|
||||
ok, errCode, desc = self:parseResponse(data)
|
||||
end
|
||||
if retCode == 0 and ok == "true" then
|
||||
retVal = 0
|
||||
self.syslog("info", string.format(
|
||||
"%s: Message sent to chat %s", self.name, self.tgChatId))
|
||||
else
|
||||
if errCode == 400 or errCode == 406 then
|
||||
retVal = 2
|
||||
elseif (errCode == 401 or
|
||||
errCode == 403 or
|
||||
errCode == 404 or
|
||||
errCode == 420) then
|
||||
retVal = 3
|
||||
end
|
||||
if errCode and desc then
|
||||
self.syslog("warning", string.format(
|
||||
"%s: %s %s", self.name, tostring(errCode), tostring(desc)))
|
||||
end
|
||||
end
|
||||
return retVal
|
||||
end
|
||||
|
||||
function Module:sendMessage(msg, textPattern)
|
||||
local retVal = self:messageRequest(msg, textPattern)
|
||||
if retVal == 0 then
|
||||
self._msgBuffer = {}
|
||||
elseif retVal == 2 then
|
||||
self.syslog("err", string.format(
|
||||
"%s: Server error (invalid API token or chat ID)", self.name))
|
||||
else
|
||||
self.syslog("err", string.format(
|
||||
"%s: An error occured while sending message", self.name))
|
||||
end
|
||||
return retVal
|
||||
end
|
||||
|
||||
function Module:run(currentStatus, lastStatus, timeDiff, timeNow, inetChecked)
|
||||
if not self._enabled then
|
||||
return
|
||||
end
|
||||
|
||||
if currentStatus == 1 then
|
||||
self._aliveCounter = 0
|
||||
self._msgSentConnect = false
|
||||
|
||||
if not self._disconnected then
|
||||
self._disconnected = true
|
||||
self:appendNotice(string.format(
|
||||
self.msgDisconnectPattern, os.date("%Y.%m.%d %H:%M:%S", os.time())))
|
||||
end
|
||||
if not self._msgSentDisconnect and (self.mode == 1 or self.mode == 2) then
|
||||
if self._deadCounter >= self.deadPeriod then
|
||||
self._msgSendCounter = 0
|
||||
self._msgSentDisconnect = true
|
||||
else
|
||||
self._deadCounter = self._deadCounter + timeDiff
|
||||
end
|
||||
end
|
||||
self._connected = false
|
||||
elseif currentStatus == 0 then
|
||||
self._deadCounter = 0
|
||||
self._msgSentDisconnect = false
|
||||
|
||||
if not self._connected then
|
||||
self._connected = true
|
||||
self:appendNotice(string.format(
|
||||
self.msgConnectPattern, os.date("%Y.%m.%d %H:%M:%S", os.time())))
|
||||
end
|
||||
if not self._msgSentConnect and (self.mode == 0 or self.mode == 2) then
|
||||
if self._aliveCounter >= self.alivePeriod then
|
||||
self._msgSendCounter = 0
|
||||
self._msgSentConnect = true
|
||||
else
|
||||
self._aliveCounter = self._aliveCounter + timeDiff
|
||||
end
|
||||
end
|
||||
self._disconnected = false
|
||||
end
|
||||
|
||||
if self._msgSendCounter < self.msgSendAttempts then
|
||||
if self._msgTimeoutCounter >= self.msgSendTimeout then
|
||||
if #self._msgBuffer > 0 then
|
||||
local retVal = self:sendMessage(table.concat(self._msgBuffer, self.msgSeparator), self.msgTextPattern)
|
||||
if retVal == 1 then
|
||||
self._msgSendCounter = self._msgSendCounter + 1
|
||||
else
|
||||
self._msgSendCounter = self.msgSendAttempts
|
||||
end
|
||||
end
|
||||
self._msgTimeoutCounter = 0
|
||||
else
|
||||
self._msgTimeoutCounter = self._msgTimeoutCounter + timeDiff
|
||||
end
|
||||
else
|
||||
self._msgTimeoutCounter = self.msgSendTimeout
|
||||
end
|
||||
end
|
||||
|
||||
function Module:onExit()
|
||||
return true
|
||||
end
|
||||
|
||||
return Module
|
||||
@@ -1,12 +1,12 @@
|
||||
#
|
||||
# (с) 2021 gSpot (https://github.com/gSpotx2f/luci-app-internet-detector)
|
||||
# (с) 2025 gSpot (https://github.com/gSpotx2f/luci-app-internet-detector)
|
||||
#
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=internet-detector
|
||||
PKG_VERSION:=0.6
|
||||
PKG_RELEASE:=0
|
||||
PKG_VERSION:=1.7.0
|
||||
PKG_RELEASE:=1
|
||||
PKG_MAINTAINER:=gSpot <https://github.com/gSpotx2f/luci-app-internet-detector>
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
@@ -17,17 +17,21 @@ define Package/$(PKG_NAME)
|
||||
TITLE:=Internet detector
|
||||
URL:=https://github.com/gSpotx2f/luci-app-internet-detector
|
||||
PKGARCH:=all
|
||||
DEPENDS:=+lua +luci-lib-nixio +libuci-lua
|
||||
DEPENDS:=+lua +luaposix +libuci-lua
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/description
|
||||
Internet-detector is a small daemon
|
||||
for checking Internet availability.
|
||||
Written in Lua using the nixio library.
|
||||
Written in Lua using the luaposix library.
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/conffiles
|
||||
/etc/config/internet-detector
|
||||
/etc/internet-detector/down-script.internet
|
||||
/etc/internet-detector/up-script.internet
|
||||
/etc/internet-detector/public-ip-script.internet
|
||||
/etc/internet-detector/regular-script.internet
|
||||
endef
|
||||
|
||||
define Build/Configure
|
||||
@@ -40,20 +44,24 @@ define Package/$(PKG_NAME)/install
|
||||
$(INSTALL_DIR) $(1)/etc/config
|
||||
$(INSTALL_CONF) ./files/etc/config/internet-detector $(1)/etc/config/internet-detector
|
||||
$(INSTALL_DIR) $(1)/etc/internet-detector
|
||||
$(INSTALL_BIN) ./files/etc/internet-detector/down-script $(1)/etc/internet-detector/down-script
|
||||
$(INSTALL_BIN) ./files/etc/internet-detector/up-script $(1)/etc/internet-detector/up-script
|
||||
$(INSTALL_DATA) ./files/etc/internet-detector/down-script.internet $(1)/etc/internet-detector/down-script.internet
|
||||
$(INSTALL_DATA) ./files/etc/internet-detector/up-script.internet $(1)/etc/internet-detector/up-script.internet
|
||||
$(INSTALL_DATA) ./files/etc/internet-detector/public-ip-script.internet $(1)/etc/internet-detector/public-ip-script.internet
|
||||
$(INSTALL_DATA) ./files/etc/internet-detector/regular-script.internet $(1)/etc/internet-detector/regular-script.internet
|
||||
$(INSTALL_DIR) $(1)/etc/init.d
|
||||
$(INSTALL_BIN) ./files/etc/init.d/internet-detector $(1)/etc/init.d/internet-detector
|
||||
$(INSTALL_DIR) $(1)/usr/bin
|
||||
$(INSTALL_BIN) ./files/usr/bin/internet-detector $(1)/usr/bin/internet-detector
|
||||
$(INSTALL_DIR) $(1)/usr/lib/internet-detector
|
||||
$(INSTALL_DATA) ./files/usr/lib/internet-detector/mod_email.lua $(1)/usr/lib/internet-detector/mod_email.lua
|
||||
$(INSTALL_DATA) ./files/usr/lib/internet-detector/mod_public_ip.lua $(1)/usr/lib/internet-detector/mod_public_ip.lua
|
||||
$(INSTALL_DATA) ./files/usr/lib/internet-detector/mod_led_control.lua $(1)/usr/lib/internet-detector/mod_led_control.lua
|
||||
$(INSTALL_DATA) ./files/usr/lib/internet-detector/mod_modem_restart.lua $(1)/usr/lib/internet-detector/mod_modem_restart.lua
|
||||
$(INSTALL_DATA) ./files/usr/lib/internet-detector/mod_network_restart.lua $(1)/usr/lib/internet-detector/mod_network_restart.lua
|
||||
$(INSTALL_DATA) ./files/usr/lib/internet-detector/mod_reboot.lua $(1)/usr/lib/internet-detector/mod_reboot.lua
|
||||
$(INSTALL_DATA) ./files/usr/lib/internet-detector/mod_user_scripts.lua $(1)/usr/lib/internet-detector/mod_user_scripts.lua
|
||||
$(INSTALL_DIR) $(1)/usr/lib/lua/internet-detector
|
||||
$(INSTALL_DATA) ./files/usr/lib/lua/internet-detector/main.lua $(1)/usr/lib/lua/internet-detector/main.lua
|
||||
$(INSTALL_DATA) ./files/usr/lib/lua/internet-detector/init.lua $(1)/usr/lib/lua/internet-detector/init.lua
|
||||
$(INSTALL_DIR) $(1)/usr/lib/lua/internet-detector/modules
|
||||
$(INSTALL_DATA) ./files/usr/lib/lua/internet-detector/modules/mod_led_control.lua $(1)/usr/lib/lua/internet-detector/modules/mod_led_control.lua
|
||||
$(INSTALL_DATA) ./files/usr/lib/lua/internet-detector/modules/mod_reboot.lua $(1)/usr/lib/lua/internet-detector/modules/mod_reboot.lua
|
||||
$(INSTALL_DATA) ./files/usr/lib/lua/internet-detector/modules/mod_network_restart.lua $(1)/usr/lib/lua/internet-detector/modules/mod_network_restart.lua
|
||||
$(INSTALL_DATA) ./files/usr/lib/lua/internet-detector/modules/mod_public_ip.lua $(1)/usr/lib/lua/internet-detector/modules/mod_public_ip.lua
|
||||
$(INSTALL_DATA) ./files/usr/lib/lua/internet-detector/modules/mod_user_scripts.lua $(1)/usr/lib/lua/internet-detector/modules/mod_user_scripts.lua
|
||||
$(INSTALL_DATA) ./files/usr/lib/lua/internet-detector/modules/mod_regular_script.lua $(1)/usr/lib/lua/internet-detector/modules/mod_regular_script.lua
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,$(PKG_NAME)))
|
||||
|
||||
@@ -1,52 +1,65 @@
|
||||
|
||||
config main 'config'
|
||||
option mode '2'
|
||||
option mode '1'
|
||||
option logging_level '6'
|
||||
|
||||
config instance 'internet'
|
||||
option enabled '1'
|
||||
option description 'Default instance'
|
||||
list hosts '8.8.8.8'
|
||||
list hosts '1.1.1.1'
|
||||
list urls 'https://www.google.com'
|
||||
option check_type '0'
|
||||
option tcp_port '53'
|
||||
option ui_interval_up '6'
|
||||
option ui_interval_down '1'
|
||||
option ui_connection_attempts '1'
|
||||
option ui_connection_timeout '1'
|
||||
option service_interval_up '30'
|
||||
option service_interval_down '5'
|
||||
option service_connection_attempts '2'
|
||||
option service_connection_timeout '2'
|
||||
option service_enable_logger '1'
|
||||
|
||||
config module 'mod_led_control'
|
||||
option enabled '0'
|
||||
|
||||
config module 'mod_reboot'
|
||||
option enabled '0'
|
||||
option dead_period '3600'
|
||||
option force_reboot_delay '300'
|
||||
|
||||
config module 'mod_network_restart'
|
||||
option enabled '0'
|
||||
option dead_period '900'
|
||||
option attempts '1'
|
||||
option restart_timeout '0'
|
||||
|
||||
config module 'mod_modem_restart'
|
||||
option enabled '0'
|
||||
option dead_period '600'
|
||||
option any_band '0'
|
||||
|
||||
config module 'mod_public_ip'
|
||||
option enabled '0'
|
||||
option provider 'opendns1'
|
||||
option interval '600'
|
||||
option timeout '3'
|
||||
|
||||
config module 'mod_email'
|
||||
option enabled '0'
|
||||
option alive_period '0'
|
||||
option mail_smtp 'smtp.gmail.com'
|
||||
option mail_smtp_port '587'
|
||||
option mail_security 'tls'
|
||||
|
||||
config module 'mod_user_scripts'
|
||||
option enabled '0'
|
||||
option alive_period '0'
|
||||
option dead_period '0'
|
||||
option interval_up '30'
|
||||
option interval_down '5'
|
||||
option connection_attempts '2'
|
||||
option connection_timeout '2'
|
||||
option mod_led_control_enabled '0'
|
||||
option mod_reboot_enabled '0'
|
||||
option mod_reboot_disconnected_at_startup '0'
|
||||
option mod_reboot_dead_period '3600'
|
||||
option mod_reboot_force_reboot_delay '300'
|
||||
option mod_network_restart_enabled '0'
|
||||
option mod_network_restart_disconnected_at_startup '0'
|
||||
option mod_network_restart_dead_period '900'
|
||||
option mod_network_restart_attempts '1'
|
||||
option mod_network_restart_attempt_interval '60'
|
||||
option mod_network_restart_device_timeout '0'
|
||||
option mod_modem_restart_enabled '0'
|
||||
option mod_modem_restart_disconnected_at_startup '0'
|
||||
option mod_modem_restart_dead_period '600'
|
||||
option mod_modem_restart_attempts '1'
|
||||
option mod_modem_restart_attempt_interval '60'
|
||||
option mod_modem_restart_iface_timeout '0'
|
||||
option mod_modem_restart_any_band '0'
|
||||
option mod_public_ip_enabled '0'
|
||||
option mod_public_ip_provider 'opendns1'
|
||||
option mod_public_ip_qtype '0'
|
||||
option mod_public_ip_interval '600'
|
||||
option mod_public_ip_interval_failed '60'
|
||||
option mod_public_ip_request_attempts '2'
|
||||
option mod_public_ip_timeout '2'
|
||||
option mod_public_ip_enable_ip_script '0'
|
||||
option mod_email_enabled '0'
|
||||
option mod_email_message_at_startup '0'
|
||||
option mod_email_mode '0'
|
||||
option mod_email_alive_period '0'
|
||||
option mod_email_mail_security 'tls'
|
||||
option mod_telegram_enabled '0'
|
||||
option mod_telegram_message_at_startup '0'
|
||||
option mod_telegram_mode '0'
|
||||
option mod_telegram_dead_period '0'
|
||||
option mod_telegram_alive_period '0'
|
||||
option mod_user_scripts_enabled '0'
|
||||
option mod_user_scripts_alive_period '0'
|
||||
option mod_user_scripts_up_script_attempts '1'
|
||||
option mod_user_scripts_up_script_attempt_interval '60'
|
||||
option mod_user_scripts_connected_at_startup '0'
|
||||
option mod_user_scripts_dead_period '0'
|
||||
option mod_user_scripts_down_script_attempts '1'
|
||||
option mod_user_scripts_down_script_attempt_interval '60'
|
||||
option mod_user_scripts_disconnected_at_startup '0'
|
||||
option mod_regular_script_enabled '0'
|
||||
option mod_regular_script_inet_state '2'
|
||||
option mod_regular_script_interval '3600'
|
||||
|
||||
@@ -3,17 +3,37 @@
|
||||
START=97
|
||||
STOP=01
|
||||
|
||||
ID="/usr/bin/internet-detector"
|
||||
USE_PROCD=1
|
||||
PROG="/usr/bin/internet-detector"
|
||||
|
||||
start() {
|
||||
$ID
|
||||
run_instance() {
|
||||
config_get enabled "$1" enabled "0"
|
||||
if [ $enabled = "1" ]; then
|
||||
procd_open_instance "$1"
|
||||
procd_set_param command "$PROG" "-a" "nodaemon" "-i" "$1"
|
||||
procd_set_param respawn
|
||||
procd_set_param term_timeout 60
|
||||
procd_close_instance
|
||||
fi
|
||||
}
|
||||
|
||||
stop() {
|
||||
$ID stop
|
||||
start_service() {
|
||||
config_load "internet-detector"
|
||||
config_get mode "config" mode "0"
|
||||
if [ $mode = "1" ]; then
|
||||
config_foreach run_instance "instance"
|
||||
fi
|
||||
}
|
||||
|
||||
restart() {
|
||||
stop_service() {
|
||||
$PROG -a stop
|
||||
}
|
||||
|
||||
reload_service() {
|
||||
stop
|
||||
start
|
||||
}
|
||||
|
||||
service_triggers() {
|
||||
procd_add_reload_trigger "internet-detector"
|
||||
}
|
||||
|
||||
0
internet-detector/files/etc/internet-detector/down-script → internet-detector/files/etc/internet-detector/down-script.internet
Executable file → Normal file
@@ -0,0 +1,2 @@
|
||||
# Shell commands that run when the public IP address changes.
|
||||
# New IP is available as value of the $PUBLIC_IP variable.
|
||||
@@ -0,0 +1 @@
|
||||
# Shell commands that are run regularly
|
||||
0
internet-detector/files/etc/internet-detector/up-script → internet-detector/files/etc/internet-detector/up-script.internet
Executable file → Normal file
@@ -5,551 +5,94 @@
|
||||
|
||||
Dependences:
|
||||
lua
|
||||
luci-lib-nixio
|
||||
luaposix
|
||||
libuci-lua
|
||||
|
||||
(с) 2021 gSpot (https://github.com/gSpotx2f/luci-app-internet-detector)
|
||||
(с) 2025 gSpot (https://github.com/gSpotx2f/luci-app-internet-detector)
|
||||
--]]
|
||||
|
||||
-- Default settings
|
||||
local getopt = require("posix.unistd").getopt
|
||||
local InternetDetector = require("internet-detector.main")
|
||||
|
||||
local Config = {
|
||||
mode = 2,
|
||||
enableLogger = true,
|
||||
intervalUp = 30,
|
||||
intervalDown = 5,
|
||||
connectionAttempts = 2,
|
||||
connectionTimeout = 2,
|
||||
UIConnectionAttempts = 1,
|
||||
UIConnectionTimeout = 1,
|
||||
hosts = {
|
||||
[1] = "8.8.8.8",
|
||||
[2] = "1.1.1.1",
|
||||
},
|
||||
tcpPort = 53,
|
||||
pingPacketSize = 56,
|
||||
iface = nil,
|
||||
checkType = 0, -- 0: TCP, 1: ping
|
||||
hostname = "OpenWrt",
|
||||
appName = "internet-detector",
|
||||
commonDir = "/tmp/run",
|
||||
debugLog = "/tmp/internet-detector.debug",
|
||||
pingCmd = "/bin/ping",
|
||||
pingParams = "-c 1",
|
||||
debug = false,
|
||||
modules = {},
|
||||
parsedHosts = {},
|
||||
}
|
||||
Config.configDir = string.format("/etc/%s", Config.appName)
|
||||
Config.modulesDir = string.format("/usr/lib/%s", Config.appName)
|
||||
Config.pidFile = string.format("%s/%s.pid", Config.commonDir, Config.appName)
|
||||
Config.statusFile = string.format("%s/%s.status", Config.commonDir, Config.appName)
|
||||
|
||||
-- Importing packages
|
||||
|
||||
local function prequire(package)
|
||||
local retVal, pkg = pcall(require, package)
|
||||
return retVal and pkg
|
||||
local function help()
|
||||
return table.concat({
|
||||
[1] = string.format(
|
||||
"Usage: %s -a daemon -i <UCI instance> | -a nodaemon -i <UCI instance> | -a debug -i <UCI instance> | -a stop | -S | -I | -U | -h",
|
||||
arg[0]),
|
||||
[2] = " -a ARG action: daemon | nodaemon | debug | stop",
|
||||
[3] = " -i ARG instance: UCI instance name",
|
||||
[4] = " -S status",
|
||||
[5] = " -I inet status",
|
||||
[6] = " -U uipoll",
|
||||
[7] = " -h print this help text"
|
||||
}, "\n")
|
||||
end
|
||||
|
||||
local nixio = prequire("nixio")
|
||||
if not nixio then
|
||||
error("You need to install nixio...")
|
||||
end
|
||||
|
||||
local uci = prequire("uci")
|
||||
if not uci then
|
||||
error("You need to install libuci-lua...")
|
||||
end
|
||||
|
||||
-- Loading settings from UCI
|
||||
|
||||
local uciCursor = uci.cursor()
|
||||
Config.mode = tonumber(uciCursor:get(
|
||||
Config.appName, "config", "mode"))
|
||||
Config.enableLogger = (tonumber(uciCursor:get(
|
||||
Config.appName, "config", "service_enable_logger")) ~= 0)
|
||||
Config.intervalUp = tonumber(uciCursor:get(
|
||||
Config.appName, "config", "service_interval_up"))
|
||||
Config.intervalDown = tonumber(uciCursor:get(
|
||||
Config.appName, "config", "service_interval_down"))
|
||||
Config.connectionAttempts = tonumber(uciCursor:get(
|
||||
Config.appName, "config", "service_connection_attempts"))
|
||||
Config.connectionTimeout = tonumber(uciCursor:get(
|
||||
Config.appName, "config", "service_connection_timeout"))
|
||||
Config.UIConnectionAttempts = tonumber(uciCursor:get(
|
||||
Config.appName, "config", "ui_connection_attempts"))
|
||||
Config.UIConnectionTimeout = tonumber(uciCursor:get(
|
||||
Config.appName, "config", "ui_connection_timeout"))
|
||||
Config.hosts = uciCursor:get(Config.appName, "config", "hosts")
|
||||
|
||||
local tcpPort = uciCursor:get(
|
||||
Config.appName, "config", "tcp_port")
|
||||
if tcpPort ~= nil then
|
||||
Config.tcpPort = tonumber(tcpPort)
|
||||
end
|
||||
|
||||
local pingPacketSize = uciCursor:get(
|
||||
Config.appName, "config", "ping_packet_size")
|
||||
if pingPacketSize ~= nil then
|
||||
Config.pingPacketSize = tonumber(pingPacketSize)
|
||||
end
|
||||
|
||||
local iface = uciCursor:get(
|
||||
Config.appName, "config", "iface")
|
||||
if iface ~= nil then
|
||||
Config.iface = iface
|
||||
end
|
||||
|
||||
Config.checkType = tonumber(uciCursor:get(
|
||||
Config.appName, "config", "check_type"))
|
||||
|
||||
local hostname = uciCursor:get("system", "@[0]", "hostname")
|
||||
if hostname ~= nil then
|
||||
Config.hostname = hostname
|
||||
end
|
||||
|
||||
|
||||
local function writeValueToFile(filePath, str)
|
||||
local retValue = false
|
||||
local fh = io.open(filePath, "w")
|
||||
if fh then
|
||||
fh:setvbuf("no")
|
||||
fh:write(string.format("%s\n", str))
|
||||
fh:close()
|
||||
retValue = true
|
||||
end
|
||||
return retValue
|
||||
end
|
||||
|
||||
local function readValueFromFile(filePath)
|
||||
local retValue
|
||||
local fh = io.open(filePath, "r")
|
||||
if fh then
|
||||
retValue = fh:read("*l")
|
||||
fh:close()
|
||||
end
|
||||
return retValue
|
||||
end
|
||||
|
||||
local function statusJson(inet, t)
|
||||
local lines = { [1] = string.format('"inet":%d', inet) }
|
||||
if t then
|
||||
for k, v in pairs(t) do
|
||||
lines[#lines + 1] = string.format('"%s":"%s"', k, v)
|
||||
end
|
||||
end
|
||||
return "{" .. table.concat(lines, ",") .. "}"
|
||||
end
|
||||
|
||||
local function writeLogMessage(level, msg)
|
||||
if Config.enableLogger then
|
||||
nixio.syslog(level, msg)
|
||||
end
|
||||
end
|
||||
|
||||
local function loadModules()
|
||||
package.path = string.format("%s;%s/?.lua", package.path, Config.modulesDir)
|
||||
Config.modules = {}
|
||||
uciCursor:foreach(
|
||||
Config.appName,
|
||||
"module",
|
||||
function(s)
|
||||
local mod_name = s[".name"]
|
||||
if mod_name and s.enabled == "1" then
|
||||
local m = prequire(mod_name)
|
||||
if m then
|
||||
m.config = Config
|
||||
m.syslog = writeLogMessage
|
||||
m.writeValue = writeValueToFile
|
||||
m.readValue = readValueFromFile
|
||||
m:init(s)
|
||||
Config.modules[#Config.modules + 1] = m
|
||||
end
|
||||
end
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
local function parseHost(host)
|
||||
local addr, port = host:match("^([^:]+):?(%d*)")
|
||||
return addr, tonumber(port) or false
|
||||
end
|
||||
|
||||
local function parseHosts()
|
||||
Config.parsedHosts = {}
|
||||
for k, v in ipairs(Config.hosts) do
|
||||
local addr, port = parseHost(v)
|
||||
Config.parsedHosts[k] = { addr = addr, port = port }
|
||||
end
|
||||
end
|
||||
|
||||
local function pingHost(host)
|
||||
local ping = string.format(
|
||||
"%s %s -W %d -s %d%s %s > /dev/null 2>&1",
|
||||
Config.pingCmd,
|
||||
Config.pingParams,
|
||||
Config.connectionTimeout,
|
||||
Config.pingPacketSize,
|
||||
Config.iface and (" -I " .. Config.iface) or "",
|
||||
host
|
||||
)
|
||||
local retCode = os.execute(ping)
|
||||
|
||||
-- Debug
|
||||
if Config.debug then
|
||||
io.stdout:write(string.format(
|
||||
"--- Ping ---\ntime = %s\n%s\nretCode = %s\n", os.time(), ping, retCode)
|
||||
)
|
||||
io.stdout:flush()
|
||||
end
|
||||
|
||||
return retCode
|
||||
end
|
||||
|
||||
local function TCPConnectionToHost(host, port)
|
||||
local retCode = 1
|
||||
local addrInfo = nixio.getaddrinfo(host, "any")
|
||||
if addrInfo then
|
||||
local family = addrInfo[1].family
|
||||
if family then
|
||||
local socket = nixio.socket(family, "stream")
|
||||
socket:setopt("socket", "sndtimeo", Config.connectionTimeout)
|
||||
socket:setopt("socket", "rcvtimeo", Config.connectionTimeout)
|
||||
if Config.iface then
|
||||
socket:setopt("socket", "bindtodevice", Config.iface)
|
||||
end
|
||||
local success = socket:connect(host, port or Config.tcpPort)
|
||||
|
||||
-- Debug
|
||||
if Config.debug then
|
||||
local sockAddr, sockPort = socket:getsockname()
|
||||
local peerAddr, peerPort = socket:getpeername()
|
||||
io.stdout:write(string.format(
|
||||
"--- TCP ---\ntime = %s\nconnectionTimeout = %s\niface = %s\nhost:port = %s:%s\nsockname = %s:%s\npeername = %s:%s\nsuccess = %s\n",
|
||||
os.time(),
|
||||
Config.connectionTimeout,
|
||||
tostring(Config.iface),
|
||||
host,
|
||||
port or Config.tcpPort,
|
||||
tostring(sockAddr),
|
||||
tostring(sockPort),
|
||||
tostring(peerAddr),
|
||||
tostring(peerPort),
|
||||
tostring(success))
|
||||
)
|
||||
io.stdout:flush()
|
||||
end
|
||||
|
||||
socket:close()
|
||||
retCode = success and 0 or 1
|
||||
end
|
||||
end
|
||||
return retCode
|
||||
end
|
||||
|
||||
local function checkHosts()
|
||||
local checkFunc = (Config.checkType == 1) and pingHost or TCPConnectionToHost
|
||||
local retCode = 1
|
||||
for k, v in ipairs(Config.parsedHosts) do
|
||||
for i = 1, Config.connectionAttempts do
|
||||
if checkFunc(v.addr, v.port) == 0 then
|
||||
retCode = 0
|
||||
break
|
||||
end
|
||||
end
|
||||
if retCode == 0 then
|
||||
break
|
||||
end
|
||||
end
|
||||
return retCode
|
||||
end
|
||||
|
||||
local function main()
|
||||
local lastStatus, currentStatus, timeNow, timeDiff, lastTime
|
||||
local interval = Config.intervalUp
|
||||
local counter = 0
|
||||
|
||||
while true do
|
||||
if counter == 0 or counter >= interval then
|
||||
currentStatus = checkHosts()
|
||||
if not nixio.fs.access(Config.statusFile, "r") then
|
||||
writeValueToFile(Config.statusFile, statusJson(currentStatus))
|
||||
end
|
||||
|
||||
if currentStatus == 0 then
|
||||
interval = Config.intervalUp
|
||||
if lastStatus ~= nil and currentStatus ~= lastStatus then
|
||||
writeValueToFile(Config.statusFile, statusJson(currentStatus))
|
||||
writeLogMessage("notice", "Internet connected")
|
||||
local action, instance
|
||||
local params = {}
|
||||
local last_index = 1
|
||||
for r, optarg, optind in getopt(arg, "a:i:SIUh") do
|
||||
if r == "?" then
|
||||
print("Error! Unrecognized option")
|
||||
os.exit(1)
|
||||
end
|
||||
last_index = optind
|
||||
if r == "a" then
|
||||
action = optarg
|
||||
elseif r == "i" then
|
||||
instance = optarg
|
||||
else
|
||||
interval = Config.intervalDown
|
||||
if lastStatus ~= nil and currentStatus ~= lastStatus then
|
||||
writeValueToFile(Config.statusFile, statusJson(currentStatus))
|
||||
writeLogMessage("notice", "Internet disconnected")
|
||||
params[#params + 1] = r
|
||||
end
|
||||
end
|
||||
counter = 0
|
||||
if action == "stop" then
|
||||
InternetDetector:stop()
|
||||
elseif action then
|
||||
if not instance then
|
||||
print("Error! Instance not specified [-i]")
|
||||
os.exit(1)
|
||||
end
|
||||
|
||||
timeDiff = 0
|
||||
for _, e in ipairs(Config.modules) do
|
||||
timeNow = nixio.sysinfo().uptime
|
||||
if lastTime then
|
||||
timeDiff = timeDiff + timeNow - lastTime
|
||||
else
|
||||
timeDiff = 1
|
||||
end
|
||||
lastTime = timeNow
|
||||
e:run(currentStatus, lastStatus, timeDiff)
|
||||
end
|
||||
|
||||
local modulesStatus = {}
|
||||
for k, v in ipairs(Config.modules) do
|
||||
if v.status ~= nil then
|
||||
modulesStatus[v.name] = v.status
|
||||
end
|
||||
end
|
||||
if next(modulesStatus) then
|
||||
writeValueToFile(Config.statusFile, statusJson(currentStatus, modulesStatus))
|
||||
end
|
||||
|
||||
lastStatus = currentStatus
|
||||
nixio.nanosleep(1)
|
||||
counter = counter + 1
|
||||
end
|
||||
end
|
||||
|
||||
local function removeProcessFiles()
|
||||
os.remove(Config.pidFile)
|
||||
os.remove(Config.statusFile)
|
||||
end
|
||||
|
||||
local function status()
|
||||
if nixio.fs.access(Config.pidFile, "r") then
|
||||
return "running"
|
||||
else
|
||||
return "stoped"
|
||||
end
|
||||
end
|
||||
|
||||
local function poll(attempts, timeout)
|
||||
if Config.mode == 1 then
|
||||
Config.connectionAttempts = Config.UIConnectionAttempts
|
||||
Config.connectionTimeout = Config.UIConnectionTimeout
|
||||
end
|
||||
if attempts then
|
||||
Config.connectionAttempts = attempts
|
||||
end
|
||||
if timeout then
|
||||
Config.connectionTimeout = timeout
|
||||
end
|
||||
if checkHosts() == 0 then
|
||||
return statusJson(0)
|
||||
else
|
||||
return statusJson(1)
|
||||
end
|
||||
end
|
||||
|
||||
local function inetStatus(json)
|
||||
local inetStat = 1
|
||||
if nixio.fs.access(Config.statusFile, "r") then
|
||||
local inetStatVal = readValueFromFile(Config.statusFile)
|
||||
inetStat = inetStatVal
|
||||
elseif Config.mode == 1 then
|
||||
inetStat = poll()
|
||||
if action == "daemon" then
|
||||
if InternetDetector:setServiceConfig(instance) then
|
||||
InternetDetector:daemon()
|
||||
else
|
||||
os.exit(126)
|
||||
end
|
||||
if not json then
|
||||
local sVal = inetStat:match('"inet":[0-9]')
|
||||
if sVal then
|
||||
sVal = sVal:match("[0-9]")
|
||||
inetStat = (tonumber(sVal) == 0) and "up" or "down"
|
||||
elseif action == "nodaemon" then
|
||||
if InternetDetector:setServiceConfig(instance) then
|
||||
InternetDetector:noDaemon()
|
||||
else
|
||||
os.exit(126)
|
||||
end
|
||||
elseif action == "debug" then
|
||||
if InternetDetector:setServiceConfig(instance) then
|
||||
InternetDetector.debug = true
|
||||
InternetDetector:noDaemon()
|
||||
else
|
||||
os.exit(126)
|
||||
end
|
||||
return inetStat
|
||||
else
|
||||
print("Error! Wrong action [-a]")
|
||||
os.exit(1)
|
||||
end
|
||||
|
||||
local function stop()
|
||||
local pidValue
|
||||
if Config.enableLogger then
|
||||
nixio.openlog(Config.appName)
|
||||
else
|
||||
if params[1] == "S" then
|
||||
print(InternetDetector:status())
|
||||
elseif params[1] == "I" then
|
||||
print(InternetDetector:inetStatus())
|
||||
elseif params[1] == "U" then
|
||||
if InternetDetector:status() == "stoped" then
|
||||
os.exit(126)
|
||||
else
|
||||
InternetDetector:setSIGUSR()
|
||||
print(InternetDetector:inetStatus())
|
||||
end
|
||||
if nixio.fs.access(Config.pidFile, "r") then
|
||||
pidValue = readValueFromFile(Config.pidFile)
|
||||
if pidValue then
|
||||
local success
|
||||
for i = 0, 10 do
|
||||
success = nixio.kill(tonumber(pidValue), 15)
|
||||
if success then
|
||||
break
|
||||
end
|
||||
end
|
||||
if not success then
|
||||
io.stderr:write(string.format('No such process: "%s"\n', pidValue))
|
||||
end
|
||||
writeLogMessage("info", string.format("[%s] stoped", pidValue))
|
||||
removeProcessFiles()
|
||||
end
|
||||
end
|
||||
if not pidValue then
|
||||
io.stderr:write(
|
||||
string.format('PID file "%s" does not exist. %s not running?\n',
|
||||
Config.pidFile, Config.appName))
|
||||
end
|
||||
if Config.enableLogger then
|
||||
nixio.closelog()
|
||||
end
|
||||
end
|
||||
|
||||
local function preRun()
|
||||
-- Exit if internet-detector mode != 2(Service)
|
||||
if Config.mode ~= 2 then
|
||||
io.stderr:write(string.format('Start failed, mode != 2\n', Config.appName))
|
||||
os.exit(0)
|
||||
end
|
||||
if nixio.fs.access(Config.pidFile, "r") then
|
||||
io.stderr:write(
|
||||
string.format('PID file "%s" already exist. %s already running?\n',
|
||||
Config.pidFile, Config.appName))
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
local function run()
|
||||
local pidValue = nixio.getpid()
|
||||
writeValueToFile(Config.pidFile, pidValue)
|
||||
if Config.enableLogger then
|
||||
nixio.openlog(Config.appName, "pid")
|
||||
end
|
||||
writeLogMessage("info", "started")
|
||||
loadModules()
|
||||
|
||||
-- Loaded modules
|
||||
local modules = {}
|
||||
for _, v in ipairs(Config.modules) do
|
||||
modules[#modules + 1] = string.format("%s", v.name)
|
||||
end
|
||||
if #modules > 0 then
|
||||
writeLogMessage(
|
||||
"info", string.format("Loaded modules: %s", table.concat(modules, ", "))
|
||||
)
|
||||
end
|
||||
|
||||
-- Debug
|
||||
if Config.debug then
|
||||
|
||||
local function inspectTable()
|
||||
local tables = {}, f
|
||||
f = function(t, prefix)
|
||||
tables[t] = true
|
||||
for k, v in pairs(t) do
|
||||
io.stdout:write(string.format(
|
||||
"%s%s = %s\n", prefix, k, tostring(v))
|
||||
)
|
||||
if type(v) == "table" and not tables[v] then
|
||||
f(v, string.format("%s%s.", prefix, k))
|
||||
end
|
||||
end
|
||||
end
|
||||
return f
|
||||
end
|
||||
|
||||
io.stdout:write("--- Config ---\n")
|
||||
inspectTable()(Config, "Config.")
|
||||
io.stdout:flush()
|
||||
end
|
||||
|
||||
main()
|
||||
if Config.enableLogger then
|
||||
nixio.closelog()
|
||||
end
|
||||
end
|
||||
|
||||
local function noDaemon()
|
||||
if not preRun() then
|
||||
return
|
||||
end
|
||||
run()
|
||||
end
|
||||
|
||||
local function daemon(debug)
|
||||
if not preRun() then
|
||||
return
|
||||
end
|
||||
-- UNIX double fork
|
||||
if nixio.fork() == 0 then
|
||||
nixio.setsid()
|
||||
if nixio.fork() == 0 then
|
||||
nixio.chdir("/")
|
||||
nixio.umask(0)
|
||||
local output = "/dev/null"
|
||||
if debug then
|
||||
output = Config.debugLog
|
||||
Config.debug = true
|
||||
end
|
||||
io.stdout:flush()
|
||||
io.stderr:flush()
|
||||
nixio.dup(io.open("/dev/null", "r"), io.stdin)
|
||||
nixio.dup(io.open(output, "a+"), io.stdout)
|
||||
nixio.dup(io.open(output, "a+"), io.stderr)
|
||||
run()
|
||||
end
|
||||
os.exit(0)
|
||||
end
|
||||
os.exit(0)
|
||||
end
|
||||
|
||||
local function restart()
|
||||
stop()
|
||||
daemon()
|
||||
end
|
||||
|
||||
-- Main section
|
||||
|
||||
parseHosts()
|
||||
|
||||
local function help()
|
||||
return string.format(
|
||||
"Usage: %s [start|stop|restart|no-daemon|debug|status|inet-status|inet-status-json|poll [<attempts num>] [<timeout sec>]|--help]",
|
||||
arg[0]
|
||||
)
|
||||
end
|
||||
|
||||
local helpArgs = { ["-h"] = true, ["--help"] = true, ["help"] = true }
|
||||
if arg[1] == "start" or #arg == 0 then
|
||||
daemon()
|
||||
elseif arg[1] == "no-daemon" then
|
||||
noDaemon()
|
||||
elseif arg[1] == "debug" then
|
||||
daemon(true)
|
||||
elseif arg[1] == "stop" then
|
||||
stop()
|
||||
elseif arg[1] == "restart" then
|
||||
restart()
|
||||
elseif arg[1] == "status" then
|
||||
print(status())
|
||||
elseif arg[1] == "inet-status" then
|
||||
print(inetStatus())
|
||||
elseif arg[1] == "inet-status-json" then
|
||||
print(inetStatus(true))
|
||||
elseif arg[1] == "poll" then
|
||||
local attempts, timeout
|
||||
if arg[2] and arg[2]:match("[0-9]+") then
|
||||
attempts = tonumber(arg[2])
|
||||
if arg[3] and arg[3]:match("[0-9]+") then
|
||||
timeout = tonumber(arg[3])
|
||||
end
|
||||
end
|
||||
print(poll(attempts, timeout))
|
||||
elseif helpArgs[arg[1]] then
|
||||
elseif params[1] == "h" then
|
||||
print(help())
|
||||
else
|
||||
print(help())
|
||||
os.exit(1)
|
||||
end
|
||||
end
|
||||
|
||||
os.exit(0)
|
||||
|
||||
@@ -1,139 +0,0 @@
|
||||
--[[
|
||||
Dependences:
|
||||
mailsend
|
||||
--]]
|
||||
local nixio = require("nixio")
|
||||
|
||||
local Module = {
|
||||
name = "mod_email",
|
||||
config = {},
|
||||
syslog = function(level, msg) return true end,
|
||||
writeValue = function(filePath, str) return false end,
|
||||
readValue = function(filePath) return nil end,
|
||||
alivePeriod = 0,
|
||||
hostAlias = "OpenWrt",
|
||||
mta = "/usr/bin/mailsend",
|
||||
mailRecipient = "email@gmail.com",
|
||||
mailSender = "email@gmail.com",
|
||||
mailUser = "email@gmail.com",
|
||||
mailPassword = "password",
|
||||
mailSmtp = "smtp.gmail.com",
|
||||
mailSmtpPort = '587',
|
||||
mailSecurity = "tls",
|
||||
status = nil,
|
||||
_enabled = false,
|
||||
_aliveCounter = 0,
|
||||
_msgSent = true,
|
||||
_disconnected = true,
|
||||
_lastDisconnection = nil,
|
||||
_lastConnection = nil,
|
||||
}
|
||||
|
||||
function Module:init(t)
|
||||
self.alivePeriod = tonumber(t.alive_period)
|
||||
if t.host_alias then
|
||||
self.hostAlias = t.host_alias
|
||||
else
|
||||
self.hostAlias = self.config.hostname
|
||||
end
|
||||
|
||||
self.mailRecipient = t.mail_recipient
|
||||
self.mailSender = t.mail_sender
|
||||
self.mailUser = t.mail_user
|
||||
self.mailPassword = t.mail_password
|
||||
self.mailSmtp = t.mail_smtp
|
||||
self.mailSmtpPort = t.mail_smtp_port
|
||||
self.mailSecurity = t.mail_security
|
||||
|
||||
if nixio.fs.access(self.mta, "x") then
|
||||
self._enabled = true
|
||||
else
|
||||
self._enabled = false
|
||||
self.syslog("warning", string.format("%s: %s is not available", self.name, self.mta))
|
||||
end
|
||||
|
||||
if (not self.mailRecipient or
|
||||
not self.mailSender or
|
||||
not self.mailUser or
|
||||
not self.mailPassword or
|
||||
not self.mailSmtp or
|
||||
not self.mailSmtpPort) then
|
||||
self._enabled = false
|
||||
self.syslog("warning", string.format(
|
||||
"%s: Insufficient data to connect to the SMTP server", self.name))
|
||||
end
|
||||
end
|
||||
|
||||
function Module:sendMessage(msg)
|
||||
local verboseArg = ""
|
||||
-- Debug
|
||||
if self.config.debug then
|
||||
verboseArg = " -v"
|
||||
io.stdout:write("--- mod_email ---\n")
|
||||
io.stdout:flush()
|
||||
end
|
||||
local securityArgs = "-starttls -auth-login"
|
||||
if self.mailSecurity == "ssl" then
|
||||
securityArgs = "-ssl -auth"
|
||||
end
|
||||
local mtaCmd = string.format(
|
||||
'%s%s %s -smtp "%s" -port %s -cs utf-8 -user "%s" -pass "%s" -f "%s" -t "%s" -sub "%s" -M "%s"',
|
||||
self.mta, verboseArg, securityArgs, self.mailSmtp, self.mailSmtpPort,
|
||||
self.mailUser, self.mailPassword, self.mailSender, self.mailRecipient,
|
||||
string.format("%s notification", self.hostAlias),
|
||||
string.format("%s:\n%s", self.hostAlias, msg))
|
||||
|
||||
if os.execute(mtaCmd) ~= 0 then
|
||||
self.syslog("err", string.format(
|
||||
"%s: An error occured while sending message", self.name))
|
||||
else
|
||||
self.syslog("info", string.format(
|
||||
"%s: Message sent to %s", self.name, self.mailRecipient))
|
||||
end
|
||||
end
|
||||
|
||||
function Module:run(currentStatus, lastStatus, timeDiff)
|
||||
if not self._enabled then
|
||||
return
|
||||
end
|
||||
|
||||
if currentStatus == 1 then
|
||||
self._aliveCounter = 0
|
||||
self._msgSent = false
|
||||
self._lastConnection = nil
|
||||
if not self._disconnected then
|
||||
self._disconnected = true
|
||||
if not self._lastDisconnection then
|
||||
self._lastDisconnection = os.date("%Y.%m.%d %H:%M:%S", os.time())
|
||||
end
|
||||
end
|
||||
else
|
||||
if not self._msgSent then
|
||||
|
||||
if not self._lastConnection then
|
||||
self._lastConnection = os.date("%Y.%m.%d %H:%M:%S", os.time())
|
||||
end
|
||||
|
||||
if self._aliveCounter >= self.alivePeriod then
|
||||
local message = {}
|
||||
if self._lastDisconnection then
|
||||
message[#message + 1] = string.format(
|
||||
"Internet disconnected: %s", self._lastDisconnection)
|
||||
self._lastDisconnection = nil
|
||||
end
|
||||
if self._lastConnection then
|
||||
message[#message + 1] = string.format(
|
||||
"Internet connected: %s", self._lastConnection)
|
||||
self:sendMessage(table.concat(message, ", "))
|
||||
self._msgSent = true
|
||||
end
|
||||
else
|
||||
self._aliveCounter = self._aliveCounter + timeDiff
|
||||
end
|
||||
end
|
||||
|
||||
self._disconnected = false
|
||||
end
|
||||
end
|
||||
|
||||
return Module
|
||||
@@ -1,91 +0,0 @@
|
||||
|
||||
local nixio = require("nixio")
|
||||
|
||||
local Module = {
|
||||
name = "mod_led_control",
|
||||
config = {},
|
||||
syslog = function(level, msg) return true end,
|
||||
writeValue = function(filePath, str) return false end,
|
||||
readValue = function(filePath) return nil end,
|
||||
runInterval = 5,
|
||||
sysLedsDir = "/sys/class/leds",
|
||||
ledName = nil,
|
||||
status = nil,
|
||||
_enabled = false,
|
||||
_ledDir = nil,
|
||||
_ledMaxBrightnessFile = nil,
|
||||
_ledBrightnessFile = nil,
|
||||
_ledMaxBrightness = nil,
|
||||
_counter = 0,
|
||||
}
|
||||
|
||||
function Module:resetLeds()
|
||||
local dir = nixio.fs.dir(self.sysLedsDir)
|
||||
if not dir then
|
||||
return
|
||||
end
|
||||
for led in dir do
|
||||
local brightness = string.format("%s/%s/brightness", self.sysLedsDir, led)
|
||||
if nixio.fs.access(brightness, "w") then
|
||||
self.writeValue(brightness, 0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Module:init(t)
|
||||
self.ledName = t.led_name
|
||||
if not self.ledName then
|
||||
return
|
||||
end
|
||||
self._ledDir = string.format("%s/%s", self.sysLedsDir, self.ledName)
|
||||
self._ledMaxBrightnessFile = string.format("%s/max_brightness", self._ledDir)
|
||||
self._ledBrightnessFile = string.format("%s/brightness", self._ledDir)
|
||||
self._ledMaxBrightness = self.readValue(self._ledMaxBrightnessFile) or 1
|
||||
if (not nixio.fs.access(self._ledDir, "r") or
|
||||
not nixio.fs.access(self._ledBrightnessFile, "r", "w")) then
|
||||
self._enabled = false
|
||||
self.syslog("warning", string.format("%s: LED '%s' is not available", self.name, self.ledName))
|
||||
else
|
||||
self._enabled = true
|
||||
-- Reset all LEDs
|
||||
--self:resetLeds()
|
||||
end
|
||||
end
|
||||
|
||||
function Module:getCurrentState()
|
||||
local state = self.readValue(self._ledBrightnessFile)
|
||||
if state and tonumber(state) > 0 then
|
||||
return tonumber(state)
|
||||
end
|
||||
end
|
||||
|
||||
function Module:on()
|
||||
self.writeValue(self._ledBrightnessFile, self._ledMaxBrightness)
|
||||
end
|
||||
|
||||
function Module:off()
|
||||
self.writeValue(self._ledBrightnessFile, 0)
|
||||
end
|
||||
|
||||
function Module:run(currentStatus, lastStatus, timeDiff)
|
||||
if not self._enabled then
|
||||
return
|
||||
end
|
||||
if self._counter == 0 or self._counter >= self.runInterval or currentStatus ~= lastStatus then
|
||||
|
||||
if currentStatus == 0 then
|
||||
if not self:getCurrentState() then
|
||||
self:on()
|
||||
end
|
||||
else
|
||||
if self:getCurrentState() then
|
||||
self:off()
|
||||
end
|
||||
end
|
||||
|
||||
self._counter = 0
|
||||
end
|
||||
self._counter = self._counter + timeDiff
|
||||
end
|
||||
|
||||
return Module
|
||||
@@ -1,85 +0,0 @@
|
||||
--[[
|
||||
Dependences:
|
||||
modemmanager
|
||||
--]]
|
||||
local nixio = require("nixio")
|
||||
|
||||
local Module = {
|
||||
name = "mod_modem_restart",
|
||||
config = {},
|
||||
syslog = function(level, msg) return true end,
|
||||
writeValue = function(filePath, str) return false end,
|
||||
readValue = function(filePath) return nil end,
|
||||
mmcli = "/usr/bin/mmcli",
|
||||
mmInit = "/etc/init.d/modemmanager",
|
||||
deadPeriod = 0,
|
||||
iface = nil,
|
||||
anyBand = false,
|
||||
status = nil,
|
||||
_enabled = false,
|
||||
_deadCounter = 0,
|
||||
_restarted = false,
|
||||
}
|
||||
|
||||
function Module:toggleIface(flag)
|
||||
return os.execute(
|
||||
string.format("%s %s", (flag and "/sbin/ifup" or "/sbin/ifdown"), self.iface)
|
||||
)
|
||||
end
|
||||
|
||||
function Module:restartMM()
|
||||
if self.anyBand then
|
||||
self.syslog("info", string.format(
|
||||
"%s: resetting current-bands to 'any'", self.name))
|
||||
os.execute(string.format("%s -m any --set-current-bands=any", self.mmcli))
|
||||
end
|
||||
|
||||
self.syslog("info", string.format("%s: reconnecting modem", self.name))
|
||||
os.execute(string.format("%s restart", self.mmInit))
|
||||
|
||||
if self.iface then
|
||||
self.syslog("info", string.format(
|
||||
"%s: restarting network interface '%s'", self.name, self.iface))
|
||||
self:toggleIface(false)
|
||||
self:toggleIface(true)
|
||||
end
|
||||
end
|
||||
|
||||
function Module:init(t)
|
||||
self.deadPeriod = tonumber(t.dead_period)
|
||||
self.iface = t.iface
|
||||
self.anyBand = (tonumber(t.any_band) ~= 0)
|
||||
|
||||
if not nixio.fs.access(self.mmcli, "x") then
|
||||
self.anyBand = false
|
||||
end
|
||||
|
||||
if nixio.fs.access(self.mmInit, "x") then
|
||||
self._enabled = true
|
||||
else
|
||||
self._enabled = false
|
||||
self.syslog("warning", string.format(
|
||||
"%s: modemmanager service is not available", self.name))
|
||||
end
|
||||
end
|
||||
|
||||
function Module:run(currentStatus, lastStatus, timeDiff)
|
||||
if not self._enabled then
|
||||
return
|
||||
end
|
||||
if currentStatus == 1 then
|
||||
if not self._restarted then
|
||||
if self._deadCounter >= self.deadPeriod then
|
||||
self:restartMM()
|
||||
self._restarted = true
|
||||
else
|
||||
self._deadCounter = self._deadCounter + timeDiff
|
||||
end
|
||||
end
|
||||
else
|
||||
self._deadCounter = 0
|
||||
self._restarted = false
|
||||
end
|
||||
end
|
||||
|
||||
return Module
|
||||
@@ -1,92 +0,0 @@
|
||||
|
||||
local nixio = require("nixio")
|
||||
|
||||
local Module = {
|
||||
name = "mod_network_restart",
|
||||
config = {},
|
||||
syslog = function(level, msg) return true end,
|
||||
writeValue = function(filePath, str) return false end,
|
||||
readValue = function(filePath) return nil end,
|
||||
iface = false,
|
||||
attempts = 0,
|
||||
deadPeriod = 0,
|
||||
restartTimeout = 0,
|
||||
status = nil,
|
||||
_attemptsCounter = 0,
|
||||
_deadCounter = 0,
|
||||
}
|
||||
|
||||
function Module:toggleFunc(flag)
|
||||
return
|
||||
end
|
||||
|
||||
function Module:toggleDevice(flag)
|
||||
local ip = "/sbin/ip"
|
||||
if nixio.fs.access(ip, "x") then
|
||||
return os.execute(
|
||||
string.format("%s link set dev %s %s", ip, self.iface, (flag and "up" or "down"))
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
function Module:toggleIface(flag)
|
||||
return os.execute(
|
||||
string.format("%s %s", (flag and "/sbin/ifup" or "/sbin/ifdown"), self.iface)
|
||||
)
|
||||
end
|
||||
|
||||
function Module:ifaceUp()
|
||||
self:toggleFunc(true)
|
||||
end
|
||||
|
||||
function Module:ifaceDown()
|
||||
self:toggleFunc(false)
|
||||
end
|
||||
|
||||
function Module:networkRestart()
|
||||
return os.execute("/etc/init.d/network restart")
|
||||
end
|
||||
|
||||
function Module:init(t)
|
||||
local iface = t.iface
|
||||
if iface then
|
||||
self.iface = iface
|
||||
if self.iface:match("^@") then
|
||||
self.iface = self.iface:gsub("^@", "")
|
||||
self.toggleFunc = self.toggleIface
|
||||
else
|
||||
self.toggleFunc = self.toggleDevice
|
||||
end
|
||||
end
|
||||
self.attempts = tonumber(t.attempts)
|
||||
self.deadPeriod = tonumber(t.dead_period)
|
||||
self.restartTimeout = tonumber(t.restart_timeout)
|
||||
end
|
||||
|
||||
function Module:run(currentStatus, lastStatus, timeDiff)
|
||||
if currentStatus == 1 then
|
||||
if self.attempts == 0 or self._attemptsCounter < self.attempts then
|
||||
if self._deadCounter >= self.deadPeriod then
|
||||
if self.iface then
|
||||
self.syslog("info", string.format(
|
||||
"%s: restarting network interface '%s'", self.name, self.iface))
|
||||
self:ifaceDown()
|
||||
nixio.nanosleep(self.restartTimeout)
|
||||
self:ifaceUp()
|
||||
else
|
||||
self.syslog("info", string.format("%s: restarting network", self.name))
|
||||
self:networkRestart()
|
||||
end
|
||||
self._deadCounter = 0
|
||||
self._attemptsCounter = self._attemptsCounter + 1
|
||||
else
|
||||
self._deadCounter = self._deadCounter + timeDiff
|
||||
end
|
||||
end
|
||||
else
|
||||
self._attemptsCounter = 0
|
||||
self._deadCounter = 0
|
||||
end
|
||||
end
|
||||
|
||||
return Module
|
||||
@@ -1,139 +0,0 @@
|
||||
|
||||
local nixio = require("nixio")
|
||||
|
||||
local Module = {
|
||||
name = "mod_public_ip",
|
||||
config = {},
|
||||
syslog = function(level, msg) return true end,
|
||||
writeValue = function(filePath, str) return false end,
|
||||
readValue = function(filePath) return nil end,
|
||||
runInterval = 600,
|
||||
nslookup = "/usr/bin/nslookup",
|
||||
timeout = 3,
|
||||
providers = {
|
||||
opendns1 = {
|
||||
name = "opendns1", server = "208.67.222.222",
|
||||
host = "myip.opendns.com", queryType = "a"
|
||||
},
|
||||
opendns2 = {
|
||||
name = "opendns2", server = "208.67.220.220",
|
||||
host = "myip.opendns.com", queryType = "a"
|
||||
},
|
||||
opendns3 = {
|
||||
name = "opendns3", server = "208.67.222.220",
|
||||
host = "myip.opendns.com", queryType = "a"
|
||||
},
|
||||
opendns4 = {
|
||||
name = "opendns4", server = "208.67.220.222",
|
||||
host = "myip.opendns.com", queryType = "a"
|
||||
},
|
||||
akamai = {
|
||||
name = "akamai", server = "ns1-1.akamaitech.net",
|
||||
host = "whoami.akamai.net", queryType = "a"
|
||||
},
|
||||
google = {
|
||||
name = "google", server = "ns1.google.com",
|
||||
host = "o-o.myaddr.l.google.com", queryType = "txt"
|
||||
},
|
||||
},
|
||||
status = nil,
|
||||
_provider = nil,
|
||||
_nslookupCmd = nil,
|
||||
_currentIp = nil,
|
||||
_enabled = false,
|
||||
_counter = 0,
|
||||
}
|
||||
|
||||
function Module:parseA(str)
|
||||
res = str:match("Name:%s+" .. self._provider.host .. "\nAddress:%s+[%w.:]+")
|
||||
if res then
|
||||
return res:match("[%w.:]+$")
|
||||
end
|
||||
end
|
||||
|
||||
function Module:parseGoogle(str)
|
||||
res = str:match(self._provider.host .. '%s+text%s+=%s+"[%w.:]+"')
|
||||
if res then
|
||||
return res:gsub('"', ''):match("[%w.:]+$")
|
||||
end
|
||||
end
|
||||
|
||||
function Module:resolveIP()
|
||||
local res
|
||||
local fh = io.popen(self._nslookupCmd, "r")
|
||||
if fh then
|
||||
output = fh:read("*a")
|
||||
fh:close()
|
||||
if self._provider.name == "google" then
|
||||
res = self:parseGoogle(output)
|
||||
else
|
||||
res = self:parseA(output)
|
||||
end
|
||||
else
|
||||
self.syslog("err", string.format(
|
||||
"%s: Nslookup call failed (%s)", self.name, self.nslookup))
|
||||
end
|
||||
return res or "Undefined"
|
||||
end
|
||||
|
||||
function Module:init(t)
|
||||
if t.interval then
|
||||
self.runInterval = tonumber(t.interval)
|
||||
end
|
||||
if t.timeout then
|
||||
self.timeout = tonumber(t.timeout)
|
||||
end
|
||||
if t.provider then
|
||||
self._provider = self.providers[t.provider]
|
||||
else
|
||||
self._provider = self.providers.opendns1
|
||||
end
|
||||
if not nixio.fs.access(self.nslookup, "x") then
|
||||
self._enabled = false
|
||||
self.syslog(
|
||||
"warning",
|
||||
string.format("%s: '%s' does not exists", self.name, self.nslookup)
|
||||
)
|
||||
else
|
||||
self._enabled = true
|
||||
self._nslookupCmd = string.format(
|
||||
"%s -type=%s -timeout=%d %s %s",
|
||||
self.nslookup,
|
||||
self._provider.queryType,
|
||||
self.timeout,
|
||||
self._provider.host,
|
||||
self._provider.server
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
function Module:run(currentStatus, lastStatus, timeDiff)
|
||||
if not self._enabled then
|
||||
return
|
||||
end
|
||||
if currentStatus == 0 then
|
||||
if self._counter == 0 or self._counter >= self.runInterval or currentStatus ~= lastStatus then
|
||||
local ip = self:resolveIP()
|
||||
if ip ~= self._currentIp then
|
||||
self.status = ip
|
||||
self.syslog(
|
||||
"notice",
|
||||
string.format("%s: public IP address %s", self.name, ip)
|
||||
)
|
||||
else
|
||||
self.status = nil
|
||||
end
|
||||
self._currentIp = ip
|
||||
self._counter = 0
|
||||
else
|
||||
self.status = nil
|
||||
end
|
||||
else
|
||||
self.status = nil
|
||||
self._currentIp = nil
|
||||
self._counter = 0
|
||||
end
|
||||
self._counter = self._counter + timeDiff
|
||||
end
|
||||
|
||||
return Module
|
||||
@@ -1,47 +0,0 @@
|
||||
|
||||
local nixio = require("nixio")
|
||||
|
||||
local Module = {
|
||||
name = "mod_reboot",
|
||||
config = {},
|
||||
syslog = function(level, msg) return true end,
|
||||
writeValue = function(filePath, str) return false end,
|
||||
readValue = function(filePath) return nil end,
|
||||
deadPeriod = 0,
|
||||
forceRebootDelay = 0,
|
||||
status = nil,
|
||||
_deadCounter = 0,
|
||||
}
|
||||
|
||||
function Module:rebootDevice()
|
||||
self.syslog("warning", string.format("%s: reboot", self.name))
|
||||
os.execute("/sbin/reboot &")
|
||||
|
||||
if self.forceRebootDelay > 0 then
|
||||
nixio.nanosleep(self.forceRebootDelay)
|
||||
self.syslog("warning", string.format("%s: force reboot", self.name))
|
||||
self.writeValue("/proc/sys/kernel/sysrq", "1")
|
||||
self.writeValue("/proc/sysrq-trigger", "b")
|
||||
end
|
||||
end
|
||||
|
||||
function Module:init(t)
|
||||
self.deadPeriod = tonumber(t.dead_period)
|
||||
self.forceRebootDelay = tonumber(t.force_reboot_delay)
|
||||
end
|
||||
|
||||
function Module:run(currentStatus, lastStatus, timeDiff)
|
||||
if currentStatus == 1 then
|
||||
if self._deadCounter >= self.deadPeriod then
|
||||
self:rebootDevice()
|
||||
self._deadCounter = 0
|
||||
else
|
||||
self._deadCounter = self._deadCounter + timeDiff
|
||||
end
|
||||
|
||||
else
|
||||
self._deadCounter = 0
|
||||
end
|
||||
end
|
||||
|
||||
return Module
|
||||
@@ -1,64 +0,0 @@
|
||||
|
||||
local nixio = require("nixio")
|
||||
|
||||
local Module = {
|
||||
name = "mod_user_scripts",
|
||||
config = {},
|
||||
syslog = function(level, msg) return true end,
|
||||
writeValue = function(filePath, str) return false end,
|
||||
readValue = function(filePath) return nil end,
|
||||
deadPeriod = 0,
|
||||
alivePeriod = 0,
|
||||
upScript = "",
|
||||
downScript = "",
|
||||
status = nil,
|
||||
_deadCounter = 0,
|
||||
_aliveCounter = 0,
|
||||
_upScriptExecuted = true,
|
||||
_downScriptExecuted = true,
|
||||
}
|
||||
|
||||
function Module:runExternalScript(scriptPath)
|
||||
if nixio.fs.access(scriptPath, "x") then
|
||||
os.execute(string.format('/bin/sh -c "%s" &', scriptPath))
|
||||
end
|
||||
end
|
||||
|
||||
function Module:init(t)
|
||||
self.deadPeriod = tonumber(t.dead_period)
|
||||
self.alivePeriod = tonumber(t.alive_period)
|
||||
if self.config.configDir then
|
||||
self.upScript = string.format("%s/up-script", self.config.configDir)
|
||||
self.downScript = string.format("%s/down-script", self.config.configDir)
|
||||
end
|
||||
end
|
||||
|
||||
function Module:run(currentStatus, lastStatus, timeDiff)
|
||||
if currentStatus == 1 then
|
||||
self._aliveCounter = 0
|
||||
self._downScriptExecuted = false
|
||||
|
||||
if not self._upScriptExecuted then
|
||||
if self._deadCounter >= self.deadPeriod then
|
||||
self:runExternalScript(self.downScript)
|
||||
self._upScriptExecuted = true
|
||||
else
|
||||
self._deadCounter = self._deadCounter + timeDiff
|
||||
end
|
||||
end
|
||||
else
|
||||
self._deadCounter = 0
|
||||
self._upScriptExecuted = false
|
||||
|
||||
if not self._downScriptExecuted then
|
||||
if self._aliveCounter >= self.alivePeriod then
|
||||
self:runExternalScript(self.upScript)
|
||||
self._downScriptExecuted = true
|
||||
else
|
||||
self._aliveCounter = self._aliveCounter + timeDiff
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return Module
|
||||
@@ -0,0 +1 @@
|
||||
return require("internet-detector.main")
|
||||
794
internet-detector/files/usr/lib/lua/internet-detector/main.lua
Normal file
@@ -0,0 +1,794 @@
|
||||
|
||||
local dirent = require("posix.dirent")
|
||||
local fcntl = require("posix.fcntl")
|
||||
local signal = require("posix.signal")
|
||||
local socket = require("posix.sys.socket")
|
||||
local stat = require("posix.sys.stat")
|
||||
local syslog = require("posix.syslog")
|
||||
local time = require("posix.time")
|
||||
local unistd = require("posix.unistd")
|
||||
local uci = require("uci")
|
||||
|
||||
-- Default settings
|
||||
|
||||
local InternetDetector = {
|
||||
appName = "internet-detector",
|
||||
libDir = "/usr/lib/lua",
|
||||
logLevels = {
|
||||
emerg = { level = syslog.LOG_EMERG, num = 0 },
|
||||
alert = { level = syslog.LOG_ALERT, num = 1 },
|
||||
crit = { level = syslog.LOG_CRIT, num = 2 },
|
||||
err = { level = syslog.LOG_ERR, num = 3 },
|
||||
warning = { level = syslog.LOG_WARNING, num = 4 },
|
||||
notice = { level = syslog.LOG_NOTICE, num = 5 },
|
||||
info = { level = syslog.LOG_INFO, num = 6 },
|
||||
debug = { level = syslog.LOG_DEBUG, num = 7 },
|
||||
},
|
||||
pingCmd = "/bin/ping",
|
||||
pingParams = "-c 1",
|
||||
curlExec = "/usr/bin/curl",
|
||||
curlParams = '-s --no-keepalive --head --user-agent "Mozilla/5.0 (X11; Linux x86_64; rv:142.0) Gecko/20100101 Firefox/142.0"',
|
||||
mode = 0, -- 0: disabled, 1: Service, 2: UI detector
|
||||
loggingLevel = 6,
|
||||
hostname = "OpenWrt",
|
||||
uiRunTime = 30,
|
||||
noModules = false,
|
||||
uiAvailModules = { mod_public_ip = true },
|
||||
debug = false,
|
||||
serviceConfig = {
|
||||
hosts = {
|
||||
[1] = "8.8.8.8",
|
||||
[2] = "1.1.1.1",
|
||||
},
|
||||
urls = {
|
||||
[1] = "https://www.google.com",
|
||||
},
|
||||
check_type = 0, -- 0: TCP, 1: ICMP
|
||||
tcp_port = 53,
|
||||
icmp_packet_size = 56,
|
||||
interval_up = 30,
|
||||
interval_down = 5,
|
||||
connection_attempts = 2,
|
||||
connection_timeout = 2,
|
||||
proxy_type = nil,
|
||||
proxy_host = nil,
|
||||
proxy_port = nil,
|
||||
iface = nil,
|
||||
instance = nil,
|
||||
},
|
||||
modules = {},
|
||||
parsedHosts = {},
|
||||
proxyString = "",
|
||||
uiCounter = 0,
|
||||
pidFile = nil,
|
||||
statusFile = nil,
|
||||
}
|
||||
InternetDetector.configDir = string.format("/etc/%s", InternetDetector.appName)
|
||||
InternetDetector.modulesDir = string.format(
|
||||
"%s/%s/modules", InternetDetector.libDir, InternetDetector.appName)
|
||||
InternetDetector.commonDir = string.format("/tmp/run/%s", InternetDetector.appName)
|
||||
InternetDetector.appNamePattern = InternetDetector.appName:gsub("-", "%%-")
|
||||
InternetDetector.pidFilePattern = "^" .. InternetDetector.appNamePattern .. ".-%.pid$"
|
||||
|
||||
-- Loading settings from UCI
|
||||
|
||||
local uciCursor = uci.cursor()
|
||||
local mode, err = uciCursor:get(InternetDetector.appName, "config", "mode")
|
||||
if mode ~= nil then
|
||||
InternetDetector.mode = tonumber(mode)
|
||||
elseif err then
|
||||
io.stderr:write(string.format("Error: %s\n", err))
|
||||
end
|
||||
local loggingLevel, err = uciCursor:get(InternetDetector.appName, "config", "logging_level")
|
||||
if loggingLevel ~= nil then
|
||||
InternetDetector.loggingLevel = tonumber(loggingLevel)
|
||||
elseif err then
|
||||
io.stderr:write(string.format("Error: %s\n", err))
|
||||
end
|
||||
local hostname, err = uciCursor:get("system", "@[0]", "hostname")
|
||||
if hostname ~= nil then
|
||||
InternetDetector.hostname = hostname
|
||||
elseif err then
|
||||
io.stderr:write(string.format("Error: %s\n", err))
|
||||
end
|
||||
|
||||
function InternetDetector:prequire(package)
|
||||
local ok, pkg = pcall(require, package)
|
||||
return ok and pkg
|
||||
end
|
||||
|
||||
function InternetDetector:loadInstanceConfig(instance)
|
||||
local sections = uciCursor:get_all(self.appName)
|
||||
local t = sections[instance]
|
||||
if t then
|
||||
for k, v in pairs(t) do
|
||||
if type(v) == "string" and v:match("^[%d]+$") then
|
||||
v = tonumber(v)
|
||||
end
|
||||
self.serviceConfig[k] = v
|
||||
end
|
||||
self.serviceConfig.instance = instance
|
||||
self.serviceConfig.instanceNum = t[".index"]
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function InternetDetector:writeValueToFile(filePath, str)
|
||||
local retValue = false
|
||||
local fh = io.open(filePath, "w")
|
||||
if fh then
|
||||
fh:setvbuf("no")
|
||||
fh:write(string.format("%s\n", str))
|
||||
fh:close()
|
||||
retValue = true
|
||||
end
|
||||
return retValue
|
||||
end
|
||||
|
||||
function InternetDetector:readValueFromFile(filePath)
|
||||
local retValue
|
||||
local fh = io.open(filePath, "r")
|
||||
if fh then
|
||||
retValue = fh:read("*l")
|
||||
fh:close()
|
||||
end
|
||||
return retValue
|
||||
end
|
||||
|
||||
function InternetDetector:statusJson(inet, instance, t)
|
||||
local lines = { [1] = string.format(
|
||||
'{"instance":"%s","num":"%d","inet":%d',
|
||||
instance,
|
||||
self.serviceConfig.instanceNum,
|
||||
inet)}
|
||||
if t then
|
||||
for k, v in pairs(t) do
|
||||
lines[#lines + 1] = string.format('"%s":"%s"', k, v)
|
||||
end
|
||||
end
|
||||
return table.concat(lines, ",") .. "}"
|
||||
end
|
||||
|
||||
function InternetDetector:writeLogMessage(level, msg)
|
||||
local levelItem = self.logLevels[level]
|
||||
local levelValue = (levelItem and levelItem.level) or self.logLevels["info"].level
|
||||
local num = (levelItem and levelItem.num) or self.logLevels["info"].num
|
||||
if num <= self.loggingLevel then
|
||||
syslog.syslog(levelValue, string.format(
|
||||
"%s: %s", self.serviceConfig.instance or "", msg))
|
||||
end
|
||||
end
|
||||
|
||||
function InternetDetector:debugOutput(msg)
|
||||
if self.debug then
|
||||
io.stdout:write(string.format("[%s] %s\n", os.date("%Y.%m.%d-%H:%M:%S"), msg))
|
||||
io.stdout:flush()
|
||||
end
|
||||
end
|
||||
|
||||
function InternetDetector:loadModules()
|
||||
self.modules = {}
|
||||
local ok, modulesDir = pcall(dirent.files, self.modulesDir)
|
||||
if ok then
|
||||
for item in modulesDir do
|
||||
if item:match("^mod_") then
|
||||
local modName = item:gsub("%.lua$", "")
|
||||
if self.noModules and not self.uiAvailModules[modName] then
|
||||
else
|
||||
local modConfig = {}
|
||||
for k, v in pairs(self.serviceConfig) do
|
||||
if k:match("^" .. modName) then
|
||||
modConfig[k:gsub("^" .. modName .. "_", "")] = v
|
||||
end
|
||||
end
|
||||
if modConfig.enabled == 1 then
|
||||
local m
|
||||
if self.debug then
|
||||
m = require(string.format("%s.modules.%s", self.appName, modName))
|
||||
else
|
||||
m = self:prequire(string.format("%s.modules.%s", self.appName, modName))
|
||||
end
|
||||
if m then
|
||||
m.config = self
|
||||
m.syslog = function(level, msg) self:writeLogMessage(level, msg) end
|
||||
m.debugOutput = function(msg) self:debugOutput(msg) end
|
||||
m.writeValue = function(filePath, str) return self:writeValueToFile(filePath, str) end
|
||||
m.readValue = function(filePath) return self:readValueFromFile(filePath) end
|
||||
m:init(modConfig)
|
||||
self.modules[#self.modules + 1] = m
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
table.sort(self.modules, function(a, b) return a.runPrio < b.runPrio end)
|
||||
end
|
||||
end
|
||||
|
||||
function InternetDetector:parseHost(host)
|
||||
local addr, port = host:match("^([^%[%]:]+):?(%d?%d?%d?%d?%d?)$")
|
||||
if not addr then
|
||||
addr, port = host:match("^%[?([^%[%]]+)%]?:?(%d?%d?%d?%d?%d?)$")
|
||||
end
|
||||
return addr, tonumber(port)
|
||||
end
|
||||
|
||||
function InternetDetector:parseHosts()
|
||||
self.parsedHosts = {}
|
||||
for k, v in ipairs(self.serviceConfig.hosts) do
|
||||
local addr, port = self:parseHost(v)
|
||||
self.parsedHosts[k] = { addr = addr, port = port }
|
||||
end
|
||||
end
|
||||
|
||||
function InternetDetector:parseUrls()
|
||||
self.parsedHosts = {}
|
||||
for k, v in ipairs(self.serviceConfig.urls) do
|
||||
self.parsedHosts[k] = { addr = v }
|
||||
end
|
||||
end
|
||||
|
||||
function InternetDetector:pingHost(host)
|
||||
local ping = string.format(
|
||||
"%s %s -W %d -s %d%s %s > /dev/null 2>&1",
|
||||
self.pingCmd,
|
||||
self.pingParams,
|
||||
self.serviceConfig.connection_timeout,
|
||||
self.serviceConfig.icmp_packet_size,
|
||||
self.serviceConfig.iface and (" -I " .. self.serviceConfig.iface) or "",
|
||||
host
|
||||
)
|
||||
local retCode = os.execute(ping)
|
||||
|
||||
self:debugOutput(string.format(
|
||||
"--- Ping ---\ntime = %s\n%s\nretCode = %s", os.time(), ping, retCode))
|
||||
|
||||
return retCode
|
||||
end
|
||||
|
||||
function InternetDetector:TCPConnectionToHost(host, port)
|
||||
local retCode = 1
|
||||
local saTable, errMsg, errNum = socket.getaddrinfo(host, port or self.serviceConfig.tcp_port)
|
||||
|
||||
if not saTable then
|
||||
self:debugOutput(string.format(
|
||||
"GETADDRINFO ERROR: %s, %s", errMsg, errNum))
|
||||
else
|
||||
local family = saTable[1].family
|
||||
|
||||
if family then
|
||||
local sock, errMsg, errNum = socket.socket(family, socket.SOCK_STREAM, 0)
|
||||
|
||||
if not sock then
|
||||
self:debugOutput(string.format(
|
||||
"SOCKET ERROR: %s, %s", errMsg, errNum))
|
||||
return retCode
|
||||
end
|
||||
|
||||
socket.setsockopt(sock, socket.SOL_SOCKET,
|
||||
socket.SO_SNDTIMEO, self.serviceConfig.connection_timeout, 0)
|
||||
socket.setsockopt(sock, socket.SOL_SOCKET,
|
||||
socket.SO_RCVTIMEO, self.serviceConfig.connection_timeout, 0)
|
||||
|
||||
if self.serviceConfig.iface then
|
||||
local ok, errMsg, errNum = socket.setsockopt(sock, socket.SOL_SOCKET,
|
||||
socket.SO_BINDTODEVICE, self.serviceConfig.iface)
|
||||
if not ok then
|
||||
self:debugOutput(string.format(
|
||||
"SOCKET ERROR: %s, %s", errMsg, errNum))
|
||||
unistd.close(sock)
|
||||
return retCode
|
||||
end
|
||||
end
|
||||
|
||||
local success = socket.connect(sock, saTable[1])
|
||||
|
||||
if self.debug then
|
||||
if not success then
|
||||
self:debugOutput(string.format(
|
||||
"SOCKET CONNECT ERROR: %s", tostring(success)))
|
||||
end
|
||||
local sockTable, err_s, e_s = socket.getsockname(sock)
|
||||
local peerTable, err_p, e_p = socket.getpeername(sock)
|
||||
if not sockTable then
|
||||
sockTable = {}
|
||||
self:debugOutput(
|
||||
string.format("SOCKET ERROR: %s, %s", err_s, e_s))
|
||||
end
|
||||
if not peerTable then
|
||||
peerTable = {}
|
||||
self:debugOutput(
|
||||
string.format("SOCKET ERROR: %s, %s", err_p, e_p))
|
||||
end
|
||||
self:debugOutput(string.format(
|
||||
"--- TCP ---\ntime = %s\nconnection_timeout = %s\niface = %s\nhost:port = [%s]:%s\nsockname = [%s]:%s\npeername = [%s]:%s\nsuccess = %s",
|
||||
os.time(),
|
||||
self.serviceConfig.connection_timeout,
|
||||
tostring(self.serviceConfig.iface),
|
||||
host,
|
||||
port or self.serviceConfig.tcp_port,
|
||||
tostring(sockTable.addr),
|
||||
tostring(sockTable.port),
|
||||
tostring(peerTable.addr),
|
||||
tostring(peerTable.port),
|
||||
tostring(success))
|
||||
)
|
||||
end
|
||||
|
||||
socket.shutdown(sock, socket.SHUT_RDWR)
|
||||
unistd.close(sock)
|
||||
retCode = success and 0 or 1
|
||||
end
|
||||
end
|
||||
return retCode
|
||||
end
|
||||
|
||||
function InternetDetector:httpRequest(url)
|
||||
local retCode = 1, data
|
||||
local curl = string.format(
|
||||
'%s%s%s --connect-timeout %s %s "%s"; printf "\n$?";',
|
||||
self.curlExec,
|
||||
self.serviceConfig.iface and (" --interface " .. self.serviceConfig.iface) or "",
|
||||
self.proxyString,
|
||||
self.serviceConfig.connection_timeout,
|
||||
self.curlParams,
|
||||
url
|
||||
)
|
||||
local fh = io.popen(curl, "r")
|
||||
if fh then
|
||||
data = fh:read("*a")
|
||||
fh:close()
|
||||
if data ~= nil then
|
||||
local s, e = data:find("[0-9]+\n?$")
|
||||
retCode = tonumber(data:sub(s))
|
||||
data = data:sub(0, s - 2)
|
||||
if not data or data == "" then
|
||||
data = nil
|
||||
end
|
||||
end
|
||||
else
|
||||
retCode = 1
|
||||
end
|
||||
|
||||
self:debugOutput(string.format(
|
||||
"--- Curl ---\ntime = %s\n%s\nretCode = %s\ndata = [\n%s]\n",
|
||||
os.time(),
|
||||
curl,
|
||||
retCode,
|
||||
tostring(data)))
|
||||
return retCode, data
|
||||
end
|
||||
|
||||
function InternetDetector:getHTTPCode(data)
|
||||
local httpCode
|
||||
local respHeader = data:match("^HTTP/[^%c]+")
|
||||
if respHeader then
|
||||
httpCode = respHeader:match("%d%d%d")
|
||||
end
|
||||
return tonumber(httpCode)
|
||||
end
|
||||
|
||||
function InternetDetector:checkURL(url)
|
||||
local httpCode
|
||||
local retCode, data = self:httpRequest(url)
|
||||
if retCode == 0 and data then
|
||||
httpCode = self:getHTTPCode(data)
|
||||
end
|
||||
return (httpCode ~= 200) and 1 or 0
|
||||
end
|
||||
|
||||
function InternetDetector:exit()
|
||||
for _, e in ipairs(self.modules) do
|
||||
e:onExit()
|
||||
end
|
||||
self:removeProcessFiles()
|
||||
if self.loggingLevel > 0 then
|
||||
self:writeLogMessage("info", "stoped")
|
||||
syslog.closelog()
|
||||
end
|
||||
os.exit(0)
|
||||
end
|
||||
|
||||
function InternetDetector:resetUiCounter(signo)
|
||||
self.uiCounter = 0
|
||||
end
|
||||
|
||||
function InternetDetector:mainLoop()
|
||||
signal.signal(signal.SIGTERM, function(signo) self:exit(signo) end)
|
||||
signal.signal(signal.SIGINT, function(signo) self:exit(signo) end)
|
||||
signal.signal(signal.SIGQUIT, function(signo) self:exit(signo) end)
|
||||
signal.signal(signal.SIGUSR1, function(signo) self:resetUiCounter(signo) end)
|
||||
|
||||
local mTimeNow, mTimeDiff, mLastTime, uiTimeNow, uiLastTime
|
||||
local lastStatus = -1
|
||||
local currentStatus = -1
|
||||
local interval = self.serviceConfig.interval_up
|
||||
local modulesStatus = {}
|
||||
local counter = 0
|
||||
local inetChecked = false
|
||||
local checking = false
|
||||
local hostNum = 1
|
||||
local attempt = 1
|
||||
|
||||
local checkFunc = self.TCPConnectionToHost
|
||||
if self.serviceConfig.check_type == 1 then
|
||||
checkFunc = self.pingHost
|
||||
self:parseHosts()
|
||||
elseif self.serviceConfig.check_type == 2 then
|
||||
checkFunc = self.checkURL
|
||||
self:parseUrls()
|
||||
if (self.serviceConfig.proxy_type and self.serviceConfig.proxy_host and
|
||||
self.serviceConfig.proxy_port) then
|
||||
self.proxyString = string.format(
|
||||
" --proxy %s://%s:%d",
|
||||
self.serviceConfig.proxy_type,
|
||||
self.serviceConfig.proxy_host,
|
||||
self.serviceConfig.proxy_port)
|
||||
end
|
||||
else
|
||||
self:parseHosts()
|
||||
end
|
||||
|
||||
self:writeValueToFile(
|
||||
self.statusFile, self:statusJson(currentStatus, self.serviceConfig.instance))
|
||||
|
||||
while true do
|
||||
if counter == 0 or counter >= interval then
|
||||
checking = true
|
||||
end
|
||||
|
||||
inetChecked = false
|
||||
|
||||
if checking then
|
||||
local newStatus = 1
|
||||
if hostNum <= #self.parsedHosts then
|
||||
if attempt <= self.serviceConfig.connection_attempts then
|
||||
local addr = self.parsedHosts[hostNum].addr
|
||||
local port = self.parsedHosts[hostNum].port
|
||||
local retCode = 1
|
||||
if self.debug then
|
||||
retCode = checkFunc(self, addr, port)
|
||||
else
|
||||
local ok, status = pcall(checkFunc, self, addr, port)
|
||||
if ok then
|
||||
retCode = status
|
||||
else
|
||||
self:writeLogMessage("err", string.format(
|
||||
"An error occurred while checking the host %s: %s",
|
||||
tostring(addr),
|
||||
tostring(status))
|
||||
)
|
||||
end
|
||||
end
|
||||
if retCode == 0 then
|
||||
attempt = 1
|
||||
hostNum = 1
|
||||
checking = false
|
||||
newStatus = 0
|
||||
counter = 0
|
||||
inetChecked = true
|
||||
else
|
||||
attempt = attempt + 1
|
||||
if attempt > self.serviceConfig.connection_attempts then
|
||||
attempt = 1
|
||||
hostNum = hostNum + 1
|
||||
end
|
||||
end
|
||||
else
|
||||
attempt = 1
|
||||
hostNum = hostNum + 1
|
||||
end
|
||||
if hostNum > #self.parsedHosts then
|
||||
hostNum = 1
|
||||
checking = false
|
||||
counter = 0
|
||||
inetChecked = true
|
||||
end
|
||||
else
|
||||
hostNum = 1
|
||||
checking = false
|
||||
counter = 0
|
||||
inetChecked = true
|
||||
end
|
||||
|
||||
if inetChecked then
|
||||
currentStatus = newStatus
|
||||
if not stat.stat(self.statusFile) then
|
||||
self:writeValueToFile(self.statusFile, self:statusJson(
|
||||
currentStatus, self.serviceConfig.instance))
|
||||
end
|
||||
if currentStatus == 0 then
|
||||
interval = self.serviceConfig.interval_up
|
||||
if currentStatus ~= lastStatus then
|
||||
self:writeValueToFile(self.statusFile, self:statusJson(
|
||||
currentStatus, self.serviceConfig.instance))
|
||||
self:writeLogMessage("notice", "Connected")
|
||||
end
|
||||
elseif currentStatus == 1 then
|
||||
interval = self.serviceConfig.interval_down
|
||||
if currentStatus ~= lastStatus then
|
||||
self:writeValueToFile(self.statusFile, self:statusJson(
|
||||
currentStatus, self.serviceConfig.instance))
|
||||
self:writeLogMessage("notice", "Disconnected")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
mTimeDiff = 0
|
||||
for _, e in ipairs(self.modules) do
|
||||
mTimeNow = time.clock_gettime(time.CLOCK_MONOTONIC).tv_sec
|
||||
if mLastTime then
|
||||
mTimeDiff = mTimeDiff + mTimeNow - mLastTime
|
||||
else
|
||||
mTimeDiff = 1
|
||||
end
|
||||
mLastTime = mTimeNow
|
||||
|
||||
if self.debug then
|
||||
e:run(currentStatus, lastStatus, mTimeDiff, mTimeNow, inetChecked)
|
||||
else
|
||||
local ok, err = pcall(e.run, e, currentStatus, lastStatus, mTimeDiff, mTimeNow, inetChecked)
|
||||
if not ok then
|
||||
self:writeLogMessage("err", string.format(
|
||||
"%s: Module error: %s", e.name, tostring(err)))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local modStatusChanged = false
|
||||
for k, v in ipairs(self.modules) do
|
||||
if modulesStatus[v.name] ~= v.status then
|
||||
modulesStatus[v.name] = v.status
|
||||
modStatusChanged = true
|
||||
end
|
||||
end
|
||||
if modStatusChanged and next(modulesStatus) then
|
||||
self:writeValueToFile(self.statusFile, self:statusJson(
|
||||
currentStatus, self.serviceConfig.instance, modulesStatus))
|
||||
end
|
||||
|
||||
unistd.sleep(1)
|
||||
|
||||
if not checking then
|
||||
lastStatus = currentStatus
|
||||
counter = counter + 1
|
||||
end
|
||||
|
||||
if self.mode == 2 then
|
||||
uiTimeNow = time.clock_gettime(time.CLOCK_MONOTONIC).tv_sec
|
||||
if uiLastTime then
|
||||
self.uiCounter = self.uiCounter + uiTimeNow - uiLastTime
|
||||
else
|
||||
self.uiCounter = self.uiCounter + 1
|
||||
end
|
||||
uiLastTime = uiTimeNow
|
||||
if self.uiCounter >= self.uiRunTime then
|
||||
self:exit(signal.SIGTERM)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function InternetDetector:removeProcessFiles()
|
||||
os.remove(self.statusFile)
|
||||
os.remove(self.pidFile)
|
||||
end
|
||||
|
||||
function InternetDetector:status()
|
||||
local ok, commonDir = pcall(dirent.files, self.commonDir)
|
||||
if ok then
|
||||
for item in commonDir do
|
||||
if item:match(self.pidFilePattern) then
|
||||
return "running"
|
||||
end
|
||||
end
|
||||
end
|
||||
return "stoped"
|
||||
end
|
||||
|
||||
function InternetDetector:inetStatus()
|
||||
local inetStat = '{"instances":[]}'
|
||||
local ok, commonDir = pcall(dirent.files, self.commonDir)
|
||||
if ok then
|
||||
local statusFilePattern = "^" .. self.appNamePattern .. ".-%.status$"
|
||||
local lines = {}
|
||||
for item in commonDir do
|
||||
if item:match(statusFilePattern) then
|
||||
lines[#lines + 1] = self:readValueFromFile(
|
||||
string.format("%s/%s", self.commonDir, item))
|
||||
end
|
||||
end
|
||||
inetStat = '{"instances":[' .. table.concat(lines, ",") .. "]}"
|
||||
end
|
||||
return inetStat
|
||||
end
|
||||
|
||||
function InternetDetector:stopInstance(pidFile)
|
||||
local retVal = false, pidValue
|
||||
if stat.stat(pidFile) then
|
||||
pidValue = self:readValueFromFile(pidFile)
|
||||
if pidValue then
|
||||
local ok, errMsg, errNum
|
||||
for i = 0, 10 do
|
||||
ok, errMsg, errNum = signal.kill(tonumber(pidValue), signal.SIGTERM)
|
||||
if ok then
|
||||
break
|
||||
end
|
||||
end
|
||||
if not ok then
|
||||
io.stderr:write(string.format(
|
||||
'Process stopping error: %s (%s). PID: "%s"\n', errMsg, errNum, pidValue))
|
||||
end
|
||||
if errNum == 3 then
|
||||
os.remove(pidFile)
|
||||
end
|
||||
retVal = true
|
||||
else
|
||||
os.remove(pidFile)
|
||||
end
|
||||
end
|
||||
if not pidValue then
|
||||
io.stderr:write(
|
||||
string.format('PID file "%s" does not exists. Is the %s not running?\n',
|
||||
pidFile, self.appName))
|
||||
end
|
||||
return retVal
|
||||
end
|
||||
|
||||
function InternetDetector:stop()
|
||||
local nopids = false
|
||||
for i = 0, 100 do
|
||||
nopids = true
|
||||
local ok, commonDir = pcall(dirent.files, self.commonDir)
|
||||
if ok then
|
||||
for item in commonDir do
|
||||
if item:match(self.pidFilePattern) then
|
||||
if self:stopInstance(string.format("%s/%s", self.commonDir, item)) then
|
||||
nopids = false
|
||||
end
|
||||
end
|
||||
end
|
||||
if nopids then
|
||||
break
|
||||
end
|
||||
time.nanosleep({ tv_sec = 0, tv_nsec = 10000000 })
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function InternetDetector:setSIGUSR()
|
||||
local ok, commonDir = pcall(dirent.files, self.commonDir)
|
||||
if ok then
|
||||
for item in commonDir do
|
||||
if item:match(self.pidFilePattern) then
|
||||
pidValue = self:readValueFromFile(string.format("%s/%s", self.commonDir, item))
|
||||
if pidValue then
|
||||
signal.kill(tonumber(pidValue), signal.SIGUSR1)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function InternetDetector:preRun()
|
||||
-- Exit if internet-detector mode != (1 or 2)
|
||||
if self.mode ~= 1 and self.mode ~= 2 then
|
||||
io.stderr:write(string.format('Start failed, mode != (1 or 2)\n', self.appName))
|
||||
os.exit(0)
|
||||
end
|
||||
local s = stat.stat(self.commonDir)
|
||||
if not s or not (stat.S_ISDIR(s.st_mode) ~= 0) then
|
||||
if not stat.mkdir(self.commonDir) then
|
||||
io.stderr:write(
|
||||
string.format('Error occurred while creating %s. Exit.\n', self.commonDir))
|
||||
os.exit(1)
|
||||
end
|
||||
end
|
||||
if self.serviceConfig.check_type == 2 and not unistd.access(self.curlExec, "x") then
|
||||
io.stderr:write(string.format(
|
||||
"Error, %s is not available. You need to install curl.\n", self.curlExec))
|
||||
os.exit(1)
|
||||
end
|
||||
local ok, commonDir = pcall(dirent.files, self.commonDir)
|
||||
if ok then
|
||||
local instancePattern = "^" .. self.appNamePattern .. "%." .. self.serviceConfig.instance .. "%.[%d]+%.pid$"
|
||||
for item in commonDir do
|
||||
if item:match(instancePattern) then
|
||||
self:stopInstance(string.format("%s/%s", self.commonDir, item))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function InternetDetector:run()
|
||||
local pidValue = unistd.getpid()
|
||||
self.pidFile = string.format(
|
||||
"%s/%s.%s.%s.pid", self.commonDir, self.appName, self.serviceConfig.instance, pidValue)
|
||||
self.statusFile = string.format(
|
||||
"%s/%s.%s.status", self.commonDir, self.appName, self.serviceConfig.instance)
|
||||
self:writeValueToFile(self.pidFile, pidValue)
|
||||
|
||||
if self.loggingLevel > 0 then
|
||||
syslog.openlog(self.appName, syslog.LOG_PID, syslog.LOG_DAEMON)
|
||||
end
|
||||
self:writeLogMessage("info", "started")
|
||||
self:loadModules()
|
||||
|
||||
-- Loaded modules
|
||||
local modules = {}
|
||||
for _, v in ipairs(self.modules) do
|
||||
modules[#modules + 1] = string.format("%s", v.name)
|
||||
end
|
||||
if #modules > 0 then
|
||||
self:writeLogMessage(
|
||||
"info", string.format("Loaded modules: %s", table.concat(modules, ", "))
|
||||
)
|
||||
end
|
||||
|
||||
if self.debug then
|
||||
local function inspectTable()
|
||||
local tables = {}, f
|
||||
f = function(t, prefix)
|
||||
tables[t] = true
|
||||
for k, v in pairs(t) do
|
||||
self:debugOutput(string.format(
|
||||
"%s%s = %s", prefix, k, tostring(v))
|
||||
)
|
||||
if type(v) == "table" and not tables[v] then
|
||||
f(v, string.format("%s%s.", prefix, k))
|
||||
end
|
||||
end
|
||||
end
|
||||
return f
|
||||
end
|
||||
|
||||
self:debugOutput("--- Config ---")
|
||||
inspectTable()(self, "self.")
|
||||
end
|
||||
|
||||
self:mainLoop()
|
||||
self:exit()
|
||||
end
|
||||
|
||||
function InternetDetector:noDaemon()
|
||||
self:preRun()
|
||||
self:run()
|
||||
end
|
||||
|
||||
function InternetDetector:daemon()
|
||||
self:preRun()
|
||||
-- UNIX double fork
|
||||
if unistd.fork() == 0 then
|
||||
unistd.setpid("s")
|
||||
if unistd.fork() == 0 then
|
||||
unistd.chdir("/")
|
||||
stat.umask(0)
|
||||
local devnull = fcntl.open("/dev/null", fcntl.O_RDWR)
|
||||
io.stdout:flush()
|
||||
io.stderr:flush()
|
||||
unistd.dup2(devnull, 0) -- io.stdin
|
||||
unistd.dup2(devnull, 1) -- io.stdout
|
||||
unistd.dup2(devnull, 2) -- io.stderr
|
||||
self:run()
|
||||
unistd.close(devnull)
|
||||
end
|
||||
os.exit(0)
|
||||
end
|
||||
os.exit(0)
|
||||
end
|
||||
|
||||
function InternetDetector:setServiceConfig(instance)
|
||||
if self:loadInstanceConfig(instance) then
|
||||
if self.mode == 2 then
|
||||
self.loggingLevel = 0
|
||||
self.noModules = true
|
||||
end
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return InternetDetector
|
||||
@@ -0,0 +1,334 @@
|
||||
|
||||
local dirent = require("posix.dirent")
|
||||
local time = require("posix.time")
|
||||
local unistd = require("posix.unistd")
|
||||
|
||||
local Module = {
|
||||
name = "mod_led_control",
|
||||
runPrio = 10,
|
||||
config = {},
|
||||
syslog = function(level, msg) return true end,
|
||||
debugOutput = function(msg) return true end,
|
||||
writeValue = function(filePath, str) return false end,
|
||||
readValue = function(filePath) return nil end,
|
||||
runInterval = 5,
|
||||
sysLedsDir = "/sys/class/leds",
|
||||
ledsPerInstance = 3,
|
||||
ledAction1Default = 1, -- 1: off, 2: on, 3: blinking, 4: netdev
|
||||
ledAction2Default = 1,
|
||||
ledBlinkDelayDefault = 500,
|
||||
ledNetlinkDeviceDefault = nil,
|
||||
ledNetdevModeLinkDefault = "1",
|
||||
ledNetdevModeRxDefault = "0",
|
||||
ledNetdevModeTxDefault = "0",
|
||||
status = nil,
|
||||
_enabled = false,
|
||||
_leds = {},
|
||||
_counter = 0,
|
||||
_exit = false,
|
||||
}
|
||||
|
||||
function Module:setLedAttrs(t)
|
||||
t.ledDir = string.format("%s/%s", self.sysLedsDir, t.ledName)
|
||||
t.ledMaxBrightnessFile = string.format("%s/max_brightness", t.ledDir)
|
||||
t.ledBrightnessFile = string.format("%s/brightness", t.ledDir)
|
||||
t.ledMaxBrightness = self.readValue(t.ledMaxBrightnessFile) or "1"
|
||||
t.ledTriggerFile = string.format("%s/trigger", t.ledDir)
|
||||
t.ledDelayOnFile = string.format("%s/delay_on", t.ledDir)
|
||||
t.ledDelayOffFile = string.format("%s/delay_off", t.ledDir)
|
||||
t.ledDeviceNameFile = string.format("%s/device_name", t.ledDir)
|
||||
t.ledLinkFile = string.format("%s/link", t.ledDir)
|
||||
t.ledRxFile = string.format("%s/rx", t.ledDir)
|
||||
t.ledTxFile = string.format("%s/tx", t.ledDir)
|
||||
t.ledPrevState = {
|
||||
brightness = self.readValue(t.ledBrightnessFile),
|
||||
trigger = self.readValue(t.ledTriggerFile),
|
||||
}
|
||||
if t.ledPrevState.trigger then
|
||||
local val = t.ledPrevState.trigger:match("%[[%w%-_]+%]")
|
||||
if val then
|
||||
t.ledPrevState.trigger = val:gsub("[%]%[]", "")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Module:checkLed(t)
|
||||
return (unistd.access(t.ledDir, "r") and
|
||||
unistd.access(t.ledBrightnessFile, "rw") and
|
||||
unistd.access(t.ledTriggerFile, "rw"))
|
||||
end
|
||||
|
||||
function Module:init(t)
|
||||
for i = 1, self.ledsPerInstance do
|
||||
self._leds[i] = {}
|
||||
end
|
||||
if t.led1_name then
|
||||
self._enabled = true
|
||||
else
|
||||
return
|
||||
end
|
||||
for i, l in ipairs(self._leds) do
|
||||
local led = "led" .. i
|
||||
if t[led .. "_name"] ~= nil then
|
||||
l.ledName = t[led .. "_name"]
|
||||
l.ledAction1 = tonumber(t[led .. "_action_1"]) or self.ledAction1Default
|
||||
l.ledAction2 = tonumber(t[led .. "_action_2"]) or self.ledAction2Default
|
||||
l.ledBlinkOnDelay1 = tonumber(t[led .. "_blink_on_delay_1"]) or self.ledBlinkDelayDefault
|
||||
l.ledBlinkOffDelay1 = tonumber(t[led .. "_blink_off_delay_1"]) or self.ledBlinkDelayDefault
|
||||
l.ledBlinkOnDelay2 = tonumber(t[led .. "_blink_on_delay_2"]) or self.ledBlinkDelayDefault
|
||||
l.ledBlinkOffDelay2 = tonumber(t[led .. "_blink_off_delay_2"]) or self.ledBlinkDelayDefault
|
||||
l.ledNetlinkDevice1 = t[led .. "_netdev_device_1"] or self.ledNetlinkDeviceDefault
|
||||
l.ledNetlinkDevice2 = t[led .. "_netdev_device_2"] or self.ledNetlinkDeviceDefault
|
||||
l.ledNetdevModeLink1 = self.ledNetdevModeLinkDefault
|
||||
l.ledNetdevModeTx1 = self.ledNetdevModeTxDefault
|
||||
l.ledNetdevModeRx1 = self.ledNetdevModeRxDefault
|
||||
l.ledNetdevModeLink2 = self.ledNetdevModeLinkDefault
|
||||
l.ledNetdevModeTx2 = self.ledNetdevModeTxDefault
|
||||
l.ledNetdevModeRx2 = self.ledNetdevModeRxDefault
|
||||
local ndm1 = t[led .. "_netdev_mode_1"]
|
||||
if ndm1 ~= nil and type(ndm1) == "table" then
|
||||
local enabledFlags = {}
|
||||
for _, v in ipairs(ndm1) do
|
||||
enabledFlags[v] = "1"
|
||||
end
|
||||
l.ledNetdevModeLink1 = enabledFlags.link or "0"
|
||||
l.ledNetdevModeTx1 = enabledFlags.tx or "0"
|
||||
l.ledNetdevModeRx1 = enabledFlags.rx or "0"
|
||||
end
|
||||
local ndm2 = t[led .. "_netdev_mode_2"]
|
||||
if ndm2 ~= nil and type(ndm2) == "table" then
|
||||
local enabledFlags = {}
|
||||
for _, v in ipairs(ndm2) do
|
||||
enabledFlags[v] = "1"
|
||||
end
|
||||
l.ledNetdevModeLink2 = enabledFlags.link or "0"
|
||||
l.ledNetdevModeTx2 = enabledFlags.tx or "0"
|
||||
l.ledNetdevModeRx2 = enabledFlags.rx or "0"
|
||||
end
|
||||
self:setLedAttrs(l)
|
||||
l.enabled = true
|
||||
else
|
||||
l.enabled = false
|
||||
end
|
||||
if l.enabled and not self:checkLed(l) then
|
||||
self._enabled = false
|
||||
self.syslog("err", string.format(
|
||||
"%s: module disabled. LED '%s' is not available", self.name, l.ledName))
|
||||
end
|
||||
self._exit = false
|
||||
end
|
||||
end
|
||||
|
||||
function Module:checkLedTimer(t)
|
||||
return (unistd.access(t.ledDelayOnFile, "rw") and unistd.access(t.ledDelayOffFile, "rw"))
|
||||
end
|
||||
|
||||
function Module:checkLedNetdev(t)
|
||||
return (unistd.access(t.ledDeviceNameFile, "rw") and
|
||||
unistd.access(t.ledLinkFile, "rw") and
|
||||
unistd.access(t.ledRxFile, "rw") and
|
||||
unistd.access(t.ledTxFile, "rw"))
|
||||
end
|
||||
|
||||
function Module:setTriggerNone(t)
|
||||
self.writeValue(t.ledTriggerFile, "none")
|
||||
self.debugOutput(string.format(
|
||||
"%s: LED TRIGGER SET: none, %s", self.name, t.ledTriggerFile))
|
||||
end
|
||||
|
||||
function Module:setTriggerTimer(t, delayOn, delayOff)
|
||||
if not delayOn then
|
||||
delayOn = self.ledBlinkDelayDefault
|
||||
end
|
||||
if not delayOff then
|
||||
delayOff = self.ledBlinkDelayDefault
|
||||
end
|
||||
|
||||
self.writeValue(t.ledTriggerFile, "timer")
|
||||
|
||||
for i = 0, 10 do
|
||||
if self:checkLedTimer(t) then
|
||||
self.writeValue(t.ledDelayOnFile, delayOn)
|
||||
self.writeValue(t.ledDelayOffFile, delayOff)
|
||||
break
|
||||
else
|
||||
time.nanosleep({ tv_sec = 0, tv_nsec = 500000 })
|
||||
end
|
||||
end
|
||||
|
||||
self.debugOutput(string.format(
|
||||
"%s: LED TRIGGER SET: timer, %s; delayOn = %s, delayOff = %s",
|
||||
self.name, t.ledTriggerFile, tostring(delayOn), tostring(delayOff))
|
||||
)
|
||||
end
|
||||
|
||||
function Module:setTriggerNetdev(t, device, link, tx, rx)
|
||||
if not device then
|
||||
return
|
||||
end
|
||||
|
||||
self.writeValue(t.ledTriggerFile, "netdev")
|
||||
|
||||
for i = 0, 10 do
|
||||
if self:checkLedNetdev(t) then
|
||||
self.writeValue(t.ledDeviceNameFile, device)
|
||||
self.writeValue(t.ledLinkFile, link)
|
||||
self.writeValue(t.ledTxFile, tx)
|
||||
self.writeValue(t.ledRxFile, rx)
|
||||
break
|
||||
else
|
||||
time.nanosleep({ tv_sec = 0, tv_nsec = 500000 })
|
||||
end
|
||||
end
|
||||
|
||||
self.debugOutput(string.format(
|
||||
"%s: LED TRIGGER SET: netdev, %s; device = %s, link = %s, rx = %s, tx = %s",
|
||||
self.name, t.ledTriggerFile, tostring(device), tostring(link), tostring(rx), tostring(tx))
|
||||
)
|
||||
end
|
||||
|
||||
function Module:getCurrentTrigger(t)
|
||||
local trigger = self.readValue(t.ledTriggerFile)
|
||||
if trigger then
|
||||
if trigger:match("%[timer%]") then
|
||||
return "timer"
|
||||
elseif trigger:match("%[netdev%]") then
|
||||
return "netdev"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Module:getTriggerValues(t, trigger)
|
||||
local currentTrigger = self:getCurrentTrigger(t)
|
||||
if trigger == currentTrigger then
|
||||
if trigger == "timer" then
|
||||
return {
|
||||
trigger = currentTrigger,
|
||||
delayOn = tonumber(self.readValue(t.ledDelayOnFile)),
|
||||
delayOff = tonumber(self.readValue(t.ledDelayOffFile)),
|
||||
}
|
||||
elseif trigger == "netdev" then
|
||||
return {
|
||||
trigger = currentTrigger,
|
||||
device = self.readValue(t.ledDeviceNameFile),
|
||||
link = self.readValue(t.ledLinkFile),
|
||||
tx = self.readValue(t.ledTxFile),
|
||||
rx = self.readValue(t.ledRxFile),
|
||||
}
|
||||
end
|
||||
end
|
||||
return {}
|
||||
end
|
||||
|
||||
function Module:on(t)
|
||||
self:setTriggerNone(t)
|
||||
self.writeValue(t.ledBrightnessFile, t.ledMaxBrightness)
|
||||
|
||||
self.debugOutput(string.format("%s: LED ON: %s", self.name, t.ledBrightnessFile))
|
||||
end
|
||||
|
||||
function Module:off(t)
|
||||
self:setTriggerNone(t)
|
||||
self.writeValue(t.ledBrightnessFile, "0")
|
||||
|
||||
self.debugOutput(string.format("%s: LED OFF: %s", self.name, t.ledBrightnessFile))
|
||||
end
|
||||
|
||||
function Module:getCurrentState(t)
|
||||
local state = self.readValue(t.ledBrightnessFile)
|
||||
if state and tonumber(state) > 0 then
|
||||
return tonumber(state)
|
||||
end
|
||||
end
|
||||
|
||||
function Module:ledRunFunc(t, currentStatus)
|
||||
if currentStatus == 0 then
|
||||
if t.ledAction1 == 1 then
|
||||
if self:getCurrentState(t) or self:getCurrentTrigger(t) then
|
||||
self:off(t)
|
||||
end
|
||||
elseif t.ledAction1 == 2 then
|
||||
if not self:getCurrentState(t) or self:getCurrentTrigger(t) then
|
||||
self:on(t)
|
||||
end
|
||||
elseif t.ledAction1 == 3 then
|
||||
local triggerValues = self:getTriggerValues(t, "timer")
|
||||
if (not next(triggerValues)) or (triggerValues.delayOn ~= t.ledBlinkOnDelay1 or
|
||||
triggerValues.delayOff ~= t.ledBlinkOffDelay1) then
|
||||
self:setTriggerTimer(t, t.ledBlinkOnDelay1, t.ledBlinkOffDelay1)
|
||||
end
|
||||
elseif t.ledAction1 == 4 then
|
||||
local triggerValues = self:getTriggerValues(t, "netdev")
|
||||
if (not next(triggerValues)) or (triggerValues.device ~= t.ledNetlinkDevice1 or
|
||||
triggerValues.link ~= t.ledNetdevModeLink1 or
|
||||
triggerValues.tx ~= t.ledNetdevModeTx1 or
|
||||
triggerValues.rx ~= t.ledNetdevModeRx1) then
|
||||
self:setTriggerNetdev(t,
|
||||
t.ledNetlinkDevice1, t.ledNetdevModeLink1,
|
||||
t.ledNetdevModeTx1, t.ledNetdevModeRx1
|
||||
)
|
||||
end
|
||||
end
|
||||
elseif currentStatus == 1 then
|
||||
if t.ledAction2 == 1 then
|
||||
if self:getCurrentState(t) or self:getCurrentTrigger(t) then
|
||||
self:off(t)
|
||||
end
|
||||
elseif t.ledAction2 == 2 then
|
||||
if not self:getCurrentState(t) or self:getCurrentTrigger(t) then
|
||||
self:on(t)
|
||||
end
|
||||
elseif t.ledAction2 == 3 then
|
||||
local triggerValues = self:getTriggerValues(t, "timer")
|
||||
if (not next(triggerValues)) or (triggerValues.delayOn ~= t.ledBlinkOnDelay2 or
|
||||
triggerValues.delayOff ~= t.ledBlinkOffDelay2) then
|
||||
self:setTriggerTimer(t, t.ledBlinkOnDelay2, t.ledBlinkOffDelay2)
|
||||
end
|
||||
elseif t.ledAction2 == 4 then
|
||||
local triggerValues = self:getTriggerValues(t, "netdev")
|
||||
if (not next(triggerValues)) or (triggerValues.device ~= t.ledNetlinkDevice2 or
|
||||
triggerValues.link ~= t.ledNetdevModeLink2 or
|
||||
triggerValues.tx ~= t.ledNetdevModeTx2 or
|
||||
triggerValues.rx ~= t.ledNetdevModeRx2) then
|
||||
self:setTriggerNetdev(t,
|
||||
t.ledNetlinkDevice2, t.ledNetdevModeLink2,
|
||||
t.ledNetdevModeTx2, t.ledNetdevModeRx2
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Module:run(currentStatus, lastStatus, timeDiff, timeNow, inetChecked)
|
||||
if not self._enabled then
|
||||
return
|
||||
end
|
||||
if self._counter == 0 or self._counter >= self.runInterval or currentStatus ~= lastStatus then
|
||||
for _, t in ipairs(self._leds) do
|
||||
if self._exit then
|
||||
break
|
||||
end
|
||||
if t.enabled then
|
||||
self:ledRunFunc(t, currentStatus)
|
||||
end
|
||||
end
|
||||
self._counter = 0
|
||||
end
|
||||
self._counter = self._counter + timeDiff
|
||||
end
|
||||
|
||||
function Module:onExit()
|
||||
self._exit = true
|
||||
for _, l in ipairs(self._leds) do
|
||||
if l.ledPrevState then
|
||||
if l.ledPrevState.brightness then
|
||||
self.writeValue(l.ledBrightnessFile, l.ledPrevState.brightness)
|
||||
end
|
||||
if l.ledPrevState.trigger then
|
||||
self.writeValue(l.ledTriggerFile, l.ledPrevState.trigger)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return Module
|
||||
@@ -0,0 +1,160 @@
|
||||
|
||||
local unistd = require("posix.unistd")
|
||||
|
||||
local Module = {
|
||||
name = "mod_network_restart",
|
||||
runPrio = 30,
|
||||
config = {},
|
||||
syslog = function(level, msg) return true end,
|
||||
debugOutput = function(msg) return true end,
|
||||
writeValue = function(filePath, str) return false end,
|
||||
readValue = function(filePath) return nil end,
|
||||
deadPeriod = 900,
|
||||
attempts = 1,
|
||||
attemptInterval = 15,
|
||||
deviceTimeout = 0,
|
||||
status = nil,
|
||||
_attemptsCounter = 0,
|
||||
_attemptIntervalCounter = 0,
|
||||
_deadCounter = 0,
|
||||
_firstAttempt = true,
|
||||
_ifaceRestarting = false,
|
||||
_ifaceRestartCounter = 0,
|
||||
_netIfaces = {},
|
||||
_netDevices = {},
|
||||
_netItemsNum = 0,
|
||||
_disconnectedAtStartup = false,
|
||||
}
|
||||
|
||||
function Module:toggleDevices(flag)
|
||||
if #self._netDevices == 0 then
|
||||
return
|
||||
end
|
||||
local ip = "/sbin/ip"
|
||||
if unistd.access(ip, "x") then
|
||||
for _, v in ipairs(self._netDevices) do
|
||||
os.execute(string.format("%s link set dev %s %s", ip, v, (flag and "up" or "down")))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Module:toggleIfaces(flag)
|
||||
if #self._netIfaces == 0 then
|
||||
return
|
||||
end
|
||||
for _, v in ipairs(self._netIfaces) do
|
||||
os.execute(string.format("%s %s", (flag and "/sbin/ifup" or "/sbin/ifdown"), v))
|
||||
end
|
||||
end
|
||||
|
||||
function Module:netItemsUp()
|
||||
self:toggleDevices(true)
|
||||
self:toggleIfaces(true)
|
||||
end
|
||||
|
||||
function Module:netItemsDown()
|
||||
self:toggleIfaces(false)
|
||||
self:toggleDevices(false)
|
||||
end
|
||||
|
||||
function Module:restartNetworkService()
|
||||
return os.execute("/etc/init.d/network restart")
|
||||
end
|
||||
|
||||
function Module:init(t)
|
||||
if t.ifaces ~= nil and type(t.ifaces) == "table" then
|
||||
self._netIfaces = {}
|
||||
self._netDevices = {}
|
||||
self._netItemsNum = 0
|
||||
for k, v in ipairs(t.ifaces) do
|
||||
if v:match("^@") then
|
||||
self._netIfaces[#self._netIfaces + 1] = v:gsub("^@", "")
|
||||
else
|
||||
self._netDevices[#self._netDevices + 1] = v
|
||||
end
|
||||
self._netItemsNum = self._netItemsNum + 1
|
||||
end
|
||||
end
|
||||
if t.dead_period ~= nil then
|
||||
self.deadPeriod = tonumber(t.dead_period)
|
||||
end
|
||||
if t.attempts ~= nil then
|
||||
self.attempts = tonumber(t.attempts)
|
||||
end
|
||||
if t.attempt_interval ~= nil then
|
||||
self.attemptInterval = tonumber(t.attempt_interval)
|
||||
end
|
||||
if t.device_timeout ~= nil then
|
||||
self.deviceTimeout = tonumber(t.device_timeout)
|
||||
end
|
||||
if tonumber(t.disconnected_at_startup) == 1 then
|
||||
self._disconnectedAtStartup = true
|
||||
end
|
||||
end
|
||||
|
||||
function Module:networkRestartFunc()
|
||||
if self._netItemsNum > 0 then
|
||||
if #self._netIfaces > 0 then
|
||||
self.syslog("info", string.format("%s: restarting interfaces: %s",
|
||||
self.name, table.concat(self._netIfaces, ", ")))
|
||||
end
|
||||
if #self._netDevices > 0 then
|
||||
self.syslog("info", string.format("%s: restarting devices: %s",
|
||||
self.name, table.concat(self._netDevices, ", ")))
|
||||
end
|
||||
self:netItemsDown()
|
||||
if self.deviceTimeout < 1 then
|
||||
self:netItemsUp()
|
||||
else
|
||||
self._ifaceRestarting = true
|
||||
end
|
||||
else
|
||||
self.syslog("info", string.format(
|
||||
"%s: restarting network", self.name))
|
||||
self:restartNetworkService()
|
||||
end
|
||||
if self.attempts > 0 then
|
||||
self._attemptsCounter = self._attemptsCounter + 1
|
||||
end
|
||||
end
|
||||
|
||||
function Module:run(currentStatus, lastStatus, timeDiff, timeNow, inetChecked)
|
||||
if self._ifaceRestarting then
|
||||
if self._ifaceRestartCounter >= self.deviceTimeout then
|
||||
self:netItemsUp()
|
||||
self._ifaceRestarting = false
|
||||
self._ifaceRestartCounter = 0
|
||||
else
|
||||
self._ifaceRestartCounter = self._ifaceRestartCounter + timeDiff
|
||||
end
|
||||
else
|
||||
if currentStatus == 1 then
|
||||
if self._disconnectedAtStartup and self._deadCounter >= self.deadPeriod then
|
||||
if self.attempts == 0 or self._attemptsCounter < self.attempts then
|
||||
if self._firstAttempt or self._attemptIntervalCounter >= self.attemptInterval then
|
||||
self:networkRestartFunc()
|
||||
self._attemptIntervalCounter = 0
|
||||
self._firstAttempt = false
|
||||
else
|
||||
self._attemptIntervalCounter = self._attemptIntervalCounter + timeDiff
|
||||
end
|
||||
end
|
||||
else
|
||||
self._deadCounter = self._deadCounter + timeDiff
|
||||
end
|
||||
else
|
||||
self._attemptsCounter = 0
|
||||
self._attemptIntervalCounter = 0
|
||||
self._deadCounter = 0
|
||||
self._disconnectedAtStartup = true
|
||||
self._firstAttempt = true
|
||||
end
|
||||
self._ifaceRestartCounter = 0
|
||||
end
|
||||
end
|
||||
|
||||
function Module:onExit()
|
||||
return true
|
||||
end
|
||||
|
||||
return Module
|
||||
@@ -0,0 +1,533 @@
|
||||
|
||||
local socket = require("posix.sys.socket")
|
||||
local stdlib = require("posix.stdlib")
|
||||
local unistd = require("posix.unistd")
|
||||
|
||||
local Module = {
|
||||
name = "mod_public_ip",
|
||||
runPrio = 50,
|
||||
config = {
|
||||
noModules = false,
|
||||
debug = false,
|
||||
serviceConfig = {
|
||||
iface = nil,
|
||||
proxy_type = nil,
|
||||
proxy_host = nil,
|
||||
proxy_port = nil,
|
||||
},
|
||||
},
|
||||
syslog = function(level, msg) return true end,
|
||||
debugOutput = function(msg) return true end,
|
||||
writeValue = function(filePath, str) return false end,
|
||||
readValue = function(filePath) return nil end,
|
||||
port = 53,
|
||||
runInterval = 600,
|
||||
runIntervalFailed = 60,
|
||||
runIntervalIPFailed = 1,
|
||||
requestAttempts = 2,
|
||||
timeout = 3,
|
||||
curlExec = "/usr/bin/curl",
|
||||
curlParams = '-s --no-keepalive --user-agent "Mozilla/5.0 (X11; Linux x86_64; rv:142.0) Gecko/20100101 Firefox/142.0"',
|
||||
providers = {
|
||||
opendns1 = {
|
||||
name = "opendns1", type = "dns", host = "myip.opendns.com",
|
||||
server = "208.67.222.222", server6 = "2620:119:35::35",
|
||||
port = 53, queryType = "A", queryType6 = "AAAA",
|
||||
},
|
||||
opendns2 = {
|
||||
name = "opendns2", type = "dns", host = "myip.opendns.com",
|
||||
server = "208.67.220.220", server6 = "2620:119:35::35",
|
||||
port = 53, queryType = "A", queryType6 = "AAAA",
|
||||
},
|
||||
opendns3 = {
|
||||
name = "opendns3", type = "dns", host = "myip.opendns.com",
|
||||
server = "208.67.222.220", server6 = "2620:119:35::35",
|
||||
port = 53, queryType = "A", queryType6 = "AAAA",
|
||||
},
|
||||
opendns4 = {
|
||||
name = "opendns4", type = "dns", host = "myip.opendns.com",
|
||||
server = "208.67.220.222", server6 = "2620:119:35::35",
|
||||
port = 53, queryType = "A", queryType6 = "AAAA",
|
||||
},
|
||||
google = {
|
||||
name = "google", type = "dns", host = "o-o.myaddr.l.google.com",
|
||||
server = "ns1.google.com", server6 = "ns1.google.com",
|
||||
port = 53, queryType = "TXT", queryType6 = "TXT",
|
||||
},
|
||||
akamai = {
|
||||
name = "akamai", type = "dns", host = "whoami.akamai.net",
|
||||
server = "ns1-1.akamaitech.net", server6 = "ns1-1.akamaitech.net",
|
||||
port = 53, queryType = "A", queryType6 = "AAAA",
|
||||
},
|
||||
akamai_http = {
|
||||
name = "akamai_http", type = "http", url = "http://whatismyip.akamai.com/",
|
||||
parseResponseFunc = nil,
|
||||
},
|
||||
amazonaws= {
|
||||
name = "amazonaws", type = "http", url = "http://checkip.amazonaws.com/",
|
||||
parseResponseFunc = nil,
|
||||
},
|
||||
wgetip= {
|
||||
name = "wgetip", type = "http", url = "http://wgetip.com/",
|
||||
parseResponseFunc = nil,
|
||||
},
|
||||
ifconfig= {
|
||||
name = "ifconfig", type = "http", url = "http://ifconfig.me/",
|
||||
parseResponseFunc = nil,
|
||||
},
|
||||
ipecho= {
|
||||
name = "ipecho", type = "http", url = "http://ipecho.net/plain",
|
||||
parseResponseFunc = nil,
|
||||
},
|
||||
canhazip= {
|
||||
name = "canhazip", type = "http", url = "http://canhazip.com/",
|
||||
parseResponseFunc = nil,
|
||||
},
|
||||
icanhazip = {
|
||||
name = "icanhazip", type = "http", url = "http://icanhazip.com/",
|
||||
parseResponseFunc = nil,
|
||||
},
|
||||
},
|
||||
ipScript = "",
|
||||
enableIpScript = false,
|
||||
status = nil,
|
||||
_proxyString = "",
|
||||
_provider = nil,
|
||||
_qtype = false,
|
||||
_currentIp = nil,
|
||||
_lastResolvedIp = nil,
|
||||
_enabled = false,
|
||||
_counter = 0,
|
||||
_IPFalseCounter = 0,
|
||||
_interval = 600,
|
||||
_DNSPacket = nil,
|
||||
_requestIP = nil,
|
||||
}
|
||||
|
||||
function Module:runIpScript()
|
||||
if not self.config.noModules and self.enableIpScript and unistd.access(self.ipScript, "r") then
|
||||
stdlib.setenv("PUBLIC_IP", self.status)
|
||||
os.execute(string.format('/bin/sh "%s" &', self.ipScript))
|
||||
end
|
||||
end
|
||||
|
||||
function Module:getQueryType(type)
|
||||
local types = {
|
||||
A = 1,
|
||||
NS = 2,
|
||||
MD = 3,
|
||||
MF = 4,
|
||||
CNAME = 5,
|
||||
SOA = 6,
|
||||
MB = 7,
|
||||
MG = 8,
|
||||
MR = 9,
|
||||
NULL = 10,
|
||||
WKS = 11,
|
||||
PTS = 12,
|
||||
HINFO = 13,
|
||||
MINFO = 14,
|
||||
MX = 15,
|
||||
TXT = 16,
|
||||
AAAA = 28,
|
||||
}
|
||||
return types[type]
|
||||
end
|
||||
|
||||
function Module:buildMessage(address, queryType)
|
||||
if not queryType then
|
||||
queryType = "A"
|
||||
end
|
||||
queryType = self:getQueryType(queryType)
|
||||
|
||||
local addressString = ""
|
||||
for part in address:gmatch("[^.]+") do
|
||||
local t = {}
|
||||
for i in part:gmatch(".") do
|
||||
t[#t + 1] = i
|
||||
end
|
||||
addrLen = #part
|
||||
addrPart = table.concat(t)
|
||||
addressString = addressString .. string.char(addrLen) .. addrPart
|
||||
end
|
||||
|
||||
local data = (
|
||||
string.char(
|
||||
0xaa, 0xaa,
|
||||
0x01, 0x00,
|
||||
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||
) ..
|
||||
addressString ..
|
||||
string.char(
|
||||
0x00,
|
||||
0x00, queryType,
|
||||
0x00, 0x01
|
||||
)
|
||||
)
|
||||
return data
|
||||
end
|
||||
|
||||
function Module:sendUDPMessage(message, server, port)
|
||||
local success
|
||||
local retCode = 1
|
||||
local data
|
||||
|
||||
self.debugOutput(string.format("--- %s ---", self.name))
|
||||
|
||||
local saTable, errMsg, errNum = socket.getaddrinfo(server, port)
|
||||
|
||||
if not saTable then
|
||||
self.debugOutput(string.format(
|
||||
"GETADDRINFO ERROR: %s, %s", errMsg, errNum))
|
||||
else
|
||||
local family = saTable[1].family
|
||||
|
||||
if family then
|
||||
local sock, errMsg, errNum = socket.socket(family, socket.SOCK_DGRAM, 0)
|
||||
|
||||
if not sock then
|
||||
self.debugOutput(string.format(
|
||||
"SOCKET ERROR: %s, %s", errMsg, errNum))
|
||||
return retCode
|
||||
end
|
||||
|
||||
socket.setsockopt(sock, socket.SOL_SOCKET,
|
||||
socket.SO_SNDTIMEO, self.timeout, 0)
|
||||
socket.setsockopt(sock, socket.SOL_SOCKET,
|
||||
socket.SO_RCVTIMEO, self.timeout, 0)
|
||||
|
||||
if self.config.serviceConfig.iface then
|
||||
local ok, errMsg, errNum = socket.setsockopt(sock, socket.SOL_SOCKET,
|
||||
socket.SO_BINDTODEVICE, self.config.serviceConfig.iface)
|
||||
if not ok then
|
||||
self.debugOutput(string.format(
|
||||
"SOCKET ERROR: %s, %s", errMsg, errNum))
|
||||
unistd.close(sock)
|
||||
return retCode
|
||||
end
|
||||
end
|
||||
|
||||
local ok, errMsg, errNum = socket.sendto(sock, message, saTable[1])
|
||||
|
||||
local response = {}
|
||||
if ok then
|
||||
local ret, resp, errNum = socket.recvfrom(sock, 1024)
|
||||
data = ret
|
||||
if data then
|
||||
success = true
|
||||
response = resp
|
||||
else
|
||||
self.debugOutput(string.format(
|
||||
"SOCKET RECV ERROR: %s, %s", tostring(resp), tostring(errNum)))
|
||||
end
|
||||
else
|
||||
self.debugOutput(string.format(
|
||||
"SOCKET SEND ERROR: %s, %s", tostring(errMsg), tostring(errNum)))
|
||||
end
|
||||
|
||||
self.debugOutput(string.format(
|
||||
"--- UDP ---\ntime = %s\nconnection_timeout = %s\niface = %s\nserver = %s:%s\nsockname = %s:%s\nsuccess = %s",
|
||||
os.time(),
|
||||
self.timeout,
|
||||
tostring(self.config.serviceConfig.iface),
|
||||
server,
|
||||
tostring(port),
|
||||
tostring(response.addr),
|
||||
tostring(response.port),
|
||||
tostring(success))
|
||||
)
|
||||
|
||||
unistd.close(sock)
|
||||
retCode = success and 0 or 1
|
||||
end
|
||||
end
|
||||
return retCode, tostring(data)
|
||||
end
|
||||
|
||||
function Module:parseParts(message, start, parts)
|
||||
local partStart = start + 2
|
||||
local partLen = message:sub(start, start + 1)
|
||||
if #partLen == 0 then
|
||||
return parts
|
||||
end
|
||||
local partEnd = partStart + (tonumber(partLen, 16) * 2)
|
||||
parts[#parts + 1] = message:sub(partStart, partEnd - 1)
|
||||
if message:sub(partEnd, partEnd + 1) == "00" or partEnd > #message then
|
||||
return parts
|
||||
else
|
||||
return self:parseParts(message, partEnd, parts)
|
||||
end
|
||||
end
|
||||
|
||||
function Module:decodeMessage(message)
|
||||
local retTable = {}
|
||||
local t = {}
|
||||
for i = 1, #message do
|
||||
t[#t + 1] = string.format("%.2x", string.byte(message, i))
|
||||
end
|
||||
message = table.concat(t)
|
||||
|
||||
local ANCOUNT = message:sub(13, 16)
|
||||
local NSCOUNT = message:sub(17, 20)
|
||||
local ARCOUNT = message:sub(21, 24)
|
||||
|
||||
-- Question section
|
||||
local questionSectionStarts = 25
|
||||
local questionParts = self:parseParts(message, questionSectionStarts, {})
|
||||
local qtypeStarts = questionSectionStarts + (#table.concat(questionParts)) + (#questionParts * 2) + 1
|
||||
local qclassStarts = qtypeStarts + 4
|
||||
|
||||
-- Answer section
|
||||
local answerSectionStarts = qclassStarts + 4
|
||||
local numAnswers = math.max(
|
||||
tonumber(ANCOUNT, 16), tonumber(NSCOUNT, 16), tonumber(ARCOUNT, 16))
|
||||
|
||||
if numAnswers > 0 then
|
||||
for answerCount = 1, numAnswers do
|
||||
if answerSectionStarts < #message then
|
||||
local ATYPE = tonumber(
|
||||
message:sub(answerSectionStarts + 5, answerSectionStarts + 8), 16)
|
||||
local RDLENGTH = tonumber(
|
||||
message:sub(answerSectionStarts + 21, answerSectionStarts + 24), 16)
|
||||
local RDDATA = message:sub(
|
||||
answerSectionStarts + 25, answerSectionStarts + 24 + (RDLENGTH * 2))
|
||||
local RDDATA_decoded = ""
|
||||
|
||||
if #RDDATA > 0 then
|
||||
if ATYPE == self:getQueryType("A") or ATYPE == self:getQueryType("AAAA") then
|
||||
local octets = {}
|
||||
local sep = "."
|
||||
if #RDDATA > 8 then
|
||||
sep = ":"
|
||||
for i = 1, #RDDATA, 4 do
|
||||
local string = RDDATA:sub(i, i + 3)
|
||||
string = string:gsub("^00?0?", "")
|
||||
octets[#octets + 1] = string
|
||||
end
|
||||
else
|
||||
for i = 1, #RDDATA, 2 do
|
||||
octets[#octets + 1] = tonumber(RDDATA:sub(i, i + 1), 16)
|
||||
end
|
||||
end
|
||||
RDDATA_decoded = table.concat(octets, sep):gsub("0:[0:]+", "::", 1):gsub("::+", "::")
|
||||
else
|
||||
local rdata_t = {}
|
||||
for _, v in ipairs(self:parseParts(RDDATA, 1, {})) do
|
||||
local t = {}
|
||||
for i = 1, #v, 2 do
|
||||
t[#t + 1] = string.char(tonumber(v:sub(i, i + 1), 16))
|
||||
end
|
||||
rdata_t[#rdata_t + 1] = table.concat(t)
|
||||
end
|
||||
RDDATA_decoded = table.concat(rdata_t)
|
||||
end
|
||||
end
|
||||
answerSectionStarts = answerSectionStarts + 24 + (RDLENGTH * 2)
|
||||
|
||||
if RDDATA_decoded:match("^[a-fA-F0-9.:]+$") then
|
||||
retTable[#retTable + 1] = RDDATA_decoded
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return retTable
|
||||
end
|
||||
|
||||
function Module:requestIPDNS()
|
||||
local res
|
||||
local qtype = self._qtype and self._provider.queryType6 or self._provider.queryType
|
||||
local server = self._qtype and self._provider.server6 or self._provider.server
|
||||
local port = self._provider.port or self.port
|
||||
if not self._DNSPacket then
|
||||
self._DNSPacket = self:buildMessage(self._provider.host, qtype)
|
||||
end
|
||||
local retCode, response = self:sendUDPMessage(self._DNSPacket, server, port)
|
||||
if retCode == 0 and response then
|
||||
local retTable = self:decodeMessage(response)
|
||||
if #retTable > 0 then
|
||||
res = table.concat(retTable, ", ")
|
||||
end
|
||||
else
|
||||
self.syslog("warning", string.format(
|
||||
"%s: UDP error when requesting an IP address", self.name))
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
function Module:httpRequest(url)
|
||||
local retCode = 1, data
|
||||
local curl = string.format(
|
||||
'%s%s%s --connect-timeout %s %s "%s"; printf "\n$?";',
|
||||
self.curlExec,
|
||||
self.config.serviceConfig.iface and (" --interface " .. self.config.serviceConfig.iface) or "",
|
||||
self._proxyString,
|
||||
self.timeout,
|
||||
self.curlParams,
|
||||
url
|
||||
)
|
||||
local fh = io.popen(curl, "r")
|
||||
if fh then
|
||||
data = fh:read("*a")
|
||||
fh:close()
|
||||
if data ~= nil then
|
||||
local s, e = data:find("[0-9]+\n?$")
|
||||
retCode = tonumber(data:sub(s))
|
||||
data = data:sub(0, s - 2)
|
||||
if not data or data == "" then
|
||||
data = nil
|
||||
end
|
||||
end
|
||||
else
|
||||
retCode = 1
|
||||
end
|
||||
|
||||
self.debugOutput(string.format(
|
||||
"--- Curl ---\ntime = %s\n%s\nretCode = %s\ndata = [\n%s]\n",
|
||||
os.time(),
|
||||
curl,
|
||||
retCode,
|
||||
tostring(data))
|
||||
)
|
||||
return retCode, data
|
||||
end
|
||||
|
||||
function Module:parseHTTPResponse(data)
|
||||
data = data:gsub("^[%s%c]+", ""):gsub("[%s%c]+$", "")
|
||||
if data:match("^[a-fA-F0-9.:]+$") then
|
||||
return data
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
function Module:requestIPHTTP()
|
||||
local res
|
||||
local url = self._provider.url
|
||||
local parseResponseFunc = self._provider.parseResponseFunc
|
||||
if url then
|
||||
local retCode, data = self:httpRequest(url)
|
||||
if retCode == 0 and data then
|
||||
if type(parseResponseFunc) == "function" then
|
||||
res = parseResponseFunc(data)
|
||||
else
|
||||
res = self:parseHTTPResponse(data)
|
||||
end
|
||||
else
|
||||
self.syslog("warning", string.format(
|
||||
"%s: HTTP error when requesting an IP address", self.name))
|
||||
end
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
function Module:init(t)
|
||||
if t.interval ~= nil then
|
||||
self.runInterval = tonumber(t.interval)
|
||||
end
|
||||
if t.interval_failed ~= nil then
|
||||
self.runIntervalFailed = tonumber(t.interval_failed)
|
||||
end
|
||||
if t.request_attempts ~= nil then
|
||||
self.requestAttempts = tonumber(t.request_attempts)
|
||||
end
|
||||
if t.timeout ~= nil then
|
||||
self.timeout = tonumber(t.timeout)
|
||||
end
|
||||
if t.provider ~= nil then
|
||||
self._provider = self.providers[t.provider]
|
||||
else
|
||||
self._provider = self.providers.opendns1
|
||||
end
|
||||
if self.config.configDir then
|
||||
self.ipScript = string.format(
|
||||
"%s/public-ip-script.%s", self.config.configDir, self.config.serviceConfig.instance)
|
||||
if t.enable_ip_script ~= nil then
|
||||
self.enableIpScript = (tonumber(t.enable_ip_script) ~= 0)
|
||||
end
|
||||
end
|
||||
if t.qtype ~= nil then
|
||||
self._qtype = (tonumber(t.qtype) ~= 0)
|
||||
end
|
||||
self._currentIp = nil
|
||||
self._lastResolvedIp = nil
|
||||
self._DNSPacket = nil
|
||||
self._interval = self.runInterval
|
||||
self._IPFalseCounter = 0
|
||||
self._enabled = true
|
||||
if not self._provider then
|
||||
self._enabled = false
|
||||
else
|
||||
if self._provider.url and not unistd.access(self.curlExec, "x") then
|
||||
self._enabled = false
|
||||
self.syslog("err", string.format(
|
||||
"%s: %s is not available. You need to install curl.", self.name, self.curlExec))
|
||||
end
|
||||
if self._provider.type == "dns" then
|
||||
self._requestIP = self.requestIPDNS
|
||||
elseif self._provider.type == "http" then
|
||||
self._requestIP = self.requestIPHTTP
|
||||
else
|
||||
self._enabled = false
|
||||
end
|
||||
end
|
||||
if (self.config.serviceConfig.proxy_type and
|
||||
self.config.serviceConfig.proxy_host and
|
||||
self.config.serviceConfig.proxy_port) then
|
||||
self._proxyString = string.format(
|
||||
" --proxy %s://%s:%d",
|
||||
self.config.serviceConfig.proxy_type,
|
||||
self.config.serviceConfig.proxy_host,
|
||||
self.config.serviceConfig.proxy_port)
|
||||
end
|
||||
end
|
||||
|
||||
function Module:run(currentStatus, lastStatus, timeDiff, timeNow, inetChecked)
|
||||
if not self._enabled then
|
||||
return
|
||||
end
|
||||
if currentStatus == 0 then
|
||||
if self._counter == 0 or self._counter >= self._interval or currentStatus ~= lastStatus then
|
||||
local ip = self:_requestIP()
|
||||
if not ip then
|
||||
ip = ""
|
||||
self._IPFalseCounter = self._IPFalseCounter + 1
|
||||
if self._IPFalseCounter >= self.requestAttempts then
|
||||
self._interval = self.runIntervalFailed
|
||||
self._IPFalseCounter = 0
|
||||
else
|
||||
self._interval = self.runIntervalIPFailed
|
||||
end
|
||||
else
|
||||
self._interval = self.runInterval
|
||||
self._IPFalseCounter = 0
|
||||
end
|
||||
if ip ~= self._currentIp then
|
||||
self.status = ip
|
||||
if ip ~= "" then
|
||||
if self._counter > 0 and ip ~= self._lastResolvedIp then
|
||||
self.syslog(
|
||||
"notice",
|
||||
string.format("%s: public IP address changed to %s", self.name, ip)
|
||||
)
|
||||
self:runIpScript()
|
||||
end
|
||||
self._lastResolvedIp = ip
|
||||
end
|
||||
end
|
||||
self._currentIp = ip
|
||||
self._counter = 0
|
||||
end
|
||||
else
|
||||
self._currentIp = nil
|
||||
self.status = self._currentIp
|
||||
self._IPFalseCounter = 0
|
||||
self._counter = 0
|
||||
self._interval = self.runInterval
|
||||
end
|
||||
self._counter = self._counter + timeDiff
|
||||
end
|
||||
|
||||
function Module:onExit()
|
||||
return true
|
||||
end
|
||||
|
||||
return Module
|
||||
@@ -0,0 +1,63 @@
|
||||
|
||||
local unistd = require("posix.unistd")
|
||||
|
||||
local Module = {
|
||||
name = "mod_reboot",
|
||||
runPrio = 20,
|
||||
config = {},
|
||||
syslog = function(level, msg) return true end,
|
||||
debugOutput = function(msg) return true end,
|
||||
writeValue = function(filePath, str) return false end,
|
||||
readValue = function(filePath) return nil end,
|
||||
deadPeriod = 3600,
|
||||
forceRebootDelay = 300,
|
||||
antiBootloopDelay = 300,
|
||||
status = nil,
|
||||
_deadCounter = 0,
|
||||
_rebooted = true,
|
||||
}
|
||||
|
||||
function Module:rebootDevice()
|
||||
self.syslog("warning", string.format("%s: reboot", self.name))
|
||||
os.execute("/sbin/reboot &")
|
||||
if self.forceRebootDelay > 0 then
|
||||
unistd.sleep(self.forceRebootDelay)
|
||||
self.syslog("warning", string.format("%s: force reboot", self.name))
|
||||
self.writeValue("/proc/sys/kernel/sysrq", "1")
|
||||
self.writeValue("/proc/sysrq-trigger", "b")
|
||||
end
|
||||
end
|
||||
|
||||
function Module:init(t)
|
||||
if t.dead_period ~= nil then
|
||||
self.deadPeriod = tonumber(t.dead_period)
|
||||
end
|
||||
if t.force_reboot_delay ~= nil then
|
||||
self.forceRebootDelay = tonumber(t.force_reboot_delay)
|
||||
end
|
||||
if tonumber(t.disconnected_at_startup) == 1 then
|
||||
self._rebooted = false
|
||||
end
|
||||
end
|
||||
|
||||
function Module:run(currentStatus, lastStatus, timeDiff, timeNow, inetChecked)
|
||||
if currentStatus == 1 then
|
||||
if not self._rebooted then
|
||||
if timeNow >= self.antiBootloopDelay and self._deadCounter >= self.deadPeriod then
|
||||
self:rebootDevice()
|
||||
self._rebooted = true
|
||||
else
|
||||
self._deadCounter = self._deadCounter + timeDiff
|
||||
end
|
||||
end
|
||||
else
|
||||
self._deadCounter = 0
|
||||
self._rebooted = false
|
||||
end
|
||||
end
|
||||
|
||||
function Module:onExit()
|
||||
return true
|
||||
end
|
||||
|
||||
return Module
|
||||
@@ -0,0 +1,67 @@
|
||||
|
||||
local stdlib = require("posix.stdlib")
|
||||
local time = require("posix.time")
|
||||
local unistd = require("posix.unistd")
|
||||
|
||||
local Module = {
|
||||
name = "mod_regular_script",
|
||||
runPrio = 90,
|
||||
config = {},
|
||||
syslog = function(level, msg) return true end,
|
||||
debugOutput = function(msg) return true end,
|
||||
writeValue = function(filePath, str) return false end,
|
||||
readValue = function(filePath) return nil end,
|
||||
inetState = 2, -- 0: connected, 1: disconnected, 2: both
|
||||
runInterval = 3600,
|
||||
script = "",
|
||||
status = nil,
|
||||
_nextTime = nil,
|
||||
_firstRun = true,
|
||||
}
|
||||
|
||||
function Module:runExternalScript(scriptPath, currentStatus)
|
||||
if unistd.access(scriptPath, "r") then
|
||||
stdlib.setenv("INET_STATE", currentStatus)
|
||||
os.execute(string.format('/bin/sh "%s" &', scriptPath))
|
||||
end
|
||||
end
|
||||
|
||||
function Module:init(t)
|
||||
if t.inet_state ~= nil then
|
||||
self.inetState = tonumber(t.inet_state)
|
||||
end
|
||||
if t.interval ~= nil then
|
||||
self.runInterval = tonumber(t.interval)
|
||||
end
|
||||
if self.config.configDir then
|
||||
self.script = string.format(
|
||||
"%s/regular-script.%s", self.config.configDir, self.config.serviceConfig.instance)
|
||||
end
|
||||
end
|
||||
|
||||
function Module:run(currentStatus, lastStatus, timeDiff, timeNow, inetChecked)
|
||||
if not self._nextTime then
|
||||
if timeNow < self.runInterval then
|
||||
self._nextTime = self.runInterval
|
||||
else
|
||||
self._nextTime = timeNow - (timeNow % self.runInterval) + self.runInterval
|
||||
end
|
||||
end
|
||||
if self._firstRun then
|
||||
self.status = time.strftime ("%Y-%m-%d %H:%M:%S %z", time.localtime(time.time() + self._nextTime - timeNow))
|
||||
self._firstRun = false
|
||||
end
|
||||
if timeNow >= self._nextTime then
|
||||
self._nextTime = self._nextTime + self.runInterval
|
||||
if self.inetState == 2 or (self.inetState == 0 and currentStatus == 0) or (self.inetState == 1 and currentStatus == 1) then
|
||||
self.status = time.strftime ("%Y-%m-%d %H:%M:%S %z", time.localtime(time.time() + self._nextTime - timeNow))
|
||||
self:runExternalScript(self.script, currentStatus)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Module:onExit()
|
||||
return true
|
||||
end
|
||||
|
||||
return Module
|
||||
@@ -0,0 +1,134 @@
|
||||
|
||||
local unistd = require("posix.unistd")
|
||||
|
||||
local Module = {
|
||||
name = "mod_user_scripts",
|
||||
runPrio = 80,
|
||||
config = {},
|
||||
syslog = function(level, msg) return true end,
|
||||
debugOutput = function(msg) return true end,
|
||||
writeValue = function(filePath, str) return false end,
|
||||
readValue = function(filePath) return nil end,
|
||||
deadPeriod = 0,
|
||||
alivePeriod = 0,
|
||||
upScript = "",
|
||||
downScript = "",
|
||||
upScriptAttempts = 1,
|
||||
upScriptAttemptInterval = 15,
|
||||
downScriptAttempts = 1,
|
||||
downScriptAttemptInterval = 15,
|
||||
status = nil,
|
||||
_deadCounter = 0,
|
||||
_aliveCounter = 0,
|
||||
_upScriptAttemptsCounter = 0,
|
||||
_upScriptAttemptIntervalCounter = 0,
|
||||
_downScriptAttemptsCounter = 0,
|
||||
_downScriptAttemptIntervalCounter = 0,
|
||||
_upScriptFirstAttempt = true,
|
||||
_downScriptFirstAttempt = true,
|
||||
_disconnectedAtStartup = false,
|
||||
_connectedAtStartup = false,
|
||||
}
|
||||
|
||||
function Module:runExternalScript(scriptPath)
|
||||
if unistd.access(scriptPath, "r") then
|
||||
os.execute(string.format('/bin/sh "%s" &', scriptPath))
|
||||
end
|
||||
end
|
||||
|
||||
function Module:init(t)
|
||||
if t.dead_period ~= nil then
|
||||
self.deadPeriod = tonumber(t.dead_period)
|
||||
end
|
||||
if t.alive_period ~= nil then
|
||||
self.alivePeriod = tonumber(t.alive_period)
|
||||
end
|
||||
if t.up_script_attempts ~= nil then
|
||||
self.upScriptAttempts = tonumber(t.up_script_attempts)
|
||||
end
|
||||
if t.up_script_attempt_interval ~= nil then
|
||||
self.upScriptAttemptInterval = tonumber(t.up_script_attempt_interval)
|
||||
end
|
||||
if t.down_script_attempts ~= nil then
|
||||
self.downScriptAttempts = tonumber(t.down_script_attempts)
|
||||
end
|
||||
if t.down_script_attempt_interval ~= nil then
|
||||
self.downScriptAttemptInterval = tonumber(t.down_script_attempt_interval)
|
||||
end
|
||||
if self.config.configDir then
|
||||
self.upScript = string.format(
|
||||
"%s/up-script.%s", self.config.configDir, self.config.serviceConfig.instance)
|
||||
self.downScript = string.format(
|
||||
"%s/down-script.%s", self.config.configDir, self.config.serviceConfig.instance)
|
||||
end
|
||||
if tonumber(t.connected_at_startup) == 1 then
|
||||
self._connectedAtStartup = true
|
||||
end
|
||||
if tonumber(t.disconnected_at_startup) == 1 then
|
||||
self._disconnectedAtStartup = true
|
||||
end
|
||||
end
|
||||
|
||||
function Module:runUpScriptFunc()
|
||||
self:runExternalScript(self.upScript)
|
||||
if self.upScriptAttempts > 0 then
|
||||
self._upScriptAttemptsCounter = self._upScriptAttemptsCounter + 1
|
||||
end
|
||||
end
|
||||
|
||||
function Module:runDownScriptFunc()
|
||||
self:runExternalScript(self.downScript)
|
||||
if self.downScriptAttempts > 0 then
|
||||
self._downScriptAttemptsCounter = self._downScriptAttemptsCounter + 1
|
||||
end
|
||||
end
|
||||
|
||||
function Module:run(currentStatus, lastStatus, timeDiff, timeNow, inetChecked)
|
||||
if currentStatus == 1 then
|
||||
self._upScriptAttemptsCounter = 0
|
||||
self._upScriptAttemptIntervalCounter = 0
|
||||
self._aliveCounter = 0
|
||||
self._connectedAtStartup = true
|
||||
self._upScriptFirstAttempt = true
|
||||
|
||||
if self._disconnectedAtStartup and self._deadCounter >= self.deadPeriod then
|
||||
if self.downScriptAttempts == 0 or self._downScriptAttemptsCounter < self.downScriptAttempts then
|
||||
if self._downScriptFirstAttempt or self._downScriptAttemptIntervalCounter >= self.downScriptAttemptInterval then
|
||||
self:runDownScriptFunc()
|
||||
self._downScriptAttemptIntervalCounter = 0
|
||||
self._downScriptFirstAttempt = false
|
||||
else
|
||||
self._downScriptAttemptIntervalCounter = self._downScriptAttemptIntervalCounter + timeDiff
|
||||
end
|
||||
end
|
||||
else
|
||||
self._deadCounter = self._deadCounter + timeDiff
|
||||
end
|
||||
elseif currentStatus == 0 then
|
||||
self._downScriptAttemptsCounter = 0
|
||||
self._downScriptAttemptIntervalCounter = 0
|
||||
self._deadCounter = 0
|
||||
self._disconnectedAtStartup = true
|
||||
self._downScriptFirstAttempt = true
|
||||
|
||||
if self._connectedAtStartup and self._aliveCounter >= self.alivePeriod then
|
||||
if self.upScriptAttempts == 0 or self._upScriptAttemptsCounter < self.upScriptAttempts then
|
||||
if self._upScriptFirstAttempt or self._upScriptAttemptIntervalCounter >= self.upScriptAttemptInterval then
|
||||
self:runUpScriptFunc()
|
||||
self._upScriptAttemptIntervalCounter = 0
|
||||
self._upScriptFirstAttempt = false
|
||||
else
|
||||
self._upScriptAttemptIntervalCounter = self._upScriptAttemptIntervalCounter + timeDiff
|
||||
end
|
||||
end
|
||||
else
|
||||
self._aliveCounter = self._aliveCounter + timeDiff
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Module:onExit()
|
||||
return true
|
||||
end
|
||||
|
||||
return Module
|
||||
@@ -1,10 +1,12 @@
|
||||
#
|
||||
# (с) 2021 gSpot (https://github.com/gSpotx2f/luci-app-internet-detector)
|
||||
# (с) 2025 gSpot (https://github.com/gSpotx2f/luci-app-internet-detector)
|
||||
#
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_VERSION:=0.6-1
|
||||
PKG_NAME:=luci-app-internet-detector
|
||||
PKG_VERSION:=1.7.0
|
||||
PKG_RELEASE:=1
|
||||
LUCI_TITLE:=LuCI support for internet-detector
|
||||
LUCI_DEPENDS:=+internet-detector
|
||||
LUCI_PKGARCH:=all
|
||||
|
||||
@@ -1,118 +1,150 @@
|
||||
'use strict';
|
||||
'require baseclass';
|
||||
'require fs';
|
||||
'require rpc';
|
||||
'require uci';
|
||||
|
||||
document.head.append(E('style', {'type': 'text/css'},
|
||||
`
|
||||
:root {
|
||||
--app-id-font-color: #fff;
|
||||
--app-id-connected-color: #2ea256;
|
||||
--app-id-disconnected-color: #ff4e54;
|
||||
--app-id-undefined-color: #8a8a8a;
|
||||
--app-id-font-color: #454545;
|
||||
--app-id-font-shadow: #fff;
|
||||
--app-id-connected-color: #6bdebb;
|
||||
--app-id-disconnected-color: #f8aeba;
|
||||
--app-id-undefined-color: #dfdfdf;
|
||||
}
|
||||
:root[data-darkmode="true"] {
|
||||
--app-id-font-color: #f6f6f6;
|
||||
--app-id-font-shadow: #4d4d4d;
|
||||
--app-id-connected-color: #005F20;
|
||||
--app-id-disconnected-color: #a93734;
|
||||
--app-id-undefined-color: #4d4d4d;
|
||||
}
|
||||
.id-connected {
|
||||
--on-color: var(--app-id-font-color);
|
||||
background-color: var(--app-id-connected-color) !important;
|
||||
border-color: var(--app-id-connected-color) !important;
|
||||
color: var(--app-id-font-color) !important;
|
||||
text-shadow: 0 1px 1px var(--app-id-font-shadow);
|
||||
}
|
||||
.id-disconnected {
|
||||
--on-color: var(--app-id-font-color);
|
||||
background-color: var(--app-id-disconnected-color) !important;
|
||||
border-color: var(--app-id-disconnected-color) !important;
|
||||
color: var(--app-id-font-color) !important;
|
||||
text-shadow: 0 1px 1px var(--app-id-font-shadow);
|
||||
}
|
||||
.id-undefined {
|
||||
--on-color: var(--app-id-font-color);
|
||||
background-color: var(--app-id-undefined-color) !important;
|
||||
border-color: var(--app-id-undefined-color) !important;
|
||||
color: var(--app-id-font-color) !important;
|
||||
text-shadow: 0 1px 1px var(--app-id-font-shadow);
|
||||
}
|
||||
.id-label-status {
|
||||
display: inline-block;
|
||||
word-wrap: break-word;
|
||||
margin: 2px !important;
|
||||
padding: 4px 8px;
|
||||
border: 1px solid;
|
||||
-webkit-border-radius: 4px;
|
||||
-moz-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
font-weight: bold;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
`));
|
||||
|
||||
return baseclass.extend({
|
||||
title : _('Internet'),
|
||||
appName : 'internet-detector',
|
||||
execPath : '/usr/bin/internet-detector',
|
||||
currentAppMode : null,
|
||||
inetStatus : null,
|
||||
publicIp : null,
|
||||
|
||||
inetStatusFromJson: function(res) {
|
||||
let curInetStatus = null;
|
||||
let curPubIp = null;
|
||||
if(res.code === 0) {
|
||||
try {
|
||||
let json = JSON.parse(res.stdout.trim());
|
||||
curInetStatus = json.inet;
|
||||
curPubIp = json.mod_public_ip;
|
||||
} catch(e) {};
|
||||
};
|
||||
return [ curInetStatus, curPubIp ];
|
||||
callUIPoll: rpc.declare({
|
||||
object: 'luci.internet-detector',
|
||||
method: 'UIPoll',
|
||||
expect: { '': {} }
|
||||
}),
|
||||
|
||||
getUIPoll() {
|
||||
return this.callUIPoll().then(data => {
|
||||
return data;
|
||||
});
|
||||
},
|
||||
|
||||
load: async function() {
|
||||
if(!(
|
||||
'uiCheckIntervalUp' in window &&
|
||||
'uiCheckIntervalDown' in window &&
|
||||
'currentAppMode' in window
|
||||
)) {
|
||||
callInetStatus: rpc.declare({
|
||||
object: 'luci.internet-detector',
|
||||
method: 'InetStatus',
|
||||
expect: { '': {} }
|
||||
}),
|
||||
|
||||
getInetStatus() {
|
||||
return this.callInetStatus().then(data => {
|
||||
return data;
|
||||
});
|
||||
},
|
||||
|
||||
async load() {
|
||||
if(!this.currentAppMode) {
|
||||
await uci.load(this.appName).then(data => {
|
||||
window.uiCheckIntervalUp = Number(uci.get(this.appName, 'config', 'ui_interval_up'));
|
||||
window.uiCheckIntervalDown = Number(uci.get(this.appName, 'config', 'ui_interval_down'));
|
||||
window.currentAppMode = uci.get(this.appName, 'config', 'mode');
|
||||
this.currentAppMode = uci.get(this.appName, 'config', 'mode');
|
||||
}).catch(e => {});
|
||||
};
|
||||
|
||||
if(window.currentAppMode === '1' || window.currentAppMode === '2') {
|
||||
window.internetDetectorCounter = ('internetDetectorCounter' in window) ?
|
||||
++window.internetDetectorCounter : 0;
|
||||
|
||||
if(!('internetDetectorState' in window)) {
|
||||
window.internetDetectorState = 2;
|
||||
};
|
||||
|
||||
if(window.currentAppMode === '1' && (
|
||||
(window.internetDetectorState === 0 && window.internetDetectorCounter % window.uiCheckIntervalUp) ||
|
||||
(window.internetDetectorState === 1 && window.internetDetectorCounter % window.uiCheckIntervalDown)
|
||||
)) {
|
||||
return;
|
||||
};
|
||||
|
||||
window.internetDetectorCounter = 0;
|
||||
return L.resolveDefault(fs.exec(this.execPath, [ 'inet-status-json' ]), null);
|
||||
if(this.currentAppMode == '2') {
|
||||
return this.getUIPoll();
|
||||
}
|
||||
else {
|
||||
window.internetDetectorState = 2;
|
||||
else if(this.currentAppMode == '1') {
|
||||
return L.resolveDefault(this.getInetStatus(), null);
|
||||
};
|
||||
},
|
||||
|
||||
render: function(data) {
|
||||
if(window.currentAppMode === '0') {
|
||||
return
|
||||
render(data) {
|
||||
if(this.currentAppMode == '0') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.inetStatus = data;
|
||||
|
||||
let inetStatusArea = E('div', {});
|
||||
|
||||
if(!this.inetStatus || !this.inetStatus.instances || this.inetStatus.instances.length == 0) {
|
||||
let label = E('span', { 'class': 'id-label-status id-undefined' }, _('Undefined'));
|
||||
if(this.currentAppMode == '2') {
|
||||
label.classList.add('spinning');
|
||||
};
|
||||
inetStatusArea.append(label);
|
||||
} else {
|
||||
this.inetStatus.instances.sort((a, b) => a.num - b.num);
|
||||
|
||||
for(let i of this.inetStatus.instances) {
|
||||
let status = _('Disconnected');
|
||||
let className = 'id-label-status id-disconnected';
|
||||
if(i.inet == 0) {
|
||||
status = _('Connected');
|
||||
className = 'id-label-status id-connected';
|
||||
}
|
||||
else if(i.inet == -1) {
|
||||
status = _('Undefined');
|
||||
className = 'id-label-status id-undefined spinning';
|
||||
};
|
||||
|
||||
if(data) {
|
||||
[ window.internetDetectorState, this.publicIp ] = this.inetStatusFromJson(data);
|
||||
let publicIp = (i.mod_public_ip !== undefined) ?
|
||||
' | %s: %s'.format(_('Public IP'), (i.mod_public_ip == '') ? _('Undefined') : _(i.mod_public_ip))
|
||||
: '';
|
||||
|
||||
inetStatusArea.append(
|
||||
E('span', { 'class': className }, '%s%s%s'.format(
|
||||
i.instance + ': ', status, publicIp)
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
let internetStatus = E('span', { 'class': 'label' });
|
||||
|
||||
if(window.internetDetectorState === 0) {
|
||||
internetStatus.textContent = _('Connected') + (this.publicIp ? ' | %s: %s'.format(_('Public IP'), _(this.publicIp)) : '');
|
||||
internetStatus.className = "label id-connected";
|
||||
}
|
||||
else if(window.internetDetectorState === 1) {
|
||||
internetStatus.textContent = _('Disconnected');
|
||||
internetStatus.className = "label id-disconnected";
|
||||
}
|
||||
else {
|
||||
internetStatus.textContent = _('Undefined');
|
||||
internetStatus.className = "label id-undefined";
|
||||
};
|
||||
|
||||
return E('div', {
|
||||
'class': 'cbi-section',
|
||||
'style': 'margin-bottom:1em',
|
||||
}, internetStatus);
|
||||
}, inetStatusArea);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -20,9 +20,25 @@ msgid "<abbr title=\"Light Emitting Diode\">LED</abbr> control"
|
||||
msgstr "Управление <abbr title=\"Светодиод\">LED</abbr>"
|
||||
|
||||
msgid ""
|
||||
"<abbr title=\"Light Emitting Diode\">LED</abbr> is on when Internet is "
|
||||
"available."
|
||||
msgstr "<abbr title=\"Светодиод\">LED</abbr> включен если Интернет доступен."
|
||||
"<abbr title=\"Light Emitting Diode\">LED</abbr> indicates the Internet status."
|
||||
msgstr "<abbr title=\"Светодиод\">LED</abbr> отображает статус Интернет."
|
||||
|
||||
msgid "<abbr title=\"Light Emitting Diode\">LED</abbr> mode"
|
||||
msgstr "Режим <abbr title=\"Светодиод\">LED</abbr>"
|
||||
|
||||
msgid ""
|
||||
"<abbr title=\"Light Emitting Diode\">LED</abbr> will display the link activity of this network device."
|
||||
msgstr ""
|
||||
"<abbr title=\"Светодиод\">LED</abbr> будет отображать активность линка этого сетевого устройства."
|
||||
|
||||
msgid "Add instance"
|
||||
msgstr "Добавить экземпляр"
|
||||
|
||||
msgid "After connection"
|
||||
msgstr "После подключения"
|
||||
|
||||
msgid "After disconnection"
|
||||
msgstr "После отключения"
|
||||
|
||||
msgid "Alive interval"
|
||||
msgstr "Интервал при подключении"
|
||||
@@ -30,17 +46,33 @@ msgstr "Интервал при подключении"
|
||||
msgid "Alive period"
|
||||
msgstr "Период после подключения"
|
||||
|
||||
msgid ""
|
||||
"An email will be sent when the internet connection is restored after being "
|
||||
"disconnected."
|
||||
msgstr "Сообщение будет отправлено при восстановлении соединения с Интернет после отключения."
|
||||
msgid "An email will be sent when connected or disconnected from the Internet."
|
||||
msgstr "Сообщение будет отправлено на email при подключении или отключении от Интернет."
|
||||
|
||||
msgid "An error has occurred"
|
||||
msgstr "Произошла ошибка"
|
||||
|
||||
msgid "Attempt interval"
|
||||
msgstr "Интервал попыток"
|
||||
|
||||
msgid "Attempts"
|
||||
msgstr "Попытки"
|
||||
|
||||
msgid "Big: 248 bytes"
|
||||
msgstr "Большой: 248 байт"
|
||||
|
||||
msgid "Blinking (kernel: timer)"
|
||||
msgstr "Мигание (kernel: timer)"
|
||||
|
||||
msgid "Bot API token is missing!"
|
||||
msgstr "Отсутствует API токен бота!"
|
||||
|
||||
msgid "Bot token"
|
||||
msgstr "Токен бота"
|
||||
|
||||
msgid "ID чата"
|
||||
msgstr ""
|
||||
|
||||
msgid "Check type"
|
||||
msgstr "Тип проверки"
|
||||
|
||||
@@ -65,15 +97,27 @@ msgstr "Таймаут соединения"
|
||||
msgid "Contents have been saved."
|
||||
msgstr "Содержимое сохранено."
|
||||
|
||||
msgid "Curl is not available..."
|
||||
msgstr "Curl недоступен..."
|
||||
|
||||
msgid "Dead interval"
|
||||
msgstr "Интервал при отключении"
|
||||
|
||||
msgid "Dead period"
|
||||
msgstr "Период после отключения"
|
||||
|
||||
msgid "Debug"
|
||||
msgstr "Отладка"
|
||||
|
||||
msgid "Default port value for TCP connections."
|
||||
msgstr "Стандартное значение порта для TCP-подключений."
|
||||
|
||||
msgid "Description"
|
||||
msgstr "Описание"
|
||||
|
||||
msgid "Device"
|
||||
msgstr "Устройство"
|
||||
|
||||
msgid "Device will be rebooted when the Internet is disconnected."
|
||||
msgstr "Устройство будет перезагружено при отключении Интернет."
|
||||
|
||||
@@ -92,8 +136,8 @@ msgstr "Отключен"
|
||||
msgid "Dismiss"
|
||||
msgstr "Закрыть"
|
||||
|
||||
msgid "DNS provider"
|
||||
msgstr "DNS провайдер"
|
||||
msgid "DNS query type"
|
||||
msgstr "Тип DNS-запроса"
|
||||
|
||||
msgid "Edit"
|
||||
msgstr "Изменить"
|
||||
@@ -101,6 +145,12 @@ msgstr "Изменить"
|
||||
msgid "Edit down-script"
|
||||
msgstr "Изменить down-script"
|
||||
|
||||
msgid "Edit regular-script"
|
||||
msgstr "Изменить regular-script"
|
||||
|
||||
msgid "Edit public-ip-script"
|
||||
msgstr "Изменить public-ip-script"
|
||||
|
||||
msgid "Edit up-script"
|
||||
msgstr "Изменить up-script"
|
||||
|
||||
@@ -116,20 +166,23 @@ msgstr "Email-адрес отправителя."
|
||||
msgid "Enable"
|
||||
msgstr "Включить"
|
||||
|
||||
msgid "Enable logging"
|
||||
msgstr "Включить запись событий в лог"
|
||||
msgid "Enable public-ip-script"
|
||||
msgstr "Включить public-ip-script"
|
||||
|
||||
msgid "Enabled"
|
||||
msgstr "Включен"
|
||||
|
||||
msgid "Error"
|
||||
msgstr "Ошибка"
|
||||
|
||||
msgid "Error!"
|
||||
msgstr "Ошибка!"
|
||||
|
||||
msgid "Expecting:"
|
||||
msgstr "Ожидается:"
|
||||
|
||||
msgid "Public IP"
|
||||
msgstr "Публичный IP"
|
||||
|
||||
msgid "Public IP address"
|
||||
msgstr "Публичный IP адрес"
|
||||
msgid "Failed interval"
|
||||
msgstr "Интервал при неудаче"
|
||||
|
||||
msgid "Failed to get %s init status: %s"
|
||||
msgstr "Не удалось получить статус инициализации %s: %s"
|
||||
@@ -159,8 +212,8 @@ msgid "Hosts polling interval when the Internet is up."
|
||||
msgstr "Интервал опроса хостов если Интернет доступен."
|
||||
|
||||
msgid ""
|
||||
"Hosts to check Internet availability. Hosts are polled (in list order) until "
|
||||
"at least one of them responds."
|
||||
"Hosts for checking Internet availability. Hosts are polled (in list "
|
||||
"order) until at least one responds."
|
||||
msgstr ""
|
||||
"Хосты для проверки доступности Интернет. Хосты опрашиваются (в порядке "
|
||||
"списка) до тех пор, пока хотя бы один из них не ответит."
|
||||
@@ -168,9 +221,30 @@ msgstr ""
|
||||
msgid "Huge: 1492 bytes"
|
||||
msgstr "Огромный: 1492 байта"
|
||||
|
||||
msgid "ID of the Telegram chat to which messages will be sent."
|
||||
msgstr "ID чата Telegram в который будут отправлены сообщения."
|
||||
|
||||
msgid "ICMP-echo request (ping)"
|
||||
msgstr "Запрос ICMP-echo (ping)"
|
||||
|
||||
msgid "ICMP packet data size"
|
||||
msgstr "Размер данных ICMP-пакета"
|
||||
|
||||
msgid "Incorrect bot token?"
|
||||
msgstr "Неправильный токен бота?"
|
||||
|
||||
msgid "Info"
|
||||
msgstr "Информация"
|
||||
|
||||
msgid "Instances"
|
||||
msgstr "Экземпляры"
|
||||
|
||||
msgid "Interface"
|
||||
msgstr "Интерфейс"
|
||||
|
||||
msgid "Interface timeout"
|
||||
msgstr "Таймаут интерфейса"
|
||||
|
||||
msgid "Internet"
|
||||
msgstr "Интернет"
|
||||
|
||||
@@ -186,47 +260,41 @@ msgstr "Статус Интернет"
|
||||
msgid "Interval between IP address requests."
|
||||
msgstr "Интервал между запросами IP адреса."
|
||||
|
||||
msgid "Interval between IP address requests if the IP address is not defined."
|
||||
msgstr "Интервал между запросами IP адреса, если IP адрес не определён."
|
||||
|
||||
msgid "Interval between modem restarts."
|
||||
msgstr "Интервал между перезапусками модема."
|
||||
|
||||
msgid "Interval between network restarts."
|
||||
msgstr "Интервал между перезапусками сети."
|
||||
|
||||
msgid "Interval between down-script runs."
|
||||
msgstr "Интервал между запусками down-script."
|
||||
|
||||
msgid "Interval between up-script runs."
|
||||
msgstr "Интервал между запусками up-script."
|
||||
|
||||
msgid "Invalid instance name..."
|
||||
msgstr "Недопустимое имя экземпляра..."
|
||||
|
||||
msgid "Jumbo: 9000 bytes"
|
||||
msgstr "Гигантский: 9000 байт"
|
||||
|
||||
msgid "LED control"
|
||||
msgstr "Управление LED"
|
||||
|
||||
msgid "Link On"
|
||||
msgstr "Подключение"
|
||||
|
||||
msgid "Loading"
|
||||
msgstr "Загрузка"
|
||||
|
||||
msgid ""
|
||||
"Longest period of time after connecting to Internet before \"up-script\" "
|
||||
"runs."
|
||||
msgstr ""
|
||||
"Максимальный промежуток времени после подключения к Интернет перед запуском "
|
||||
"\"up-script\"."
|
||||
msgid "Log event level."
|
||||
msgstr "Уровень событий лога."
|
||||
|
||||
msgid ""
|
||||
"Longest period of time after connecting to the Internet before sending a "
|
||||
"message."
|
||||
msgstr "Максимальный промежуток времени после отключения Интернет перед отправкой сообщения."
|
||||
|
||||
msgid ""
|
||||
"Longest period of time after disconnecting from Internet before \"down-script"
|
||||
"\" runs."
|
||||
msgstr ""
|
||||
"Максимальный промежуток времени после отключения Интернет перед запуском "
|
||||
"\"down-script\"."
|
||||
|
||||
msgid "Longest period of time without Internet access before modem restart."
|
||||
msgstr ""
|
||||
"Максимальное время отсутствия доступа в Интренет перед перезапуском модема."
|
||||
|
||||
msgid "Longest period of time without Internet access before network restart."
|
||||
msgstr ""
|
||||
"Максимальное время отсутствия доступа в Интренет перед перезапуском сети."
|
||||
|
||||
msgid ""
|
||||
"Longest period of time without Internet access until the device is rebooted."
|
||||
msgstr ""
|
||||
"Максимальное время отсутствия доступа в Интренет перед перезагрузкой "
|
||||
"устройства."
|
||||
msgid "Logging"
|
||||
msgstr "Запись событий в лог"
|
||||
|
||||
msgid "Mailsend is not available..."
|
||||
msgstr "Mailsend недоступен..."
|
||||
@@ -244,15 +312,26 @@ msgstr ""
|
||||
"Максимальное количество попыток перезапуска сети до появления доступа в "
|
||||
"Интренет."
|
||||
|
||||
msgid ""
|
||||
"Maximum number of up-script run attempts when connected to the Internet."
|
||||
msgstr ""
|
||||
"Максимальное количество попыток запуска up-script при подключении к Интренет."
|
||||
|
||||
msgid ""
|
||||
"Maximum number of down-script run attempts before Internet access is available."
|
||||
msgstr ""
|
||||
"Максимальное количество попыток запуска down-script до появления доступа в "
|
||||
"Интренет."
|
||||
|
||||
msgid "Maximum timeout for waiting for a response from the host."
|
||||
msgstr "Максимальный таймаут ожидания ответа от хоста."
|
||||
|
||||
msgid "Modem will be restarted when the Internet is disconnected."
|
||||
msgstr "Модем будет перезапущен при отключении Интернет."
|
||||
|
||||
msgid "ModemManager is not available..."
|
||||
msgstr "ModemManager недоступен..."
|
||||
|
||||
msgid "Modem will be restarted when the Internet is disconnected."
|
||||
msgstr "Модем будет перезапущен при отключении Интернет."
|
||||
|
||||
msgid ""
|
||||
"ModemManger interface. If specified, it will be restarted after restarting "
|
||||
"ModemManager."
|
||||
@@ -261,24 +340,63 @@ msgstr ""
|
||||
"ModemManger."
|
||||
|
||||
msgid ""
|
||||
"Network interface for Internet access. If not specified, the default "
|
||||
"interface is used."
|
||||
"Network device for Internet access. If not specified, the default "
|
||||
"device is used."
|
||||
msgstr ""
|
||||
"Сетевой интерфейс для доступа в Интернет. Если не указан, используется "
|
||||
"интерфейс по умолчанию."
|
||||
"Сетевое устройство для доступа в Интернет. Если не указано, используется "
|
||||
"устройство по умолчанию."
|
||||
|
||||
msgid "Network device activity (kernel: netdev)"
|
||||
msgstr "Активность сетевого устройства (kernel: netdev)"
|
||||
|
||||
msgid ""
|
||||
"Network interface to restart. If not specified, then the network service is restarted."
|
||||
"Network device or interface to restart. If not specified, then the network service is restarted."
|
||||
msgstr ""
|
||||
"Сетевой интерфейс для перезапуска. Если не задан, то будет перезапущена сетевая "
|
||||
"Сетевое устройство или интерфейс для перезапуска. Если не задано, то будет перезапущена сетевая "
|
||||
"служба."
|
||||
|
||||
msgid "Network will be restarted when the Internet is disconnected."
|
||||
msgstr "Сеть будет перезапущена при отключении Интернет."
|
||||
|
||||
msgid "Next run"
|
||||
msgstr "Следующий запуск"
|
||||
|
||||
msgid "No <abbr title=\"Light Emitting Diode\">LED</abbr>s available..."
|
||||
msgstr "Нет доступных <abbr title=\"Светодиод\">LED</abbr>..."
|
||||
|
||||
msgid "No messages available. Write something to the bot and try again."
|
||||
msgstr "Нет доступных сообщений. Напишите что-нибудь боту и попробуйте ещё раз."
|
||||
|
||||
msgid "Not scheduled"
|
||||
msgstr "Не запланирован"
|
||||
|
||||
msgid "Notice"
|
||||
msgstr "Сообщение"
|
||||
|
||||
msgid "Number of attempts to request an IP address."
|
||||
msgstr "Количество попыток запроса IP адреса"
|
||||
|
||||
msgid "Off"
|
||||
msgstr "Выключить"
|
||||
|
||||
msgid "Off-state delay"
|
||||
msgstr "Задержка выключенного состояния"
|
||||
|
||||
msgid "Off-state delay for blinking option."
|
||||
msgstr "Задержка выключенного состояния для опции мигания."
|
||||
|
||||
msgid "On"
|
||||
msgstr "Включить"
|
||||
|
||||
msgid "On startup"
|
||||
msgstr "При запуске"
|
||||
|
||||
msgid "On-state delay"
|
||||
msgstr "Задержка включённого состояния"
|
||||
|
||||
msgid "On-state delay for blinking option."
|
||||
msgstr "Задержка включённого состояния для опции мигания."
|
||||
|
||||
msgid "One of the following:"
|
||||
msgstr "Одно из следующих значений:"
|
||||
|
||||
@@ -289,27 +407,73 @@ msgid "Password for SMTP authentication."
|
||||
msgstr "Пароль для SMTP-аутентификации."
|
||||
|
||||
msgid ""
|
||||
"Performing actions when connecting and disconnecting the Internet (available "
|
||||
"in the \"Service\" mode)."
|
||||
"Period of time after connecting to Internet before up-script runs."
|
||||
msgstr ""
|
||||
"Выполнение действий при подключении и отключении Интернет (доступно в режиме "
|
||||
"\"Служба\")."
|
||||
"Период времени после подключения к Интернет перед запуском up-script."
|
||||
|
||||
msgid "Ping host"
|
||||
msgstr "Пинг хоста"
|
||||
msgid ""
|
||||
"Period of time after connecting to the Internet before sending a message."
|
||||
msgstr "Период времени после подключения к Интернет перед отправкой сообщения."
|
||||
|
||||
msgid "Ping packet size"
|
||||
msgstr "Размер пакета Ping"
|
||||
msgid ""
|
||||
"Period of time after disconnecting from Internet before down-script runs."
|
||||
msgstr ""
|
||||
"Период времени после отключения от Интернет перед запуском down-script."
|
||||
|
||||
msgid ""
|
||||
"Period of time after disconnecting from Internet before sending a message."
|
||||
msgstr "Период времени отсутствия доступа в Интренет перед отправкой сообщения."
|
||||
|
||||
msgid "Period of time without Internet access before modem restart."
|
||||
msgstr "Период времени отсутствия доступа в Интренет перед перезапуском модема."
|
||||
|
||||
msgid "Period of time without Internet access before network restart."
|
||||
msgstr "Период времени отсутствия доступа в Интренет перед перезапуском сети."
|
||||
|
||||
msgid ""
|
||||
"Period of time without Internet access until the device is rebooted."
|
||||
msgstr ""
|
||||
"Период времени отсутствия доступа в Интренет перед перезагрузкой устройства."
|
||||
|
||||
msgid "Regular script"
|
||||
msgstr "Регулярный скрипт"
|
||||
|
||||
msgid "Polling interval"
|
||||
msgstr "Интервал опроса"
|
||||
|
||||
msgid "Provider"
|
||||
msgstr "Провайдер"
|
||||
|
||||
msgid "Proxy"
|
||||
msgstr "Прокси"
|
||||
|
||||
msgid "Proxy host"
|
||||
msgstr "Хост прокси"
|
||||
|
||||
msgid "Proxy port"
|
||||
msgstr "Порт прокси"
|
||||
|
||||
msgid "Public IP"
|
||||
msgstr "Публичный IP"
|
||||
|
||||
msgid "Public IP address"
|
||||
msgstr "Публичный IP адрес"
|
||||
|
||||
msgid "Reboot device"
|
||||
msgstr "Перезагрузка устройства"
|
||||
|
||||
msgid "Reboot device if the Internet is disconnected at service startup."
|
||||
msgstr "Перезагрузка устройства если Интренет отключен при запуске службы."
|
||||
|
||||
msgid "Receive"
|
||||
msgstr "Приём"
|
||||
|
||||
msgid "Recipient"
|
||||
msgstr "Получатель"
|
||||
|
||||
msgid "Request chat ID from bot API"
|
||||
msgstr "Запросить ID чата через API бота"
|
||||
|
||||
msgid "Restart"
|
||||
msgstr "Перезапуск"
|
||||
|
||||
@@ -319,18 +483,36 @@ msgstr "Попытки перезапуска"
|
||||
msgid "Restart modem"
|
||||
msgstr "Перезапуск модема"
|
||||
|
||||
msgid "Restart modem if the Internet is disconnected at service startup."
|
||||
msgstr "Перезапуск модема если Интренет отключен при запуске службы."
|
||||
|
||||
msgid "Restart network"
|
||||
msgstr "Перезапуск сети"
|
||||
|
||||
msgid "Restart network if the Internet is disconnected at service startup."
|
||||
msgstr "Перезапуск сети если Интренет отключен при запуске службы."
|
||||
|
||||
msgid "Restart service"
|
||||
msgstr "Перезапуск службы"
|
||||
|
||||
msgid "Restart timeout"
|
||||
msgstr "Таймаут перезапуска"
|
||||
msgid "Device timeout"
|
||||
msgstr "Таймаут устройства"
|
||||
|
||||
msgid "Run interval"
|
||||
msgstr "Интервал запуска"
|
||||
|
||||
msgid "Run if Internet state is"
|
||||
msgstr "Выполнять если статус Интернет"
|
||||
|
||||
msgid "Run service at startup"
|
||||
msgstr "Запуск службы при старте"
|
||||
|
||||
msgid "Run down-script if the Internet is disconnected at service startup."
|
||||
msgstr "Выполнить down-script если Интренет отключен при запуске службы."
|
||||
|
||||
msgid "Run up-script if the Internet is connected at service startup."
|
||||
msgstr "Выполнить up-script если Интренет подключен при запуске службы."
|
||||
|
||||
msgid "Running"
|
||||
msgstr "Выполняется"
|
||||
|
||||
@@ -349,6 +531,9 @@ msgstr "Сохранить"
|
||||
msgid "Security"
|
||||
msgstr "Безопасность"
|
||||
|
||||
msgid "Send message on service startup."
|
||||
msgstr "Отправлять сообщение при запуске службы."
|
||||
|
||||
msgid "Sender"
|
||||
msgstr "Отправитель"
|
||||
|
||||
@@ -364,11 +549,8 @@ msgstr "Не удалось выполнить действие службы \"%
|
||||
msgid "Service configuration"
|
||||
msgstr "Конфигурация службы"
|
||||
|
||||
msgid "Service for determining the public IP address through DNS."
|
||||
msgstr "Сервис для определения публичного IP адреса через DNS."
|
||||
|
||||
msgid "Service modules"
|
||||
msgstr "Модули службы"
|
||||
msgid "Service for determining the public IP address."
|
||||
msgstr "Сервис для определения публичного IP адреса."
|
||||
|
||||
msgid "Service: detector always runs as a system service."
|
||||
msgstr "Служба: детектор работает постоянно, как системная служба."
|
||||
@@ -376,9 +558,18 @@ msgstr "Служба: детектор работает постоянно, ка
|
||||
msgid "Set the modem to be allowed to use any band."
|
||||
msgstr "Разрешить модему использование любой частоты."
|
||||
|
||||
msgid "Shell commands that are run regularly."
|
||||
msgstr "Команды shell выполняемые регулярно."
|
||||
|
||||
msgid "Shell commands that run when connected to the Internet."
|
||||
msgstr "Команды shell выполняемые при подключении к Интернет."
|
||||
|
||||
msgid "Shell commands that run regularly at a specified interval. Current state of the Internet is available as value of the <code>$INET_STATE</code> variable (<code>0</code> - connected, <code>1</code> - disconnected)."
|
||||
msgstr "Команды shell выполняемые регулярно с заданным интервалом. Текущее состояние Интернет доступно как значение переменной <code>$INET_STATE</code> (<code>0</code> - подключен, <code>1</code> - отключен)."
|
||||
|
||||
msgid "Shell commands that run when the public IP address changes. New IP is available as value of the <code>$PUBLIC_IP</code> variable (empty string if undefined)."
|
||||
msgstr "Команды shell выполняемые при изменении публичного IP адреса. Новый IP доступен как значение переменной <code>$PUBLIC_IP</code> (пустая строка если не определён)."
|
||||
|
||||
msgid "Shell commands to run when connected or disconnected from the Internet."
|
||||
msgstr "Команды shell выполняемые при подключении или отключении Интернет."
|
||||
|
||||
@@ -400,18 +591,36 @@ msgstr "TCP-порт"
|
||||
msgid "TCP port connection"
|
||||
msgstr "Подключение к TCP-порту"
|
||||
|
||||
msgid "Telegram bot API token."
|
||||
msgstr "API токен Telegram бота."
|
||||
|
||||
msgid "Telegram message will be sent when connected or disconnected from the Internet."
|
||||
msgstr "Сообщение в Telegram будет отправлено при подключении или отключении от Интернет."
|
||||
|
||||
msgid "The type of record requested in the DNS query (if the service supports it)."
|
||||
msgstr "Тип записи запрашиваемой в DNS-запросе (если сервис поддерживает)."
|
||||
|
||||
msgid "TLS: use STARTTLS if the server supports it."
|
||||
msgstr "TLS: использовать STARTTLS если сервер поддерживает."
|
||||
|
||||
msgid "Timeout between stopping and starting the interface."
|
||||
msgstr "Таймаут между остановкой и запуском интерфейса."
|
||||
msgid "Timeout between stopping and starting a network device when restarting."
|
||||
msgstr "Таймаут между остановкой и запуском сетевого устройства при перезапуске."
|
||||
|
||||
msgid "Timeout between stopping and starting a ModemManger interface when restarting."
|
||||
msgstr "Таймаут между остановкой и запуском интерфейса ModemManger при перезапуске."
|
||||
|
||||
msgid "To support HTTP services, you need to install curl."
|
||||
msgstr "Для поддержки HTTP сервисов необходимо установить curl."
|
||||
|
||||
msgid "To support URL checking, you need to install curl."
|
||||
msgstr "Для поддержки проверки URL необходимо установить curl."
|
||||
|
||||
msgid "Transmit"
|
||||
msgstr "Передача"
|
||||
|
||||
msgid "Type a time string"
|
||||
msgstr "Введите строку времени"
|
||||
|
||||
msgid "UI detector configuration"
|
||||
msgstr "Конфигурация UI детектора"
|
||||
|
||||
msgid "Unable to read the contents"
|
||||
msgstr "Невозможно прочитать содержимое"
|
||||
|
||||
@@ -424,6 +633,16 @@ msgstr "Неопределён"
|
||||
msgid "Unlock modem bands"
|
||||
msgstr "Освободить частоты модема"
|
||||
|
||||
msgid "URL test (HTTP)"
|
||||
msgstr "Тест URL (HTTP)"
|
||||
|
||||
msgid ""
|
||||
"URLs for checking Internet availability. URLs are polled (in list "
|
||||
"order) until at least one returns HTTP status code 200."
|
||||
msgstr ""
|
||||
"URL для проверки доступности Интернет. URL опрашиваются (в порядке "
|
||||
"списка) до тех пор, пока хотя бы один из них не вернёт код статуса HTTP 200."
|
||||
|
||||
msgid "User"
|
||||
msgstr "Пользователь"
|
||||
|
||||
@@ -438,19 +657,51 @@ msgstr ""
|
||||
"Ожидание завершения перезагрузки перед выполнением принудительной "
|
||||
"перезагрузки."
|
||||
|
||||
msgid "Web UI only"
|
||||
msgstr "Только web-интерфейс"
|
||||
msgid "Warning"
|
||||
msgstr "Внимание"
|
||||
|
||||
msgid "Web UI only (UI detector)"
|
||||
msgstr "Только web-интерфейс (UI детектор)"
|
||||
|
||||
msgid "Web UI only: detector works only when the Web UI is open (UI detector)."
|
||||
msgstr ""
|
||||
"Только web-интерфейс: детектор работает только в web-интерфейсе (UI "
|
||||
"детектор)."
|
||||
|
||||
msgid "When email will be sent"
|
||||
msgstr "Когда будет отправлено сообщение"
|
||||
|
||||
msgid "When message will be sent"
|
||||
msgstr "Когда будет отправлено сообщение"
|
||||
|
||||
msgid "Windows: 32 bytes"
|
||||
msgstr "Windows: 32 байта"
|
||||
|
||||
msgid "Write messages to the system log."
|
||||
msgstr "Записывать сообщения в системный журнал."
|
||||
msgid ""
|
||||
"You need to register a new %sTelegram bot%s. Then get the bot's API token and paste it into the <code>Bot token</code> field. After that, open a chat with the bot, write something (in the Telegram app) and you will be able to get the chat ID using the <code>ID</code> button."
|
||||
msgstr ""
|
||||
"Необходимо зарегистрировать новый %sTelegram бот%s. Затем получить API токен бота и вставить его в поле <code>Токен бота</code>. После этого, откройте чат с ботом, напишите что-нибудь (в приложении Telegram) и можно будет получить ID чата с помощью кнопки <code>ID</code>."
|
||||
|
||||
msgid "after connection"
|
||||
msgstr "после подключения"
|
||||
|
||||
msgid "after connection or disconnection"
|
||||
msgstr "после подключения или отключения"
|
||||
|
||||
msgid "after disconnection"
|
||||
msgstr "после отключения"
|
||||
|
||||
msgid "connected"
|
||||
msgstr "подключен"
|
||||
|
||||
msgid "connected or disconnected"
|
||||
msgstr "подключен или отключен"
|
||||
|
||||
msgid "days"
|
||||
msgstr "дни"
|
||||
|
||||
msgid "disconnected"
|
||||
msgstr "отключен"
|
||||
|
||||
msgid "down-script"
|
||||
msgstr "down-script"
|
||||
@@ -458,6 +709,9 @@ msgstr "down-script"
|
||||
msgid "hour"
|
||||
msgstr "час"
|
||||
|
||||
msgid "infinitely"
|
||||
msgstr "бесконечно"
|
||||
|
||||
msgid "hours"
|
||||
msgstr "часы"
|
||||
|
||||
@@ -467,6 +721,9 @@ msgstr "мин"
|
||||
msgid "minutes"
|
||||
msgstr "минуты"
|
||||
|
||||
msgid "msec"
|
||||
msgstr "мсек"
|
||||
|
||||
msgid "sec"
|
||||
msgstr "сек"
|
||||
|
||||
|
||||
@@ -8,8 +8,23 @@ msgid "<abbr title=\"Light Emitting Diode\">LED</abbr> control"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"<abbr title=\"Light Emitting Diode\">LED</abbr> is on when Internet is "
|
||||
"available."
|
||||
"<abbr title=\"Light Emitting Diode\">LED</abbr> indicates the Internet status."
|
||||
msgstr ""
|
||||
|
||||
msgid "<abbr title=\"Light Emitting Diode\">LED</abbr> mode"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"<abbr title=\"Light Emitting Diode\">LED</abbr> will display the link activity of this network device."
|
||||
msgstr ""
|
||||
|
||||
msgid "Add instance"
|
||||
msgstr ""
|
||||
|
||||
msgid "After connection"
|
||||
msgstr ""
|
||||
|
||||
msgid "After disconnection"
|
||||
msgstr ""
|
||||
|
||||
msgid "Alive interval"
|
||||
@@ -18,17 +33,33 @@ msgstr ""
|
||||
msgid "Alive period"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"An email will be sent when the internet connection is restored after being "
|
||||
"disconnected."
|
||||
msgid "An email will be sent when connected or disconnected from the Internet."
|
||||
msgstr ""
|
||||
|
||||
msgid "An error has occurred"
|
||||
msgstr ""
|
||||
|
||||
msgid "Attempt interval"
|
||||
msgstr ""
|
||||
|
||||
msgid "Attempts"
|
||||
msgstr ""
|
||||
|
||||
msgid "Big: 248 bytes"
|
||||
msgstr ""
|
||||
|
||||
msgid "Blinking (kernel: timer)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Bot API token is missing!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Bot token"
|
||||
msgstr ""
|
||||
|
||||
msgid "Chat ID"
|
||||
msgstr ""
|
||||
|
||||
msgid "Check type"
|
||||
msgstr ""
|
||||
|
||||
@@ -53,15 +84,27 @@ msgstr ""
|
||||
msgid "Contents have been saved."
|
||||
msgstr ""
|
||||
|
||||
msgid "Curl is not available..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Dead interval"
|
||||
msgstr ""
|
||||
|
||||
msgid "Dead period"
|
||||
msgstr ""
|
||||
|
||||
msgid "Debug"
|
||||
msgstr ""
|
||||
|
||||
msgid "Default port value for TCP connections."
|
||||
msgstr ""
|
||||
|
||||
msgid "Description"
|
||||
msgstr ""
|
||||
|
||||
msgid "Device"
|
||||
msgstr ""
|
||||
|
||||
msgid "Device will be rebooted when the Internet is disconnected."
|
||||
msgstr ""
|
||||
|
||||
@@ -80,7 +123,7 @@ msgstr ""
|
||||
msgid "Dismiss"
|
||||
msgstr ""
|
||||
|
||||
msgid "DNS provider"
|
||||
msgid "DNS query type"
|
||||
msgstr ""
|
||||
|
||||
msgid "Edit"
|
||||
@@ -89,6 +132,12 @@ msgstr ""
|
||||
msgid "Edit down-script"
|
||||
msgstr ""
|
||||
|
||||
msgid "Edit regular-script"
|
||||
msgstr ""
|
||||
|
||||
msgid "Edit public-ip-script"
|
||||
msgstr ""
|
||||
|
||||
msgid "Edit up-script"
|
||||
msgstr ""
|
||||
|
||||
@@ -104,19 +153,22 @@ msgstr ""
|
||||
msgid "Enable"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable logging"
|
||||
msgid "Enable public-ip-script"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enabled"
|
||||
msgstr ""
|
||||
|
||||
msgid "Error"
|
||||
msgstr ""
|
||||
|
||||
msgid "Error!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Expecting:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Public IP"
|
||||
msgstr ""
|
||||
|
||||
msgid "Public IP address"
|
||||
msgid "Failed interval"
|
||||
msgstr ""
|
||||
|
||||
msgid "Failed to get %s init status: %s"
|
||||
@@ -147,16 +199,37 @@ msgid "Hosts polling interval when the Internet is up."
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Hosts to check Internet availability. Hosts are polled (in list order) until "
|
||||
"at least one of them responds."
|
||||
"Hosts for checking Internet availability. Hosts are polled (in list "
|
||||
"order) until at least one responds."
|
||||
msgstr ""
|
||||
|
||||
msgid "Huge: 1492 bytes"
|
||||
msgstr ""
|
||||
|
||||
msgid "ID of the Telegram chat to which messages will be sent."
|
||||
msgstr ""
|
||||
|
||||
msgid "ICMP-echo request (ping)"
|
||||
msgstr ""
|
||||
|
||||
msgid "ICMP packet data size"
|
||||
msgstr ""
|
||||
|
||||
msgid "Incorrect bot token?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Info"
|
||||
msgstr ""
|
||||
|
||||
msgid "Instances"
|
||||
msgstr ""
|
||||
|
||||
msgid "Interface"
|
||||
msgstr ""
|
||||
|
||||
msgid "Interface timeout"
|
||||
msgstr ""
|
||||
|
||||
msgid "Internet"
|
||||
msgstr ""
|
||||
|
||||
@@ -172,38 +245,40 @@ msgstr ""
|
||||
msgid "Interval between IP address requests."
|
||||
msgstr ""
|
||||
|
||||
msgid "Interval between IP address requests if the IP address is not defined."
|
||||
msgstr ""
|
||||
|
||||
msgid "Interval between modem restarts."
|
||||
msgstr ""
|
||||
|
||||
msgid "Interval between network restarts."
|
||||
msgstr ""
|
||||
|
||||
msgid "Interval between down-script runs."
|
||||
msgstr ""
|
||||
|
||||
msgid "Interval between up-script runs."
|
||||
msgstr ""
|
||||
|
||||
msgid "Invalid instance name..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Jumbo: 9000 bytes"
|
||||
msgstr ""
|
||||
|
||||
msgid "LED control"
|
||||
msgstr ""
|
||||
|
||||
msgid "Link On"
|
||||
msgstr ""
|
||||
|
||||
msgid "Loading"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Longest period of time after connecting to Internet before \"up-script\" "
|
||||
"runs."
|
||||
msgid "Log event level."
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Longest period of time after connecting to the Internet before sending a "
|
||||
"message."
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Longest period of time after disconnecting from Internet before \"down-script"
|
||||
"\" runs."
|
||||
msgstr ""
|
||||
|
||||
msgid "Longest period of time without Internet access before modem restart."
|
||||
msgstr ""
|
||||
|
||||
msgid "Longest period of time without Internet access before network restart."
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Longest period of time without Internet access until the device is rebooted."
|
||||
msgid "Logging"
|
||||
msgstr ""
|
||||
|
||||
msgid "Mailsend is not available..."
|
||||
@@ -220,13 +295,21 @@ msgid ""
|
||||
"available."
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Maximum number of up-script run attempts when connected to the Internet."
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Maximum number of down-script run attempts before Internet access is available."
|
||||
msgstr ""
|
||||
|
||||
msgid "Maximum timeout for waiting for a response from the host."
|
||||
msgstr ""
|
||||
|
||||
msgid "Modem will be restarted when the Internet is disconnected."
|
||||
msgid "ModemManager is not available..."
|
||||
msgstr ""
|
||||
|
||||
msgid "ModemManager is not available..."
|
||||
msgid "Modem will be restarted when the Internet is disconnected."
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
@@ -235,20 +318,59 @@ msgid ""
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Network interface for Internet access. If not specified, the default "
|
||||
"Network device for Internet access. If not specified, the default "
|
||||
"interface is used."
|
||||
msgstr ""
|
||||
|
||||
msgid "Network device activity (kernel: netdev)"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Network interface to restart. If not specified, then the network service is restarted."
|
||||
"Network device or interface to restart. If not specified, then the network service is restarted."
|
||||
msgstr ""
|
||||
|
||||
msgid "Network will be restarted when the Internet is disconnected."
|
||||
msgstr ""
|
||||
|
||||
msgid "Next run"
|
||||
msgstr ""
|
||||
|
||||
msgid "No <abbr title=\"Light Emitting Diode\">LED</abbr>s available..."
|
||||
msgstr ""
|
||||
|
||||
msgid "No messages available. Write something to the bot and try again."
|
||||
msgstr ""
|
||||
|
||||
msgid "Not scheduled"
|
||||
msgstr ""
|
||||
|
||||
msgid "Notice"
|
||||
msgstr ""
|
||||
|
||||
msgid "Number of attempts to request an IP address."
|
||||
msgstr ""
|
||||
|
||||
msgid "Off"
|
||||
msgstr ""
|
||||
|
||||
msgid "Off-state delay"
|
||||
msgstr ""
|
||||
|
||||
msgid "Off-state delay for blinking option."
|
||||
msgstr ""
|
||||
|
||||
msgid "On"
|
||||
msgstr ""
|
||||
|
||||
msgid "On startup"
|
||||
msgstr ""
|
||||
|
||||
msgid "On-state delay"
|
||||
msgstr ""
|
||||
|
||||
msgid "On-state delay for blinking option."
|
||||
msgstr ""
|
||||
|
||||
msgid "One of the following:"
|
||||
msgstr ""
|
||||
|
||||
@@ -259,25 +381,70 @@ msgid "Password for SMTP authentication."
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Performing actions when connecting and disconnecting the Internet (available "
|
||||
"in the \"Service\" mode)."
|
||||
"Period of time after connecting to Internet before up-script runs."
|
||||
msgstr ""
|
||||
|
||||
msgid "Ping host"
|
||||
msgid ""
|
||||
"Period of time after connecting to the Internet before sending a message."
|
||||
msgstr ""
|
||||
|
||||
msgid "Ping packet size"
|
||||
msgid ""
|
||||
"Period of time after disconnecting from Internet before down-script runs."
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Period of time after disconnecting from Internet before sending a message."
|
||||
msgstr ""
|
||||
|
||||
msgid "Period of time without Internet access before modem restart."
|
||||
msgstr ""
|
||||
|
||||
msgid "Period of time without Internet access before network restart."
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Period of time without Internet access until the device is rebooted."
|
||||
msgstr ""
|
||||
|
||||
msgid "Regular script"
|
||||
msgstr ""
|
||||
|
||||
msgid "Polling interval"
|
||||
msgstr ""
|
||||
|
||||
msgid "Provider"
|
||||
msgstr ""
|
||||
|
||||
msgid "Proxy"
|
||||
msgstr ""
|
||||
|
||||
msgid "Proxy host"
|
||||
msgstr ""
|
||||
|
||||
msgid "Proxy port"
|
||||
msgstr ""
|
||||
|
||||
msgid "Public IP"
|
||||
msgstr ""
|
||||
|
||||
msgid "Public IP address"
|
||||
msgstr ""
|
||||
|
||||
msgid "Reboot device"
|
||||
msgstr ""
|
||||
|
||||
msgid "Reboot device if the Internet is disconnected at service startup."
|
||||
msgstr ""
|
||||
|
||||
msgid "Receive"
|
||||
msgstr ""
|
||||
|
||||
msgid "Recipient"
|
||||
msgstr ""
|
||||
|
||||
msgid "Request chat ID from bot API"
|
||||
msgstr ""
|
||||
|
||||
msgid "Restart"
|
||||
msgstr ""
|
||||
|
||||
@@ -287,18 +454,36 @@ msgstr ""
|
||||
msgid "Restart modem"
|
||||
msgstr ""
|
||||
|
||||
msgid "Restart modem if the Internet is disconnected at service startup."
|
||||
msgstr ""
|
||||
|
||||
msgid "Restart network"
|
||||
msgstr ""
|
||||
|
||||
msgid "Restart network if the Internet is disconnected at service startup."
|
||||
msgstr ""
|
||||
|
||||
msgid "Restart service"
|
||||
msgstr ""
|
||||
|
||||
msgid "Restart timeout"
|
||||
msgid "Device timeout"
|
||||
msgstr ""
|
||||
|
||||
msgid "Run interval"
|
||||
msgstr ""
|
||||
|
||||
msgid "Run if Internet state is"
|
||||
msgstr ""
|
||||
|
||||
msgid "Run service at startup"
|
||||
msgstr ""
|
||||
|
||||
msgid "Run down-script if the Internet is disconnected at service startup."
|
||||
msgstr ""
|
||||
|
||||
msgid "Run up-script if the Internet is connected at service startup."
|
||||
msgstr ""
|
||||
|
||||
msgid "Running"
|
||||
msgstr ""
|
||||
|
||||
@@ -317,6 +502,9 @@ msgstr ""
|
||||
msgid "Security"
|
||||
msgstr ""
|
||||
|
||||
msgid "Send message on service startup."
|
||||
msgstr ""
|
||||
|
||||
msgid "Sender"
|
||||
msgstr ""
|
||||
|
||||
@@ -332,10 +520,7 @@ msgstr ""
|
||||
msgid "Service configuration"
|
||||
msgstr ""
|
||||
|
||||
msgid "Service for determining the public IP address through DNS."
|
||||
msgstr ""
|
||||
|
||||
msgid "Service modules"
|
||||
msgid "Service for determining the public IP address."
|
||||
msgstr ""
|
||||
|
||||
msgid "Service: detector always runs as a system service."
|
||||
@@ -344,9 +529,18 @@ msgstr ""
|
||||
msgid "Set the modem to be allowed to use any band."
|
||||
msgstr ""
|
||||
|
||||
msgid "Shell commands that are run regularly."
|
||||
msgstr ""
|
||||
|
||||
msgid "Shell commands that run when connected to the Internet."
|
||||
msgstr ""
|
||||
|
||||
msgid "Shell commands that run regularly at a specified interval. Current state of the Internet is available as value of the <code>$INET_STATE</code> variable (<code>0</code> - connected, <code>1</code> - disconnected)."
|
||||
msgstr ""
|
||||
|
||||
msgid "Shell commands that run when the public IP address changes. New IP is available as value of the <code>$PUBLIC_IP</code> variable (empty string if undefined)."
|
||||
msgstr ""
|
||||
|
||||
msgid "Shell commands to run when connected or disconnected from the Internet."
|
||||
msgstr ""
|
||||
|
||||
@@ -368,18 +562,36 @@ msgstr ""
|
||||
msgid "TCP port connection"
|
||||
msgstr ""
|
||||
|
||||
msgid "Telegram bot API token."
|
||||
msgstr ""
|
||||
|
||||
msgid "Telegram message will be sent when connected or disconnected from the Internet."
|
||||
msgstr ""
|
||||
|
||||
msgid "The type of record requested in the DNS query (if the service supports it)."
|
||||
msgstr ""
|
||||
|
||||
msgid "TLS: use STARTTLS if the server supports it."
|
||||
msgstr ""
|
||||
|
||||
msgid "Timeout between stopping and starting the interface."
|
||||
msgid "Timeout between stopping and starting a network device when restarting."
|
||||
msgstr ""
|
||||
|
||||
msgid "Timeout between stopping and starting a ModemManger interface when restarting."
|
||||
msgstr ""
|
||||
|
||||
msgid "To support HTTP services, you need to install curl."
|
||||
msgstr ""
|
||||
|
||||
msgid "To support URL checking, you need to install curl."
|
||||
msgstr ""
|
||||
|
||||
msgid "Transmit"
|
||||
msgstr ""
|
||||
|
||||
msgid "Type a time string"
|
||||
msgstr ""
|
||||
|
||||
msgid "UI detector configuration"
|
||||
msgstr ""
|
||||
|
||||
msgid "Unable to read the contents"
|
||||
msgstr ""
|
||||
|
||||
@@ -392,6 +604,14 @@ msgstr ""
|
||||
msgid "Unlock modem bands"
|
||||
msgstr ""
|
||||
|
||||
msgid "URL test (HTTP)"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"URLs for checking Internet availability. URLs are polled (in list "
|
||||
"order) until at least one returns HTTP status code 200."
|
||||
msgstr ""
|
||||
|
||||
msgid "User"
|
||||
msgstr ""
|
||||
|
||||
@@ -404,16 +624,44 @@ msgstr ""
|
||||
msgid "Waiting for a reboot to complete before performing a forced reboot."
|
||||
msgstr ""
|
||||
|
||||
msgid "Web UI only"
|
||||
msgid "Warning"
|
||||
msgstr ""
|
||||
|
||||
msgid "Web UI only (UI detector)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Web UI only: detector works only when the Web UI is open (UI detector)."
|
||||
msgstr ""
|
||||
|
||||
msgid "When email will be sent"
|
||||
msgstr ""
|
||||
|
||||
msgid "When message will be sent"
|
||||
msgstr ""
|
||||
|
||||
msgid "Windows: 32 bytes"
|
||||
msgstr ""
|
||||
|
||||
msgid "Write messages to the system log."
|
||||
msgid ""
|
||||
"You need to register a new %sTelegram bot%s. Then get the bot's API token and paste it into the <code>Bot token</code> field. After that, open a chat with the bot, write something (in the Telegram app) and you will be able to get the chat ID using the <code>ID</code> button."
|
||||
msgstr ""
|
||||
|
||||
msgid "after connection"
|
||||
msgstr ""
|
||||
|
||||
msgid "after disconnection"
|
||||
msgstr ""
|
||||
|
||||
msgid "connected"
|
||||
msgstr ""
|
||||
|
||||
msgid "connected or disconnected"
|
||||
msgstr ""
|
||||
|
||||
msgid "days"
|
||||
msgstr ""
|
||||
|
||||
msgid "disconnected"
|
||||
msgstr ""
|
||||
|
||||
msgid "down-script"
|
||||
@@ -425,12 +673,18 @@ msgstr ""
|
||||
msgid "hours"
|
||||
msgstr ""
|
||||
|
||||
msgid "infinitely"
|
||||
msgstr ""
|
||||
|
||||
msgid "min"
|
||||
msgstr ""
|
||||
|
||||
msgid "minutes"
|
||||
msgstr ""
|
||||
|
||||
msgid "msec"
|
||||
msgstr ""
|
||||
|
||||
msgid "sec"
|
||||
msgstr ""
|
||||
|
||||
|
||||
97
luci-app-internet-detector/root/usr/libexec/rpcd/luci.internet-detector
Executable file
@@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env lua
|
||||
|
||||
local appName = "internet-detector"
|
||||
local appExec = "/usr/bin/internet-detector"
|
||||
local mailsendExec = "/usr/bin/mailsend"
|
||||
local modemManagerInit = "/etc/init.d/modemmanager"
|
||||
local curlExec = "/usr/bin/curl"
|
||||
|
||||
local InternetDetector = require(appName .. ".main")
|
||||
local uci = require("uci")
|
||||
local unistd = require("posix.unistd")
|
||||
|
||||
local function prequire(package)
|
||||
local retVal, pkg = pcall(require, package)
|
||||
return retVal and pkg
|
||||
end
|
||||
|
||||
local function init()
|
||||
local lines = {}
|
||||
if prequire(appName .. ".modules.mod_modem_restart") then
|
||||
lines[#lines + 1] = '"mm_mod":true'
|
||||
if (unistd.access(modemManagerInit, "x") and
|
||||
os.execute(modemManagerInit .. " enabled") == 0) then
|
||||
lines[#lines + 1] = '"mm_init":true'
|
||||
else
|
||||
lines[#lines + 1] = '"mm_init":false'
|
||||
end
|
||||
else
|
||||
lines[#lines + 1] = '"mm_mod":false'
|
||||
end
|
||||
if prequire(appName .. ".modules.mod_email") then
|
||||
lines[#lines + 1] = '"email_mod":true'
|
||||
if unistd.access(mailsendExec, "x") then
|
||||
lines[#lines + 1] = '"email_exec":true'
|
||||
else
|
||||
lines[#lines + 1] = '"email_exec":false'
|
||||
end
|
||||
else
|
||||
lines[#lines + 1] = '"email_mod":false'
|
||||
end
|
||||
if prequire(appName .. ".modules.mod_telegram") then
|
||||
lines[#lines + 1] = '"telegram":true'
|
||||
else
|
||||
lines[#lines + 1] = '"telegram":false'
|
||||
end
|
||||
if unistd.access(curlExec, "x") then
|
||||
lines[#lines + 1] = '"curl_exec":true'
|
||||
else
|
||||
lines[#lines + 1] = '"curl_exec":false'
|
||||
end
|
||||
return string.format("{%s}", table.concat(lines, ","))
|
||||
end
|
||||
|
||||
local function startUiInstances()
|
||||
local uciCursor = uci.cursor()
|
||||
local mode = tonumber(uciCursor:get(appName, "config", "mode"))
|
||||
if mode == 2 then
|
||||
uciCursor:foreach(
|
||||
appName,
|
||||
"instance",
|
||||
function(s)
|
||||
if s.enabled == "1" then
|
||||
os.execute(string.format("%s -a daemon -i %s", appExec, s[".name"]))
|
||||
end
|
||||
end
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
local function uiPoll()
|
||||
if InternetDetector:status() == "stoped" then
|
||||
startUiInstances()
|
||||
else
|
||||
InternetDetector:setSIGUSR()
|
||||
end
|
||||
return InternetDetector:inetStatus()
|
||||
end
|
||||
|
||||
local function list()
|
||||
io.write('{"Init":{},"Status":{},"InetStatus":{},"UIPoll":{}}')
|
||||
end
|
||||
|
||||
if arg[1] == "list" then
|
||||
list()
|
||||
elseif arg[1] == "call" then
|
||||
if arg[2] == "Init" then
|
||||
io.write(init())
|
||||
elseif arg[2] == "Status" then
|
||||
io.write(string.format('{"status":"%s"}', tostring(InternetDetector:status())))
|
||||
elseif arg[2] == "InetStatus" then
|
||||
io.write(InternetDetector:inetStatus())
|
||||
elseif arg[2] == "UIPoll" then
|
||||
io.write(uiPoll())
|
||||
end
|
||||
end
|
||||
|
||||
os.exit(0)
|
||||
@@ -4,20 +4,23 @@
|
||||
"read": {
|
||||
"file": {
|
||||
"/sys/class/leds": [ "list" ],
|
||||
"/etc/internet-detector/up-script": [ "read" ],
|
||||
"/etc/internet-detector/down-script": [ "read" ],
|
||||
"/usr/bin/internet-detector*": [ "exec" ],
|
||||
"/usr/bin/mailsend": [ "exec" ]
|
||||
"/etc/internet-detector/up-script*": [ "read" ],
|
||||
"/etc/internet-detector/down-script*": [ "read" ],
|
||||
"/etc/internet-detector/public-ip-script*": [ "read" ],
|
||||
"/etc/internet-detector/regular-script*": [ "read" ]
|
||||
},
|
||||
"uci": [ "internet-detector" ],
|
||||
"ubus": {
|
||||
"luci": [ "getInitList", "setInitAction" ]
|
||||
"luci": [ "getInitList", "setInitAction" ],
|
||||
"luci.internet-detector": [ "Init", "Status", "InetStatus", "UIPoll" ]
|
||||
}
|
||||
},
|
||||
"write": {
|
||||
"file": {
|
||||
"/etc/internet-detector/up-script": [ "write" ],
|
||||
"/etc/internet-detector/down-script": [ "write" ]
|
||||
"/etc/internet-detector/up-script*": [ "write" ],
|
||||
"/etc/internet-detector/down-script*": [ "write" ],
|
||||
"/etc/internet-detector/public-ip-script*": [ "write" ],
|
||||
"/etc/internet-detector/regular-script*": [ "write" ]
|
||||
},
|
||||
"uci": [ "internet-detector" ]
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 233 KiB After Width: | Height: | Size: 120 KiB |
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 129 KiB |
BIN
screenshots/03.jpg
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
screenshots/04.jpg
Normal file
|
After Width: | Height: | Size: 101 KiB |
BIN
screenshots/05.jpg
Normal file
|
After Width: | Height: | Size: 146 KiB |
BIN
screenshots/06.jpg
Normal file
|
After Width: | Height: | Size: 166 KiB |