update 2024-02-17 14:17:25
@ -1,25 +1,24 @@
|
||||
#
|
||||
# Copyright (C) 2015 OpenWrt.org
|
||||
# Copyright (C) 2015-2016 OpenWrt.org
|
||||
#
|
||||
# This is free software, licensed under the GNU General Public License v2.
|
||||
# See /LICENSE for more information.
|
||||
# This is free software, licensed under the GNU General Public License v3.
|
||||
#
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=airconnect
|
||||
PKG_VERSION:=1.0.13
|
||||
PKG_RELEASE:=1
|
||||
PKG_VERSION:=1.7.0
|
||||
PKG_RELEASE=1
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_VERSION:=9fc8c184da22d6b34a5b093f6ec8cf6ecaf22dbf
|
||||
PKG_SOURCE_URL_FILE:=$(PKG_SOURCE_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://github.com/philippe44/AirConnect/archive/
|
||||
PKG_HASH:=0da27af9a1d49cd83f8381453d5e1b9c6551ad1ce45bbb7b820e5499796c5440
|
||||
PKG_SOURCE:=AirConnect-$(PKG_VERSION).zip
|
||||
PKG_SOURCE_URL:=https://github.com/philippe44/AirConnect/releases/download/$(PKG_VERSION)/
|
||||
PKG_HASH:=757f18963a2d5c178a48c3f7e1657b83003f0673e84aba01ddff2c61dfbc8b32
|
||||
|
||||
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-$(PKG_VERSION)/AirConnect-$(PKG_SOURCE_VERSION)
|
||||
PKG_BUILD_DIR:=$(BUILD_DIR)/airconnect-$(PKG_VERSION)
|
||||
|
||||
PKG_MAINTAINER:=jjm2473 <jjm2473@gmail.com>
|
||||
PKG_LICENSE:=MIT
|
||||
PKG_LICENSE_FILE:=LICENSE
|
||||
PKG_MAINTAINER:=sbwml <admin@cooluc.com>
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
@ -31,14 +30,15 @@ define Package/$(PKG_NAME)
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/description
|
||||
Use AirPlay to stream to UPnP/Sonos & Chromecast devices
|
||||
Use AirPlay to stream to UPnP/Sonos & Chromecast devices
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/conffiles
|
||||
/etc/config/airconnect
|
||||
endef
|
||||
|
||||
define Build/Configure
|
||||
define Build/Prepare
|
||||
unzip -q -d $(PKG_BUILD_DIR) $(DL_DIR)/AirConnect-$(PKG_VERSION).zip
|
||||
endef
|
||||
|
||||
define Build/Compile
|
||||
@ -46,10 +46,10 @@ endef
|
||||
|
||||
define Package/$(PKG_NAME)/install
|
||||
$(INSTALL_DIR) $(1)/usr/bin $(1)/etc/config $(1)/etc/init.d
|
||||
$(INSTALL_BIN) $(PKG_BUILD_DIR)/bin/aircast-linux-$(ARCH)-static $(1)/usr/bin/aircast
|
||||
$(INSTALL_BIN) $(PKG_BUILD_DIR)/bin/airupnp-linux-$(ARCH)-static $(1)/usr/bin/airupnp
|
||||
$(INSTALL_CONF) ./files/airconnect.config $(1)/etc/config/airconnect
|
||||
$(INSTALL_BIN) ./files/airconnect.init $(1)/etc/init.d/airconnect
|
||||
$(INSTALL_CONF) ./files/airconnect.config $(1)/etc/config/airconnect
|
||||
$(INSTALL_BIN) $(PKG_BUILD_DIR)/aircast-linux-$(ARCH)-static $(1)/usr/bin/aircast
|
||||
$(INSTALL_BIN) $(PKG_BUILD_DIR)/airupnp-linux-$(ARCH)-static $(1)/usr/bin/airupnp
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,airconnect))
|
||||
$(eval $(call BuildPackage,$(PKG_NAME)))
|
||||
|
@ -1,5 +1,7 @@
|
||||
config main
|
||||
|
||||
config airconnect 'config'
|
||||
option 'enabled' '0'
|
||||
option 'interface' 'lan'
|
||||
option 'aircast' '1'
|
||||
option 'airupnp' '1'
|
||||
option 'interface' 'br-lan'
|
||||
option 'aircast' '1'
|
||||
option 'airupnp' '1'
|
||||
|
||||
|
79
airconnect/files/airconnect.init
Executable file → Normal file
@ -1,46 +1,55 @@
|
||||
#!/bin/sh /etc/rc.common
|
||||
|
||||
START=99
|
||||
STOP=01
|
||||
USE_PROCD=1
|
||||
PROG_AIRCAST=/usr/bin/aircast
|
||||
PROG_AIRUPNP=/usr/bin/airupnp
|
||||
|
||||
service_triggers() {
|
||||
procd_add_reload_trigger "airconnect"
|
||||
}
|
||||
|
||||
main_config() {
|
||||
config_get AIRUPNP_IFACE "$1" interface "lan"
|
||||
config_get_bool ENABLE_MAIN "$1" enabled 0
|
||||
config_get_bool ENABLE_AIRCAST "$1" aircast 0
|
||||
config_get_bool ENABLE_AIRUPNP "$1" airupnp 0
|
||||
get_config() {
|
||||
config_get enabled $1 enabled "0"
|
||||
config_get interface $1 interface "br-lan"
|
||||
config_get aircast $1 aircast "1"
|
||||
config_get airupnp $1 airupnp "1"
|
||||
}
|
||||
|
||||
start_service() {
|
||||
config_load airconnect
|
||||
config_foreach main_config main
|
||||
[ "$ENABLE_MAIN" = 0 ] && return 0
|
||||
[ "$ENABLE_AIRCAST" = 0 -a "$ENABLE_AIRUPNP" = 0 ] && return 0
|
||||
config_load "airconnect"
|
||||
config_foreach get_config "airconnect"
|
||||
[ $enabled -eq 0 ] && return 0
|
||||
|
||||
local interface=$(
|
||||
. /lib/functions/network.sh
|
||||
# UPnP/Sonos
|
||||
if [ "$airupnp" -eq 1 ]; then
|
||||
procd_open_instance airupnp
|
||||
procd_set_param command $PROG_AIRUPNP
|
||||
procd_append_param command -l 1000:2000
|
||||
procd_append_param command -Z
|
||||
procd_append_param command -b $interface
|
||||
procd_set_param stdout 0
|
||||
procd_set_param stderr 0
|
||||
procd_set_param respawn
|
||||
procd_close_instance airupnp
|
||||
fi
|
||||
|
||||
network_is_up "$AIRUPNP_IFACE" || exit 0
|
||||
network_get_device device "$AIRUPNP_IFACE"
|
||||
printf "%s" "${device:-$AIRUPNP_IFACE}"
|
||||
)
|
||||
[ -z "$interface" ] && interface=br-lan
|
||||
local common_args="-Z -b $interface"
|
||||
# Chromecast
|
||||
if [ "$aircast" -eq 1 ]; then
|
||||
procd_open_instance aircast
|
||||
procd_set_param command $PROG_AIRCAST
|
||||
procd_append_param command -l 1000:2000
|
||||
procd_append_param command -Z
|
||||
procd_append_param command -b $interface
|
||||
procd_set_param stdout 0
|
||||
procd_set_param stderr 0
|
||||
procd_set_param respawn
|
||||
procd_close_instance aircast
|
||||
fi
|
||||
}
|
||||
|
||||
if [ "$ENABLE_AIRUPNP" = 1 ]; then
|
||||
procd_open_instance
|
||||
procd_set_param command /usr/bin/airupnp $common_args
|
||||
procd_set_param respawn
|
||||
procd_close_instance
|
||||
fi
|
||||
if [ "$ENABLE_AIRCAST" = 1 ]; then
|
||||
procd_open_instance
|
||||
procd_set_param command /usr/bin/aircast $common_args
|
||||
procd_set_param respawn
|
||||
procd_close_instance
|
||||
fi
|
||||
}
|
||||
service_triggers() {
|
||||
procd_add_reload_trigger "airconnect"
|
||||
}
|
||||
|
||||
reload_service() {
|
||||
stop
|
||||
sleep 1
|
||||
start
|
||||
}
|
||||
|
129
daed-next/Makefile
Normal file
@ -0,0 +1,129 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
#
|
||||
# Copyright (C) 2023 ImmortalWrt.org
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=daed-next
|
||||
PKG_VERSION:=2023-12-02-102cb56
|
||||
CORE_VERSION:=core-$(shell date "+%Y-%m-%d")
|
||||
PKG_RELEASE:=1
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_PROTO:=git
|
||||
PKG_SOURCE_VERSION:=102cb562b6962b44c47f38092284d2ab2506a702
|
||||
PKG_SOURCE_URL:=https://github.com/daeuniverse/daed-revived-next.git
|
||||
PKG_MIRROR_HASH:=skip
|
||||
|
||||
PKG_BUILD_DIR=$(BUILD_DIR)/$(PKG_NAME)-$(PKG_VERSION)/wing
|
||||
PKG_BUILD_DEPENDS:=golang/host bpf-headers
|
||||
PKG_BUILD_PARALLEL:=1
|
||||
PKG_USE_MIPS16:=0
|
||||
PKG_BUILD_FLAGS:=no-mips16
|
||||
|
||||
GO_PKG:=github.com/daeuniverse/dae-wing
|
||||
GO_PKG_LDFLAGS:= \
|
||||
-X '$(GO_PKG)/db.AppDescription=$(PKG_NAME) is a integration solution of dae, API and UI.'
|
||||
GO_PKG_LDFLAGS_X:= \
|
||||
$(GO_PKG)/db.AppName=$(PKG_NAME) \
|
||||
$(GO_PKG)/db.AppVersion=$(PKG_VERSION)-$(CORE_VERSION)
|
||||
GO_PKG_GCFLAGS:=-l=4
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
include $(INCLUDE_DIR)/bpf.mk
|
||||
include $(TOPDIR)/feeds/packages/lang/golang/golang-package.mk
|
||||
|
||||
define Package/daed-next/Default
|
||||
SECTION:=net
|
||||
CATEGORY:=Network
|
||||
SUBMENU:=Web Servers/Proxies
|
||||
URL:=https://github.com/daeuniverse/daed-revived-next
|
||||
endef
|
||||
|
||||
define Package/daed-next
|
||||
$(call Package/daed-next/Default)
|
||||
TITLE:=daed-next is a backend of dae
|
||||
# You need enable KERNEL_DEBUG_INFO_BTF and KERNEL_BPF_EVENTS
|
||||
DEPENDS:=$(GO_ARCH_DEPENDS) $(BPF_DEPENDS) \
|
||||
+ca-bundle +kmod-sched-core +kmod-sched-bpf +kmod-xdp-sockets-diag +kmod-veth \
|
||||
+node +v2ray-geoip +v2ray-geosite +zoneinfo-asia
|
||||
endef
|
||||
|
||||
define Package/daed-next/description
|
||||
daed-next is a backend of dae, provides a method to bundle arbitrary
|
||||
frontend, dae and geodata into one binary.
|
||||
endef
|
||||
|
||||
define Package/daed-next/conffiles
|
||||
/etc/daed-next/wing.db
|
||||
/etc/config/daed-next
|
||||
endef
|
||||
|
||||
define Download/daed-next
|
||||
URL:=https://github.com/QiuSimons/luci-app-daed-next/releases/download/daed-web-$(PKG_VERSION)/
|
||||
URL_FILE:=daed-web-$(PKG_VERSION).squashfs
|
||||
FILE:=daed-web-$(PKG_VERSION).squashfs
|
||||
HASH:=skip
|
||||
endef
|
||||
|
||||
define Build/Prepare
|
||||
( \
|
||||
$(TAR) --strip-components=1 -C $(PKG_BUILD_DIR)/../ -xzf $(DL_DIR)/$(PKG_NAME)-$(PKG_VERSION).tar.gz ; \
|
||||
rm -rf $(PKG_BUILD_DIR) && git clone https://github.com/daeuniverse/dae-wing $(BUILD_DIR)/$(PKG_NAME)-$(PKG_VERSION)/wing ; \
|
||||
)
|
||||
endef
|
||||
|
||||
DAE_CFLAGS:= \
|
||||
-O2 -Wall -Werror \
|
||||
-DMAX_MATCH_SET_LEN=64 \
|
||||
-I$(BPF_HEADERS_DIR)/tools/lib \
|
||||
-I$(BPF_HEADERS_DIR)/arch/$(BPF_KARCH)/include/asm/mach-generic
|
||||
|
||||
ifneq ($(CONFIG_USE_MUSL),)
|
||||
TARGET_CFLAGS += -D_LARGEFILE64_SOURCE
|
||||
endif
|
||||
|
||||
define Build/Compile
|
||||
( \
|
||||
pushd $(PKG_BUILD_DIR) ; \
|
||||
rm -rf dae-core && git clone https://github.com/daeuniverse/dae dae-core ; \
|
||||
rm -rf dae-core/control/kern/headers && git clone https://github.com/daeuniverse/dae_bpf_headers dae-core/control/kern/headers ; \
|
||||
go mod tidy ; \
|
||||
$(MAKE) deps ; \
|
||||
$(GO_GENERAL_BUILD_CONFIG_VARS) \
|
||||
$(GO_PKG_BUILD_CONFIG_VARS) \
|
||||
$(GO_PKG_BUILD_VARS) \
|
||||
go generate ./... ; \
|
||||
cd dae-core ; \
|
||||
go mod tidy ; \
|
||||
$(GO_GENERAL_BUILD_CONFIG_VARS) \
|
||||
$(GO_PKG_BUILD_CONFIG_VARS) \
|
||||
$(GO_PKG_BUILD_VARS) \
|
||||
BPF_CLANG="$(CLANG)" \
|
||||
BPF_STRIP_FLAG="-strip=$(LLVM_STRIP)" \
|
||||
BPF_CFLAGS="$(DAE_CFLAGS)" \
|
||||
BPF_TARGET="bpfel,bpfeb" \
|
||||
go generate control/control.go ; \
|
||||
popd ; \
|
||||
$(call GoPackage/Build/Compile) ; \
|
||||
)
|
||||
endef
|
||||
|
||||
define Package/daed-next/install
|
||||
$(call GoPackage/Package/Install/Bin,$(PKG_INSTALL_DIR))
|
||||
$(INSTALL_DIR) $(1)/usr/bin
|
||||
$(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/bin/dae-wing $(1)/usr/bin/dae-wing
|
||||
|
||||
$(INSTALL_DIR) $(1)/usr/share/daed-next
|
||||
$(INSTALL_DATA) $(DL_DIR)/daed-web-$(PKG_VERSION).squashfs $(1)/usr/share/daed-next/daed-web.squashfs
|
||||
|
||||
$(INSTALL_DIR) $(1)/etc/config
|
||||
$(INSTALL_CONF) $(CURDIR)/files/daed-next.config $(1)/etc/config/daed-next
|
||||
|
||||
$(INSTALL_DIR) $(1)/etc/init.d
|
||||
$(INSTALL_BIN) $(CURDIR)/files/daed-next.init $(1)/etc/init.d/daed-next
|
||||
endef
|
||||
|
||||
$(eval $(call Download,daed-next))
|
||||
$(eval $(call GoBinPackage,daed-next))
|
||||
$(eval $(call BuildPackage,daed-next))
|
8
daed-next/files/daed-next.config
Normal file
@ -0,0 +1,8 @@
|
||||
|
||||
config daed-next 'config'
|
||||
option enabled '0'
|
||||
option listen_port '3000'
|
||||
option log_enabled '0'
|
||||
option log_maxbackups '1'
|
||||
option log_maxsize '1'
|
||||
|
59
daed-next/files/daed-next.init
Executable file
@ -0,0 +1,59 @@
|
||||
#!/bin/sh /etc/rc.common
|
||||
|
||||
START=99
|
||||
USE_PROCD=1
|
||||
NODE_BIN=/usr/bin/node
|
||||
WING_BIN=/usr/bin/dae-wing
|
||||
WEB_SQUASHFS=/usr/share/daed-next/daed-web.squashfs
|
||||
GEODATA=/usr/share/v2ray
|
||||
|
||||
get_config() {
|
||||
config_get_bool enabled $1 enabled "0"
|
||||
config_get listen_port $1 listen_port "3000"
|
||||
config_get log_enabled $1 log_enabled "0"
|
||||
config_get log_maxbackups $1 log_maxbackups "1"
|
||||
config_get log_maxsize $1 log_maxsize "1"
|
||||
}
|
||||
|
||||
start_service() {
|
||||
config_load "daed-next"
|
||||
config_foreach get_config "daed-next"
|
||||
|
||||
[ "$enabled" -eq "1" ] || return 1
|
||||
[ "$log_enabled" -eq "1" ] && log_path="/var/log/daed-next/daed-next.log" || log_path="/dev/null"
|
||||
mkdir -p /var/log/daed-next
|
||||
true > /var/log/daed-next/daed-next.log
|
||||
|
||||
# dae-wing
|
||||
procd_open_instance dae-wing
|
||||
procd_set_param env DAE_LOCATION_ASSET=$GEODATA
|
||||
procd_set_param command $WING_BIN run
|
||||
procd_append_param command --config "/etc/daed-next/"
|
||||
procd_append_param command --logfile "$log_path"
|
||||
procd_append_param command --logfile-maxbackups "$log_maxbackups"
|
||||
procd_append_param command --logfile-maxsize "$log_maxsize"
|
||||
procd_set_param limits core="unlimited"
|
||||
procd_set_param limits nofile="1000000 1000000"
|
||||
procd_set_param stdout 0
|
||||
procd_set_param stderr 0
|
||||
procd_set_param respawn 300 5 10
|
||||
procd_close_instance dae-wing
|
||||
}
|
||||
|
||||
service_triggers() {
|
||||
procd_add_reload_trigger "daed-next"
|
||||
}
|
||||
|
||||
stop_service() {
|
||||
sleep 2
|
||||
mount_points=$(mount | grep '/tmp/daed-next' | awk '{print $3}')
|
||||
for mp in $mount_points; do
|
||||
umount -l "$mp" || { echo "Failed to force unmount $mp"; }
|
||||
done
|
||||
}
|
||||
|
||||
reload_service() {
|
||||
stop
|
||||
sleep 2
|
||||
start
|
||||
}
|
@ -1,15 +1,20 @@
|
||||
|
||||
# Copyright (C) 2016 Openwrt.org
|
||||
#
|
||||
# This is free software, licensed under the Apache License, Version 2.0 .
|
||||
#
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_VERSION:=1.0.0-20221219
|
||||
PKG_RELEASE:=
|
||||
PKG_NAME:=luci-app-airconnect
|
||||
PKG_VERSION:=1.0.0
|
||||
PKG_RELEASE:=1
|
||||
|
||||
LUCI_TITLE:=LuCI support for airconnect
|
||||
LUCI_PKGARCH:=all
|
||||
LUCI_TITLE:=LuCI support for AirConnect
|
||||
LUCI_DEPENDS:=+airconnect
|
||||
LUCI_PKGARCH:=all
|
||||
|
||||
PKG_MAINTAINER:=sbwml <admin@cooluc.com>
|
||||
|
||||
include $(TOPDIR)/feeds/luci/luci.mk
|
||||
|
||||
# call BuildPackage - OpenWrt buildroot signature
|
||||
|
||||
|
@ -1,7 +1,20 @@
|
||||
|
||||
module("luci.controller.airconnect", package.seeall)
|
||||
|
||||
function index()
|
||||
entry({"admin", "services", "airconnect"}, alias("admin", "services", "airconnect", "config"), _("AirConnect"), 90).dependent = true
|
||||
entry({"admin", "services", "airconnect", "config"}, cbi("airconnect"))
|
||||
if not nixio.fs.access("/etc/config/airconnect") then
|
||||
return
|
||||
end
|
||||
|
||||
local page = entry({"admin", "services", "airconnect"}, cbi("airconnect"), _("AirConnect"))
|
||||
page.dependent = true
|
||||
page.acl_depends = { "luci-app-airconnect" }
|
||||
|
||||
entry({"admin", "services", "airconnect", "status"}, call("act_status")).leaf = true
|
||||
end
|
||||
|
||||
function act_status()
|
||||
local e = {}
|
||||
e.running = luci.sys.call("pgrep aircast >/dev/null") == 0 or luci.sys.call("pgrep airupnp >/dev/null") == 0
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write_json(e)
|
||||
end
|
||||
|
@ -1,31 +1,30 @@
|
||||
--[[
|
||||
LuCI - Lua Configuration Interface
|
||||
]]--
|
||||
local i = require 'luci.sys'
|
||||
local m, e
|
||||
|
||||
local m, s, o
|
||||
m = Map('airconnect', translate('AirConnect'))
|
||||
m.description = translate('Send audio to UPnP/Sonos/Chromecast players using AirPlay.')
|
||||
|
||||
m = Map("airconnect", translate("AirConnect"), translate("Use AirPlay to stream to UPnP/Sonos & Chromecast devices"))
|
||||
m:section(SimpleSection).template = 'airconnect/airconnect_status'
|
||||
|
||||
s = m:section(TypedSection, "main", translate("Global Settings"))
|
||||
s.addremove=false
|
||||
s.anonymous=true
|
||||
e = m:section(TypedSection, 'airconnect')
|
||||
e.addremove = false
|
||||
e.anonymous = true
|
||||
|
||||
o = s:option(Flag, "enabled", translate("Enable"))
|
||||
o.default = 0
|
||||
o = e:option(Flag, 'enabled', translate('Enabled'))
|
||||
o.rmempty = false
|
||||
|
||||
o = s:option(Value, "interface", translate("Interface"), translate("Network interface for serving, usually LAN"))
|
||||
o.template = "cbi/network_netlist"
|
||||
o.nocreate = true
|
||||
o.default = "lan"
|
||||
o.datatype = "string"
|
||||
|
||||
o = s:option(Flag, "aircast", translate("Supports Chromecast"), translate("Select this if you have Chromecast devices"))
|
||||
o.default = 1
|
||||
o = e:option(Value, 'interface', translate('Bind interface'))
|
||||
for t, e in ipairs(i.net.devices()) do
|
||||
if e ~= 'lo' and not string.match(e, '^docker.*$') and not string.match(e, '^sit.*$') and not string.match(e, '^dummy.*$') and not string.match(e, '^teql.*$') and not string.match(e, '^veth.*$') and not string.match(e, '^ztly.*$') then
|
||||
o:value(e)
|
||||
end
|
||||
end
|
||||
o.rmempty = false
|
||||
|
||||
o = s:option(Flag, "airupnp", translate("Supports UPnP/Sonos"), translate("Select this if you have UPnP/Sonos devices"))
|
||||
o.default = 1
|
||||
o = e:option(Flag, 'airupnp', translate('UPnP/Sonos'), translate('Enable UPnP/Sonos Device Support'))
|
||||
o.rmempty = false
|
||||
|
||||
o = e:option(Flag, 'aircast', translate('Chromecast'), translate('Enable Chromecast Device Support'))
|
||||
o.rmempty = false
|
||||
|
||||
return m
|
||||
|
@ -0,0 +1,23 @@
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
XHR.poll(1, '<%=url([[admin]], [[services]], [[airconnect]], [[status]])%>', null,
|
||||
function(x, data) {
|
||||
var tb = document.getElementById('airconnect_status');
|
||||
if (data && tb) {
|
||||
if (data.running) {
|
||||
var links = '<em style=\"color:green\"><b>AirConnect <%:RUNNING%></b></em>';
|
||||
tb.innerHTML = links;
|
||||
} else {
|
||||
tb.innerHTML = '<em style=\"color:red\"><b>AirConnect <%:NOT RUNNING%></b></em>';
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
//]]>
|
||||
</script>
|
||||
<style>.mar-10 {margin-left: 50px; margin-right: 10px;}</style>
|
||||
<fieldset class="cbi-section">
|
||||
<p id="airconnect_status">
|
||||
<em><%:Collecting data...%></em>
|
||||
</p>
|
||||
</fieldset>
|
1
luci-app-airconnect/po/zh-cn
Symbolic link
@ -0,0 +1 @@
|
||||
zh_Hans
|
@ -1,23 +0,0 @@
|
||||
msgid ""
|
||||
msgstr "Content-Type: text/plain; charset=UTF-8"
|
||||
|
||||
msgid "Use AirPlay to stream to UPnP/Sonos & Chromecast devices"
|
||||
msgstr "AirConnect 让 UPnP/Sonos 和 Chromecast 设备支持 AirPlay 音频串流"
|
||||
|
||||
msgid "Global Settings"
|
||||
msgstr "全局设置"
|
||||
|
||||
msgid "Network interface for serving, usually LAN"
|
||||
msgstr "提供服务的网络接口,通常是 LAN 口"
|
||||
|
||||
msgid "Supports Chromecast"
|
||||
msgstr "支持 Chromecast 设备"
|
||||
|
||||
msgid "Supports UPnP/Sonos"
|
||||
msgstr "支持 UPnP/Sonos 设备"
|
||||
|
||||
msgid "Select this if you have Chromecast devices"
|
||||
msgstr "如果你有 Chromecast 设备就选中这个"
|
||||
|
||||
msgid "Select this if you have UPnP/Sonos devices"
|
||||
msgstr "如果你有 UPnP/Sonos 设备就选中这个"
|
@ -1 +0,0 @@
|
||||
zh-cn
|
26
luci-app-airconnect/po/zh_Hans/airconnect.po
Normal file
@ -0,0 +1,26 @@
|
||||
msgid "AirConnect"
|
||||
msgstr "隔空播放"
|
||||
|
||||
msgid "Send audio to UPnP/Sonos/Chromecast players using AirPlay."
|
||||
msgstr "通过 AirPlay 将音频流传输到 UPnP/Sonos 和 Chromecast 设备。"
|
||||
|
||||
msgid "RUNNING"
|
||||
msgstr "运行中"
|
||||
|
||||
msgid "NOT RUNNING"
|
||||
msgstr "未运行"
|
||||
|
||||
msgid "Collecting data..."
|
||||
msgstr "获取数据中..."
|
||||
|
||||
msgid "Enabled"
|
||||
msgstr "启用"
|
||||
|
||||
msgid "Bind interface"
|
||||
msgstr "绑定接口"
|
||||
|
||||
msgid "Enable UPnP/Sonos Device Support"
|
||||
msgstr "启用 UPnP/Sonos 设备支持"
|
||||
|
||||
msgid "Enable Chromecast Device Support"
|
||||
msgstr "启用 Chromecast 设备支持"
|
11
luci-app-airconnect/root/etc/uci-defaults/luci-airconnect
Executable file
@ -0,0 +1,11 @@
|
||||
#!/bin/sh
|
||||
|
||||
uci -q batch <<-EOF >/dev/null
|
||||
delete ucitrack.@airconnect[-1]
|
||||
add ucitrack airconnect
|
||||
set ucitrack.@airconnect[-1].init=airconnect
|
||||
commit ucitrack
|
||||
EOF
|
||||
|
||||
rm -rf /tmp/luci-indexcache*
|
||||
exit 0
|
@ -0,0 +1,11 @@
|
||||
{
|
||||
"luci-app-airconnect": {
|
||||
"description": "Grant UCI access for luci-app-airconnect",
|
||||
"read": {
|
||||
"uci": [ "airconnect" ]
|
||||
},
|
||||
"write": {
|
||||
"uci": [ "airconnect" ]
|
||||
}
|
||||
}
|
||||
}
|
18
luci-app-daed-next/Makefile
Normal file
@ -0,0 +1,18 @@
|
||||
# Copyright (C) 2016 Openwrt.org
|
||||
#
|
||||
# This is free software, licensed under the Apache License, Version 2.0 .
|
||||
#
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-daed-next
|
||||
PKG_VERSION:=1.0.0
|
||||
PKG_RELEASE:=1
|
||||
|
||||
LUCI_TITLE:=LuCI support for daed-next
|
||||
LUCI_PKGARCH:=all
|
||||
LUCI_DEPENDS:=+daed-next +bash +luci-compat
|
||||
|
||||
include $(TOPDIR)/feeds/luci/luci.mk
|
||||
|
||||
# call BuildPackage - OpenWrt buildroot signature
|
36
luci-app-daed-next/luasrc/controller/daed-next.lua
Normal file
@ -0,0 +1,36 @@
|
||||
local sys = require "luci.sys"
|
||||
local http = require "luci.http"
|
||||
|
||||
module("luci.controller.daed-next", package.seeall)
|
||||
|
||||
function index()
|
||||
if not nixio.fs.access("/etc/config/daed-next") then
|
||||
return
|
||||
end
|
||||
|
||||
local page = entry({"admin", "services", "daed-next"}, alias("admin", "services", "daed-next", "basic"), _("DAED Next"), -1)
|
||||
page.dependent = true
|
||||
page.acl_depends = { "luci-app-daed-next" }
|
||||
|
||||
entry({"admin", "services", "daed-next", "basic"}, cbi("daed-next/basic"), _("Basic Setting"), 1).leaf = true
|
||||
entry({"admin", "services", "daed-next", "dashboard"}, template("daed-next/dashboard"), _("Dashboard"), 2).leaf = true
|
||||
entry({"admin", "services", "daed-next", "log"}, cbi("daed-next/log"), _("Logs"), 3).leaf = true
|
||||
entry({"admin", "services", "daed-next", "status"}, call("act_status")).leaf = true
|
||||
entry({"admin", "services", "daed-next", "get_log"}, call("get_log")).leaf = true
|
||||
entry({"admin", "services", "daed-next", "clear_log"}, call("clear_log")).leaf = true
|
||||
end
|
||||
|
||||
function act_status()
|
||||
local e = {}
|
||||
e.running = sys.call("pgrep -x /usr/bin/dae-wing >/dev/null") == 0
|
||||
http.prepare_content("application/json")
|
||||
http.write_json(e)
|
||||
end
|
||||
|
||||
function get_log()
|
||||
http.write(sys.exec("cat /var/log/daed-next/daed-next.log"))
|
||||
end
|
||||
|
||||
function clear_log()
|
||||
sys.call("true > /var/log/daed-next/daed-next.log")
|
||||
end
|
76
luci-app-daed-next/luasrc/model/cbi/daed-next/basic.lua
Normal file
@ -0,0 +1,76 @@
|
||||
local m, s
|
||||
|
||||
m = Map("daed-next", translate("DAE Dashboard"))
|
||||
m.description = translate("A Linux high-performance transparent proxy solution based on eBPF")
|
||||
|
||||
m:section(SimpleSection).template = "daed-next/daed-next_status"
|
||||
|
||||
s = m:section(TypedSection, "daed-next")
|
||||
s.addremove = false
|
||||
s.anonymous = true
|
||||
|
||||
if nixio.fs.stat("/sys/fs/bpf","type") ~= "dir" then
|
||||
s.rawhtml = true
|
||||
s.template = "daed-next/daed-next_error"
|
||||
end
|
||||
|
||||
o = s:option(Flag, "enabled", translate("Enabled"))
|
||||
o.rmempty = false
|
||||
|
||||
o = s:option(Button, "Dashboard Toggle", translate("Dashboard Toggle"))
|
||||
o.inputtitle = translate("Toggle")
|
||||
o.inputstyle = "reload"
|
||||
o.description = translate("Dashboard is a frontend management panel, meant for configuration use only.")
|
||||
o.write = function()
|
||||
luci.sys.exec("/etc/daed-next/dashboard.sh &> /dev/null &")
|
||||
end
|
||||
|
||||
enable = s:option(Flag, "subscribe_auto_update", translate("Enable Auto Subscribe Update"))
|
||||
enable.rmempty = false
|
||||
|
||||
o = s:option(Value, "daed_username", translate("Username"))
|
||||
o.default = Username
|
||||
o.password = true
|
||||
o:depends('subscribe_auto_update', '1')
|
||||
|
||||
o = s:option(Value, "daed_password", translate("Password"))
|
||||
o.default = Password
|
||||
o.password = true
|
||||
o:depends('subscribe_auto_update', '1')
|
||||
|
||||
o = s:option(ListValue, "subscribe_update_week_time", translate("Update Cycle"))
|
||||
o:value("*", translate("Every Day"))
|
||||
o:value("1", translate("Every Monday"))
|
||||
o:value("2", translate("Every Tuesday"))
|
||||
o:value("3", translate("Every Wednesday"))
|
||||
o:value("4", translate("Every Thursday"))
|
||||
o:value("5", translate("Every Friday"))
|
||||
o:value("6", translate("Every Saturday"))
|
||||
o:value("7", translate("Every Sunday"))
|
||||
o.default = "*"
|
||||
o:depends('subscribe_auto_update', '1')
|
||||
|
||||
update_time = s:option(ListValue, "subscribe_update_day_time", translate("Update Time (Every Day)"))
|
||||
for t = 0, 23 do
|
||||
update_time:value(t, t..":00")
|
||||
end
|
||||
update_time.default = 0
|
||||
update_time:depends('subscribe_auto_update', '1')
|
||||
|
||||
o = s:option(Value, "listen_port", translate("Web Listen port"))
|
||||
o.datatype = "and(port,min(1))"
|
||||
o.default = 3000
|
||||
|
||||
o = s:option(Flag, "log_enabled", translate("Enable Logs"))
|
||||
o.default = 0
|
||||
o.rmempty = false
|
||||
|
||||
o = s:option(Value, "log_maxbackups", translate("Logfile retention count"))
|
||||
o.default = 1
|
||||
o:depends("log_enabled", "1")
|
||||
|
||||
o = s:option(Value, "log_maxsize", translate("Logfile Max Size (MB)"))
|
||||
o.default = 1
|
||||
o:depends("log_enabled", "1")
|
||||
|
||||
return m
|
5
luci-app-daed-next/luasrc/model/cbi/daed-next/log.lua
Normal file
@ -0,0 +1,5 @@
|
||||
m = Map("daed-next")
|
||||
|
||||
m:append(Template("daed-next/daed-next_log"))
|
||||
|
||||
return m
|
33
luci-app-daed-next/luasrc/view/daed-next/daed-next_log.htm
Normal file
@ -0,0 +1,33 @@
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
function clear_log(btn) {
|
||||
XHR.get('<%=url([[admin]], [[services]], [[daed-next]], [[clear_log]])%>', null,
|
||||
function(x, data) {
|
||||
if(x && x.status == 200) {
|
||||
var log_textarea = document.getElementById('log_textarea');
|
||||
log_textarea.innerHTML = "";
|
||||
log_textarea.scrollTop = log_textarea.scrollHeight;
|
||||
}
|
||||
location.reload();
|
||||
}
|
||||
);
|
||||
}
|
||||
var scrolled = false;
|
||||
XHR.poll(2, '<%=url([[admin]], [[services]], [[daed-next]], [[get_log]])%>', null,
|
||||
function(x, data) {
|
||||
if(x && x.status == 200) {
|
||||
var log_textarea = document.getElementById('log_textarea');
|
||||
log_textarea.innerHTML = x.responseText;
|
||||
if (!scrolled) {
|
||||
log_textarea.scrollTop = log_textarea.scrollHeight;
|
||||
scrolled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
//]]>
|
||||
</script>
|
||||
<fieldset class="cbi-section" id="_log_fieldset">
|
||||
<input class="cbi-button cbi-input-remove" type="button" onclick="clear_log()" value="<%:Clear logs%>" style="margin-left: 10px; margin-top: 10px;">
|
||||
<textarea id="log_textarea" class="cbi-input-textarea" style="width: calc(100% - 20px); height: 645px; margin: 10px;" data-update="change" rows="5" wrap="off" readonly="readonly"></textarea>
|
||||
</fieldset>
|
@ -0,0 +1,46 @@
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
XHR.poll(5, '<%=url("admin/services/daed-next/status")%>', null,
|
||||
function(x, data)
|
||||
{
|
||||
var tb = document.getElementById('daed-next_status');
|
||||
if (data && tb)
|
||||
{
|
||||
if (data.running)
|
||||
{
|
||||
tb.innerHTML = '<em style=\"color:green\"><b><%:dae-wing%> <%:RUNNING%></b></em>';
|
||||
}
|
||||
else
|
||||
{
|
||||
tb.innerHTML = '<em style=\"color:red\"><b><%:dae-wing%> <%:NOT RUNNING%></b></em>';
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
//]]></script>
|
||||
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
XHR.poll(5, '<%=url("admin/services/daed-next/dashboardstatus")%>', null,
|
||||
function(x, data) {
|
||||
console.log('Received data:', data);
|
||||
|
||||
var dashboardstatus = document.getElementById('daed-next_dashboardstatus');
|
||||
var dashboardPidExists = <%=luci.sys.exec("[ -e '/tmp/log/daed-next/dashboard.pid' ] && echo '1' || echo '0'")%>;
|
||||
|
||||
if (dashboardPidExists) {
|
||||
dashboardstatus.innerHTML = '<em style=\"color:green\"><b><%:Dashboard%> <%:RUNNING%></b></em>';
|
||||
} else {
|
||||
dashboardstatus.innerHTML = '<em style=\"color:red\"><b><%:Dashboard%> <%:NOT RUNNING%></b></em>';
|
||||
}
|
||||
}
|
||||
);
|
||||
//]]></script>
|
||||
|
||||
<style>.mar-10 {margin-left: 50px; margin-right: 10px;}</style>
|
||||
<fieldset class="cbi-section">
|
||||
<p id="daed-next_status">
|
||||
<em><b><%:Collecting data...%></b></em>
|
||||
</p>
|
||||
<p id="daed-next_dashboardstatus">
|
||||
<em><b><%:Collecting data...%></b></em>
|
||||
</p>
|
||||
</fieldset>
|
27
luci-app-daed-next/luasrc/view/daed-next/dashboard.htm
Normal file
1
luci-app-daed-next/po/zh-cn
Symbolic link
@ -0,0 +1 @@
|
||||
zh_Hans
|
86
luci-app-daed-next/po/zh_Hans/daed-next.po
Normal file
@ -0,0 +1,86 @@
|
||||
msgid "DAED Next"
|
||||
msgstr ""
|
||||
|
||||
msgid "DAE Dashboard"
|
||||
msgstr "DAE 仪表板"
|
||||
|
||||
msgid "A Linux high-performance transparent proxy solution based on eBPF"
|
||||
msgstr "一个基于 eBPF 的 Linux 高性能透明代理解决方案"
|
||||
|
||||
msgid "Enabled"
|
||||
msgstr "启用"
|
||||
|
||||
msgid "Web Listen port"
|
||||
msgstr "Web 监听端口"
|
||||
|
||||
msgid "Enable Logs"
|
||||
msgstr "启用日志"
|
||||
|
||||
msgid "Logfile retention count"
|
||||
msgstr "日志文件保留数量"
|
||||
|
||||
msgid "Logfile Max Size (MB)"
|
||||
msgstr "日志文件大小(MB)"
|
||||
|
||||
msgid "Logs"
|
||||
msgstr "日志"
|
||||
|
||||
msgid "Clear logs"
|
||||
msgstr "清空日志"
|
||||
|
||||
msgid "Basic Setting"
|
||||
msgstr "基本设置"
|
||||
|
||||
msgid "RUNNING"
|
||||
msgstr "运行中"
|
||||
|
||||
msgid "NOT RUNNING"
|
||||
msgstr "未运行"
|
||||
|
||||
msgid "Collecting data..."
|
||||
msgstr "收集数据..."
|
||||
|
||||
msgid "Open Web Interface"
|
||||
msgstr "打开 Web 界面"
|
||||
|
||||
msgid "The kernel does not support eBPF"
|
||||
msgstr "内核不支持 eBPF"
|
||||
|
||||
msgid "Dashboard"
|
||||
msgstr "仪表板"
|
||||
|
||||
msgid "DAED Next is not running"
|
||||
msgstr "DAED Next 未运行"
|
||||
|
||||
msgid "Please start the DAED Next service first and try again"
|
||||
msgstr "请先启动 DAED Next 服务后重试"
|
||||
|
||||
msgid "dae-wing or dashboard is not running"
|
||||
msgstr "DAE-WING 或 仪表板 未运行"
|
||||
|
||||
msgid "Please start the dae-wing service and the dashboard service first and try again"
|
||||
msgstr "请先启动 DAE-WING 和 仪表板 服务后重试"
|
||||
|
||||
msgid "Enable Auto Subscribe Update"
|
||||
msgstr "启用订阅自动更新"
|
||||
|
||||
msgid "Update Cycle"
|
||||
msgstr "更新周期"
|
||||
|
||||
msgid "Update Time (Every Day)"
|
||||
msgstr "更新时间(每天)"
|
||||
|
||||
msgid "Username"
|
||||
msgstr "用户名"
|
||||
|
||||
msgid "Password"
|
||||
msgstr "密码"
|
||||
|
||||
msgid "Dashboard Toggle"
|
||||
msgstr "仪表板开关"
|
||||
|
||||
msgid "Toggle"
|
||||
msgstr "开/关"
|
||||
|
||||
msgid "Dashboard is a frontend management panel, meant for configuration use only."
|
||||
msgstr "仪表板是一个前端管理面板,仅应在配置过程中启用。"
|
36
luci-app-daed-next/root/etc/daed-next/daed-next_sub.sh
Executable file
@ -0,0 +1,36 @@
|
||||
#!/bin/sh
|
||||
|
||||
USERNAME=$(uci -q get daed-next.config.daed_username)
|
||||
PASSWORD=$(uci -q get daed-next.config.daed_password)
|
||||
PORT=2023
|
||||
GRAPHQL_URL="http://127.0.0.1:"$PORT"/graphql"
|
||||
CRON_FILE="/etc/crontabs/root"
|
||||
RANDOM_SEED=$RANDOM
|
||||
RANDOM_NUM=$((RANDOM_SEED % 10 + 1))
|
||||
|
||||
login() {
|
||||
LOGIN=$(curl -s -X POST -H "Content-Type: application/json" -d '{"query":"query Token($username: String!, $password: String!) {\n token(username: $username, password: $password)\n}","variables":{"username":"'"$USERNAME"'","password":"'"$PASSWORD"'"}}' $GRAPHQL_URL)
|
||||
JSON=${LOGIN#\"}
|
||||
JSON=${LOGIN%\"}
|
||||
TOKEN=$(echo $JSON | sed -n 's/.*"token":"\([^"]*\)".*/\1/p')
|
||||
}
|
||||
|
||||
update_subscription() {
|
||||
SUBSCRIPTION_ID_LIST=$(curl -s -X POST -H "Authorization: $TOKEN" -d '{"query": "query Subscriptions {\n subscriptions {\nid\ntag\nstatus\nlink\ninfo\nupdatedAt\nnodes {\nedges {\nid\nname\nprotocol\nlink\n}\n}\n}\n}", "operationName": "Subscriptions"}' $GRAPHQL_URL | grep -o '"id":"[^"]*","tag"' | grep -o 'id":"[^"]*' | grep -o '[^"]*$')
|
||||
echo "$SUBSCRIPTION_ID_LIST" | while read -r id; do
|
||||
curl -X POST -H "Authorization: $TOKEN" -d '{"query":"mutation UpdateSubscription($id: ID!) {\n updateSubscription(id: $id) {\n id\n }\n}","variables":{"id":"'"$id"'"},"operationName":"UpdateSubscription"}' $GRAPHQL_URL
|
||||
done
|
||||
}
|
||||
|
||||
reload() {
|
||||
curl -X POST -H "Authorization: $TOKEN" -d '{"query":"mutation Run($dry: Boolean!) {\n run(dry: $dry)\n}","variables":{"dry":false},"operationName":"Run"}' $GRAPHQL_URL
|
||||
}
|
||||
|
||||
resetcron() {
|
||||
touch $CRON_FILE
|
||||
sed -i '/daed-next_sub.sh/d' $CRON_FILE 2>/dev/null
|
||||
[ "$(uci -q get daed-next.config.subscribe_auto_update)" -eq 1 ] && echo "${RANDOM_NUM} $(uci -q get daed-next.config.subscribe_update_day_time) * * $(uci -q get daed-next.config.subscribe_update_week_time) /etc/daed-next/daed-next_sub.sh >/dev/null 2>&1" >>$CRON_FILE
|
||||
crontab $CRON_FILE
|
||||
}
|
||||
|
||||
login && update_subscription && reload && resetcron
|
37
luci-app-daed-next/root/etc/daed-next/dashboard.sh
Executable file
@ -0,0 +1,37 @@
|
||||
#!/bin/sh
|
||||
|
||||
NODE_BIN=/usr/bin/node
|
||||
WEB_SQUASHFS=/usr/share/daed-next/daed-web.squashfs
|
||||
PID_FILE=/tmp/log/daed-next/dashboard.pid
|
||||
DAED_NEXT_DIR=/var/daed-next
|
||||
|
||||
start_server() {
|
||||
listen_port=$(uci -q get daed-next.config.listen_port)
|
||||
if [ ! -d "$DAED_NEXT_DIR" ]; then
|
||||
mkdir -p "$DAED_NEXT_DIR"
|
||||
fi
|
||||
mount -t squashfs "$WEB_SQUASHFS" "$DAED_NEXT_DIR" || { echo "Mount failed"; }
|
||||
|
||||
ARGS="PORT=$listen_port HOSTNAME=0.0.0.0"
|
||||
/bin/sh -c "$ARGS $NODE_BIN $DAED_NEXT_DIR/server.js" &
|
||||
echo $! > "$PID_FILE"
|
||||
}
|
||||
|
||||
stop_server() {
|
||||
if [ -f "$PID_FILE" ]; then
|
||||
PID=$(cat "$PID_FILE")
|
||||
kill "$PID" || { echo "Failed to kill process $PID"; }
|
||||
rm -f "$PID_FILE"
|
||||
|
||||
mount_points=$(mount | grep '/tmp/daed-next' | awk '{print $3}')
|
||||
for mp in $mount_points; do
|
||||
umount -l "$mp" || { echo "Failed to force unmount $mp"; }
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
if [ -e "$PID_FILE" ]; then
|
||||
stop_server
|
||||
else
|
||||
start_server
|
||||
fi
|
44
luci-app-daed-next/root/etc/init.d/luci_daed-next
Executable file
@ -0,0 +1,44 @@
|
||||
#!/bin/sh /etc/rc.common
|
||||
# Copyright (C) 2023 Tianling Shen <cnsztl@immortalwrt.org>
|
||||
|
||||
USE_PROCD=0
|
||||
START=99
|
||||
|
||||
CONF="daed-next"
|
||||
PROG="/usr/bin/dae-wing"
|
||||
LOG="/var/log/daed-next/daed-next.log"
|
||||
CRON_FILE="/etc/crontabs/root"
|
||||
RANDOM_SEED=$RANDOM
|
||||
RANDOM_NUM=$((RANDOM_SEED % 10 + 1))
|
||||
|
||||
setcron() {
|
||||
touch $CRON_FILE
|
||||
sed -i '/daed-next_sub.sh/d' $CRON_FILE 2>/dev/null
|
||||
[ "$(uci -q get daed-next.config.subscribe_auto_update)" -eq 1 ] && echo "${RANDOM_NUM} $(uci -q get daed-next.config.subscribe_update_day_time) * * $(uci -q get daed-next.config.subscribe_update_week_time) /etc/daed-next/daed-next_sub.sh >/dev/null 2>&1" >>$CRON_FILE
|
||||
crontab $CRON_FILE
|
||||
}
|
||||
|
||||
delcron() {
|
||||
sed -i '/daed-next_sub.sh/d' $CRON_FILE 2>/dev/null
|
||||
crontab $CRON_FILE
|
||||
}
|
||||
|
||||
start_service() {
|
||||
config_load "$CONF"
|
||||
|
||||
local enabled
|
||||
config_get_bool enabled "config" "enabled" "0"
|
||||
if [ "$enabled" -eq 0 ]; then
|
||||
delcron
|
||||
return 1
|
||||
fi
|
||||
setcron
|
||||
}
|
||||
|
||||
stop_service() {
|
||||
delcron
|
||||
}
|
||||
|
||||
service_triggers() {
|
||||
procd_add_reload_trigger "$CONF"
|
||||
}
|
5
luci-app-daed-next/root/etc/uci-defaults/luci-daed-next
Executable file
@ -0,0 +1,5 @@
|
||||
#!/bin/sh
|
||||
|
||||
rm -rf /tmp/luci-*
|
||||
|
||||
exit 0
|
@ -0,0 +1,11 @@
|
||||
{
|
||||
"luci-app-daed-next": {
|
||||
"description": "Grant UCI access for luci-app-daed-next",
|
||||
"read": {
|
||||
"uci": [ "daed-next" ]
|
||||
},
|
||||
"write": {
|
||||
"uci": [ "daed-next" ]
|
||||
}
|
||||
}
|
||||
}
|
201
luci-app-internet-detector/LICENSE
Normal file
@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
49
luci-app-internet-detector/README.md
Normal file
@ -0,0 +1,49 @@
|
||||
# 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, luaposix, libuci-lua.
|
||||
|
||||
**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.
|
||||
- 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 luaposix library.
|
||||
|
||||
## Installation notes
|
||||
|
||||
**OpenWrt >= 21.02:**
|
||||
|
||||
opkg update
|
||||
wget --no-check-certificate -O /tmp/internet-detector_1.2-0_all.ipk https://github.com/gSpotx2f/packages-openwrt/raw/master/current/internet-detector_1.2-0_all.ipk
|
||||
opkg install /tmp/internet-detector_1.2-0_all.ipk
|
||||
rm /tmp/internet-detector_1.2-0_all.ipk
|
||||
/etc/init.d/internet-detector start
|
||||
/etc/init.d/internet-detector enable
|
||||
|
||||
wget --no-check-certificate -O /tmp/luci-app-internet-detector_1.2-0_all.ipk https://github.com/gSpotx2f/packages-openwrt/raw/master/current/luci-app-internet-detector_1.2-0_all.ipk
|
||||
opkg install /tmp/luci-app-internet-detector_1.2-0_all.ipk
|
||||
rm /tmp/luci-app-internet-detector_1.2-0_all.ipk
|
||||
/etc/init.d/rpcd restart
|
||||
|
||||
Email notification:
|
||||
|
||||
opkg install mailsend
|
||||
|
||||
i18n-ru:
|
||||
|
||||
wget --no-check-certificate -O /tmp/luci-i18n-internet-detector-ru_1.2-0_all.ipk https://github.com/gSpotx2f/packages-openwrt/raw/master/current/luci-i18n-internet-detector-ru_1.2-0_all.ipk
|
||||
opkg install /tmp/luci-i18n-internet-detector-ru_1.2-0_all.ipk
|
||||
rm /tmp/luci-i18n-internet-detector-ru_1.2-0_all.ipk
|
||||
|
||||
**[OpenWrt 19.07](https://github.com/gSpotx2f/luci-app-internet-detector/tree/19.07)**
|
||||
|
||||
## Screenshots:
|
||||
|
||||

|
||||

|
||||

|
63
luci-app-internet-detector/internet-detector/Makefile
Normal file
@ -0,0 +1,63 @@
|
||||
#
|
||||
# (с) 2024 gSpot (https://github.com/gSpotx2f/luci-app-internet-detector)
|
||||
#
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=internet-detector
|
||||
PKG_VERSION:=1.2
|
||||
PKG_RELEASE:=0
|
||||
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:=Internet detector
|
||||
URL:=https://github.com/gSpotx2f/luci-app-internet-detector
|
||||
PKGARCH:=all
|
||||
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 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
|
||||
endef
|
||||
|
||||
define Build/Configure
|
||||
endef
|
||||
|
||||
define Build/Compile
|
||||
endef
|
||||
|
||||
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_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_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
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,$(PKG_NAME)))
|
@ -0,0 +1,39 @@
|
||||
config main 'config'
|
||||
option mode '1'
|
||||
option enable_logger '1'
|
||||
|
||||
config instance 'internet'
|
||||
option enabled '1'
|
||||
list hosts '8.8.8.8'
|
||||
list hosts '1.1.1.1'
|
||||
option check_type '0'
|
||||
option tcp_port '53'
|
||||
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_dead_period '3600'
|
||||
option mod_reboot_force_reboot_delay '300'
|
||||
option mod_network_restart_enabled '0'
|
||||
option mod_network_restart_dead_period '900'
|
||||
option mod_network_restart_attempts '1'
|
||||
option mod_network_restart_restart_timeout '0'
|
||||
option mod_modem_restart_enabled '0'
|
||||
option mod_modem_restart_dead_period '600'
|
||||
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_timeout '3'
|
||||
option mod_public_ip_enable_ip_script '0'
|
||||
option mod_email_enabled '0'
|
||||
option mod_email_alive_period '0'
|
||||
option mod_email_mail_smtp 'smtp.gmail.com'
|
||||
option mod_email_mail_smtp_port '587'
|
||||
option mod_email_mail_security 'tls'
|
||||
option mod_user_scripts_enabled '0'
|
||||
option mod_user_scripts_alive_period '0'
|
||||
option mod_user_scripts_dead_period '0'
|
@ -0,0 +1,25 @@
|
||||
#!/bin/sh /etc/rc.common
|
||||
|
||||
START=97
|
||||
STOP=01
|
||||
|
||||
PROG="/usr/bin/internet-detector"
|
||||
|
||||
run_instance() {
|
||||
config_get enabled "$1" enabled "0"
|
||||
if [ $enabled = "1" ]; then
|
||||
$PROG service "$1"
|
||||
fi
|
||||
}
|
||||
|
||||
start() {
|
||||
config_load internet-detector
|
||||
config_get mode "config" mode "0"
|
||||
if [ $mode = "1" ]; then
|
||||
config_foreach run_instance "instance"
|
||||
fi
|
||||
}
|
||||
|
||||
stop() {
|
||||
$PROG stop
|
||||
}
|
@ -0,0 +1 @@
|
||||
# Shell commands to run when disconnected from the Internet
|
@ -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 run when connected to the Internet
|
704
luci-app-internet-detector/internet-detector/files/usr/bin/internet-detector
Executable file
@ -0,0 +1,704 @@
|
||||
#!/usr/bin/env lua
|
||||
|
||||
--[[
|
||||
Internet detector daemon for OpenWrt.
|
||||
|
||||
Dependences:
|
||||
lua
|
||||
luaposix
|
||||
libuci-lua
|
||||
|
||||
(с) 2024 gSpot (https://github.com/gSpotx2f/luci-app-internet-detector)
|
||||
--]]
|
||||
|
||||
-- Importing packages
|
||||
|
||||
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 = {
|
||||
mode = 0, -- 0: disabled, 1: Service, 2: UI detector
|
||||
enableLogger = true,
|
||||
hostname = "OpenWrt",
|
||||
appName = "internet-detector",
|
||||
commonDir = "/tmp/run",
|
||||
pingCmd = "/bin/ping",
|
||||
pingParams = "-c 1",
|
||||
uiRunTime = 30,
|
||||
noModules = false,
|
||||
uiAvailModules = { mod_public_ip = true },
|
||||
debug = false,
|
||||
serviceConfig = {
|
||||
hosts = {
|
||||
[1] = "8.8.8.8",
|
||||
[2] = "1.1.1.1",
|
||||
},
|
||||
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,
|
||||
iface = nil,
|
||||
instance = nil,
|
||||
},
|
||||
modules = {},
|
||||
parsedHosts = {},
|
||||
uiCounter = 0,
|
||||
}
|
||||
InternetDetector.configDir = string.format("/etc/%s", InternetDetector.appName)
|
||||
InternetDetector.modulesDir = string.format("/usr/lib/%s", InternetDetector.appName)
|
||||
|
||||
-- Loading settings from UCI
|
||||
|
||||
local uciCursor = uci.cursor()
|
||||
InternetDetector.mode = tonumber(
|
||||
uciCursor:get(InternetDetector.appName, "config", "mode"))
|
||||
InternetDetector.enableLogger = (tonumber(
|
||||
uciCursor:get(InternetDetector.appName, "config", "enable_logger")) ~= 0)
|
||||
local hostname = uciCursor:get("system", "@[0]", "hostname")
|
||||
if hostname ~= nil then
|
||||
InternetDetector.hostname = hostname
|
||||
end
|
||||
|
||||
local RUNNING
|
||||
|
||||
function InternetDetector:prequire(package)
|
||||
local retVal, pkg = pcall(require, package)
|
||||
return retVal and pkg
|
||||
end
|
||||
|
||||
function InternetDetector:loadUCIConfig(sType, instance)
|
||||
local success
|
||||
local num = 0
|
||||
uciCursor:foreach(
|
||||
self.appName,
|
||||
sType,
|
||||
function(s)
|
||||
if s[".name"] == instance then
|
||||
for k, v in pairs(s) do
|
||||
if type(v) == "string" and v:match("^[%d]+$") then
|
||||
v = tonumber(v)
|
||||
end
|
||||
self.serviceConfig[k] = v
|
||||
end
|
||||
success = true
|
||||
self.serviceConfig.instanceNum = num
|
||||
end
|
||||
num = num + 1
|
||||
end
|
||||
)
|
||||
self.serviceConfig.instance = instance
|
||||
self.pidFile = string.format(
|
||||
"%s/%s.%s.pid", self.commonDir, self.appName, instance)
|
||||
self.statusFile = string.format(
|
||||
"%s/%s.%s.status", self.commonDir, self.appName, instance)
|
||||
return success
|
||||
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)
|
||||
if self.enableLogger then
|
||||
local levels = {
|
||||
emerg = syslog.LOG_EMERG,
|
||||
alert = syslog.LOG_ALERT,
|
||||
crit = syslog.LOG_CRIT,
|
||||
err = syslog.LOG_ERR,
|
||||
warning = syslog.LOG_WARNING,
|
||||
notice = syslog.LOG_NOTICE,
|
||||
info = syslog.LOG_INFO,
|
||||
debug = syslog.LOG_DEBUG,
|
||||
}
|
||||
syslog.syslog(levels[level] or syslog.LOG_INFO, string.format(
|
||||
"%s: %s", self.serviceConfig.instance or "", msg))
|
||||
end
|
||||
end
|
||||
|
||||
function InternetDetector:loadModules()
|
||||
package.path = string.format("%s;%s/?.lua", package.path, self.modulesDir)
|
||||
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 = self:prequire(modName)
|
||||
if m then
|
||||
m.config = self
|
||||
m.syslog = function(level, msg) self:writeLogMessage(level, 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: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)
|
||||
|
||||
if self.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
|
||||
|
||||
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
|
||||
if self.debug then
|
||||
io.stdout:write(string.format(
|
||||
"GETADDRINFO ERROR: %s, %s\n", errMsg, errNum))
|
||||
end
|
||||
else
|
||||
local family = saTable[1].family
|
||||
|
||||
if family then
|
||||
local sock, errMsg, errNum = socket.socket(family, socket.SOCK_STREAM, 0)
|
||||
|
||||
if not sock then
|
||||
if self.debug then
|
||||
io.stdout:write(string.format(
|
||||
"SOCKET ERROR: %s, %s\n", errMsg, errNum))
|
||||
end
|
||||
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
|
||||
if self.debug then
|
||||
io.stdout:write(string.format(
|
||||
"SOCKET ERROR: %s, %s\n", errMsg, errNum))
|
||||
end
|
||||
unistd.close(sock)
|
||||
return retCode
|
||||
end
|
||||
end
|
||||
|
||||
local success = socket.connect(sock, saTable[1])
|
||||
|
||||
if self.debug then
|
||||
if not success then
|
||||
io.stdout:write(string.format(
|
||||
"SOCKET CONNECT ERROR: %s\n", 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 = {}
|
||||
io.stdout:write(
|
||||
string.format("SOCKET ERROR: %s, %s\n", err_s, e_s))
|
||||
end
|
||||
if not peerTable then
|
||||
peerTable = {}
|
||||
io.stdout:write(
|
||||
string.format("SOCKET ERROR: %s, %s\n", err_p, e_p))
|
||||
end
|
||||
io.stdout:write(string.format(
|
||||
"--- TCP ---\ntime = %s\nconnection_timeout = %s\niface = %s\nhost:port = [%s]:%s\nsockname = [%s]:%s\npeername = [%s]:%s\nsuccess = %s\n",
|
||||
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))
|
||||
)
|
||||
io.stdout:flush()
|
||||
end
|
||||
|
||||
socket.shutdown(sock, socket.SHUT_RDWR)
|
||||
unistd.close(sock)
|
||||
retCode = success and 0 or 1
|
||||
end
|
||||
end
|
||||
return retCode
|
||||
end
|
||||
|
||||
function InternetDetector:checkHosts()
|
||||
local checkFunc = (self.serviceConfig.check_type == 1) and self.pingHost or self.TCPConnectionToHost
|
||||
local retCode = 1
|
||||
for k, v in ipairs(self.parsedHosts) do
|
||||
for i = 1, self.serviceConfig.connection_attempts do
|
||||
if checkFunc(self, v.addr, v.port) == 0 then
|
||||
retCode = 0
|
||||
break
|
||||
end
|
||||
end
|
||||
if retCode == 0 then
|
||||
break
|
||||
end
|
||||
end
|
||||
return retCode
|
||||
end
|
||||
|
||||
function InternetDetector:breakMain(signo)
|
||||
RUNNING = false
|
||||
end
|
||||
|
||||
function InternetDetector:resetUiCounter(signo)
|
||||
self.uiCounter = 0
|
||||
end
|
||||
|
||||
function InternetDetector:main()
|
||||
signal.signal(signal.SIGTERM, function(signo) self:breakMain(signo) end)
|
||||
signal.signal(signal.SIGINT, function(signo) self:breakMain(signo) end)
|
||||
signal.signal(signal.SIGQUIT, function(signo) self:breakMain(signo) end)
|
||||
signal.signal(signal.SIGUSR1, function(signo) self:resetUiCounter(signo) end)
|
||||
|
||||
local lastStatus, currentStatus, mTimeNow, mTimeDiff, mLastTime, uiTimeNow, uiLastTime
|
||||
local interval = self.serviceConfig.interval_up
|
||||
local counter = 0
|
||||
local onStart = true
|
||||
RUNNING = true
|
||||
while RUNNING do
|
||||
if counter == 0 or counter >= interval then
|
||||
currentStatus = self:checkHosts()
|
||||
if onStart or not stat.stat(self.statusFile) then
|
||||
self:writeValueToFile(self.statusFile, self:statusJson(
|
||||
currentStatus, self.serviceConfig.instance))
|
||||
onStart = false
|
||||
end
|
||||
|
||||
if currentStatus == 0 then
|
||||
interval = self.serviceConfig.interval_up
|
||||
if lastStatus ~= nil and currentStatus ~= lastStatus then
|
||||
self:writeValueToFile(self.statusFile, self:statusJson(
|
||||
currentStatus, self.serviceConfig.instance))
|
||||
self:writeLogMessage("notice", "Connected")
|
||||
end
|
||||
else
|
||||
interval = self.serviceConfig.interval_down
|
||||
if lastStatus ~= nil and currentStatus ~= lastStatus then
|
||||
self:writeValueToFile(self.statusFile, self:statusJson(
|
||||
currentStatus, self.serviceConfig.instance))
|
||||
self:writeLogMessage("notice", "Disconnected")
|
||||
end
|
||||
end
|
||||
|
||||
counter = 0
|
||||
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
|
||||
e:run(currentStatus, lastStatus, mTimeDiff)
|
||||
end
|
||||
|
||||
local modulesStatus = {}
|
||||
for k, v in ipairs(self.modules) do
|
||||
if v.status ~= nil then
|
||||
modulesStatus[v.name] = v.status
|
||||
end
|
||||
end
|
||||
if next(modulesStatus) then
|
||||
self:writeValueToFile(self.statusFile, self:statusJson(
|
||||
currentStatus, self.serviceConfig.instance, modulesStatus))
|
||||
end
|
||||
|
||||
lastStatus = currentStatus
|
||||
unistd.sleep(1)
|
||||
counter = counter + 1
|
||||
|
||||
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:breakMain(signal.SIGTERM)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function InternetDetector:removeProcessFiles()
|
||||
os.remove(string.format(
|
||||
"%s/%s.%s.pid", self.commonDir, self.appName, self.serviceConfig.instance))
|
||||
os.remove(string.format(
|
||||
"%s/%s.%s.status", self.commonDir, self.appName, self.serviceConfig.instance))
|
||||
end
|
||||
|
||||
function InternetDetector:status()
|
||||
local ok, commonDir = pcall(dirent.files, self.commonDir)
|
||||
if ok then
|
||||
local appName = self.appName:gsub("-", "%%-")
|
||||
for item in commonDir do
|
||||
if item:match("^" .. appName .. ".-%.pid$") 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 appName = self.appName:gsub("-", "%%-")
|
||||
local lines = {}
|
||||
for item in commonDir do
|
||||
if item:match("^" .. appName .. ".-%.status$") 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
|
||||
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 stop error: %s (%s). PID: "%s"\n', errMsg, errNum, pidValue))
|
||||
end
|
||||
if errNum == 3 then
|
||||
os.remove(pidFile)
|
||||
end
|
||||
retVal = true
|
||||
end
|
||||
end
|
||||
if not pidValue then
|
||||
io.stderr:write(
|
||||
string.format('PID file "%s" does not exist. %s not running?\n',
|
||||
pidFile, self.appName))
|
||||
end
|
||||
return retVal
|
||||
end
|
||||
|
||||
function InternetDetector:stop()
|
||||
local appName = self.appName:gsub("-", "%%-")
|
||||
local success
|
||||
for i = 0, 10 do
|
||||
success = true
|
||||
local ok, commonDir = pcall(dirent.files, self.commonDir)
|
||||
if ok then
|
||||
for item in commonDir do
|
||||
if item:match("^" .. appName .. ".-%.pid$") then
|
||||
self:stopInstance(string.format("%s/%s", self.commonDir, item))
|
||||
success = false
|
||||
end
|
||||
end
|
||||
if success then
|
||||
break
|
||||
end
|
||||
unistd.sleep(1)
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function InternetDetector:setSIGUSR()
|
||||
local appName = self.appName:gsub("-", "%%-")
|
||||
local ok, commonDir = pcall(dirent.files, self.commonDir)
|
||||
if ok then
|
||||
for item in commonDir do
|
||||
if item:match("^" .. appName .. ".-%.pid$") 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
|
||||
if stat.stat(self.pidFile) then
|
||||
io.stderr:write(
|
||||
string.format('PID file "%s" already exist. %s already running?\n',
|
||||
self.pidFile, self.appName))
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function InternetDetector:run()
|
||||
local pidValue = unistd.getpid()
|
||||
self:writeValueToFile(self.pidFile, pidValue)
|
||||
if self.enableLogger 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
|
||||
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()(self, "self.")
|
||||
io.stdout:flush()
|
||||
end
|
||||
|
||||
self:writeValueToFile(
|
||||
self.statusFile, self:statusJson(-1, self.serviceConfig.instance))
|
||||
|
||||
self:main()
|
||||
|
||||
self:removeProcessFiles()
|
||||
if self.enableLogger then
|
||||
self:writeLogMessage("info", "stoped")
|
||||
syslog.closelog()
|
||||
end
|
||||
end
|
||||
|
||||
function InternetDetector:noDaemon()
|
||||
if not self:preRun() then
|
||||
return
|
||||
end
|
||||
self:run()
|
||||
end
|
||||
|
||||
function InternetDetector:daemon()
|
||||
if not self:preRun() then
|
||||
return
|
||||
end
|
||||
-- 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:loadUCIConfig("instance", instance) then
|
||||
self:parseHosts()
|
||||
if self.mode == 2 then
|
||||
self.enableLogger = false
|
||||
self.noModules = true
|
||||
end
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
-- Main section
|
||||
|
||||
local function help()
|
||||
return string.format(
|
||||
"Usage: %s service <UCI instance> | nodaemon <UCI instance> | debug <UCI instance> | stop | status | inet-status | uipoll | --help",
|
||||
arg[0]
|
||||
)
|
||||
end
|
||||
|
||||
local helpArgs = { ["-h"] = true, ["--help"] = true, help = true }
|
||||
if arg[1] == "service" then
|
||||
if arg[2] then
|
||||
if InternetDetector:setServiceConfig(arg[2]) then
|
||||
InternetDetector:daemon()
|
||||
else
|
||||
os.exit(126)
|
||||
end
|
||||
else
|
||||
print(help())
|
||||
os.exit(1)
|
||||
end
|
||||
elseif arg[1] == "nodaemon" then
|
||||
if arg[2] then
|
||||
if InternetDetector:setServiceConfig(arg[2]) then
|
||||
InternetDetector:noDaemon()
|
||||
else
|
||||
os.exit(126)
|
||||
end
|
||||
else
|
||||
print(help())
|
||||
os.exit(1)
|
||||
end
|
||||
elseif arg[1] == "debug" then
|
||||
if arg[2] then
|
||||
if InternetDetector:setServiceConfig(arg[2]) then
|
||||
InternetDetector.debug = true
|
||||
InternetDetector:noDaemon()
|
||||
else
|
||||
os.exit(126)
|
||||
end
|
||||
else
|
||||
print(help())
|
||||
os.exit(1)
|
||||
end
|
||||
elseif arg[1] == "stop" then
|
||||
InternetDetector:stop()
|
||||
elseif arg[1] == "status" then
|
||||
print(InternetDetector:status())
|
||||
elseif arg[1] == "inet-status" then
|
||||
print(InternetDetector:inetStatus())
|
||||
elseif arg[1] == "uipoll" then
|
||||
if InternetDetector:status() == "stoped" then
|
||||
os.exit(126)
|
||||
else
|
||||
InternetDetector:setSIGUSR()
|
||||
print(InternetDetector:inetStatus())
|
||||
end
|
||||
elseif helpArgs[arg[1]] then
|
||||
print(help())
|
||||
else
|
||||
print(help())
|
||||
os.exit(1)
|
||||
end
|
||||
|
||||
os.exit(0)
|
@ -0,0 +1,147 @@
|
||||
--[[
|
||||
Dependences:
|
||||
mailsend
|
||||
--]]
|
||||
local unistd = require("posix.unistd")
|
||||
|
||||
local Module = {
|
||||
name = "mod_email",
|
||||
runPrio = 60,
|
||||
config = {
|
||||
debug = false,
|
||||
},
|
||||
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 unistd.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(string.format("--- %s ---\n", self.name))
|
||||
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
|
@ -0,0 +1,137 @@
|
||||
|
||||
local unistd = require("posix.unistd")
|
||||
local dirent = require("posix.dirent")
|
||||
|
||||
local Module = {
|
||||
name = "mod_led_control",
|
||||
runPrio = 10,
|
||||
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,
|
||||
ledAction1 = 2, -- 1: off, 2: on, 3: blink
|
||||
ledAction2 = 1, -- 1: off, 2: on, 3: blink
|
||||
status = nil,
|
||||
_enabled = false,
|
||||
_ledDir = nil,
|
||||
_ledMaxBrightnessFile = nil,
|
||||
_ledBrightnessFile = nil,
|
||||
_ledMaxBrightness = nil,
|
||||
_ledTriggerFile = nil,
|
||||
_counter = 0,
|
||||
}
|
||||
|
||||
function Module:resetLeds()
|
||||
local ok, dir = pcall(dirent.files, self.sysLedsDir)
|
||||
if not ok then
|
||||
return
|
||||
end
|
||||
for led in dir do
|
||||
local brightness = string.format("%s/%s/brightness", self.sysLedsDir, led)
|
||||
if unistd.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.ledAction1 = tonumber(t.led_action_1)
|
||||
self.ledAction2 = tonumber(t.led_action_2)
|
||||
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
|
||||
self._ledTriggerFile = string.format("%s/trigger", self._ledDir)
|
||||
|
||||
if (not unistd.access(self._ledDir, "r") or
|
||||
not unistd.access(self._ledBrightnessFile, "rw") or
|
||||
not unistd.access(self._ledTriggerFile, "rw")) 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:SetTriggerTimer()
|
||||
self.writeValue(self._ledTriggerFile, "timer")
|
||||
end
|
||||
|
||||
function Module:SetTriggerNone()
|
||||
self.writeValue(self._ledTriggerFile, "none")
|
||||
end
|
||||
|
||||
function Module:getCurrentTrigger()
|
||||
local trigger = self.readValue(self._ledTriggerFile)
|
||||
if trigger and trigger:match("%[timer%]") then
|
||||
return 1
|
||||
end
|
||||
end
|
||||
|
||||
function Module:on()
|
||||
self:SetTriggerNone()
|
||||
self.writeValue(self._ledBrightnessFile, self._ledMaxBrightness)
|
||||
end
|
||||
|
||||
function Module:off()
|
||||
self:SetTriggerNone()
|
||||
self.writeValue(self._ledBrightnessFile, 0)
|
||||
end
|
||||
|
||||
function Module:getCurrentState()
|
||||
local state = self.readValue(self._ledBrightnessFile)
|
||||
if state and tonumber(state) > 0 then
|
||||
return tonumber(state)
|
||||
end
|
||||
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 self.ledAction1 == 1 then
|
||||
if self:getCurrentState() or self:getCurrentTrigger() then
|
||||
self:off()
|
||||
end
|
||||
elseif self.ledAction1 == 2 then
|
||||
if not self:getCurrentState() or self:getCurrentTrigger() then
|
||||
self:on()
|
||||
end
|
||||
elseif self.ledAction1 == 3 then
|
||||
if not self:getCurrentTrigger() then
|
||||
self:SetTriggerTimer()
|
||||
end
|
||||
end
|
||||
else
|
||||
if self.ledAction2 == 1 then
|
||||
if self:getCurrentState() or self:getCurrentTrigger() then
|
||||
self:off()
|
||||
end
|
||||
elseif self.ledAction2 == 2 then
|
||||
if not self:getCurrentState() or self:getCurrentTrigger() then
|
||||
self:on()
|
||||
end
|
||||
elseif self.ledAction2 == 3 then
|
||||
if not self:getCurrentTrigger() then
|
||||
self:SetTriggerTimer()
|
||||
end
|
||||
end
|
||||
end
|
||||
self._counter = 0
|
||||
end
|
||||
self._counter = self._counter + timeDiff
|
||||
end
|
||||
|
||||
return Module
|
@ -0,0 +1,86 @@
|
||||
--[[
|
||||
Dependences:
|
||||
modemmanager
|
||||
--]]
|
||||
local unistd = require("posix.unistd")
|
||||
|
||||
local Module = {
|
||||
name = "mod_modem_restart",
|
||||
runPrio = 40,
|
||||
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 unistd.access(self.mmcli, "x") then
|
||||
self.anyBand = false
|
||||
end
|
||||
|
||||
if unistd.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
|
@ -0,0 +1,94 @@
|
||||
|
||||
local unistd = require("posix.unistd")
|
||||
|
||||
local Module = {
|
||||
name = "mod_network_restart",
|
||||
runPrio = 30,
|
||||
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 unistd.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()
|
||||
unistd.sleep(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
|
@ -0,0 +1,405 @@
|
||||
|
||||
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,
|
||||
},
|
||||
},
|
||||
syslog = function(level, msg) return true end,
|
||||
writeValue = function(filePath, str) return false end,
|
||||
readValue = function(filePath) return nil end,
|
||||
port = 53,
|
||||
runInterval = 600,
|
||||
runIntervalFailed = 60,
|
||||
timeout = 3,
|
||||
reqAttempts = 3,
|
||||
providers = {
|
||||
opendns1 = {
|
||||
name = "opendns1", host = "myip.opendns.com",
|
||||
server = "208.67.222.222", server6 = "2620:119:35::35",
|
||||
port = 53, queryType = "A", queryType6 = "AAAA",
|
||||
},
|
||||
opendns2 = {
|
||||
name = "opendns2", host = "myip.opendns.com",
|
||||
server = "208.67.220.220", server6 = "2620:119:35::35",
|
||||
port = 53, queryType = "A", queryType6 = "AAAA",
|
||||
},
|
||||
opendns3 = {
|
||||
name = "opendns3", host = "myip.opendns.com",
|
||||
server = "208.67.222.220", server6 = "2620:119:35::35",
|
||||
port = 53, queryType = "A", queryType6 = "AAAA",
|
||||
},
|
||||
opendns4 = {
|
||||
name = "opendns4", host = "myip.opendns.com",
|
||||
server = "208.67.220.222", server6 = "2620:119:35::35",
|
||||
port = 53, queryType = "A", queryType6 = "AAAA",
|
||||
},
|
||||
akamai = {
|
||||
name = "akamai", host = "whoami.akamai.net",
|
||||
server = "ns1-1.akamaitech.net", server6 = "ns1-1.akamaitech.net",
|
||||
port = 53, queryType = "A", queryType6 = "AAAA",
|
||||
},
|
||||
google = {
|
||||
name = "google", host = "o-o.myaddr.l.google.com",
|
||||
server = "ns1.google.com", server6 = "ns1.google.com",
|
||||
port = 53, queryType = "TXT", queryType6 = "TXT",
|
||||
},
|
||||
},
|
||||
ipScript = "",
|
||||
enableIpScript = false,
|
||||
status = nil,
|
||||
_provider = nil,
|
||||
_qtype = false,
|
||||
_currentIp = nil,
|
||||
_enabled = false,
|
||||
_counter = 0,
|
||||
_interval = 600,
|
||||
_DNSPacket = 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
|
||||
|
||||
if self.config.debug then
|
||||
io.stdout:write(string.format("--- %s ---\n", self.name))
|
||||
io.stdout:flush()
|
||||
end
|
||||
|
||||
local saTable, errMsg, errNum = socket.getaddrinfo(server, port)
|
||||
|
||||
if not saTable then
|
||||
if self.config.debug then
|
||||
io.stdout:write(string.format(
|
||||
"GETADDRINFO ERROR: %s, %s\n", errMsg, errNum))
|
||||
end
|
||||
else
|
||||
local family = saTable[1].family
|
||||
|
||||
if family then
|
||||
local sock, errMsg, errNum = socket.socket(family, socket.SOCK_DGRAM, 0)
|
||||
|
||||
if not sock then
|
||||
if self.config.debug then
|
||||
io.stdout:write(string.format(
|
||||
"SOCKET ERROR: %s, %s\n", errMsg, errNum))
|
||||
end
|
||||
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
|
||||
if self.config.debug then
|
||||
io.stdout:write(string.format(
|
||||
"SOCKET ERROR: %s, %s\n", errMsg, errNum))
|
||||
end
|
||||
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
|
||||
elseif self.config.debug then
|
||||
io.stdout:write(string.format(
|
||||
"SOCKET RECV ERROR: %s, %s\n", tostring(resp), tostring(errNum)))
|
||||
end
|
||||
elseif self.config.debug then
|
||||
io.stdout:write(string.format(
|
||||
"SOCKET SEND ERROR: %s, %s\n", tostring(errMsg), tostring(errNum)))
|
||||
end
|
||||
|
||||
if self.config.debug then
|
||||
io.stdout:write(string.format(
|
||||
"--- UDP ---\ntime = %s\nconnection_timeout = %s\niface = %s\nserver = %s:%s\nsockname = %s:%s\nsuccess = %s\n",
|
||||
os.time(),
|
||||
self.timeout,
|
||||
tostring(self.config.serviceConfig.iface),
|
||||
server,
|
||||
tostring(port),
|
||||
tostring(response.addr),
|
||||
tostring(response.port),
|
||||
tostring(success))
|
||||
)
|
||||
io.stdout:flush()
|
||||
end
|
||||
|
||||
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)
|
||||
|
||||
local questionSectionStarts = 25
|
||||
local questionParts = self:parseParts(message, questionSectionStarts, {})
|
||||
local qtypeStarts = questionSectionStarts + (#table.concat(questionParts)) + (#questionParts * 2) + 1
|
||||
local qclassStarts = qtypeStarts + 4
|
||||
|
||||
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-f0-9.:]+$") then
|
||||
retTable[#retTable + 1] = RDDATA_decoded
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return retTable
|
||||
end
|
||||
|
||||
function Module:resolveIP()
|
||||
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 then
|
||||
local retTable = self:decodeMessage(response)
|
||||
if #retTable > 0 then
|
||||
res = table.concat(retTable, ", ")
|
||||
end
|
||||
else
|
||||
self.syslog("err", string.format(
|
||||
"%s: DNS error when requesting an IP address", self.name))
|
||||
end
|
||||
|
||||
return res
|
||||
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 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 then
|
||||
self.enableIpScript = (tonumber(t.enable_ip_script) ~= 0)
|
||||
end
|
||||
end
|
||||
self._qtype = (tonumber(t.qtype) ~= 0)
|
||||
self._currentIp = nil
|
||||
self._DNSPacket = nil
|
||||
self._interval = self.runInterval
|
||||
self._enabled = true
|
||||
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._interval or currentStatus ~= lastStatus then
|
||||
|
||||
local ip = self:resolveIP()
|
||||
|
||||
if not ip then
|
||||
ip = ""
|
||||
self._interval = self.runIntervalFailed
|
||||
else
|
||||
self._interval = self.runInterval
|
||||
end
|
||||
|
||||
if ip ~= self._currentIp then
|
||||
self.status = ip
|
||||
self.syslog(
|
||||
"notice",
|
||||
string.format("%s: public IP address %s", self.name, (ip == "") and "Undefined" or ip)
|
||||
)
|
||||
if self._counter > 0 then
|
||||
self:runIpScript()
|
||||
end
|
||||
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
|
||||
self._interval = self.runInterval
|
||||
end
|
||||
|
||||
self._counter = self._counter + timeDiff
|
||||
end
|
||||
|
||||
return Module
|
@ -0,0 +1,47 @@
|
||||
|
||||
local unistd = require("posix.unistd")
|
||||
|
||||
local Module = {
|
||||
name = "mod_reboot",
|
||||
runPrio = 20,
|
||||
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
|
||||
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)
|
||||
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
|
@ -0,0 +1,65 @@
|
||||
|
||||
local unistd = require("posix.unistd")
|
||||
|
||||
local Module = {
|
||||
name = "mod_user_scripts",
|
||||
runPrio = 70,
|
||||
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 unistd.access(scriptPath, "r") then
|
||||
os.execute(string.format('/bin/sh "%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.%s", self.config.configDir, self.config.serviceConfig.instance)
|
||||
self.downScript = string.format(
|
||||
"%s/down-script.%s", self.config.configDir, self.config.serviceConfig.instance)
|
||||
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,15 @@
|
||||
#
|
||||
# (с) 2024 gSpot (https://github.com/gSpotx2f/luci-app-internet-detector)
|
||||
#
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_VERSION:=1.2-0
|
||||
LUCI_TITLE:=LuCI support for internet-detector
|
||||
LUCI_DEPENDS:=+internet-detector
|
||||
LUCI_PKGARCH:=all
|
||||
|
||||
#include ../../luci.mk
|
||||
include $(TOPDIR)/feeds/luci/luci.mk
|
||||
|
||||
# call BuildPackage - OpenWrt buildroot signature
|
@ -0,0 +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: #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;
|
||||
}
|
||||
`));
|
||||
|
||||
return baseclass.extend({
|
||||
title : _('Internet'),
|
||||
appName : 'internet-detector',
|
||||
execPath : '/usr/bin/internet-detector',
|
||||
currentAppMode : null,
|
||||
inetStatus : null,
|
||||
|
||||
callUIPoll: rpc.declare({
|
||||
object: 'luci.internet-detector',
|
||||
method: 'UIPoll',
|
||||
expect: { '': {} }
|
||||
}),
|
||||
|
||||
getUIPoll() {
|
||||
return this.callUIPoll().then(data => {
|
||||
return data;
|
||||
});
|
||||
},
|
||||
|
||||
inetStatusFromJson(res) {
|
||||
let inetStatData = null;
|
||||
if(res.code === 0) {
|
||||
try {
|
||||
inetStatData = JSON.parse(res.stdout.trim());
|
||||
} catch(e) {};
|
||||
};
|
||||
return inetStatData;
|
||||
},
|
||||
|
||||
async load() {
|
||||
if(!this.currentAppMode) {
|
||||
await uci.load(this.appName).then(data => {
|
||||
this.currentAppMode = uci.get(this.appName, 'config', 'mode');
|
||||
}).catch(e => {});
|
||||
};
|
||||
|
||||
if(this.currentAppMode === '2') {
|
||||
return this.getUIPoll();
|
||||
}
|
||||
else if(this.currentAppMode === '1') {
|
||||
return L.resolveDefault(fs.exec(this.execPath, [ 'inet-status' ]), null);
|
||||
};
|
||||
},
|
||||
|
||||
render(data) {
|
||||
if(this.currentAppMode === '0') {
|
||||
return;
|
||||
}
|
||||
else if(this.currentAppMode === '1' && data) {
|
||||
data = this.inetStatusFromJson(data);
|
||||
};
|
||||
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';
|
||||
};
|
||||
|
||||
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)
|
||||
)
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
return E('div', {
|
||||
'class': 'cbi-section',
|
||||
'style': 'margin-bottom:1em',
|
||||
}, inetStatusArea);
|
||||
},
|
||||
});
|
@ -0,0 +1,499 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Project-Id-Version: \n"
|
||||
"POT-Creation-Date: \n"
|
||||
"PO-Revision-Date: \n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Generator: Poedit 2.4.2\n"
|
||||
"Last-Translator: \n"
|
||||
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? "
|
||||
"1 : 2);\n"
|
||||
"Language: ru\n"
|
||||
|
||||
msgid "<abbr title=\"Light Emitting Diode\">LED</abbr> Name"
|
||||
msgstr "Имя <abbr title=\"Светодиод\">LED</abbr>"
|
||||
|
||||
msgid "<abbr title=\"Light Emitting Diode\">LED</abbr> control"
|
||||
msgstr "Управление <abbr title=\"Светодиод\">LED</abbr>"
|
||||
|
||||
msgid ""
|
||||
"<abbr title=\"Light Emitting Diode\">LED</abbr> indicates the Internet status."
|
||||
msgstr "<abbr title=\"Светодиод\">LED</abbr> отображает статус Интернет."
|
||||
|
||||
msgid "Action when connected"
|
||||
msgstr "Действие при подключении"
|
||||
|
||||
msgid "Action when disconnected"
|
||||
msgstr "Действие при отключении"
|
||||
|
||||
msgid "Add instance"
|
||||
msgstr "Добавить экземпляр"
|
||||
|
||||
msgid "Alive interval"
|
||||
msgstr "Интервал при подключении"
|
||||
|
||||
msgid "Alive period"
|
||||
msgstr "Период после подключения"
|
||||
|
||||
msgid ""
|
||||
"An email will be sent when the internet connection is restored after being "
|
||||
"disconnected."
|
||||
msgstr "Сообщение будет отправлено при восстановлении соединения с Интернет после отключения."
|
||||
|
||||
msgid "An error has occurred"
|
||||
msgstr "Произошла ошибка"
|
||||
|
||||
msgid "Big: 248 bytes"
|
||||
msgstr "Большой: 248 байт"
|
||||
|
||||
msgid "Blink"
|
||||
msgstr "Мигание"
|
||||
|
||||
msgid "Check type"
|
||||
msgstr "Тип проверки"
|
||||
|
||||
msgid "Checking Internet availability."
|
||||
msgstr "Проверка доступности Интернет."
|
||||
|
||||
msgid "Checking the real public IP address."
|
||||
msgstr "Проверка реального публичного IP адреса."
|
||||
|
||||
msgid "Command failed"
|
||||
msgstr "Команда не выполнена"
|
||||
|
||||
msgid "Connected"
|
||||
msgstr "Подключен"
|
||||
|
||||
msgid "Connection attempts"
|
||||
msgstr "Попытки подключения"
|
||||
|
||||
msgid "Connection timeout"
|
||||
msgstr "Таймаут соединения"
|
||||
|
||||
msgid "Contents have been saved."
|
||||
msgstr "Содержимое сохранено."
|
||||
|
||||
msgid "Dead interval"
|
||||
msgstr "Интервал при отключении"
|
||||
|
||||
msgid "Dead period"
|
||||
msgstr "Период после отключения"
|
||||
|
||||
msgid "Default port value for TCP connections."
|
||||
msgstr "Стандартное значение порта для TCP-подключений."
|
||||
|
||||
msgid "Device will be rebooted when the Internet is disconnected."
|
||||
msgstr "Устройство будет перезагружено при отключении Интернет."
|
||||
|
||||
msgid "Disable forced reboot"
|
||||
msgstr "Отключить принудительную перезагрузку"
|
||||
|
||||
msgid "Disabled"
|
||||
msgstr "Отключен"
|
||||
|
||||
msgid "Disabled: detector is completely off."
|
||||
msgstr "Отключен: детектор полностью выключен."
|
||||
|
||||
msgid "Disconnected"
|
||||
msgstr "Отключен"
|
||||
|
||||
msgid "Dismiss"
|
||||
msgstr "Закрыть"
|
||||
|
||||
msgid "DNS query type"
|
||||
msgstr "Тип DNS-запроса"
|
||||
|
||||
msgid "DNS provider"
|
||||
msgstr "DNS провайдер"
|
||||
|
||||
msgid "Edit"
|
||||
msgstr "Изменить"
|
||||
|
||||
msgid "Edit down-script"
|
||||
msgstr "Изменить down-script"
|
||||
|
||||
msgid "Edit public-ip-script"
|
||||
msgstr "Изменить public-ip-script"
|
||||
|
||||
msgid "Edit up-script"
|
||||
msgstr "Изменить up-script"
|
||||
|
||||
msgid "Email notification"
|
||||
msgstr "Уведомление по email"
|
||||
|
||||
msgid "Email address of the recipient."
|
||||
msgstr "Email-адрес получателя."
|
||||
|
||||
msgid "Email address of the sender."
|
||||
msgstr "Email-адрес отправителя."
|
||||
|
||||
msgid "Enable"
|
||||
msgstr "Включить"
|
||||
|
||||
msgid "Enable logging"
|
||||
msgstr "Запись событий в лог"
|
||||
|
||||
msgid "Enable public-ip-script"
|
||||
msgstr "Включить public-ip-script"
|
||||
|
||||
msgid "Enabled"
|
||||
msgstr "Включен"
|
||||
|
||||
msgid "Expecting:"
|
||||
msgstr "Ожидается:"
|
||||
|
||||
msgid "Public IP"
|
||||
msgstr "Публичный IP"
|
||||
|
||||
msgid "Public IP address"
|
||||
msgstr "Публичный IP адрес"
|
||||
|
||||
msgid "Failed to get %s init status: %s"
|
||||
msgstr "Не удалось получить статус инициализации %s: %s"
|
||||
|
||||
msgid "Forced reboot delay"
|
||||
msgstr "Задержка принудительной перезагрузки"
|
||||
|
||||
msgid "Host alias"
|
||||
msgstr "Псевдоним хоста"
|
||||
|
||||
msgid "Host availability check type."
|
||||
msgstr "Тип проверки доступности хоста."
|
||||
|
||||
msgid "Host identifier in messages. If not specified, hostname will be used."
|
||||
msgstr "Идентификатор хоста в сообщениях. Если не указан, будет использовано имя хоста."
|
||||
|
||||
msgid "Hostname/IP address of the SMTP server."
|
||||
msgstr "Имя хоста/IP-адрес SMTP-сервера."
|
||||
|
||||
msgid "Hosts"
|
||||
msgstr "Хосты"
|
||||
|
||||
msgid "Hosts polling interval when the Internet is down."
|
||||
msgstr "Интервал опроса хостов если Интернет не доступен."
|
||||
|
||||
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."
|
||||
msgstr ""
|
||||
"Хосты для проверки доступности Интернет. Хосты опрашиваются (в порядке "
|
||||
"списка) до тех пор, пока хотя бы один из них не ответит."
|
||||
|
||||
msgid "Huge: 1492 bytes"
|
||||
msgstr "Огромный: 1492 байта"
|
||||
|
||||
msgid "ICMP-echo request (ping)"
|
||||
msgstr "Запрос ICMP-echo (ping)"
|
||||
|
||||
msgid "ICMP packet data size"
|
||||
msgstr "Размер данных ICMP-пакета"
|
||||
|
||||
msgid "Interface"
|
||||
msgstr "Интерфейс"
|
||||
|
||||
msgid "Internet"
|
||||
msgstr "Интернет"
|
||||
|
||||
msgid "Internet Detector"
|
||||
msgstr "Интернет-детектор"
|
||||
|
||||
msgid "Internet detector mode"
|
||||
msgstr "Режим интернет-детектора"
|
||||
|
||||
msgid "Internet status"
|
||||
msgstr "Статус Интернет"
|
||||
|
||||
msgid "Interval between IP address requests."
|
||||
msgstr "Интервал между запросами IP адреса."
|
||||
|
||||
msgid "Jumbo: 9000 bytes"
|
||||
msgstr "Гигантский: 9000 байт"
|
||||
|
||||
msgid "LED control"
|
||||
msgstr "Управление LED"
|
||||
|
||||
msgid "Loading"
|
||||
msgstr "Загрузка"
|
||||
|
||||
msgid ""
|
||||
"Longest period of time after connecting to Internet before \"up-script\" "
|
||||
"runs."
|
||||
msgstr ""
|
||||
"Максимальный промежуток времени после подключения к Интернет перед запуском "
|
||||
"\"up-script\"."
|
||||
|
||||
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 "Mailsend is not available..."
|
||||
msgstr "Mailsend недоступен..."
|
||||
|
||||
msgid "Main settings"
|
||||
msgstr "Основные настройки"
|
||||
|
||||
msgid "Maximum number of attempts to connect to each host."
|
||||
msgstr "Максимальное количество попыток подключения к каждому хосту."
|
||||
|
||||
msgid ""
|
||||
"Maximum number of network restart 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."
|
||||
msgstr "Модем будет перезапущен при отключении Интернет."
|
||||
|
||||
msgid "ModemManager is not available..."
|
||||
msgstr "ModemManager недоступен..."
|
||||
|
||||
msgid ""
|
||||
"ModemManger interface. If specified, it will be restarted after restarting "
|
||||
"ModemManager."
|
||||
msgstr ""
|
||||
"Интерфейс ModemManager. Если задан, то будет перезапущен после перезапуска "
|
||||
"ModemManger."
|
||||
|
||||
msgid ""
|
||||
"Network interface for Internet access. If not specified, the default "
|
||||
"interface is used."
|
||||
msgstr ""
|
||||
"Сетевой интерфейс для доступа в Интернет. Если не указан, используется "
|
||||
"интерфейс по умолчанию."
|
||||
|
||||
msgid ""
|
||||
"Network 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 "No <abbr title=\"Light Emitting Diode\">LED</abbr>s available..."
|
||||
msgstr "Нет доступных <abbr title=\"Светодиод\">LED</abbr>..."
|
||||
|
||||
msgid "Off"
|
||||
msgstr "Выключить"
|
||||
|
||||
msgid "On"
|
||||
msgstr "Включить"
|
||||
|
||||
msgid "One of the following:"
|
||||
msgstr "Одно из следующих значений:"
|
||||
|
||||
msgid "Password"
|
||||
msgstr "Пароль"
|
||||
|
||||
msgid "Password for SMTP authentication."
|
||||
msgstr "Пароль для SMTP-аутентификации."
|
||||
|
||||
msgid "Polling interval"
|
||||
msgstr "Интервал опроса"
|
||||
|
||||
msgid "Reboot device"
|
||||
msgstr "Перезагрузка устройства"
|
||||
|
||||
msgid "Recipient"
|
||||
msgstr "Получатель"
|
||||
|
||||
msgid "Restart"
|
||||
msgstr "Перезапуск"
|
||||
|
||||
msgid "Restart attempts"
|
||||
msgstr "Попытки перезапуска"
|
||||
|
||||
msgid "Restart modem"
|
||||
msgstr "Перезапуск модема"
|
||||
|
||||
msgid "Restart network"
|
||||
msgstr "Перезапуск сети"
|
||||
|
||||
msgid "Restart service"
|
||||
msgstr "Перезапуск службы"
|
||||
|
||||
msgid "Restart timeout"
|
||||
msgstr "Таймаут перезапуска"
|
||||
|
||||
msgid "Run service at startup"
|
||||
msgstr "Запуск службы при старте"
|
||||
|
||||
msgid "Running"
|
||||
msgstr "Выполняется"
|
||||
|
||||
msgid "SMTP server"
|
||||
msgstr "SMTP-сервер"
|
||||
|
||||
msgid "SMTP server port"
|
||||
msgstr "Порт SMTP-сервера"
|
||||
|
||||
msgid "SSL: SMTP over SSL."
|
||||
msgstr "SSL: SMTP поверх SSL."
|
||||
|
||||
msgid "Save"
|
||||
msgstr "Сохранить"
|
||||
|
||||
msgid "Security"
|
||||
msgstr "Безопасность"
|
||||
|
||||
msgid "Sender"
|
||||
msgstr "Отправитель"
|
||||
|
||||
msgid "Server response timeout"
|
||||
msgstr "Таймаут ответа сервера"
|
||||
|
||||
msgid "Service"
|
||||
msgstr "Служба"
|
||||
|
||||
msgid "Service action failed \"%s %s\": %s"
|
||||
msgstr "Не удалось выполнить действие службы \"%s %s\": %s"
|
||||
|
||||
msgid "Service configuration"
|
||||
msgstr "Конфигурация службы"
|
||||
|
||||
msgid "Service for determining the public IP address through DNS."
|
||||
msgstr "Сервис для определения публичного IP адреса через DNS."
|
||||
|
||||
msgid "Service: detector always runs as a system service."
|
||||
msgstr "Служба: детектор работает постоянно, как системная служба."
|
||||
|
||||
msgid "Service instances"
|
||||
msgstr "Экземпляры службы"
|
||||
|
||||
msgid "Set the modem to be allowed to use any band."
|
||||
msgstr "Разрешить модему использование любой частоты."
|
||||
|
||||
msgid "Shell commands that run when connected to the Internet."
|
||||
msgstr "Команды shell выполняемые при подключении к Интернет."
|
||||
|
||||
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 выполняемые при подключении или отключении Интернет."
|
||||
|
||||
msgid "Shell commands to run when disconnected from the Internet."
|
||||
msgstr "Команды shell выполняемые при отключении от Интернет."
|
||||
|
||||
msgid "Small: 1 byte"
|
||||
msgstr "Маленький: 1 байт"
|
||||
|
||||
msgid "Standard: 56 bytes"
|
||||
msgstr "Стандартный: 56 байт"
|
||||
|
||||
msgid "Stopped"
|
||||
msgstr "Остановлена"
|
||||
|
||||
msgid "TCP port"
|
||||
msgstr "TCP-порт"
|
||||
|
||||
msgid "TCP port connection"
|
||||
msgstr "Подключение к TCP-порту"
|
||||
|
||||
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 "Type a time string"
|
||||
msgstr "Введите строку времени"
|
||||
|
||||
msgid "Unable to read the contents"
|
||||
msgstr "Невозможно прочитать содержимое"
|
||||
|
||||
msgid "Unable to save the contents"
|
||||
msgstr "Невозможно сохранить содержимое"
|
||||
|
||||
msgid "Undefined"
|
||||
msgstr "Неопределён"
|
||||
|
||||
msgid "Unlock modem bands"
|
||||
msgstr "Освободить частоты модема"
|
||||
|
||||
msgid "User"
|
||||
msgstr "Пользователь"
|
||||
|
||||
msgid "User scripts"
|
||||
msgstr "Пользовательские скрипты"
|
||||
|
||||
msgid "Username for SMTP authentication."
|
||||
msgstr "Имя пользователя для SMTP-аутентификации."
|
||||
|
||||
msgid "Waiting for a reboot to complete before performing a forced reboot."
|
||||
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 "Windows: 32 bytes"
|
||||
msgstr "Windows: 32 байта"
|
||||
|
||||
msgid "Write messages to the system log."
|
||||
msgstr "Записывать сообщения в системный журнал."
|
||||
|
||||
msgid "down-script"
|
||||
msgstr "down-script"
|
||||
|
||||
msgid "hour"
|
||||
msgstr "час"
|
||||
|
||||
msgid "hours"
|
||||
msgstr "часы"
|
||||
|
||||
msgid "min"
|
||||
msgstr "мин"
|
||||
|
||||
msgid "minutes"
|
||||
msgstr "минуты"
|
||||
|
||||
msgid "sec"
|
||||
msgstr "сек"
|
||||
|
||||
msgid "seconds"
|
||||
msgstr "секунды"
|
||||
|
||||
msgid "up-script"
|
||||
msgstr "up-script"
|
@ -0,0 +1,465 @@
|
||||
msgid ""
|
||||
msgstr "Content-Type: text/plain; charset=UTF-8"
|
||||
|
||||
msgid "<abbr title=\"Light Emitting Diode\">LED</abbr> Name"
|
||||
msgstr ""
|
||||
|
||||
msgid "<abbr title=\"Light Emitting Diode\">LED</abbr> control"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"<abbr title=\"Light Emitting Diode\">LED</abbr> indicates the Internet status."
|
||||
msgstr ""
|
||||
|
||||
msgid "Action when connected"
|
||||
msgstr ""
|
||||
|
||||
msgid "Action when disconnected"
|
||||
msgstr ""
|
||||
|
||||
msgid "Add instance"
|
||||
msgstr ""
|
||||
|
||||
msgid "Alive interval"
|
||||
msgstr ""
|
||||
|
||||
msgid "Alive period"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"An email will be sent when the internet connection is restored after being "
|
||||
"disconnected."
|
||||
msgstr ""
|
||||
|
||||
msgid "An error has occurred"
|
||||
msgstr ""
|
||||
|
||||
msgid "Big: 248 bytes"
|
||||
msgstr ""
|
||||
|
||||
msgid "Blink"
|
||||
msgstr ""
|
||||
|
||||
msgid "Check type"
|
||||
msgstr ""
|
||||
|
||||
msgid "Checking Internet availability."
|
||||
msgstr ""
|
||||
|
||||
msgid "Checking the real public IP address."
|
||||
msgstr ""
|
||||
|
||||
msgid "Command failed"
|
||||
msgstr ""
|
||||
|
||||
msgid "Connected"
|
||||
msgstr ""
|
||||
|
||||
msgid "Connection attempts"
|
||||
msgstr ""
|
||||
|
||||
msgid "Connection timeout"
|
||||
msgstr ""
|
||||
|
||||
msgid "Contents have been saved."
|
||||
msgstr ""
|
||||
|
||||
msgid "Dead interval"
|
||||
msgstr ""
|
||||
|
||||
msgid "Dead period"
|
||||
msgstr ""
|
||||
|
||||
msgid "Default port value for TCP connections."
|
||||
msgstr ""
|
||||
|
||||
msgid "Device will be rebooted when the Internet is disconnected."
|
||||
msgstr ""
|
||||
|
||||
msgid "Disable forced reboot"
|
||||
msgstr ""
|
||||
|
||||
msgid "Disabled"
|
||||
msgstr ""
|
||||
|
||||
msgid "Disabled: detector is completely off."
|
||||
msgstr ""
|
||||
|
||||
msgid "Disconnected"
|
||||
msgstr ""
|
||||
|
||||
msgid "Dismiss"
|
||||
msgstr ""
|
||||
|
||||
msgid "DNS query type"
|
||||
msgstr ""
|
||||
|
||||
msgid "DNS provider"
|
||||
msgstr ""
|
||||
|
||||
msgid "Edit"
|
||||
msgstr ""
|
||||
|
||||
msgid "Edit down-script"
|
||||
msgstr ""
|
||||
|
||||
msgid "Edit public-ip-script"
|
||||
msgstr ""
|
||||
|
||||
msgid "Edit up-script"
|
||||
msgstr ""
|
||||
|
||||
msgid "Email notification"
|
||||
msgstr ""
|
||||
|
||||
msgid "Email address of the recipient."
|
||||
msgstr ""
|
||||
|
||||
msgid "Email address of the sender."
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable logging"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable public-ip-script"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enabled"
|
||||
msgstr ""
|
||||
|
||||
msgid "Expecting:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Public IP"
|
||||
msgstr ""
|
||||
|
||||
msgid "Public IP address"
|
||||
msgstr ""
|
||||
|
||||
msgid "Failed to get %s init status: %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Forced reboot delay"
|
||||
msgstr ""
|
||||
|
||||
msgid "Host alias"
|
||||
msgstr ""
|
||||
|
||||
msgid "Host availability check type."
|
||||
msgstr ""
|
||||
|
||||
msgid "Host identifier in messages. If not specified, hostname will be used."
|
||||
msgstr ""
|
||||
|
||||
msgid "Hostname/IP address of the SMTP server."
|
||||
msgstr ""
|
||||
|
||||
msgid "Hosts"
|
||||
msgstr ""
|
||||
|
||||
msgid "Hosts polling interval when the Internet is down."
|
||||
msgstr ""
|
||||
|
||||
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."
|
||||
msgstr ""
|
||||
|
||||
msgid "Huge: 1492 bytes"
|
||||
msgstr ""
|
||||
|
||||
msgid "ICMP-echo request (ping)"
|
||||
msgstr ""
|
||||
|
||||
msgid "ICMP packet data size"
|
||||
msgstr ""
|
||||
|
||||
msgid "Interface"
|
||||
msgstr ""
|
||||
|
||||
msgid "Internet"
|
||||
msgstr ""
|
||||
|
||||
msgid "Internet Detector"
|
||||
msgstr ""
|
||||
|
||||
msgid "Internet detector mode"
|
||||
msgstr ""
|
||||
|
||||
msgid "Internet status"
|
||||
msgstr ""
|
||||
|
||||
msgid "Interval between IP address requests."
|
||||
msgstr ""
|
||||
|
||||
msgid "Jumbo: 9000 bytes"
|
||||
msgstr ""
|
||||
|
||||
msgid "LED control"
|
||||
msgstr ""
|
||||
|
||||
msgid "Loading"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Longest period of time after connecting to Internet before \"up-script\" "
|
||||
"runs."
|
||||
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."
|
||||
msgstr ""
|
||||
|
||||
msgid "Mailsend is not available..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Main settings"
|
||||
msgstr ""
|
||||
|
||||
msgid "Maximum number of attempts to connect to each host."
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Maximum number of network restart 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."
|
||||
msgstr ""
|
||||
|
||||
msgid "ModemManager is not available..."
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"ModemManger interface. If specified, it will be restarted after restarting "
|
||||
"ModemManager."
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Network interface for Internet access. If not specified, the default "
|
||||
"interface is used."
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Network 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 "No <abbr title=\"Light Emitting Diode\">LED</abbr>s available..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Off"
|
||||
msgstr ""
|
||||
|
||||
msgid "On"
|
||||
msgstr ""
|
||||
|
||||
msgid "One of the following:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Password"
|
||||
msgstr ""
|
||||
|
||||
msgid "Password for SMTP authentication."
|
||||
msgstr ""
|
||||
|
||||
msgid "Polling interval"
|
||||
msgstr ""
|
||||
|
||||
msgid "Reboot device"
|
||||
msgstr ""
|
||||
|
||||
msgid "Recipient"
|
||||
msgstr ""
|
||||
|
||||
msgid "Restart"
|
||||
msgstr ""
|
||||
|
||||
msgid "Restart attempts"
|
||||
msgstr ""
|
||||
|
||||
msgid "Restart modem"
|
||||
msgstr ""
|
||||
|
||||
msgid "Restart network"
|
||||
msgstr ""
|
||||
|
||||
msgid "Restart service"
|
||||
msgstr ""
|
||||
|
||||
msgid "Restart timeout"
|
||||
msgstr ""
|
||||
|
||||
msgid "Run service at startup"
|
||||
msgstr ""
|
||||
|
||||
msgid "Running"
|
||||
msgstr ""
|
||||
|
||||
msgid "SMTP server"
|
||||
msgstr ""
|
||||
|
||||
msgid "SMTP server port"
|
||||
msgstr ""
|
||||
|
||||
msgid "SSL: SMTP over SSL."
|
||||
msgstr ""
|
||||
|
||||
msgid "Save"
|
||||
msgstr ""
|
||||
|
||||
msgid "Security"
|
||||
msgstr ""
|
||||
|
||||
msgid "Sender"
|
||||
msgstr ""
|
||||
|
||||
msgid "Server response timeout"
|
||||
msgstr ""
|
||||
|
||||
msgid "Service"
|
||||
msgstr ""
|
||||
|
||||
msgid "Service action failed \"%s %s\": %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Service configuration"
|
||||
msgstr ""
|
||||
|
||||
msgid "Service for determining the public IP address through DNS."
|
||||
msgstr ""
|
||||
|
||||
msgid "Service: detector always runs as a system service."
|
||||
msgstr ""
|
||||
|
||||
msgid "Service instances"
|
||||
msgstr ""
|
||||
|
||||
msgid "Set the modem to be allowed to use any band."
|
||||
msgstr ""
|
||||
|
||||
msgid "Shell commands that run when connected to the Internet."
|
||||
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 ""
|
||||
|
||||
msgid "Shell commands to run when disconnected from the Internet."
|
||||
msgstr ""
|
||||
|
||||
msgid "Small: 1 byte"
|
||||
msgstr ""
|
||||
|
||||
msgid "Standard: 56 bytes"
|
||||
msgstr ""
|
||||
|
||||
msgid "Stopped"
|
||||
msgstr ""
|
||||
|
||||
msgid "TCP port"
|
||||
msgstr ""
|
||||
|
||||
msgid "TCP port connection"
|
||||
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."
|
||||
msgstr ""
|
||||
|
||||
msgid "Type a time string"
|
||||
msgstr ""
|
||||
|
||||
msgid "Unable to read the contents"
|
||||
msgstr ""
|
||||
|
||||
msgid "Unable to save the contents"
|
||||
msgstr ""
|
||||
|
||||
msgid "Undefined"
|
||||
msgstr ""
|
||||
|
||||
msgid "Unlock modem bands"
|
||||
msgstr ""
|
||||
|
||||
msgid "User"
|
||||
msgstr ""
|
||||
|
||||
msgid "User scripts"
|
||||
msgstr ""
|
||||
|
||||
msgid "Username for SMTP authentication."
|
||||
msgstr ""
|
||||
|
||||
msgid "Waiting for a reboot to complete before performing a forced reboot."
|
||||
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 "Windows: 32 bytes"
|
||||
msgstr ""
|
||||
|
||||
msgid "Write messages to the system log."
|
||||
msgstr ""
|
||||
|
||||
msgid "down-script"
|
||||
msgstr ""
|
||||
|
||||
msgid "hour"
|
||||
msgstr ""
|
||||
|
||||
msgid "hours"
|
||||
msgstr ""
|
||||
|
||||
msgid "min"
|
||||
msgstr ""
|
||||
|
||||
msgid "minutes"
|
||||
msgstr ""
|
||||
|
||||
msgid "sec"
|
||||
msgstr ""
|
||||
|
||||
msgid "seconds"
|
||||
msgstr ""
|
||||
|
||||
msgid "up-script"
|
||||
msgstr ""
|
@ -0,0 +1,46 @@
|
||||
#!/bin/sh
|
||||
|
||||
. /lib/functions.sh
|
||||
. /usr/share/libubox/jshn.sh
|
||||
|
||||
readonly ID_EXEC="/usr/bin/internet-detector"
|
||||
|
||||
run_instance() {
|
||||
config_get enabled "$1" enabled "0"
|
||||
if [ $enabled = "1" ]; then
|
||||
$ID_EXEC service "$1" > /dev/null 2>&1
|
||||
fi
|
||||
}
|
||||
|
||||
start_ui_instances() {
|
||||
config_load internet-detector
|
||||
config_get mode "config" mode "0"
|
||||
if [ $mode = "2" ]; then
|
||||
config_foreach run_instance "instance"
|
||||
fi
|
||||
}
|
||||
|
||||
ui_poll() {
|
||||
$ID_EXEC uipoll
|
||||
if [ $? -eq 126 ]; then
|
||||
start_ui_instances
|
||||
$ID_EXEC inet-status
|
||||
fi
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
list)
|
||||
json_init
|
||||
json_add_object "UIPoll"
|
||||
json_close_object
|
||||
json_dump
|
||||
json_cleanup
|
||||
;;
|
||||
call)
|
||||
case "$2" in
|
||||
UIPoll)
|
||||
ui_poll
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
esac
|
@ -0,0 +1,17 @@
|
||||
{
|
||||
"admin/services/internet-detector": {
|
||||
"title": "Internet Detector",
|
||||
"order": 80,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "internet-detector"
|
||||
},
|
||||
"depends": {
|
||||
"acl": [ "luci-app-internet-detector" ],
|
||||
"fs": {
|
||||
"/usr/bin/internet-detector": "executable"
|
||||
},
|
||||
"uci": { "internet-detector": true }
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
{
|
||||
"luci-app-internet-detector": {
|
||||
"description": "Grant access to internet-detector procedures",
|
||||
"read": {
|
||||
"file": {
|
||||
"/sys/class/leds": [ "list" ],
|
||||
"/etc/internet-detector/up-script*": [ "read" ],
|
||||
"/etc/internet-detector/down-script*": [ "read" ],
|
||||
"/etc/internet-detector/public-ip-script*": [ "read" ],
|
||||
"/usr/bin/internet-detector*": [ "exec" ],
|
||||
"/usr/bin/mailsend": [ "exec" ]
|
||||
},
|
||||
"uci": [ "internet-detector" ],
|
||||
"ubus": {
|
||||
"luci": [ "getInitList", "setInitAction" ],
|
||||
"luci.internet-detector": [ "UIPoll" ]
|
||||
}
|
||||
},
|
||||
"write": {
|
||||
"file": {
|
||||
"/etc/internet-detector/up-script*": [ "write" ],
|
||||
"/etc/internet-detector/down-script*": [ "write" ],
|
||||
"/etc/internet-detector/public-ip-script*": [ "write" ]
|
||||
},
|
||||
"uci": [ "internet-detector" ]
|
||||
}
|
||||
}
|
||||
}
|
BIN
luci-app-internet-detector/screenshots/01.jpg
Normal file
After Width: | Height: | Size: 121 KiB |
BIN
luci-app-internet-detector/screenshots/02.jpg
Normal file
After Width: | Height: | Size: 137 KiB |
BIN
luci-app-internet-detector/screenshots/03.jpg
Normal file
After Width: | Height: | Size: 44 KiB |
BIN
luci-app-internet-detector/screenshots/internet-led.jpg
Normal file
After Width: | Height: | Size: 170 KiB |
201
luci-app-natmap/LICENSE
Normal file
@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
83
luci-app-natmap/README.md
Normal file
@ -0,0 +1,83 @@
|
||||
## 介绍
|
||||
|
||||
#### 本脚本为基于 openwrt master 分支的 natmap 插件
|
||||
|
||||
## 基本功能
|
||||
|
||||
### 1.目前支持第三方服务调用功能
|
||||
#### 1.1.qBittorrent
|
||||
打洞成功后,自动修改 qBittorrent 的端口号,并配置转发(可选)。
|
||||
需要配置 qBittorrent 地址、账号、密码用于修改端口。
|
||||
需要配置 qBittorrent 使用网卡的 IP 用于配置转发,端口填 0,会转发到修改后的端口。
|
||||
|
||||
#### 1.2.Transmission
|
||||
打洞成功后,自动修改 Transmission 的端口号,并配置转发(可选)。
|
||||
需要配置 Transmission 地址、账号、密码用于修改端口。
|
||||
需要配置 Transmission 使用网卡的 IP 用于配置转发,端口填 0,会转发到修改后的端口。
|
||||
|
||||
#### 1.3.Emby
|
||||
配合 Emby Connect 使用时,用户登录账号后,会从服务器获取最新的连接地址信息,此模式就是用于配置这些信息的。
|
||||
需要配置 Emby 地址和 API Key 用于修改连接地址信息。
|
||||
此模式必须配置转发,默认不更新「外部域」,如果有配置 DDNS,将 DDNS 域名填入外部域后将不需要再次修改。
|
||||
若没有域名,需要将 IP 填入外部域,可以勾选 「Update host with IP」,若对外提供的是 HTTPS 服务,需要勾选 「Update HTTPS Port」。
|
||||
|
||||
#### 1.4.Cloudflare Origin Rules
|
||||
Cloudflare Origin Rules 可以设置回源端口,配合 DDNS 使用时,可以将 DDNS 域名指向 Cloudflare,然后将回源端口设置为打洞后的端口,这样就可以通过 Cloudflare 的 CDN 加速访问。
|
||||
需要配置 Cloudflare 的 API Key,邮箱 和 Zone ID,Zone ID 可以在 Cloudflare 的域名首页找到。
|
||||
API Key 请访问 https://dash.cloudflare.com/profile/api-tokens 复制 Global API Key。
|
||||
需要先在 Cloudflare 后台的 Rules - Origin Rules 下添加一个 Origin Rules,然后将 Origin Rules 的 Name 填入配置中。
|
||||
注意:Name 请保持唯一,否则会出现奇怪的问题。
|
||||
|
||||
#### 1.5.Cloudflare Redirect Rules
|
||||
|
||||
|
||||
### 2.目前支持的通知功能
|
||||
#### 2.1. Telegram Bot
|
||||
#### 2.2. PushPlus
|
||||
#### 2.3. server酱
|
||||
#### 2.4. Gotify
|
||||
|
||||
### 3.端口转发功能
|
||||
#### 3.1.natmap转发
|
||||
支持使用natmap转发tcp和udp..
|
||||
|
||||
#### 3.2.firewall dnat转发
|
||||
支持使用openwrt防火墙转发tcp和udp。
|
||||
|
||||
#### 3.3.ikuai端口映射
|
||||
当前仅支持使用爱快系统作为主路由,可以自动设置主网关爱快系统的端口映射。
|
||||
|
||||
### 4.自定义脚本
|
||||
支持自定义脚本
|
||||
|
||||
|
||||
## 截图展示
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
## 使用
|
||||
|
||||
### openwrt编译时添加软件源至feeds.conf.default首行,以覆盖openwrt内置luci-app-natmap
|
||||
|
||||
```
|
||||
src-git zzz https://github.com/blueberry-pie-11/luci-app-natmap
|
||||
```
|
||||
|
||||
### 编译源码,尽量使用编译固件而非插件安装
|
||||
|
||||
```
|
||||
./scripts/feeds update -a
|
||||
./scripts/feeds install -a
|
||||
make
|
||||
```
|
||||
|
||||
## 本脚本相关功能依据以下代码改写:
|
||||
1. https://github.com/EkkoG/luci-app-natmap
|
||||
2. https://github.com/EkkoG/openwrt-natmap
|
||||
3. https://github.com/loyux/ikuai_local_api
|
||||
4. https://github.com/ztc1997/ikuai-bypass
|
||||
|
||||
|
||||
|
BIN
luci-app-natmap/img/natmap-1.png
Normal file
After Width: | Height: | Size: 114 KiB |
BIN
luci-app-natmap/img/natmap-2.png
Normal file
After Width: | Height: | Size: 113 KiB |
21
luci-app-natmap/luci-app-natmap/Makefile
Executable file
@ -0,0 +1,21 @@
|
||||
# This is free software, licensed under the Apache License, Version 2.0
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-natmap
|
||||
PKG_VERSION:=1.4.0
|
||||
PKG_RELEASE:=2
|
||||
|
||||
LUCI_TITLE:=LuCI Support for natmap
|
||||
LUCI_DEPENDS:=+natmap +jq +curl +openssl-util +bash
|
||||
|
||||
PKG_LICENSE:=Apache-2.0
|
||||
PKG_MAINTAINER:=Richard Yu <yurichard3839@gmail.com>
|
||||
|
||||
define Package/${PKG_NAME}/conffiles
|
||||
/etc/config/natmap
|
||||
endef
|
||||
|
||||
include $(TOPDIR)/feeds/luci/luci.mk
|
||||
|
||||
# call BuildPackage - OpenWrt buildroot signature
|
4
luci-app-natmap/luci-app-natmap/README.md
Normal file
@ -0,0 +1,4 @@
|
||||
### 支持版本
|
||||
|
||||
OpenWrt 21 及以上
|
||||
### 使用gettext命令提取js文件文本信息
|
764
luci-app-natmap/luci-app-natmap/htdocs/luci-static/resources/view/natmap.js
Executable file
@ -0,0 +1,764 @@
|
||||
"use strict";
|
||||
"require form";
|
||||
"require fs";
|
||||
"require rpc";
|
||||
"require view";
|
||||
"require tools.widgets as widgets";
|
||||
|
||||
var callServiceList = rpc.declare({
|
||||
object: "service",
|
||||
method: "list",
|
||||
params: ["name"],
|
||||
expect: { "": {} },
|
||||
});
|
||||
|
||||
function getInstances() {
|
||||
return L.resolveDefault(callServiceList("natmap"), {}).then(function (res) {
|
||||
try {
|
||||
return res.natmap.instances || {};
|
||||
} catch (e) {}
|
||||
return {};
|
||||
});
|
||||
}
|
||||
|
||||
function getStatus() {
|
||||
return getInstances().then(function (instances) {
|
||||
var promises = [];
|
||||
var status = {};
|
||||
for (var key in instances) {
|
||||
var i = instances[key];
|
||||
if (i.running && i.pid) {
|
||||
var f = "/var/run/natmap/" + i.pid + ".json";
|
||||
(function (k) {
|
||||
promises.push(
|
||||
fs
|
||||
.read(f)
|
||||
.then(function (res) {
|
||||
status[k] = JSON.parse(res);
|
||||
})
|
||||
.catch(function (e) {})
|
||||
);
|
||||
})(key);
|
||||
}
|
||||
}
|
||||
return Promise.all(promises).then(function () {
|
||||
return status;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return view.extend({
|
||||
load: function () {
|
||||
return getStatus();
|
||||
},
|
||||
render: function (status) {
|
||||
var m, s, o;
|
||||
|
||||
m = new form.Map("natmap", _("NatMap Settings"));
|
||||
s = m.section(form.GridSection, "natmap");
|
||||
s.addremove = true;
|
||||
s.anonymous = true;
|
||||
|
||||
s.tab("general", _("General Settings"));
|
||||
s.tab("forward", _("Forward Settings"));
|
||||
s.tab("notify", _("Notify Settings"));
|
||||
s.tab("link", _("Link Settings"));
|
||||
s.tab("custom", _("Custom Settings"));
|
||||
|
||||
// o = s.option(form.DummyValue, '_nat_name', _('Name'));
|
||||
// o.modalonly = false;
|
||||
// o.textvalue = function (section_id) {
|
||||
// var s = status[section_id];
|
||||
// if (s) return s.name;
|
||||
// };
|
||||
|
||||
// **********************************************************************
|
||||
// general
|
||||
// **********************************************************************
|
||||
o = s.taboption("general", form.Value, "general_nat_name", _("Name"));
|
||||
o.datatype = "string";
|
||||
// o.modalonly = true;
|
||||
o.rmempty = false;
|
||||
|
||||
o = s.taboption(
|
||||
"general",
|
||||
form.ListValue,
|
||||
"general_nat_protocol",
|
||||
_("Protocol")
|
||||
);
|
||||
o.default = "tcp";
|
||||
o.value("tcp", _("TCP"));
|
||||
o.value("udp", _("UDP"));
|
||||
|
||||
o = s.taboption(
|
||||
"general",
|
||||
form.ListValue,
|
||||
"general_ip_address_family",
|
||||
_("Restrict to address family")
|
||||
);
|
||||
o.modalonly = true;
|
||||
o.value("", _("IPv4 and IPv6"));
|
||||
o.value("ipv4", _("IPv4 only"));
|
||||
o.value("ipv6", _("IPv6 only"));
|
||||
|
||||
o = s.taboption(
|
||||
"general",
|
||||
widgets.NetworkSelect,
|
||||
"general_wan_interface",
|
||||
_("Wan Interface")
|
||||
);
|
||||
o.modalonly = true;
|
||||
o.rmempty = false;
|
||||
|
||||
o = s.taboption(
|
||||
"general",
|
||||
form.Value,
|
||||
"general_interval",
|
||||
_("Keep-alive interval")
|
||||
);
|
||||
o.datatype = "uinteger";
|
||||
o.modalonly = true;
|
||||
o.rmempty = false;
|
||||
|
||||
o = s.taboption(
|
||||
"general",
|
||||
form.Value,
|
||||
"general_stun_server",
|
||||
_("STUN server")
|
||||
);
|
||||
o.datatype = "host";
|
||||
o.modalonly = true;
|
||||
o.optional = false;
|
||||
o.rmempty = false;
|
||||
|
||||
o = s.taboption(
|
||||
"general",
|
||||
form.Value,
|
||||
"general_http_server",
|
||||
_("HTTP server"),
|
||||
_("For TCP mode")
|
||||
);
|
||||
o.datatype = "host";
|
||||
o.modalonly = true;
|
||||
o.rmempty = false;
|
||||
|
||||
o = s.taboption("general", form.Value, "general_bind_port", _("Bind port"));
|
||||
o.datatype = "port";
|
||||
o.rmempty = false;
|
||||
|
||||
// **********************************************************************
|
||||
// forward
|
||||
// **********************************************************************
|
||||
o = s.taboption(
|
||||
"forward",
|
||||
form.Flag,
|
||||
"forward_enable",
|
||||
_("Enable Forward")
|
||||
);
|
||||
o.default = false;
|
||||
o.modalonly = true;
|
||||
// o.ucioption = 'forward_mode';
|
||||
// o.load = function (section_id) {
|
||||
// return this.super('load', section_id) ? '1' : '0';
|
||||
// };
|
||||
// o.write = function (section_id, formvalue) { };
|
||||
|
||||
o = s.taboption(
|
||||
"forward",
|
||||
form.ListValue,
|
||||
"forward_mode",
|
||||
_("Forward mode")
|
||||
);
|
||||
o.default = "firewall";
|
||||
o.value("firewall", _("firewall dnat"));
|
||||
o.value("natmap", _("natmap"));
|
||||
o.value("ikuai", _("ikuai"));
|
||||
// o.depends('forward_enable', '1');
|
||||
|
||||
// forward_natmap
|
||||
o = s.taboption(
|
||||
"forward",
|
||||
form.Value,
|
||||
"forward_target_ip",
|
||||
_("Forward target")
|
||||
);
|
||||
o.datatype = "host";
|
||||
o.modalonly = true;
|
||||
o.depends("forward_mode", "firewall");
|
||||
o.depends("forward_mode", "natmap");
|
||||
o.depends("forward_mode", "ikuai");
|
||||
|
||||
o = s.taboption(
|
||||
"forward",
|
||||
form.Value,
|
||||
"forward_target_port",
|
||||
_("Forward target port"),
|
||||
_("0 will forward to the out port get from STUN")
|
||||
);
|
||||
o.datatype = "port";
|
||||
o.modalonly = true;
|
||||
o.depends("forward_mode", "firewall");
|
||||
o.depends("forward_mode", "natmap");
|
||||
o.depends("forward_mode", "ikuai");
|
||||
|
||||
o = s.taboption(
|
||||
"forward",
|
||||
widgets.NetworkSelect,
|
||||
"forward_natmap_target_interface",
|
||||
_("Target_Interface")
|
||||
);
|
||||
o.modalonly = true;
|
||||
o.depends("forward_mode", "firewall");
|
||||
|
||||
// forward_ikuai
|
||||
o = s.taboption(
|
||||
"forward",
|
||||
form.Value,
|
||||
"forward_ikuai_web_url",
|
||||
_("Ikuai Web URL"),
|
||||
_(
|
||||
"such as http://127.0.0.1:8080 or http://ikuai.lan:8080.if use host,must close Rebind protection in DHCP and DNS"
|
||||
)
|
||||
);
|
||||
o.datatype = "string";
|
||||
o.modalonly = true;
|
||||
o.depends("forward_mode", "ikuai");
|
||||
|
||||
o = s.taboption(
|
||||
"forward",
|
||||
form.Value,
|
||||
"forward_ikuai_username",
|
||||
_("Ikuai Username")
|
||||
);
|
||||
o.datatype = "string";
|
||||
o.modalonly = true;
|
||||
o.depends("forward_mode", "ikuai");
|
||||
|
||||
o = s.taboption(
|
||||
"forward",
|
||||
form.Value,
|
||||
"forward_ikuai_password",
|
||||
_("Ikuai Password")
|
||||
);
|
||||
o.datatype = "string";
|
||||
o.modalonly = true;
|
||||
o.depends("forward_mode", "ikuai");
|
||||
|
||||
o = s.taboption(
|
||||
"forward",
|
||||
form.ListValue,
|
||||
"forward_ikuai_mapping_protocol",
|
||||
_("Ikuai Mapping Protocol"),
|
||||
_("such as tcp or udp or tcp+udp")
|
||||
);
|
||||
o.modalonly = true;
|
||||
o.value("tcp+udp", _("TCP+UDP"));
|
||||
o.value("tcp", _("TCP"));
|
||||
o.value("udp", _("UDP"));
|
||||
o.depends("forward_mode", "ikuai");
|
||||
|
||||
o = s.taboption(
|
||||
"forward",
|
||||
form.Value,
|
||||
"forward_ikuai_mapping_wan_interface",
|
||||
_("Ikuai Mapping Wan Interface"),
|
||||
_("such as adsl_1 or wan")
|
||||
);
|
||||
o.datatype = "string";
|
||||
o.modalonly = true;
|
||||
o.depends("forward_mode", "ikuai");
|
||||
|
||||
// forward_advanced
|
||||
o = s.taboption(
|
||||
"forward",
|
||||
form.Flag,
|
||||
"forward_advanced_enable",
|
||||
_("Advanced Settings")
|
||||
);
|
||||
o.default = false;
|
||||
o.modalonly = true;
|
||||
o.depends("forward_mode", "ikuai");
|
||||
|
||||
o = s.taboption(
|
||||
"forward",
|
||||
form.Value,
|
||||
"forward_advanced_max_retries",
|
||||
_("Max Retries"),
|
||||
_("max retries,default 0 means execute only once")
|
||||
);
|
||||
o.datatype = "uinteger";
|
||||
o.modalonly = true;
|
||||
o.depends("forward_advanced_enable", "1");
|
||||
|
||||
o = s.taboption(
|
||||
"forward",
|
||||
form.Value,
|
||||
"forward_advanced_sleep_time",
|
||||
_("Retry Interval"),
|
||||
_("Retry Interval, unit is seconds, default 0 is 3 seconds")
|
||||
);
|
||||
o.datatype = "uinteger";
|
||||
o.modalonly = true;
|
||||
o.depends("forward_advanced_enable", "1");
|
||||
|
||||
// **********************************************************************
|
||||
// notify
|
||||
// **********************************************************************
|
||||
o = s.taboption("notify", form.Flag, "notify_enable", _("Enable Notify"));
|
||||
o.default = false;
|
||||
o.modalonly = true;
|
||||
|
||||
o = s.taboption(
|
||||
"notify",
|
||||
form.ListValue,
|
||||
"notify_mode",
|
||||
_("Notify channel")
|
||||
);
|
||||
o.default = "telegram_bot";
|
||||
o.modalonly = true;
|
||||
o.value("telegram_bot", _("Telegram Bot"));
|
||||
o.value("pushplus", _("PushPlus"));
|
||||
o.value("serverchan", _("ServerChan"));
|
||||
o.value("gotify", _("Gotify"));
|
||||
|
||||
// notify_telegram_bot
|
||||
o = s.taboption(
|
||||
"notify",
|
||||
form.Value,
|
||||
"notify_telegram_bot_chat_id",
|
||||
_("Chat ID")
|
||||
);
|
||||
o.description =
|
||||
_("Get chat_id") +
|
||||
' <a href="https://t.me/getuserIDbot" target="_blank">' +
|
||||
_("Click here") +
|
||||
"</a>" +
|
||||
_(
|
||||
"<br />If you want to send to a group/channel, please create a non-Chinese group/channel (for easier chatid lookup, you can rename it later).<br />Add the bot to the group, send a message, and use https://api.telegram.org/bot token /getUpdates to obtain the chatid."
|
||||
);
|
||||
o.datatype = "string";
|
||||
o.modalonly = true;
|
||||
o.depends("notify_mode", "telegram_bot");
|
||||
|
||||
o = s.taboption(
|
||||
"notify",
|
||||
form.Value,
|
||||
"notify_telegram_bot_token",
|
||||
_("Telegram Token")
|
||||
);
|
||||
o.description =
|
||||
_("Get Bot") +
|
||||
' <a href="https://t.me/BotFather" target="_blank">' +
|
||||
_("Click here") +
|
||||
"</a>" +
|
||||
_("<br />Send a message to the created bot to initiate a conversation.");
|
||||
o.datatype = "string";
|
||||
o.modalonly = true;
|
||||
o.depends("notify_mode", "telegram_bot");
|
||||
|
||||
o = s.taboption(
|
||||
"notify",
|
||||
form.Value,
|
||||
"notify_telegram_bot_proxy",
|
||||
_("http proxy")
|
||||
);
|
||||
o.datatype = "string";
|
||||
o.modalonly = true;
|
||||
o.depends("notify_mode", "telegram_bot");
|
||||
|
||||
//notify_pushplus
|
||||
o = s.taboption("notify", form.Value, "notify_pushplus_token", _("PushPlus Token"));
|
||||
o.description =
|
||||
_("Get Instructions") +
|
||||
' <a href="http://www.pushplus.plus/" target="_blank">' +
|
||||
_("Click here") +
|
||||
"</a>";
|
||||
o.datatype = "string";
|
||||
o.modalonly = true;
|
||||
o.depends("notify_mode", "pushplus");
|
||||
|
||||
// serverchan
|
||||
o = s.taboption(
|
||||
"notify",
|
||||
form.Value,
|
||||
"notify_serverchan_sendkey",
|
||||
_("ServerChan sendkey")
|
||||
);
|
||||
o.description =
|
||||
_("Get Instructions") +
|
||||
' <a href="https://sct.ftqq.com/" target="_blank">' +
|
||||
_("Click here") +
|
||||
"</a>" +
|
||||
_(
|
||||
"<br />Since the asynchronous push queue is used, only whether the put into the queue is successful is detected."
|
||||
);
|
||||
o.datatype = "string";
|
||||
o.modalonly = true;
|
||||
o.depends("notify_mode", "serverchan");
|
||||
|
||||
// notify_serverchan_advanced
|
||||
o = s.taboption(
|
||||
"notify",
|
||||
form.Flag,
|
||||
"notify_serverchan_advanced_enable",
|
||||
_("ServerChan Advanced Settings")
|
||||
);
|
||||
o.default = false;
|
||||
o.modalonly = true;
|
||||
o.depends("notify_mode", "serverchan");
|
||||
|
||||
o = s.taboption(
|
||||
"notify",
|
||||
form.Value,
|
||||
"notify_serverchan_advanced_url",
|
||||
_("Self-built Server Url")
|
||||
);
|
||||
o.description = _("such as http://127.0.0.1:8080 or http://ikuai.lan:8080");
|
||||
o.datatype = "string";
|
||||
o.modalonly = true;
|
||||
o.depends("notify_serverchan_advanced_enable", "1");
|
||||
|
||||
// gotify
|
||||
o = s.taboption("notify", form.Value, "notify_gotify_url", _("Gotify url"));
|
||||
o.description =
|
||||
_("Get Instructions") +
|
||||
' <a href="https://gotify.net/" target="_blank">' +
|
||||
_("Click here") +
|
||||
"</a>";
|
||||
o.datatype = "string";
|
||||
o.modalonly = true;
|
||||
o.depends("notify_mode", "gotify");
|
||||
|
||||
o = s.taboption(
|
||||
"notify",
|
||||
form.Value,
|
||||
"notify_gotify_token",
|
||||
_("Gotify Token")
|
||||
);
|
||||
o.datatype = "string";
|
||||
o.modalonly = true;
|
||||
o.depends("notify_mode", "gotify");
|
||||
|
||||
o = s.taboption(
|
||||
"notify",
|
||||
form.Value,
|
||||
"notify_gotify_priority",
|
||||
_("Gotify priority")
|
||||
);
|
||||
o.datatype = "uinteger";
|
||||
o.default = 5;
|
||||
o.modalonly = true;
|
||||
o.depends("notify_mode", "gotify");
|
||||
|
||||
// notify_advanced
|
||||
o = s.taboption(
|
||||
"notify",
|
||||
form.Flag,
|
||||
"notify_advanced_enable",
|
||||
_("Advanced Settings")
|
||||
);
|
||||
o.default = false;
|
||||
o.modalonly = true;
|
||||
o.depends("notify_mode", "pushplus");
|
||||
o.depends("notify_mode", "telegram_bot");
|
||||
o.depends("notify_mode", "serverchan");
|
||||
o.depends("notify_mode", "gotify");
|
||||
|
||||
o = s.taboption(
|
||||
"notify",
|
||||
form.Value,
|
||||
"notify_advanced_max_retries",
|
||||
_("Max Retries"),
|
||||
_("max retries,default 0 means execute only once")
|
||||
);
|
||||
o.datatype = "uinteger";
|
||||
o.modalonly = true;
|
||||
o.depends("notify_advanced_enable", "1");
|
||||
|
||||
o = s.taboption(
|
||||
"notify",
|
||||
form.Value,
|
||||
"notify_advanced_sleep_time",
|
||||
_("Retry Interval"),
|
||||
_("Retry Interval, unit is seconds, default 0 is 3 seconds")
|
||||
);
|
||||
o.datatype = "uinteger";
|
||||
o.modalonly = true;
|
||||
o.depends("notify_advanced_enable", "1");
|
||||
|
||||
// **********************************************************************
|
||||
// link
|
||||
// **********************************************************************
|
||||
o = s.taboption("link", form.Flag, "link_enable", _("Enable link setting"));
|
||||
o.modalonly = true;
|
||||
o.default = false;
|
||||
|
||||
o = s.taboption("link", form.ListValue, "link_mode", _("Service"));
|
||||
o.default = "qbittorrent";
|
||||
o.modalonly = true;
|
||||
o.value("emby", _("Emby"));
|
||||
o.value("qbittorrent", _("qBittorrent"));
|
||||
o.value("transmission", _("Transmission"));
|
||||
o.value("cloudflare_origin_rule", _("Cloudflare Origin Rule"));
|
||||
o.value("cloudflare_redirect_rule", _("Cloudflare Redirect Rule"));
|
||||
|
||||
// link_cloudflare
|
||||
o = s.taboption(
|
||||
"link",
|
||||
form.Value,
|
||||
"link_cloudflare_token",
|
||||
_("Cloudflare Token")
|
||||
);
|
||||
o.datatype = "string";
|
||||
o.modalonly = true;
|
||||
o.depends("link_mode", "cloudflare_origin_rule");
|
||||
o.depends("link_mode", "cloudflare_redirect_rule");
|
||||
|
||||
o = s.taboption(
|
||||
"link",
|
||||
form.Value,
|
||||
"link_cloudflare_zone_id",
|
||||
_("Cloudflare Zone ID")
|
||||
);
|
||||
o.datatype = "string";
|
||||
o.modalonly = true;
|
||||
o.depends("link_mode", "cloudflare_origin_rule");
|
||||
o.depends("link_mode", "cloudflare_redirect_rule");
|
||||
|
||||
// link_cloudflare_origin_rule
|
||||
o = s.taboption(
|
||||
"link",
|
||||
form.Value,
|
||||
"link_cloudflare_origin_rule_name",
|
||||
_("Origin Rule Name")
|
||||
);
|
||||
o.datatype = "string";
|
||||
o.modalonly = true;
|
||||
o.depends("link_mode", "cloudflare_origin_rule");
|
||||
|
||||
// link_cloudflare_redirect_rule
|
||||
o = s.taboption(
|
||||
"link",
|
||||
form.Value,
|
||||
"link_cloudflare_redirect_rule_name",
|
||||
_("Redirect Rule Name")
|
||||
);
|
||||
o.datatype = "string";
|
||||
o.modalonly = true;
|
||||
o.depends("link_mode", "cloudflare_redirect_rule");
|
||||
|
||||
o = s.taboption(
|
||||
"link",
|
||||
form.Value,
|
||||
"link_cloudflare_redirect_rule_target_url",
|
||||
_("Redirect Rule Target URL")
|
||||
);
|
||||
o.datatype = "string";
|
||||
o.modalonly = true;
|
||||
o.depends("link_mode", "cloudflare_redirect_rule");
|
||||
|
||||
// link_emby
|
||||
o = s.taboption(
|
||||
"link",
|
||||
form.Value,
|
||||
"link_emby_url",
|
||||
_("EMBY URL"),
|
||||
_(
|
||||
"such as http://127.0.0.1:8080 or http://ikuai.lan:8080.if use host,must close Rebind protection in DHCP/DNS"
|
||||
)
|
||||
);
|
||||
o.datatype = "string";
|
||||
o.modalonly = true;
|
||||
o.depends("link_mode", "emby");
|
||||
|
||||
o = s.taboption("link", form.Value, "link_emby_api_key", _("API Key"));
|
||||
o.datatype = "host";
|
||||
o.modalonly = true;
|
||||
o.depends("link_mode", "emby");
|
||||
|
||||
o = s.taboption(
|
||||
"link",
|
||||
form.Flag,
|
||||
"link_emby_use_https",
|
||||
_("Update HTTPS Port"),
|
||||
_("Set to False if you want to use HTTP")
|
||||
);
|
||||
o.default = false;
|
||||
o.modalonly = true;
|
||||
o.depends("link_mode", "emby");
|
||||
|
||||
o = s.taboption(
|
||||
"link",
|
||||
form.Flag,
|
||||
"link_emby_update_host_with_ip",
|
||||
_("Update host with IP")
|
||||
);
|
||||
o.default = false;
|
||||
o.modalonly = true;
|
||||
o.depends("link_mode", "emby");
|
||||
|
||||
// link_qbittorrent
|
||||
o = s.taboption(
|
||||
"link",
|
||||
form.Value,
|
||||
"link_qb_web_url",
|
||||
_("Web UI URL"),
|
||||
_(
|
||||
"such as http://127.0.0.1:8080 or http://ikuai.lan:8080.if use host,must close Rebind protection in DHCP and DNS"
|
||||
)
|
||||
);
|
||||
o.datatype = "string";
|
||||
o.modalonly = true;
|
||||
o.depends("link_mode", "qbittorrent");
|
||||
|
||||
o = s.taboption("link", form.Value, "link_qb_username", _("Username"));
|
||||
o.datatype = "string";
|
||||
o.modalonly = true;
|
||||
o.depends("link_mode", "qbittorrent");
|
||||
|
||||
o = s.taboption("link", form.Value, "link_qb_password", _("Password"));
|
||||
o.datatype = "string";
|
||||
o.modalonly = true;
|
||||
o.depends("link_mode", "qbittorrent");
|
||||
|
||||
o = s.taboption("link", form.Flag, "link_qb_allow_ipv6", _("Allow IPv6"));
|
||||
o.default = false;
|
||||
o.modalonly = true;
|
||||
o.depends("link_mode", "qbittorrent");
|
||||
|
||||
o = s.taboption(
|
||||
"link",
|
||||
form.Value,
|
||||
"link_qb_ipv6_address",
|
||||
_("IPv6 Address")
|
||||
);
|
||||
o.datatype = "string";
|
||||
o.modalonly = true;
|
||||
o.depends("link_qb_allow_ipv6", "1");
|
||||
|
||||
// link_transmission
|
||||
o = s.taboption(
|
||||
"link",
|
||||
form.Value,
|
||||
"link_tr_rpc_url",
|
||||
_("RPC URL"),
|
||||
_(
|
||||
"such as http://127.0.0.1:8080 or http://ikuai.lan:8080.if use host,must close Rebind protection in DHCP and DNS"
|
||||
)
|
||||
);
|
||||
o.datatype = "string";
|
||||
o.modalonly = true;
|
||||
o.depends("link_mode", "transmission");
|
||||
|
||||
o = s.taboption("link", form.Value, "link_tr_username", _("Username"));
|
||||
o.datatype = "string";
|
||||
o.modalonly = true;
|
||||
o.depends("link_mode", "transmission");
|
||||
|
||||
o = s.taboption("link", form.Value, "link_tr_password", _("Password"));
|
||||
o.datatype = "string";
|
||||
o.modalonly = true;
|
||||
o.depends("link_mode", "transmission");
|
||||
|
||||
o = s.taboption("link", form.Flag, "link_tr_allow_ipv6", _("Allow IPv6"));
|
||||
o.modalonly = true;
|
||||
o.default = false;
|
||||
o.depends("link_mode", "transmission");
|
||||
|
||||
o = s.taboption(
|
||||
"link",
|
||||
form.Value,
|
||||
"link_tr_ipv6_address",
|
||||
_("IPv6 Address")
|
||||
);
|
||||
o.datatype = "string";
|
||||
o.modalonly = true;
|
||||
o.depends("link_tr_allow_ipv6", "1");
|
||||
|
||||
// link_advanced
|
||||
o = s.taboption(
|
||||
"link",
|
||||
form.Flag,
|
||||
"link_advanced_enable",
|
||||
_("Advanced Settings")
|
||||
);
|
||||
o.default = false;
|
||||
o.modalonly = true;
|
||||
o.depends("link_mode", "transmission");
|
||||
o.depends("link_mode", "qbittorrent");
|
||||
o.depends("link_mode", "emby");
|
||||
o.depends("link_mode", "cloudflare_origin_rule");
|
||||
o.depends("link_mode", "cloudflare_redirect_rule");
|
||||
|
||||
o = s.taboption(
|
||||
"link",
|
||||
form.Value,
|
||||
"link_advanced_max_retries",
|
||||
_("Max Retries"),
|
||||
_("max retries,default 0 means execute only once")
|
||||
);
|
||||
o.datatype = "uinteger";
|
||||
o.modalonly = true;
|
||||
o.depends("link_advanced_enable", "1");
|
||||
|
||||
o = s.taboption(
|
||||
"link",
|
||||
form.Value,
|
||||
"link_advanced_sleep_time",
|
||||
_("Retry Interval"),
|
||||
_("Retry Interval, unit is seconds, default 0 is 3 seconds")
|
||||
);
|
||||
o.datatype = "uinteger";
|
||||
o.modalonly = true;
|
||||
o.depends("link_advanced_enable", "1");
|
||||
|
||||
// **********************************************************************
|
||||
// Custom Settings
|
||||
// **********************************************************************
|
||||
o = s.taboption(
|
||||
"custom",
|
||||
form.Flag,
|
||||
"custom_script_enable",
|
||||
_("Enable custom script's config")
|
||||
);
|
||||
o.modalonly = true;
|
||||
o.default = false;
|
||||
|
||||
o = s.taboption(
|
||||
"custom",
|
||||
form.Value,
|
||||
"custom_script_path",
|
||||
_("custom script"),
|
||||
_("custom script path,such as /etc/natmap/custom.sh")
|
||||
);
|
||||
// o.depends('custom_script_enable', '1');
|
||||
o.datatype = "file";
|
||||
o.modalonly = true;
|
||||
|
||||
// **********************************************************************
|
||||
// status
|
||||
// **********************************************************************
|
||||
o = s.option(form.DummyValue, "_external_ip", _("External IP"));
|
||||
o.modalonly = false;
|
||||
o.textvalue = function (section_id) {
|
||||
var s = status[section_id];
|
||||
if (s) return s.ip;
|
||||
};
|
||||
|
||||
o = s.option(form.DummyValue, "_external_port", _("External Port"));
|
||||
o.modalonly = false;
|
||||
o.textvalue = function (section_id) {
|
||||
var s = status[section_id];
|
||||
if (s) return s.port;
|
||||
};
|
||||
|
||||
// **********************************************************************
|
||||
// natmap_enable
|
||||
// **********************************************************************
|
||||
o = s.option(form.Flag, "natmap_enable", _("enable"));
|
||||
o.editable = true;
|
||||
o.modalonly = false;
|
||||
|
||||
return m.render();
|
||||
},
|
||||
});
|
397
luci-app-natmap/luci-app-natmap/po/en/luci-app-natmap.po
Normal file
@ -0,0 +1,397 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Generator: POEditor.com\n"
|
||||
"Project-Id-Version: luci-app-natmap\n"
|
||||
"Language: en\n"
|
||||
|
||||
#: natmap.js:62
|
||||
msgid "General Settings"
|
||||
msgstr "General Settings"
|
||||
|
||||
#: natmap.js:63
|
||||
msgid "Forward Settings"
|
||||
msgstr "Forward Settings"
|
||||
|
||||
#: natmap.js:64
|
||||
msgid "Notify Settings"
|
||||
msgstr "Notify Settings"
|
||||
|
||||
#: natmap.js:65
|
||||
msgid "Link Settings"
|
||||
msgstr "Link Settings"
|
||||
|
||||
#: natmap.js:66
|
||||
msgid "Custom Settings"
|
||||
msgstr "Custom Settings"
|
||||
|
||||
#: natmap.js:78
|
||||
msgid "Name"
|
||||
msgstr "Name"
|
||||
|
||||
#: natmap.js:87
|
||||
msgid "Protocol"
|
||||
msgstr "Protocol"
|
||||
|
||||
#: natmap.js:90 natmap.js:256
|
||||
msgid "TCP"
|
||||
msgstr "TCP"
|
||||
|
||||
#: natmap.js:91 natmap.js:257
|
||||
msgid "UDP"
|
||||
msgstr "UDP"
|
||||
|
||||
#: natmap.js:97
|
||||
msgid "Restrict to address family"
|
||||
msgstr "Restrict to address family"
|
||||
|
||||
#: natmap.js:100
|
||||
msgid "IPv4 and IPv6"
|
||||
msgstr "IPv4 and IPv6"
|
||||
|
||||
#: natmap.js:101
|
||||
msgid "IPv4 only"
|
||||
msgstr "IPv4 only"
|
||||
|
||||
#: natmap.js:102
|
||||
msgid "IPv6 only"
|
||||
msgstr "IPv6 only"
|
||||
|
||||
#: natmap.js:108
|
||||
msgid "Wan Interface"
|
||||
msgstr "Wan Interface"
|
||||
|
||||
#: natmap.js:117
|
||||
msgid "Keep-alive interval"
|
||||
msgstr "Keep-alive interval"
|
||||
|
||||
#: natmap.js:127
|
||||
msgid "STUN server"
|
||||
msgstr "STUN server"
|
||||
|
||||
#: natmap.js:138
|
||||
msgid "HTTP server"
|
||||
msgstr "HTTP server"
|
||||
|
||||
#: natmap.js:139
|
||||
msgid "For TCP mode"
|
||||
msgstr "For TCP mode"
|
||||
|
||||
#: natmap.js:145
|
||||
msgid "Bind port"
|
||||
msgstr "Bind port"
|
||||
|
||||
#: natmap.js:156
|
||||
msgid "Enable Forward"
|
||||
msgstr "Enable Forward"
|
||||
|
||||
#: natmap.js:170
|
||||
msgid "Forward mode"
|
||||
msgstr "Forward mode"
|
||||
|
||||
#: natmap.js:173
|
||||
msgid "firewall dnat"
|
||||
msgstr "firewall dnat"
|
||||
|
||||
#: natmap.js:174
|
||||
msgid "natmap"
|
||||
msgstr "natmap"
|
||||
|
||||
#: natmap.js:175
|
||||
msgid "ikuai"
|
||||
msgstr "ikuai"
|
||||
|
||||
#: natmap.js:183
|
||||
msgid "Forward target"
|
||||
msgstr "Forward target"
|
||||
|
||||
#: natmap.js:195
|
||||
msgid "Forward target port"
|
||||
msgstr "Forward target port"
|
||||
|
||||
#: natmap.js:196
|
||||
msgid "0 will forward to the out port get from STUN"
|
||||
msgstr "0 will forward to the out port get from STUN"
|
||||
|
||||
#: natmap.js:208
|
||||
msgid "Target_Interface"
|
||||
msgstr "Target_Interface"
|
||||
|
||||
#: natmap.js:218
|
||||
msgid "Ikuai Web URL"
|
||||
msgstr "Ikuai Web URL"
|
||||
|
||||
#: natmap.js:220 natmap.js:607 natmap.js:646
|
||||
msgid "such as http://127.0.0.1:8080 or http://ikuai.lan:8080.if use host,must close Rebind protection in DHCP and DNS"
|
||||
msgstr "such as http://127.0.0.1:8080 or http://ikuai.lan:8080.if use host,must close Rebind protection in DHCP and DNS"
|
||||
|
||||
#: natmap.js:231
|
||||
msgid "Ikuai Username"
|
||||
msgstr "Ikuai Username"
|
||||
|
||||
#: natmap.js:241
|
||||
msgid "Ikuai Password"
|
||||
msgstr "Ikuai Password"
|
||||
|
||||
#: natmap.js:251
|
||||
msgid "Ikuai Mapping Protocol"
|
||||
msgstr "Ikuai Mapping Protocol"
|
||||
|
||||
#: natmap.js:252
|
||||
msgid "such as tcp or udp or tcp+udp"
|
||||
msgstr "such as tcp or udp or tcp+udp"
|
||||
|
||||
#: natmap.js:255
|
||||
msgid "TCP+UDP"
|
||||
msgstr "TCP+UDP"
|
||||
|
||||
#: natmap.js:264
|
||||
msgid "Ikuai Mapping Wan Interface"
|
||||
msgstr "Ikuai Mapping Wan Interface"
|
||||
|
||||
#: natmap.js:265
|
||||
msgid "such as adsl_1 or wan"
|
||||
msgstr "such as adsl_1 or wan"
|
||||
|
||||
#: natmap.js:276 natmap.js:458 natmap.js:683
|
||||
msgid "Advanced Settings"
|
||||
msgstr "Advanced Settings"
|
||||
|
||||
#: natmap.js:286 natmap.js:471 natmap.js:697
|
||||
msgid "Max Retries"
|
||||
msgstr "Max Retries"
|
||||
|
||||
#: natmap.js:287 natmap.js:472 natmap.js:698
|
||||
msgid "max retries,default 0 means execute only once"
|
||||
msgstr "max retries,default 0 means execute only once"
|
||||
|
||||
#: natmap.js:297 natmap.js:482 natmap.js:708
|
||||
msgid "Retry Interval"
|
||||
msgstr "Retry Interval"
|
||||
|
||||
#: natmap.js:298 natmap.js:483 natmap.js:709
|
||||
msgid "Retry Interval, unit is seconds, default 0 is 3 seconds"
|
||||
msgstr "Retry Interval, unit is seconds, default 0 is 3 seconds"
|
||||
|
||||
#: natmap.js:307
|
||||
msgid "Enable Notify"
|
||||
msgstr "Enable Notify"
|
||||
|
||||
#: natmap.js:315
|
||||
msgid "Notify channel"
|
||||
msgstr "Notify channel"
|
||||
|
||||
#: natmap.js:319
|
||||
msgid "Telegram Bot"
|
||||
msgstr "Telegram Bot"
|
||||
|
||||
#: natmap.js:320
|
||||
msgid "PushPlus"
|
||||
msgstr "PushPlus"
|
||||
|
||||
#: natmap.js:321
|
||||
msgid "ServerChan"
|
||||
msgstr "ServerChan"
|
||||
|
||||
#: natmap.js:322
|
||||
msgid "Gotify"
|
||||
msgstr "Gotify"
|
||||
|
||||
#: natmap.js:329
|
||||
msgid "Chat ID"
|
||||
msgstr "Chat ID"
|
||||
|
||||
#: natmap.js:332
|
||||
msgid "Get chat_id"
|
||||
msgstr "Get chat_id"
|
||||
|
||||
#: natmap.js:334 natmap.js:352 natmap.js:374 natmap.js:390 natmap.js:426
|
||||
msgid "Click here"
|
||||
msgstr "Click here"
|
||||
|
||||
#: natmap.js:337
|
||||
msgid "<br />If you want to send to a group/channel, please create a non-Chinese group/channel (for easier chatid lookup, you can rename it later).<br />Add the bot to the group, send a message, and use https://api.telegram.org/bot token /getUpdates to obtain the chatid."
|
||||
msgstr "<br />If you want to send to a group/channel, please create a non-Chinese group/channel (for easier chatid lookup, you can rename it later).<br />Add the bot to the group, send a message, and use https://api.telegram.org/bot token /getUpdates to obtain the chatid."
|
||||
|
||||
#: natmap.js:350
|
||||
msgid "Get Bot"
|
||||
msgstr "Get Bot"
|
||||
|
||||
#: natmap.js:354
|
||||
msgid "<br />Send a message to the created bot to initiate a conversation."
|
||||
msgstr "<br />Send a message to the created bot to initiate a conversation."
|
||||
|
||||
#: natmap.js:363
|
||||
msgid "http proxy"
|
||||
msgstr "http proxy"
|
||||
|
||||
#: natmap.js:372 natmap.js:388 natmap.js:424
|
||||
msgid "Get Instructions"
|
||||
msgstr "Get Instructions"
|
||||
|
||||
#: natmap.js:385
|
||||
msgid "ServerChan sendkey"
|
||||
msgstr "ServerChan sendkey"
|
||||
|
||||
#: natmap.js:393
|
||||
msgid "<br />Since the asynchronous push queue is used, only whether the put into the queue is successful is detected."
|
||||
msgstr "<br />Since the asynchronous push queue is used, only whether the put into the queue is successful is detected."
|
||||
|
||||
#: natmap.js:404
|
||||
msgid "ServerChan Advanced Settings"
|
||||
msgstr "ServerChan Advanced Settings"
|
||||
|
||||
#: natmap.js:414
|
||||
msgid "Self-built Server Url"
|
||||
msgstr "Self-built Server Url"
|
||||
|
||||
#: natmap.js:416
|
||||
msgid "such as http://127.0.0.1:8080 or http://ikuai.lan:8080"
|
||||
msgstr "such as http://127.0.0.1:8080 or http://ikuai.lan:8080"
|
||||
|
||||
#: natmap.js:422
|
||||
msgid "Gotify url"
|
||||
msgstr "Gotify url"
|
||||
|
||||
#: natmap.js:446
|
||||
msgid "Gotify priority"
|
||||
msgstr "Gotify priority"
|
||||
|
||||
#: natmap.js:492
|
||||
msgid "Enable link setting"
|
||||
msgstr "Enable link setting"
|
||||
|
||||
#: natmap.js:496
|
||||
msgid "Service"
|
||||
msgstr "Service"
|
||||
|
||||
#: natmap.js:499
|
||||
msgid "Emby"
|
||||
msgstr "Emby"
|
||||
|
||||
#: natmap.js:500
|
||||
msgid "qBittorrent"
|
||||
msgstr "qBittorrent"
|
||||
|
||||
#: natmap.js:501
|
||||
msgid "Transmission"
|
||||
msgstr "Transmission"
|
||||
|
||||
#: natmap.js:502
|
||||
msgid "Cloudflare Origin Rule"
|
||||
msgstr "Cloudflare Origin Rule"
|
||||
|
||||
#: natmap.js:503
|
||||
msgid "Cloudflare Redirect Rule"
|
||||
msgstr "Cloudflare Redirect Rule"
|
||||
|
||||
#: natmap.js:574
|
||||
msgid "API Key"
|
||||
msgstr "API Key"
|
||||
|
||||
#: natmap.js:565
|
||||
msgid "EMBY URL"
|
||||
msgstr "EMBY URL"
|
||||
|
||||
#: natmap.js:583
|
||||
msgid "Update HTTPS Port"
|
||||
msgstr "Update HTTPS Port"
|
||||
|
||||
#: natmap.js:584
|
||||
msgid "Set to False if you want to use HTTP"
|
||||
msgstr "Set to False if you want to use HTTP"
|
||||
|
||||
#: natmap.js:594
|
||||
msgid "Update host with IP"
|
||||
msgstr "Update host with IP"
|
||||
|
||||
#: natmap.js:605
|
||||
msgid "Web UI URL"
|
||||
msgstr "Web UI URL"
|
||||
|
||||
#: natmap.js:614 natmap.js:653
|
||||
msgid "Username"
|
||||
msgstr "Username"
|
||||
|
||||
#: natmap.js:619 natmap.js:658
|
||||
msgid "Password"
|
||||
msgstr "Password"
|
||||
|
||||
#: natmap.js:624 natmap.js:663
|
||||
msgid "Allow IPv6"
|
||||
msgstr "Allow IPv6"
|
||||
|
||||
#: natmap.js:633 natmap.js:672
|
||||
msgid "IPv6 Address"
|
||||
msgstr "IPv6 Address"
|
||||
|
||||
#: natmap.js:644
|
||||
msgid "RPC URL"
|
||||
msgstr "RPC URL"
|
||||
|
||||
#: natmap.js:722
|
||||
msgid "Enable custom script's config"
|
||||
msgstr "Enable custom script's config"
|
||||
|
||||
#: natmap.js:731
|
||||
msgid "custom script"
|
||||
msgstr "custom script"
|
||||
|
||||
#: natmap.js:732
|
||||
msgid "custom script path,such as /etc/natmap/custom.sh"
|
||||
msgstr "custom script path,such as /etc/natmap/custom.sh"
|
||||
|
||||
#: natmap.js:741
|
||||
msgid "External IP"
|
||||
msgstr "External IP"
|
||||
|
||||
#: natmap.js:748
|
||||
msgid "External Port"
|
||||
msgstr "External Port"
|
||||
|
||||
#: natmap.js:758
|
||||
msgid "enable"
|
||||
msgstr "enable"
|
||||
|
||||
#: natmap.js:57
|
||||
msgid "NatMap Settings"
|
||||
msgstr "NatMap Settings"
|
||||
|
||||
#: natmap.js:510
|
||||
msgid "Cloudflare Token"
|
||||
msgstr "Cloudflare Token"
|
||||
|
||||
#: natmap.js:521
|
||||
msgid "Cloudflare Zone ID"
|
||||
msgstr "Cloudflare Zone ID"
|
||||
|
||||
#: natmap.js:533
|
||||
msgid "Origin Rule Name"
|
||||
msgstr "Origin Rule Name"
|
||||
|
||||
#: natmap.js:544
|
||||
msgid "Redirect Rule Name"
|
||||
msgstr "Redirect Rule Name"
|
||||
|
||||
#: natmap.js:347
|
||||
msgid "Telegram Token"
|
||||
msgstr "Telegram Token"
|
||||
|
||||
#: natmap.js:370
|
||||
msgid "PushPlus Token"
|
||||
msgstr "PushPlus Token"
|
||||
|
||||
#: natmap.js:436
|
||||
msgid "Gotify Token"
|
||||
msgstr "Gotify Token"
|
||||
|
||||
#: natmap.js:554
|
||||
msgid "Redirect Rule Target URL"
|
||||
msgstr "Redirect Rule Target URL"
|
||||
|
||||
#: natmap.js:567
|
||||
msgid "such as http://127.0.0.1:8080 or http://ikuai.lan:8080.if use host,must close Rebind protection in DHCP/DNS"
|
||||
msgstr "such as http://127.0.0.1:8080 or http://ikuai.lan:8080.if use host,must close Rebind protection in DHCP/DNS"
|
||||
|
397
luci-app-natmap/luci-app-natmap/po/jp/luci-app-natmap.po
Normal file
@ -0,0 +1,397 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Generator: POEditor.com\n"
|
||||
"Project-Id-Version: luci-app-natmap\n"
|
||||
"Language: ja\n"
|
||||
|
||||
#: natmap.js:62
|
||||
msgid "General Settings"
|
||||
msgstr "一般設定"
|
||||
|
||||
#: natmap.js:63
|
||||
msgid "Forward Settings"
|
||||
msgstr "転送設定"
|
||||
|
||||
#: natmap.js:64
|
||||
msgid "Notify Settings"
|
||||
msgstr "通知設定"
|
||||
|
||||
#: natmap.js:65
|
||||
msgid "Link Settings"
|
||||
msgstr "リンク設定"
|
||||
|
||||
#: natmap.js:66
|
||||
msgid "Custom Settings"
|
||||
msgstr "カスタム設定"
|
||||
|
||||
#: natmap.js:78
|
||||
msgid "Name"
|
||||
msgstr "名前"
|
||||
|
||||
#: natmap.js:87
|
||||
msgid "Protocol"
|
||||
msgstr "プロトコル"
|
||||
|
||||
#: natmap.js:90 natmap.js:256
|
||||
msgid "TCP"
|
||||
msgstr "TCP"
|
||||
|
||||
#: natmap.js:91 natmap.js:257
|
||||
msgid "UDP"
|
||||
msgstr "UDP"
|
||||
|
||||
#: natmap.js:97
|
||||
msgid "Restrict to address family"
|
||||
msgstr "アドレスを家族に限定する"
|
||||
|
||||
#: natmap.js:100
|
||||
msgid "IPv4 and IPv6"
|
||||
msgstr "IPv4 と IPv6"
|
||||
|
||||
#: natmap.js:101
|
||||
msgid "IPv4 only"
|
||||
msgstr "IPv4のみ"
|
||||
|
||||
#: natmap.js:102
|
||||
msgid "IPv6 only"
|
||||
msgstr "IPv6のみ"
|
||||
|
||||
#: natmap.js:108
|
||||
msgid "Wan Interface"
|
||||
msgstr "WANインターフェース"
|
||||
|
||||
#: natmap.js:117
|
||||
msgid "Keep-alive interval"
|
||||
msgstr "キープアライブ間隔"
|
||||
|
||||
#: natmap.js:127
|
||||
msgid "STUN server"
|
||||
msgstr "STUNサーバー"
|
||||
|
||||
#: natmap.js:138
|
||||
msgid "HTTP server"
|
||||
msgstr "HTTPサーバー"
|
||||
|
||||
#: natmap.js:139
|
||||
msgid "For TCP mode"
|
||||
msgstr "TCPモードの場合"
|
||||
|
||||
#: natmap.js:145
|
||||
msgid "Bind port"
|
||||
msgstr "バインドポート"
|
||||
|
||||
#: natmap.js:156
|
||||
msgid "Enable Forward"
|
||||
msgstr "転送を有効にする"
|
||||
|
||||
#: natmap.js:170
|
||||
msgid "Forward mode"
|
||||
msgstr "順方向モード"
|
||||
|
||||
#: natmap.js:173
|
||||
msgid "firewall dnat"
|
||||
msgstr "firewall dnat"
|
||||
|
||||
#: natmap.js:174
|
||||
msgid "natmap"
|
||||
msgstr "natmap"
|
||||
|
||||
#: natmap.js:175
|
||||
msgid "ikuai"
|
||||
msgstr "ikuai"
|
||||
|
||||
#: natmap.js:183
|
||||
msgid "Forward target"
|
||||
msgstr "前方ターゲット"
|
||||
|
||||
#: natmap.js:195
|
||||
msgid "Forward target port"
|
||||
msgstr "転送ターゲットポート"
|
||||
|
||||
#: natmap.js:196
|
||||
msgid "0 will forward to the out port get from STUN"
|
||||
msgstr "0 は STUN から取得した出力ポートに転送します"
|
||||
|
||||
#: natmap.js:208
|
||||
msgid "Target_Interface"
|
||||
msgstr "ターゲットインターフェース"
|
||||
|
||||
#: natmap.js:218
|
||||
msgid "Ikuai Web URL"
|
||||
msgstr "Ikuai URL"
|
||||
|
||||
#: natmap.js:220 natmap.js:607 natmap.js:646
|
||||
msgid "such as http://127.0.0.1:8080 or http://ikuai.lan:8080.if use host,must close Rebind protection in DHCP and DNS"
|
||||
msgstr "http://127.0.0.1:8080 や http://ikuai.lan:8080 など。ホストを使用する場合は、DHCP と DNS の再バインド保護を閉じる必要があります。"
|
||||
|
||||
#: natmap.js:231
|
||||
msgid "Ikuai Username"
|
||||
msgstr "育愛ユーザー名"
|
||||
|
||||
#: natmap.js:241
|
||||
msgid "Ikuai Password"
|
||||
msgstr "育愛パスワード"
|
||||
|
||||
#: natmap.js:251
|
||||
msgid "Ikuai Mapping Protocol"
|
||||
msgstr "イクアイマッピングプロトコル"
|
||||
|
||||
#: natmap.js:252
|
||||
msgid "such as tcp or udp or tcp+udp"
|
||||
msgstr "tcp、udp、tcp+udp など"
|
||||
|
||||
#: natmap.js:255
|
||||
msgid "TCP+UDP"
|
||||
msgstr "TCP+UDP"
|
||||
|
||||
#: natmap.js:264
|
||||
msgid "Ikuai Mapping Wan Interface"
|
||||
msgstr "Ikuai マッピング Wan インターフェース"
|
||||
|
||||
#: natmap.js:265
|
||||
msgid "such as adsl_1 or wan"
|
||||
msgstr "adsl_1 や wan など"
|
||||
|
||||
#: natmap.js:276 natmap.js:458 natmap.js:683
|
||||
msgid "Advanced Settings"
|
||||
msgstr "高度な設定"
|
||||
|
||||
#: natmap.js:286 natmap.js:471 natmap.js:697
|
||||
msgid "Max Retries"
|
||||
msgstr "最大再試行回数"
|
||||
|
||||
#: natmap.js:287 natmap.js:472 natmap.js:698
|
||||
msgid "max retries,default 0 means execute only once"
|
||||
msgstr "最大再試行数、デフォルトの 0 は 1 回だけ実行することを意味します"
|
||||
|
||||
#: natmap.js:297 natmap.js:482 natmap.js:708
|
||||
msgid "Retry Interval"
|
||||
msgstr "再試行間隔"
|
||||
|
||||
#: natmap.js:298 natmap.js:483 natmap.js:709
|
||||
msgid "Retry Interval, unit is seconds, default 0 is 3 seconds"
|
||||
msgstr "再試行間隔、単位は秒、デフォルトは 0 で 3 秒です"
|
||||
|
||||
#: natmap.js:307
|
||||
msgid "Enable Notify"
|
||||
msgstr "通知を有効にする"
|
||||
|
||||
#: natmap.js:315
|
||||
msgid "Notify channel"
|
||||
msgstr "通知チャネル"
|
||||
|
||||
#: natmap.js:319
|
||||
msgid "Telegram Bot"
|
||||
msgstr "Telegram Bot"
|
||||
|
||||
#: natmap.js:320
|
||||
msgid "PushPlus"
|
||||
msgstr "PushPlus"
|
||||
|
||||
#: natmap.js:321
|
||||
msgid "ServerChan"
|
||||
msgstr "ServerChan"
|
||||
|
||||
#: natmap.js:322
|
||||
msgid "Gotify"
|
||||
msgstr "Gotify"
|
||||
|
||||
#: natmap.js:329
|
||||
msgid "Chat ID"
|
||||
msgstr "チャットID"
|
||||
|
||||
#: natmap.js:332
|
||||
msgid "Get chat_id"
|
||||
msgstr "chat_id を取得する"
|
||||
|
||||
#: natmap.js:334 natmap.js:352 natmap.js:374 natmap.js:390 natmap.js:426
|
||||
msgid "Click here"
|
||||
msgstr "ここをクリック"
|
||||
|
||||
#: natmap.js:337
|
||||
msgid "<br />If you want to send to a group/channel, please create a non-Chinese group/channel (for easier chatid lookup, you can rename it later).<br />Add the bot to the group, send a message, and use https://api.telegram.org/bot token /getUpdates to obtain the chatid."
|
||||
msgstr "<br />グループ/チャンネルに送信したい場合は、中国語以外のグループ/チャンネルを作成してください (チャット ID の検索を容易にするため、後で名前を変更できます)。<br />ボットをグループに追加し、メッセージを送信します。メッセージを取得し、https://api.telegram.org/bot token /getUpdates を使用してチャット ID を取得します。"
|
||||
|
||||
#: natmap.js:350
|
||||
msgid "Get Bot"
|
||||
msgstr "ボットを入手"
|
||||
|
||||
#: natmap.js:354
|
||||
msgid "<br />Send a message to the created bot to initiate a conversation."
|
||||
msgstr "<br />作成したボットにメッセージを送信して会話を開始します。"
|
||||
|
||||
#: natmap.js:363
|
||||
msgid "http proxy"
|
||||
msgstr "httpプロキシ"
|
||||
|
||||
#: natmap.js:372 natmap.js:388 natmap.js:424
|
||||
msgid "Get Instructions"
|
||||
msgstr "手順を確認する"
|
||||
|
||||
#: natmap.js:385
|
||||
msgid "ServerChan sendkey"
|
||||
msgstr "ServerChan 送信キー"
|
||||
|
||||
#: natmap.js:393
|
||||
msgid "<br />Since the asynchronous push queue is used, only whether the put into the queue is successful is detected."
|
||||
msgstr "<br />非同期プッシュキューを使用しているため、キューへの投入が成功したかどうかのみが検出されます。"
|
||||
|
||||
#: natmap.js:404
|
||||
msgid "ServerChan Advanced Settings"
|
||||
msgstr "ServerChan の詳細設定"
|
||||
|
||||
#: natmap.js:414
|
||||
msgid "Self-built Server Url"
|
||||
msgstr "自己構築サーバー URL"
|
||||
|
||||
#: natmap.js:416
|
||||
msgid "such as http://127.0.0.1:8080 or http://ikuai.lan:8080"
|
||||
msgstr "http://127.0.0.1:8080 や http://ikuai.lan:8080 など"
|
||||
|
||||
#: natmap.js:422
|
||||
msgid "Gotify url"
|
||||
msgstr "Gotify URL"
|
||||
|
||||
#: natmap.js:446
|
||||
msgid "Gotify priority"
|
||||
msgstr "優先順位を獲得する"
|
||||
|
||||
#: natmap.js:492
|
||||
msgid "Enable link setting"
|
||||
msgstr "リンク設定を有効にする"
|
||||
|
||||
#: natmap.js:496
|
||||
msgid "Service"
|
||||
msgstr "サービス"
|
||||
|
||||
#: natmap.js:499
|
||||
msgid "Emby"
|
||||
msgstr "Emby"
|
||||
|
||||
#: natmap.js:500
|
||||
msgid "qBittorrent"
|
||||
msgstr "qBittorrent"
|
||||
|
||||
#: natmap.js:501
|
||||
msgid "Transmission"
|
||||
msgstr "Transmission"
|
||||
|
||||
#: natmap.js:502
|
||||
msgid "Cloudflare Origin Rule"
|
||||
msgstr "Cloudflare Origin Rule"
|
||||
|
||||
#: natmap.js:503
|
||||
msgid "Cloudflare Redirect Rule"
|
||||
msgstr "Cloudflare Redirect Rule"
|
||||
|
||||
#: natmap.js:574
|
||||
msgid "API Key"
|
||||
msgstr "API Key"
|
||||
|
||||
#: natmap.js:565
|
||||
msgid "EMBY URL"
|
||||
msgstr "EMBY URL"
|
||||
|
||||
#: natmap.js:583
|
||||
msgid "Update HTTPS Port"
|
||||
msgstr "HTTPSポートを更新する"
|
||||
|
||||
#: natmap.js:584
|
||||
msgid "Set to False if you want to use HTTP"
|
||||
msgstr "HTTP を使用する場合は False に設定します。"
|
||||
|
||||
#: natmap.js:594
|
||||
msgid "Update host with IP"
|
||||
msgstr "IPを使用してホストを更新する"
|
||||
|
||||
#: natmap.js:605
|
||||
msgid "Web UI URL"
|
||||
msgstr "ウェブUIのURL"
|
||||
|
||||
#: natmap.js:614 natmap.js:653
|
||||
msgid "Username"
|
||||
msgstr "ユーザー名"
|
||||
|
||||
#: natmap.js:619 natmap.js:658
|
||||
msgid "Password"
|
||||
msgstr "パスワード"
|
||||
|
||||
#: natmap.js:624 natmap.js:663
|
||||
msgid "Allow IPv6"
|
||||
msgstr "IPv6 を許可する"
|
||||
|
||||
#: natmap.js:633 natmap.js:672
|
||||
msgid "IPv6 Address"
|
||||
msgstr "IPv6アドレス"
|
||||
|
||||
#: natmap.js:644
|
||||
msgid "RPC URL"
|
||||
msgstr "RPC URL"
|
||||
|
||||
#: natmap.js:722
|
||||
msgid "Enable custom script's config"
|
||||
msgstr "カスタムスクリプトの設定を有効にする"
|
||||
|
||||
#: natmap.js:731
|
||||
msgid "custom script"
|
||||
msgstr "カスタムスクリプト"
|
||||
|
||||
#: natmap.js:732
|
||||
msgid "custom script path,such as /etc/natmap/custom.sh"
|
||||
msgstr "カスタム スクリプト パス (/etc/natmap/custom.sh など)"
|
||||
|
||||
#: natmap.js:741
|
||||
msgid "External IP"
|
||||
msgstr "外部IP"
|
||||
|
||||
#: natmap.js:748
|
||||
msgid "External Port"
|
||||
msgstr "外部ポート"
|
||||
|
||||
#: natmap.js:758
|
||||
msgid "enable"
|
||||
msgstr "有効にする"
|
||||
|
||||
#: natmap.js:57
|
||||
msgid "NatMap Settings"
|
||||
msgstr "NatMap Settings"
|
||||
|
||||
#: natmap.js:510
|
||||
msgid "Cloudflare Token"
|
||||
msgstr "Cloudflare Token"
|
||||
|
||||
#: natmap.js:521
|
||||
msgid "Cloudflare Zone ID"
|
||||
msgstr "Cloudflare Zone ID"
|
||||
|
||||
#: natmap.js:533
|
||||
msgid "Origin Rule Name"
|
||||
msgstr "Origin Rule Name"
|
||||
|
||||
#: natmap.js:544
|
||||
msgid "Redirect Rule Name"
|
||||
msgstr "Redirect Rule Name"
|
||||
|
||||
#: natmap.js:347
|
||||
msgid "Telegram Token"
|
||||
msgstr "Telegram Token"
|
||||
|
||||
#: natmap.js:370
|
||||
msgid "PushPlus Token"
|
||||
msgstr "PushPlus Token"
|
||||
|
||||
#: natmap.js:436
|
||||
msgid "Gotify Token"
|
||||
msgstr "Gotify Token"
|
||||
|
||||
#: natmap.js:554
|
||||
msgid "Redirect Rule Target URL"
|
||||
msgstr "重定方向规则目标URL"
|
||||
|
||||
#: natmap.js:567
|
||||
msgid "such as http://127.0.0.1:8080 or http://ikuai.lan:8080.if use host,must close Rebind protection in DHCP/DNS"
|
||||
msgstr "例: http://127.0.0.1:8080 または http://ikuai.lan:8080。IP ではなくホスト アドレスを使用する場合は、DHCP/DNS 内の再設定保護を解除する必要があります。"
|
||||
|
397
luci-app-natmap/luci-app-natmap/po/zh_Hans/luci-app-natmap.po
Normal file
@ -0,0 +1,397 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Generator: POEditor.com\n"
|
||||
"Project-Id-Version: luci-app-natmap\n"
|
||||
"Language: zh-Hans\n"
|
||||
|
||||
#: natmap.js:337
|
||||
msgid "<br />If you want to send to a group/channel, please create a non-Chinese group/channel (for easier chatid lookup, you can rename it later).<br />Add the bot to the group, send a message, and use https://api.telegram.org/bot token /getUpdates to obtain the chatid."
|
||||
msgstr "<br />如需通过群组/频道推送,请创建一个非中文的群组或频道(便于查找 chatid,后续再更名)<br />将机器人添加至群组,发送信息后通过 https://api.telegram.org/bot token /getUpdates 获取 chatid"
|
||||
|
||||
#: natmap.js:354
|
||||
msgid "<br />Send a message to the created bot to initiate a conversation."
|
||||
msgstr "<br />与创建的机器人发一条消息,开启对话"
|
||||
|
||||
#: natmap.js:393
|
||||
msgid "<br />Since the asynchronous push queue is used, only whether the put into the queue is successful is detected."
|
||||
msgstr "<br />由于使用异步推送队列,所以仅检测放入队列是否成功"
|
||||
|
||||
#: natmap.js:196
|
||||
msgid "0 will forward to the out port get from STUN"
|
||||
msgstr "填写 0 ,则转发到从STUN获取的外部端口"
|
||||
|
||||
#: natmap.js:276 natmap.js:458 natmap.js:683
|
||||
msgid "Advanced Settings"
|
||||
msgstr "高级设置"
|
||||
|
||||
#: natmap.js:624 natmap.js:663
|
||||
msgid "Allow IPv6"
|
||||
msgstr "允许 IPv6"
|
||||
|
||||
#: natmap.js:574
|
||||
msgid "API Key"
|
||||
msgstr "API 密钥"
|
||||
|
||||
#: natmap.js:145
|
||||
msgid "Bind port"
|
||||
msgstr "绑定端口"
|
||||
|
||||
#: natmap.js:329
|
||||
msgid "Chat ID"
|
||||
msgstr "会话ID"
|
||||
|
||||
#: natmap.js:334 natmap.js:352 natmap.js:374 natmap.js:390 natmap.js:426
|
||||
msgid "Click here"
|
||||
msgstr "点击此处"
|
||||
|
||||
#: natmap.js:502
|
||||
msgid "Cloudflare Origin Rule"
|
||||
msgstr "Cloudflare Origin Rule"
|
||||
|
||||
#: natmap.js:503
|
||||
msgid "Cloudflare Redirect Rule"
|
||||
msgstr "Cloudflare Redirect Rule"
|
||||
|
||||
#: natmap.js:510
|
||||
msgid "Cloudflare Token"
|
||||
msgstr "Cloudflare 令牌"
|
||||
|
||||
#: natmap.js:521
|
||||
msgid "Cloudflare Zone ID"
|
||||
msgstr "Cloudflare Zone ID"
|
||||
|
||||
#: natmap.js:731
|
||||
msgid "custom script"
|
||||
msgstr "自定义脚本"
|
||||
|
||||
#: natmap.js:732
|
||||
msgid "custom script path,such as /etc/natmap/custom.sh"
|
||||
msgstr "自定义脚本路径,例如:/etc/natmap/custom.sh"
|
||||
|
||||
#: natmap.js:66
|
||||
msgid "Custom Settings"
|
||||
msgstr "自定义设置"
|
||||
|
||||
#: natmap.js:499
|
||||
msgid "Emby"
|
||||
msgstr "Emby"
|
||||
|
||||
#: natmap.js:565
|
||||
msgid "EMBY URL"
|
||||
msgstr "Emby URL"
|
||||
|
||||
#: natmap.js:758
|
||||
msgid "enable"
|
||||
msgstr "启用"
|
||||
|
||||
#: natmap.js:722
|
||||
msgid "Enable custom script's config"
|
||||
msgstr "启用自定义脚本"
|
||||
|
||||
#: natmap.js:156
|
||||
msgid "Enable Forward"
|
||||
msgstr "启用转发"
|
||||
|
||||
#: natmap.js:492
|
||||
msgid "Enable link setting"
|
||||
msgstr "启用关联设置"
|
||||
|
||||
#: natmap.js:307
|
||||
msgid "Enable Notify"
|
||||
msgstr "启用通知"
|
||||
|
||||
#: natmap.js:741
|
||||
msgid "External IP"
|
||||
msgstr "外部 ip"
|
||||
|
||||
#: natmap.js:748
|
||||
msgid "External Port"
|
||||
msgstr "外部端口"
|
||||
|
||||
#: natmap.js:173
|
||||
msgid "firewall dnat"
|
||||
msgstr "firewall dnat"
|
||||
|
||||
#: natmap.js:139
|
||||
msgid "For TCP mode"
|
||||
msgstr "用于 tcp 模式"
|
||||
|
||||
#: natmap.js:170
|
||||
msgid "Forward mode"
|
||||
msgstr "转发模式"
|
||||
|
||||
#: natmap.js:63
|
||||
msgid "Forward Settings"
|
||||
msgstr "转发设置"
|
||||
|
||||
#: natmap.js:183
|
||||
msgid "Forward target"
|
||||
msgstr "转发目标"
|
||||
|
||||
#: natmap.js:195
|
||||
msgid "Forward target port"
|
||||
msgstr "转发目标端口"
|
||||
|
||||
#: natmap.js:62
|
||||
msgid "General Settings"
|
||||
msgstr "常规设置"
|
||||
|
||||
#: natmap.js:350
|
||||
msgid "Get Bot"
|
||||
msgstr "获取机器人"
|
||||
|
||||
#: natmap.js:332
|
||||
msgid "Get chat_id"
|
||||
msgstr "获取 chat_id"
|
||||
|
||||
#: natmap.js:372 natmap.js:388 natmap.js:424
|
||||
msgid "Get Instructions"
|
||||
msgstr "获取介绍"
|
||||
|
||||
#: natmap.js:322
|
||||
msgid "Gotify"
|
||||
msgstr "Gotify"
|
||||
|
||||
#: natmap.js:446
|
||||
msgid "Gotify priority"
|
||||
msgstr "Gotify 通知优先级"
|
||||
|
||||
#: natmap.js:436
|
||||
msgid "Gotify Token"
|
||||
msgstr "Gotify 令牌"
|
||||
|
||||
#: natmap.js:422
|
||||
msgid "Gotify url"
|
||||
msgstr "Gotify URL"
|
||||
|
||||
#: natmap.js:363
|
||||
msgid "http proxy"
|
||||
msgstr "http 代理"
|
||||
|
||||
#: natmap.js:138
|
||||
msgid "HTTP server"
|
||||
msgstr "HTTP 服务器"
|
||||
|
||||
#: natmap.js:175
|
||||
msgid "ikuai"
|
||||
msgstr "ikuai"
|
||||
|
||||
#: natmap.js:251
|
||||
msgid "Ikuai Mapping Protocol"
|
||||
msgstr "端口映射协议"
|
||||
|
||||
#: natmap.js:264
|
||||
msgid "Ikuai Mapping Wan Interface"
|
||||
msgstr "端口映射外网接口"
|
||||
|
||||
#: natmap.js:241
|
||||
msgid "Ikuai Password"
|
||||
msgstr "爱快密码"
|
||||
|
||||
#: natmap.js:231
|
||||
msgid "Ikuai Username"
|
||||
msgstr "爱快用户名"
|
||||
|
||||
#: natmap.js:218
|
||||
msgid "Ikuai Web URL"
|
||||
msgstr "爱快 URL"
|
||||
|
||||
#: natmap.js:100
|
||||
msgid "IPv4 and IPv6"
|
||||
msgstr "IPv4 和 IPv6"
|
||||
|
||||
#: natmap.js:101
|
||||
msgid "IPv4 only"
|
||||
msgstr "仅 IPv4"
|
||||
|
||||
#: natmap.js:633 natmap.js:672
|
||||
msgid "IPv6 Address"
|
||||
msgstr "IPv6 地址"
|
||||
|
||||
#: natmap.js:102
|
||||
msgid "IPv6 only"
|
||||
msgstr "仅 IPv6"
|
||||
|
||||
#: natmap.js:117
|
||||
msgid "Keep-alive interval"
|
||||
msgstr "keep-alive 间隔"
|
||||
|
||||
#: natmap.js:65
|
||||
msgid "Link Settings"
|
||||
msgstr "关联设置"
|
||||
|
||||
#: natmap.js:286 natmap.js:471 natmap.js:697
|
||||
msgid "Max Retries"
|
||||
msgstr "最大重试次数"
|
||||
|
||||
#: natmap.js:287 natmap.js:472 natmap.js:698
|
||||
msgid "max retries,default 0 means execute only once"
|
||||
msgstr "最大重试次数,默认 0 表示仅执行一次调用"
|
||||
|
||||
#: natmap.js:78
|
||||
msgid "Name"
|
||||
msgstr "名称"
|
||||
|
||||
#: natmap.js:174
|
||||
msgid "natmap"
|
||||
msgstr "natmap"
|
||||
|
||||
#: natmap.js:57
|
||||
msgid "NatMap Settings"
|
||||
msgstr "NatMap 设置"
|
||||
|
||||
#: natmap.js:315
|
||||
msgid "Notify channel"
|
||||
msgstr "通知通道"
|
||||
|
||||
#: natmap.js:64
|
||||
msgid "Notify Settings"
|
||||
msgstr "通知设置"
|
||||
|
||||
#: natmap.js:533
|
||||
msgid "Origin Rule Name"
|
||||
msgstr "Origin Rules 名称"
|
||||
|
||||
#: natmap.js:619 natmap.js:658
|
||||
msgid "Password"
|
||||
msgstr "密码"
|
||||
|
||||
#: natmap.js:87
|
||||
msgid "Protocol"
|
||||
msgstr "协议"
|
||||
|
||||
#: natmap.js:320
|
||||
msgid "PushPlus"
|
||||
msgstr "PushPlus"
|
||||
|
||||
#: natmap.js:370
|
||||
msgid "PushPlus Token"
|
||||
msgstr "PushPlus 令牌"
|
||||
|
||||
#: natmap.js:500
|
||||
msgid "qBittorrent"
|
||||
msgstr "qBittorrent"
|
||||
|
||||
#: natmap.js:544
|
||||
msgid "Redirect Rule Name"
|
||||
msgstr "重定向规则名称"
|
||||
|
||||
#: natmap.js:554
|
||||
msgid "Redirect Rule Target URL"
|
||||
msgstr "重定向规则目标URL"
|
||||
|
||||
#: natmap.js:97
|
||||
msgid "Restrict to address family"
|
||||
msgstr "地址族限制"
|
||||
|
||||
#: natmap.js:297 natmap.js:482 natmap.js:708
|
||||
msgid "Retry Interval"
|
||||
msgstr "重试间隔时间"
|
||||
|
||||
#: natmap.js:298 natmap.js:483 natmap.js:709
|
||||
msgid "Retry Interval, unit is seconds, default 0 is 3 seconds"
|
||||
msgstr "单次重试间隔时间,单位是秒,默认 0 表示 3秒"
|
||||
|
||||
#: natmap.js:644
|
||||
msgid "RPC URL"
|
||||
msgstr "RPC URL"
|
||||
|
||||
#: natmap.js:414
|
||||
msgid "Self-built Server Url"
|
||||
msgstr "自建服务器 URL"
|
||||
|
||||
#: natmap.js:321
|
||||
msgid "ServerChan"
|
||||
msgstr "Server酱"
|
||||
|
||||
#: natmap.js:404
|
||||
msgid "ServerChan Advanced Settings"
|
||||
msgstr "Server酱 高级设置"
|
||||
|
||||
#: natmap.js:385
|
||||
msgid "ServerChan sendkey"
|
||||
msgstr "Server酱 sendkey"
|
||||
|
||||
#: natmap.js:496
|
||||
msgid "Service"
|
||||
msgstr "服务"
|
||||
|
||||
#: natmap.js:584
|
||||
msgid "Set to False if you want to use HTTP"
|
||||
msgstr "如果使用 HTTP,请勿勾选"
|
||||
|
||||
#: natmap.js:127
|
||||
msgid "STUN server"
|
||||
msgstr "STUN 服务器"
|
||||
|
||||
#: natmap.js:265
|
||||
msgid "such as adsl_1 or wan"
|
||||
msgstr "例如:adsl_1 或 wan"
|
||||
|
||||
#: natmap.js:416
|
||||
msgid "such as http://127.0.0.1:8080 or http://ikuai.lan:8080"
|
||||
msgstr "例如:http://127.0.0.1:8080 或 http://ikuai.lan:8080"
|
||||
|
||||
#: natmap.js:220 natmap.js:607 natmap.js:646
|
||||
msgid "such as http://127.0.0.1:8080 or http://ikuai.lan:8080.if use host,must close Rebind protection in DHCP and DNS"
|
||||
msgstr "例如:http://127.0.0.1:8080 或 http://ikuai.lan:8080。如果使用 host ,必须关闭 DHCP/DNS 中的重绑定保护"
|
||||
|
||||
#: natmap.js:567
|
||||
msgid "such as http://127.0.0.1:8080 or http://ikuai.lan:8080.if use host,must close Rebind protection in DHCP/DNS"
|
||||
msgstr "例如 http://127.0.0.1:8080 或 http://ikuai.lan:8080。如果使用 host 地址而不是ip,则必须关闭 DHCP/DNS 中的重绑定保护"
|
||||
|
||||
#: natmap.js:252
|
||||
msgid "such as tcp or udp or tcp+udp"
|
||||
msgstr "例如:tcp 或 udp 或 tcp+udp"
|
||||
|
||||
#: natmap.js:208
|
||||
msgid "Target_Interface"
|
||||
msgstr "目标接口"
|
||||
|
||||
#: natmap.js:90 natmap.js:256
|
||||
msgid "TCP"
|
||||
msgstr "TCP"
|
||||
|
||||
#: natmap.js:255
|
||||
msgid "TCP+UDP"
|
||||
msgstr "TCP+UDP"
|
||||
|
||||
#: natmap.js:319
|
||||
msgid "Telegram Bot"
|
||||
msgstr "Telegram bot"
|
||||
|
||||
#: natmap.js:347
|
||||
msgid "Telegram Token"
|
||||
msgstr "Telegram 令牌"
|
||||
|
||||
#: natmap.js:501
|
||||
msgid "Transmission"
|
||||
msgstr "Transmission"
|
||||
|
||||
#: natmap.js:91 natmap.js:257
|
||||
msgid "UDP"
|
||||
msgstr "UDP"
|
||||
|
||||
#: natmap.js:594
|
||||
msgid "Update host with IP"
|
||||
msgstr "使用 IP 更新 host"
|
||||
|
||||
#: natmap.js:583
|
||||
msgid "Update HTTPS Port"
|
||||
msgstr "更新 HTTPS 端口"
|
||||
|
||||
#: natmap.js:614 natmap.js:653
|
||||
msgid "Username"
|
||||
msgstr "用户名"
|
||||
|
||||
#: natmap.js:108
|
||||
msgid "Wan Interface"
|
||||
msgstr "WAN 接口"
|
||||
|
||||
#: natmap.js:605
|
||||
msgid "Web UI URL"
|
||||
msgstr "网页 URL"
|
||||
|
397
luci-app-natmap/luci-app-natmap/po/zh_Hant/luci-app-natmap.po
Normal file
@ -0,0 +1,397 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Generator: POEditor.com\n"
|
||||
"Project-Id-Version: luci-app-natmap\n"
|
||||
"Language: zh-Hant\n"
|
||||
|
||||
#: natmap.js:62
|
||||
msgid "General Settings"
|
||||
msgstr "常規設定"
|
||||
|
||||
#: natmap.js:63
|
||||
msgid "Forward Settings"
|
||||
msgstr "轉送設定"
|
||||
|
||||
#: natmap.js:64
|
||||
msgid "Notify Settings"
|
||||
msgstr "通知設定"
|
||||
|
||||
#: natmap.js:65
|
||||
msgid "Link Settings"
|
||||
msgstr "關聯設定"
|
||||
|
||||
#: natmap.js:66
|
||||
msgid "Custom Settings"
|
||||
msgstr "自訂設定"
|
||||
|
||||
#: natmap.js:78
|
||||
msgid "Name"
|
||||
msgstr "名稱"
|
||||
|
||||
#: natmap.js:87
|
||||
msgid "Protocol"
|
||||
msgstr "協定"
|
||||
|
||||
#: natmap.js:90 natmap.js:256
|
||||
msgid "TCP"
|
||||
msgstr "TCP"
|
||||
|
||||
#: natmap.js:91 natmap.js:257
|
||||
msgid "UDP"
|
||||
msgstr "UDP"
|
||||
|
||||
#: natmap.js:97
|
||||
msgid "Restrict to address family"
|
||||
msgstr "位址戶數限制"
|
||||
|
||||
#: natmap.js:100
|
||||
msgid "IPv4 and IPv6"
|
||||
msgstr "IPv4 和 IPv6"
|
||||
|
||||
#: natmap.js:101
|
||||
msgid "IPv4 only"
|
||||
msgstr "單一IPv4"
|
||||
|
||||
#: natmap.js:102
|
||||
msgid "IPv6 only"
|
||||
msgstr "單一IPv6"
|
||||
|
||||
#: natmap.js:108
|
||||
msgid "Wan Interface"
|
||||
msgstr "WAN 介面"
|
||||
|
||||
#: natmap.js:117
|
||||
msgid "Keep-alive interval"
|
||||
msgstr "Keep-alive 間隔"
|
||||
|
||||
#: natmap.js:127
|
||||
msgid "STUN server"
|
||||
msgstr "STUN伺服器"
|
||||
|
||||
#: natmap.js:138
|
||||
msgid "HTTP server"
|
||||
msgstr "HTTP伺服器"
|
||||
|
||||
#: natmap.js:139
|
||||
msgid "For TCP mode"
|
||||
msgstr "用於tcp模式"
|
||||
|
||||
#: natmap.js:145
|
||||
msgid "Bind port"
|
||||
msgstr "綁定連接埠"
|
||||
|
||||
#: natmap.js:156
|
||||
msgid "Enable Forward"
|
||||
msgstr "啟用轉送"
|
||||
|
||||
#: natmap.js:170
|
||||
msgid "Forward mode"
|
||||
msgstr "轉送模式"
|
||||
|
||||
#: natmap.js:173
|
||||
msgid "firewall dnat"
|
||||
msgstr "firewall dnat"
|
||||
|
||||
#: natmap.js:174
|
||||
msgid "natmap"
|
||||
msgstr "natmap"
|
||||
|
||||
#: natmap.js:175
|
||||
msgid "ikuai"
|
||||
msgstr "ikuai"
|
||||
|
||||
#: natmap.js:183
|
||||
msgid "Forward target"
|
||||
msgstr "轉送目標"
|
||||
|
||||
#: natmap.js:195
|
||||
msgid "Forward target port"
|
||||
msgstr "轉送目標連接埠"
|
||||
|
||||
#: natmap.js:196
|
||||
msgid "0 will forward to the out port get from STUN"
|
||||
msgstr "填寫 0 ,則轉送至從STUN取得的外部端口"
|
||||
|
||||
#: natmap.js:208
|
||||
msgid "Target_Interface"
|
||||
msgstr "目標介面"
|
||||
|
||||
#: natmap.js:218
|
||||
msgid "Ikuai Web URL"
|
||||
msgstr "愛快網址"
|
||||
|
||||
#: natmap.js:220 natmap.js:607 natmap.js:646
|
||||
msgid "such as http://127.0.0.1:8080 or http://ikuai.lan:8080.if use host,must close Rebind protection in DHCP and DNS"
|
||||
msgstr "例如:http://127.0.0.1:8080 或 http://ikuai.lan:8080。如果使用 host ,必須關閉 DHCP/DNS 中的重綁定保護"
|
||||
|
||||
#: natmap.js:231
|
||||
msgid "Ikuai Username"
|
||||
msgstr "愛快用戶名"
|
||||
|
||||
#: natmap.js:241
|
||||
msgid "Ikuai Password"
|
||||
msgstr "愛快密碼"
|
||||
|
||||
#: natmap.js:251
|
||||
msgid "Ikuai Mapping Protocol"
|
||||
msgstr "連接埠映射協定"
|
||||
|
||||
#: natmap.js:252
|
||||
msgid "such as tcp or udp or tcp+udp"
|
||||
msgstr "例如:tcp 或 udp 或 tcp+udp"
|
||||
|
||||
#: natmap.js:255
|
||||
msgid "TCP+UDP"
|
||||
msgstr "TCP+UDP"
|
||||
|
||||
#: natmap.js:264
|
||||
msgid "Ikuai Mapping Wan Interface"
|
||||
msgstr "連接埠映射外網介面"
|
||||
|
||||
#: natmap.js:265
|
||||
msgid "such as adsl_1 or wan"
|
||||
msgstr "例如:adsl_1 或 wan"
|
||||
|
||||
#: natmap.js:276 natmap.js:458 natmap.js:683
|
||||
msgid "Advanced Settings"
|
||||
msgstr "進階設定"
|
||||
|
||||
#: natmap.js:286 natmap.js:471 natmap.js:697
|
||||
msgid "Max Retries"
|
||||
msgstr "最大重試次數"
|
||||
|
||||
#: natmap.js:287 natmap.js:472 natmap.js:698
|
||||
msgid "max retries,default 0 means execute only once"
|
||||
msgstr "最大重試次數,預設 0 表示僅執行一次調用"
|
||||
|
||||
#: natmap.js:297 natmap.js:482 natmap.js:708
|
||||
msgid "Retry Interval"
|
||||
msgstr "重試間隔時間"
|
||||
|
||||
#: natmap.js:298 natmap.js:483 natmap.js:709
|
||||
msgid "Retry Interval, unit is seconds, default 0 is 3 seconds"
|
||||
msgstr "單次重試間隔時間,單位是秒,預設 0 表示 3秒"
|
||||
|
||||
#: natmap.js:307
|
||||
msgid "Enable Notify"
|
||||
msgstr "啟用通知"
|
||||
|
||||
#: natmap.js:315
|
||||
msgid "Notify channel"
|
||||
msgstr "通知頻道"
|
||||
|
||||
#: natmap.js:319
|
||||
msgid "Telegram Bot"
|
||||
msgstr "Telegram Bot"
|
||||
|
||||
#: natmap.js:320
|
||||
msgid "PushPlus"
|
||||
msgstr "PushPlus"
|
||||
|
||||
#: natmap.js:321
|
||||
msgid "ServerChan"
|
||||
msgstr "Server醬"
|
||||
|
||||
#: natmap.js:322
|
||||
msgid "Gotify"
|
||||
msgstr "Gotify"
|
||||
|
||||
#: natmap.js:329
|
||||
msgid "Chat ID"
|
||||
msgstr "會話ID"
|
||||
|
||||
#: natmap.js:332
|
||||
msgid "Get chat_id"
|
||||
msgstr "獲取chat_id"
|
||||
|
||||
#: natmap.js:334 natmap.js:352 natmap.js:374 natmap.js:390 natmap.js:426
|
||||
msgid "Click here"
|
||||
msgstr "點這裡"
|
||||
|
||||
#: natmap.js:337
|
||||
msgid "<br />If you want to send to a group/channel, please create a non-Chinese group/channel (for easier chatid lookup, you can rename it later).<br />Add the bot to the group, send a message, and use https://api.telegram.org/bot token /getUpdates to obtain the chatid."
|
||||
msgstr "<br />如需透過群組/自主頻道,請透過建立一個非中文的群組或頻道(此時找到chatid,後續再更名)<br />將新增機器人至群組,發送訊息後https: // /api.telegram.org/bot token /getUpdates 取得chatid"
|
||||
|
||||
#: natmap.js:350
|
||||
msgid "Get Bot"
|
||||
msgstr "取得機器人"
|
||||
|
||||
#: natmap.js:354
|
||||
msgid "<br />Send a message to the created bot to initiate a conversation."
|
||||
msgstr "<br />與創建的機器人發送訊息,開啟對話"
|
||||
|
||||
#: natmap.js:363
|
||||
msgid "http proxy"
|
||||
msgstr "http代理"
|
||||
|
||||
#: natmap.js:372 natmap.js:388 natmap.js:424
|
||||
msgid "Get Instructions"
|
||||
msgstr "獲取介紹"
|
||||
|
||||
#: natmap.js:385
|
||||
msgid "ServerChan sendkey"
|
||||
msgstr "Server醬sendkey"
|
||||
|
||||
#: natmap.js:393
|
||||
msgid "<br />Since the asynchronous push queue is used, only whether the put into the queue is successful is detected."
|
||||
msgstr "<br />由於使用佇列隊列,所以僅檢測隊列是否成功"
|
||||
|
||||
#: natmap.js:404
|
||||
msgid "ServerChan Advanced Settings"
|
||||
msgstr "伺服器醬高級設定"
|
||||
|
||||
#: natmap.js:414
|
||||
msgid "Self-built Server Url"
|
||||
msgstr "自建伺服器URL"
|
||||
|
||||
#: natmap.js:416
|
||||
msgid "such as http://127.0.0.1:8080 or http://ikuai.lan:8080"
|
||||
msgstr "例如:http://127.0.0.1:8080 或 http://ikuai.lan:8080"
|
||||
|
||||
#: natmap.js:422
|
||||
msgid "Gotify url"
|
||||
msgstr "Gotify 網址"
|
||||
|
||||
#: natmap.js:446
|
||||
msgid "Gotify priority"
|
||||
msgstr "Gotify 通知優先級"
|
||||
|
||||
#: natmap.js:492
|
||||
msgid "Enable link setting"
|
||||
msgstr "啟用關聯設定"
|
||||
|
||||
#: natmap.js:496
|
||||
msgid "Service"
|
||||
msgstr "服務"
|
||||
|
||||
#: natmap.js:499
|
||||
msgid "Emby"
|
||||
msgstr "Emby"
|
||||
|
||||
#: natmap.js:500
|
||||
msgid "qBittorrent"
|
||||
msgstr "qBittorrent"
|
||||
|
||||
#: natmap.js:501
|
||||
msgid "Transmission"
|
||||
msgstr "Transmission"
|
||||
|
||||
#: natmap.js:502
|
||||
msgid "Cloudflare Origin Rule"
|
||||
msgstr "Cloudflare Origin Rule"
|
||||
|
||||
#: natmap.js:503
|
||||
msgid "Cloudflare Redirect Rule"
|
||||
msgstr "Cloudflare Redirect Rule"
|
||||
|
||||
#: natmap.js:574
|
||||
msgid "API Key"
|
||||
msgstr "API key"
|
||||
|
||||
#: natmap.js:565
|
||||
msgid "EMBY URL"
|
||||
msgstr "EMBY 網址"
|
||||
|
||||
#: natmap.js:583
|
||||
msgid "Update HTTPS Port"
|
||||
msgstr "更新 HTTPS 連接埠"
|
||||
|
||||
#: natmap.js:584
|
||||
msgid "Set to False if you want to use HTTP"
|
||||
msgstr "若使用HTTP,請勿勾選"
|
||||
|
||||
#: natmap.js:594
|
||||
msgid "Update host with IP"
|
||||
msgstr "使用IP更新host"
|
||||
|
||||
#: natmap.js:605
|
||||
msgid "Web UI URL"
|
||||
msgstr "網址"
|
||||
|
||||
#: natmap.js:614 natmap.js:653
|
||||
msgid "Username"
|
||||
msgstr "使用者名稱"
|
||||
|
||||
#: natmap.js:619 natmap.js:658
|
||||
msgid "Password"
|
||||
msgstr "密碼"
|
||||
|
||||
#: natmap.js:624 natmap.js:663
|
||||
msgid "Allow IPv6"
|
||||
msgstr "允許IPv6"
|
||||
|
||||
#: natmap.js:633 natmap.js:672
|
||||
msgid "IPv6 Address"
|
||||
msgstr "IPv6位址"
|
||||
|
||||
#: natmap.js:644
|
||||
msgid "RPC URL"
|
||||
msgstr "RPC 位址"
|
||||
|
||||
#: natmap.js:722
|
||||
msgid "Enable custom script's config"
|
||||
msgstr "授權自訂腳本"
|
||||
|
||||
#: natmap.js:731
|
||||
msgid "custom script"
|
||||
msgstr "自訂腳本"
|
||||
|
||||
#: natmap.js:732
|
||||
msgid "custom script path,such as /etc/natmap/custom.sh"
|
||||
msgstr "自訂腳本路徑,例如:/etc/natmap/custom.sh"
|
||||
|
||||
#: natmap.js:741
|
||||
msgid "External IP"
|
||||
msgstr "外部ip"
|
||||
|
||||
#: natmap.js:748
|
||||
msgid "External Port"
|
||||
msgstr "外部連接埠"
|
||||
|
||||
#: natmap.js:758
|
||||
msgid "enable"
|
||||
msgstr "啟用"
|
||||
|
||||
#: natmap.js:57
|
||||
msgid "NatMap Settings"
|
||||
msgstr "國家地圖設置"
|
||||
|
||||
#: natmap.js:510
|
||||
msgid "Cloudflare Token"
|
||||
msgstr "Cloudflare 令牌"
|
||||
|
||||
#: natmap.js:521
|
||||
msgid "Cloudflare Zone ID"
|
||||
msgstr "Cloudflare 區域 ID"
|
||||
|
||||
#: natmap.js:533
|
||||
msgid "Origin Rule Name"
|
||||
msgstr "原產地規則名稱"
|
||||
|
||||
#: natmap.js:544
|
||||
msgid "Redirect Rule Name"
|
||||
msgstr "重定向規則名稱"
|
||||
|
||||
#: natmap.js:347
|
||||
msgid "Telegram Token"
|
||||
msgstr "電報令牌"
|
||||
|
||||
#: natmap.js:370
|
||||
msgid "PushPlus Token"
|
||||
msgstr "PushPlus 令牌"
|
||||
|
||||
#: natmap.js:436
|
||||
msgid "Gotify Token"
|
||||
msgstr "Gotify 令牌"
|
||||
|
||||
#: natmap.js:554
|
||||
msgid "Redirect Rule Target URL"
|
||||
msgstr "重定向規則目標URL"
|
||||
|
||||
#: natmap.js:567
|
||||
msgid "such as http://127.0.0.1:8080 or http://ikuai.lan:8080.if use host,must close Rebind protection in DHCP/DNS"
|
||||
msgstr "例如 http://127.0.0.1:8080 或 http://ikuai.lan:8080。如果使用主機位址而不是 ip,則必須關閉 DHCP/DNS 中的重綁定保護"
|
||||
|
68
luci-app-natmap/luci-app-natmap/root/etc/config/natmap
Executable file
@ -0,0 +1,68 @@
|
||||
config natmap
|
||||
option natmap_enable '0'
|
||||
option general_nat_name ''
|
||||
option general_nat_protocol 'tcp'
|
||||
option general_ip_address_family ''
|
||||
option general_wan_interface ''
|
||||
option general_interval ''
|
||||
option general_stun_server 'stunserver.stunprotocol.org'
|
||||
option general_http_server 'example.com'
|
||||
option general_bind_port '8080'
|
||||
option forward_enable '0'
|
||||
option forward_target_ip ''
|
||||
option forward_target_port ''
|
||||
option forward_mode 'firewall'
|
||||
option forward_natmap_target_interface ''
|
||||
option forward_ikuai_web_url ''
|
||||
option forward_ikuai_username ''
|
||||
option forward_ikuai_password ''
|
||||
option forward_ikuai_mapping_protocol ''
|
||||
option forward_ikuai_mapping_wan_interface ''
|
||||
option forward_advanced_enable '0'
|
||||
option forward_advanced_max_retries '1'
|
||||
option forward_advanced_sleep_time '3'
|
||||
option notify_enable '0'
|
||||
option notify_mode 'telegram_bot'
|
||||
option notify_telegram_bot_chat_id 'chat_id'
|
||||
option notify_telegram_bot_token 'token'
|
||||
option notify_telegram_bot_proxy ''
|
||||
option notify_pushplus_token 'token'
|
||||
option notify_serverchan_sendkey ''
|
||||
option notify_serverchan_advanced_enable '0'
|
||||
option notify_serverchan_advanced_url ''
|
||||
option notify_gotify_url ''
|
||||
option notify_gotify_token ''
|
||||
option notify_gotify_priority '5'
|
||||
option notify_advanced_enable '0'
|
||||
option notify_advanced_max_retries '1'
|
||||
option notify_advanced_sleep_time '3'
|
||||
option link_enable '0'
|
||||
option link_mode 'qbittorrent'
|
||||
option link_emby_url ''
|
||||
option link_emby_api_key ''
|
||||
option link_emby_use_https '0'
|
||||
option link_emby_update_host_with_ip '0'
|
||||
option link_qb_web_url ''
|
||||
option link_qb_username ''
|
||||
option link_qb_password ''
|
||||
option link_qb_ipv6_address '::/-64'
|
||||
option link_qb_allow_ipv6 '1'
|
||||
option link_tr_rpc_url ''
|
||||
option link_tr_username ''
|
||||
option link_tr_password ''
|
||||
option link_tr_ipv6_address '::/-64'
|
||||
option link_tr_allow_ipv6 '1'
|
||||
option link_cloudflare_token ''
|
||||
option link_cloudflare_zone_id ''
|
||||
option link_cloudflare_origin_rule_name ''
|
||||
option link_cloudflare_redirect_rule_name ''
|
||||
option link_cloudflare_redirect_rule_target_url ''
|
||||
option link_advanced_enable '0'
|
||||
option link_advanced_max_retries '1'
|
||||
option link_advanced_sleep_time '3'
|
||||
option custom_script_enable '0'
|
||||
option custom_script_path ''
|
||||
|
||||
|
||||
|
||||
|
190
luci-app-natmap/luci-app-natmap/root/etc/init.d/natmap
Executable file
@ -0,0 +1,190 @@
|
||||
#!/bin/sh /etc/rc.common
|
||||
|
||||
START=99
|
||||
USE_PROCD=1
|
||||
|
||||
NAME=natmap
|
||||
PROG=/usr/bin/$NAME
|
||||
|
||||
STATUS_PATH=/var/run/natmap
|
||||
|
||||
load_interfaces() {
|
||||
config_get interface "$1" interface
|
||||
config_get natmap_enable "$1" natmap_enable 1
|
||||
|
||||
[ "${natmap_enable}" = "1" ] && interfaces=" ${interface} ${interfaces}"
|
||||
}
|
||||
|
||||
validate_section_natmap() {
|
||||
uci_load_validate "${NAME}" natmap "$1" "$2" \
|
||||
'natmap_enable:bool:0' \
|
||||
'general_ip_address_family:string' \
|
||||
'general_nat_protocol:string' \
|
||||
'general_wan_interface:string' \
|
||||
'general_nat_name:string' \
|
||||
'general_interval:uinteger' \
|
||||
'general_stun_server:host' \
|
||||
'general_http_server:host' \
|
||||
'general_bind_port:port' \
|
||||
'forward_enable:bool:0' \
|
||||
'forward_target_ip:host' \
|
||||
'forward_target_port:port' \
|
||||
'forward_mode:string' \
|
||||
'forward_natmap_target_interface:string' \
|
||||
'forward_ikuai_web_url:string' \
|
||||
'forward_ikuai_username:string' \
|
||||
'forward_ikuai_password:string' \
|
||||
'forward_ikuai_mapping_protocol:string' \
|
||||
'forward_ikuai_mapping_wan_interface:string' \
|
||||
'forward_advanced_enable:bool:0' \
|
||||
'forward_advanced_max_retries:uinteger' \
|
||||
'forward_advanced_sleep_time:uinteger' \
|
||||
'notify_enable:bool:0' \
|
||||
'notify_mode:string' \
|
||||
'notify_telegram_bot_chat_id:string' \
|
||||
'notify_telegram_bot_token:string' \
|
||||
'notify_telegram_bot_proxy:string' \
|
||||
'notify_pushplus_token:string' \
|
||||
'notify_serverchan_sendkey:string' \
|
||||
'notify_serverchan_advanced_enable:bool:0' \
|
||||
'notify_serverchan_advanced_url:string' \
|
||||
'notify_gotify_url:string' \
|
||||
'notify_gotify_token:string' \
|
||||
'notify_gotify_priority:uinteger' \
|
||||
'notify_advanced_enable:bool:0' \
|
||||
'notify_advanced_max_retries:uinteger' \
|
||||
'notify_advanced_sleep_time:uinteger' \
|
||||
'link_enable:bool:0' \
|
||||
'link_mode:string' \
|
||||
'link_emby_url:string' \
|
||||
'link_emby_api_key:string' \
|
||||
'link_emby_use_https:bool:0' \
|
||||
'link_emby_update_host_with_ip:bool:0' \
|
||||
'link_qb_web_url:string' \
|
||||
'link_qb_username:string' \
|
||||
'link_qb_password:string' \
|
||||
'link_qb_ipv6_address:string' \
|
||||
'link_qb_allow_ipv6:bool:0' \
|
||||
'link_tr_rpc_url:string' \
|
||||
'link_tr_username:string' \
|
||||
'link_tr_password:string' \
|
||||
'link_tr_allow_ipv6:bool:0' \
|
||||
'link_tr_ipv6_address:string' \
|
||||
'link_cloudflare_token:string' \
|
||||
'link_cloudflare_zone_id:string' \
|
||||
'link_cloudflare_origin_rule_name:string' \
|
||||
'link_cloudflare_redirect_rule_name:string' \
|
||||
'link_cloudflare_redirect_rule_target_url:string' \
|
||||
'link_advanced_enable:bool:0' \
|
||||
'link_advanced_max_retries:uinteger' \
|
||||
'link_advanced_sleep_time:uinteger' \
|
||||
'custom_script_enable:bool:0' \
|
||||
'custom_script_path:file'
|
||||
}
|
||||
|
||||
natmap_instance() {
|
||||
[ "$2" = 0 ] || {
|
||||
echo "validation failed"
|
||||
return 1
|
||||
}
|
||||
|
||||
[ "${natmap_enable}" = 0 ] && return 1
|
||||
|
||||
procd_open_instance "$1"
|
||||
procd_set_param command "$PROG" \
|
||||
${general_interval:+-k "$general_interval"} \
|
||||
${general_stun_server:+-s "$general_stun_server"} \
|
||||
${general_http_server:+-h "$general_http_server"} \
|
||||
${general_bind_port:+-b "$general_bind_port"}
|
||||
|
||||
[ "${general_ip_address_family}" = ipv4 ] && procd_append_param command -4
|
||||
[ "${general_ip_address_family}" = ipv6 ] && procd_append_param command -6
|
||||
[ "${general_nat_protocol}" = 'udp' ] && procd_append_param command -u
|
||||
|
||||
[ -n "${general_wan_interface}" ] && {
|
||||
local ifname
|
||||
|
||||
network_get_device ifname "$general_wan_interface" || ifname="$general_wan_interface"
|
||||
procd_append_param command -i "$ifname"
|
||||
procd_append_param netdev "$ifname"
|
||||
}
|
||||
|
||||
[ -n "${forward_natmap_target_interface}" ] && {
|
||||
local ifname
|
||||
|
||||
network_get_device ifname "$forward_natmap_target_interface" || ifname="$forward_natmap_target_interface"
|
||||
procd_append_param command -i "$ifname"
|
||||
procd_append_param netdev "$ifname"
|
||||
}
|
||||
|
||||
[ "${forward_enable}" == 1 ] && [ "${forward_mode}" = natmap ] && [ -n "${forward_target_ip}" ] && procd_append_param command -t "$forward_target_ip" -p "$forward_target_port"
|
||||
for e in $EXTRA_ENV; do
|
||||
eval value=\$${e}
|
||||
option_name=$(echo "$e" | tr 'a-z' 'A-Z')
|
||||
if [ -n "$value" ]; then
|
||||
procd_append_param env $option_name="$value"
|
||||
fi
|
||||
done
|
||||
|
||||
# console.log "natmap"
|
||||
procd_append_param command -e /usr/share/natmap/update.sh
|
||||
|
||||
procd_set_param respawn
|
||||
procd_set_param stdout 1
|
||||
procd_set_param stderr 1
|
||||
|
||||
procd_close_instance
|
||||
}
|
||||
|
||||
clear_status_files() {
|
||||
find "${STATUS_PATH}" -type f -print0 | xargs -0 rm -f --
|
||||
}
|
||||
|
||||
service_triggers() {
|
||||
local interfaces
|
||||
|
||||
procd_add_reload_trigger "${NAME}"
|
||||
|
||||
config_load "${NAME}"
|
||||
config_foreach load_interfaces natmap
|
||||
|
||||
[ -n "${interfaces}" ] && {
|
||||
for n in $interfaces; do
|
||||
procd_add_reload_interface_trigger $n
|
||||
done
|
||||
}
|
||||
|
||||
procd_add_validation validate_section_natmap
|
||||
}
|
||||
|
||||
start_service() {
|
||||
. /lib/functions/network.sh
|
||||
config_cb() {
|
||||
[ $# -eq 0 ] && return
|
||||
|
||||
option_cb() {
|
||||
local option="$1"
|
||||
|
||||
EXTRA_ENV="$EXTRA_ENV $option"
|
||||
}
|
||||
|
||||
list_cb() {
|
||||
local name="$1"
|
||||
EXTRA_ENV="$EXTRA_ENV $name"
|
||||
}
|
||||
}
|
||||
mkdir -p "${STATUS_PATH}"
|
||||
clear_status_files
|
||||
|
||||
config_load "${NAME}"
|
||||
config_foreach validate_section_natmap natmap natmap_instance
|
||||
}
|
||||
|
||||
reload_service() {
|
||||
stop
|
||||
start
|
||||
}
|
||||
|
||||
service_stopped() {
|
||||
clear_status_files
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
{
|
||||
"admin/services/natmap": {
|
||||
"title": "NATMap",
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "natmap"
|
||||
},
|
||||
"depends": {
|
||||
"acl": [
|
||||
"luci-app-natmap"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
35
luci-app-natmap/luci-app-natmap/root/usr/share/natmap/forward.sh
Executable file
@ -0,0 +1,35 @@
|
||||
#!/bin/bash
|
||||
# NATMap
|
||||
outter_ip=$1
|
||||
outter_port=$2
|
||||
ip4p=$3
|
||||
inner_port=$4
|
||||
protocol=$5
|
||||
|
||||
# 如果$forward_target_port为空或者$forward_target_ip为空则退出
|
||||
if [ -z "$FORWARD_TARGET_PORT" ] || [ -z "$FORWARD_TARGET_IP" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
forward_script=""
|
||||
# case $FORWARD_MODE in
|
||||
# "firewall")
|
||||
# forward_script="/usr/share/natmap/plugin-forward/firewall-forward.sh"
|
||||
# ;;
|
||||
# "ikuai")
|
||||
# forward_script="/usr/share/natmap/plugin-forward/ikuai-forward.sh"
|
||||
# ;;
|
||||
# *)
|
||||
# forward_script=""
|
||||
# ;;
|
||||
# esac
|
||||
|
||||
# 如果$FORWARD_MODE非空则执行对应的脚本
|
||||
if [ -n "${FORWARD_MODE}" ]; then
|
||||
forward_script="/usr/share/natmap/plugin-forward/${FORWARD_MODE}-forward.sh"
|
||||
fi
|
||||
|
||||
if [ -n "${forward_script}" ]; then
|
||||
# echo "$GENERAL_NAT_NAME execute forward script"
|
||||
bash "$forward_script" "$@"
|
||||
fi
|
20
luci-app-natmap/luci-app-natmap/root/usr/share/natmap/link.sh
Executable file
@ -0,0 +1,20 @@
|
||||
#!/bin/bash
|
||||
# NATMap
|
||||
outter_ip=$1
|
||||
outter_port=$2
|
||||
ip4p=$3
|
||||
inner_port=$4
|
||||
protocol=$5
|
||||
|
||||
link_script=""
|
||||
# echo "LINK_MODE: $LINK_MODE"
|
||||
|
||||
# 如果$LINK_MODE非空则执行对应的脚本
|
||||
if [ -n "${LINK_MODE}" ]; then
|
||||
link_script="/usr/share/natmap/plugin-link/${LINK_MODE}.sh"
|
||||
fi
|
||||
|
||||
if [ -n "${link_script}" ]; then
|
||||
echo "$GENERAL_NAT_NAME execute link script"
|
||||
bash "${link_script}" "$@"
|
||||
fi
|
44
luci-app-natmap/luci-app-natmap/root/usr/share/natmap/notify.sh
Executable file
@ -0,0 +1,44 @@
|
||||
#!/bin/bash
|
||||
# NATMap
|
||||
outter_ip=$1
|
||||
outter_port=$2
|
||||
ip4p=$3
|
||||
inner_port=$4
|
||||
protocol=$(echo $5 | tr 'a-z' 'A-Z')
|
||||
|
||||
msg="${GENERAL_NAT_NAME}
|
||||
New ${protocol} port mapping: ${inner_port} -> ${outter_ip}:${outter_port}
|
||||
IP4P: ${ip4p}"
|
||||
if [ ! -z "$MSG_OVERRIDE" ]; then
|
||||
msg="$MSG_OVERRIDE"
|
||||
fi
|
||||
|
||||
# notify_mode 判断
|
||||
notify_script=""
|
||||
# case $NOTIFY_MODE in
|
||||
# "telegram_bot")
|
||||
# notify_script="/usr/share/natmap/plugin-notify/telegram_bot.sh"
|
||||
# ;;
|
||||
# "pushplus")
|
||||
# notify_script="/usr/share/natmap/plugin-notify/pushplus.sh"
|
||||
# ;;
|
||||
# "serverchan")
|
||||
# notify_script="/usr/share/natmap/plugin-notify/serverchan.sh"
|
||||
# ;;
|
||||
# "gotify")
|
||||
# notify_script="/usr/share/natmap/plugin-notify/gotify.sh"
|
||||
# ;;
|
||||
# *)
|
||||
# notify_script=""
|
||||
# ;;
|
||||
# esac
|
||||
|
||||
# 如果$NOTIFY_MODE非空则执行对应的脚本
|
||||
if [ -n "${NOTIFY_MODE}" ]; then
|
||||
notify_script="/usr/share/natmap/plugin-notify/$NOTIFY_MODE.sh"
|
||||
fi
|
||||
|
||||
if [ -n "${notify_script}" ]; then
|
||||
echo "$GENERAL_NAT_NAME execute notify script"
|
||||
bash "$notify_script" "$msg"
|
||||
fi
|
@ -0,0 +1,84 @@
|
||||
#!/bin/bash
|
||||
# NATMap
|
||||
outter_ip=$1
|
||||
outter_port=$2
|
||||
ip4p=$3
|
||||
inner_port=$4
|
||||
protocol=$5
|
||||
|
||||
# if [ "$FORWARD_MODE" != firewall ]; then
|
||||
# exit 0
|
||||
# fi
|
||||
|
||||
if [ -z "$FORWARD_TARGET_PORT" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ -z "$FORWARD_TARGET_IP" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# get forward target port
|
||||
# final_forward_target_port=$([ "${FORWARD_TARGET_PORT}" == 0 ] ? $outter_port : "${FORWARD_TARGET_PORT}")
|
||||
final_forward_target_port=$((FORWARD_TARGET_PORT == 0 ? outter_port : FORWARD_TARGET_PORT))
|
||||
|
||||
# ipv4 firewall
|
||||
rule_name_v4=$(echo "${GENERAL_NAT_NAME}_v4" | sed 's/[^a-zA-Z0-9]/_/g' | awk '{print tolower($0)}')
|
||||
|
||||
# ipv4 redirect
|
||||
uci set firewall.$rule_name_v4=redirect
|
||||
uci set firewall.$rule_name_v4.name="$rule_name_v4"
|
||||
uci set firewall.$rule_name_v4.proto="$protocol"
|
||||
uci set firewall.$rule_name_v4.src="$GENERAL_WAN_INTERFACE"
|
||||
uci set firewall.$rule_name_v4.dest="$FORWOARD_TARGET_INTERFACE"
|
||||
uci set firewall.$rule_name_v4.target='DNAT'
|
||||
uci set firewall.$rule_name_v4.src_dport="${inner_port}"
|
||||
uci set firewall.$rule_name_v4.dest_ip="${FORWARD_TARGET_IP}"
|
||||
uci set firewall.$rule_name_v4.dest_port="${final_forward_target_port}"
|
||||
|
||||
# --------------------------------------------------------------------------------------------
|
||||
# QB and TR ipv6 forward
|
||||
# 检测link_enable
|
||||
if [ "${LINK_ENABLE}" != 1 ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ [ "${LINK_MODE}" = transmission ] && [ "${LINK_TR_ALLOW_IPV6}" = 1 ] ] || [ [ "${LINK_MODE}" = qbittorrent ] && ["${LINK_QB_ALLOW_IPV6}" != 1 ] ]; then
|
||||
|
||||
# get rule name
|
||||
rule_name_v6=$(echo "${GENERAL_NAT_NAME}_v6_allow" | sed 's/[^a-zA-Z0-9]/_/g' | awk '{print tolower($0)}')
|
||||
|
||||
# echo "rule_name_v6: $rule_name_v6"
|
||||
# ipv6 allow
|
||||
uci set firewall.$rule_name_v6=rule
|
||||
uci set firewall.$rule_name_v6.name="$rule_name_v6"
|
||||
uci set firewall.$rule_name_v6.src="$GENERAL_WAN_INTERFACE"
|
||||
uci set firewall.$rule_name_v6.dest="$FORWOARD_TARGET_INTERFACE"
|
||||
uci set firewall.$rule_name_v6.target='ACCEPT'
|
||||
uci set firewall.$rule_name_v6.proto="$protocol"
|
||||
uci set firewall.$rule_name_v6.family='ipv6'
|
||||
uci set firewall.$rule_name_v6.dest_port="$final_forward_target_port"
|
||||
|
||||
# check if dest_ip is already set with return code
|
||||
if uci get firewall.$rule_name_v6.dest_ip >/dev/null 2>&1; then
|
||||
uci del firewall.$rule_name_v6.dest_ip
|
||||
fi
|
||||
|
||||
# add dest_ip list
|
||||
case "${LINK_MODE}" in
|
||||
"transmission")
|
||||
for ip in $LINK_TR_IPV6_ADDRESS; do
|
||||
uci add_list firewall.$rule_name_v6.dest_ip="${ip}"
|
||||
done
|
||||
;;
|
||||
"qbittorrent")
|
||||
for ip in $LINK_QB_IPV6_ADDRESS; do
|
||||
uci add_list firewall.$rule_name_v6.dest_ip="${ip}"
|
||||
done
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# reload
|
||||
uci commit firewall
|
||||
/etc/init.d/firewall reload
|
@ -0,0 +1,222 @@
|
||||
#!/bin/bash
|
||||
# ikuai_version=3.7.6
|
||||
|
||||
# natmap
|
||||
outter_ip=$1
|
||||
outter_port=$2
|
||||
ip4p=$3
|
||||
inner_port=$4
|
||||
protocol=$5
|
||||
|
||||
## ikuai参数获取
|
||||
# lan_port
|
||||
mapping_lan_port=""
|
||||
if [ -z "${FORWARD_TARGET_PORT}" ] || [ "${FORWARD_TARGET_PORT}" -eq 0 ]; then
|
||||
mapping_lan_port=$outter_port
|
||||
else
|
||||
mapping_lan_port=${FORWARD_TARGET_PORT}
|
||||
fi
|
||||
|
||||
# login api and call api
|
||||
ikuai_login_api="/Action/login"
|
||||
ikuai_call_api="/Action/call"
|
||||
call_url="$(echo $FORWARD_IKUAI_WEB_URL | sed 's/\/$//')${ikuai_call_api}"
|
||||
login_url="$(echo $FORWARD_IKUAI_WEB_URL | sed 's/\/$//')${ikuai_login_api}"
|
||||
|
||||
# 浏览器headers
|
||||
headers='{"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36",
|
||||
"Accept": "application/json",
|
||||
"Content-type": "application/json;charset=utf-8",
|
||||
"Accept-Language": "zh-CN"}'
|
||||
|
||||
# 登录ikuai
|
||||
# This function performs the login action
|
||||
# Parameters:
|
||||
# - login_username: username for login
|
||||
# - login_password: password for login
|
||||
function login_action() {
|
||||
local login_username="$1"
|
||||
local login_password="$2"
|
||||
|
||||
# Calculate the MD5 hash value of the password and convert it to hexadecimal
|
||||
local passwd=$(echo -n "$login_password" | openssl dgst -md5 -hex | awk '{print $2}')
|
||||
|
||||
# Concatenate 'salt_11' and the password, and encode it using base64
|
||||
local pass=$(echo -n "salt_11$passwd" | openssl enc -base64)
|
||||
|
||||
# Create the JSON payload for the login request
|
||||
local login_params='{
|
||||
"username": "'"$login_username"'",
|
||||
"passwd": "'"$passwd"'",
|
||||
"pass": "'"$pass"'",
|
||||
"remember_password": ""
|
||||
}'
|
||||
|
||||
# Send the login request, extract the session ID (cookie) from the response headers, and store it in a variable
|
||||
local login_cookie=$(curl -s -D - -H "$headers" -X POST -d "$login_params" "$login_url" | awk -F' ' '/Set-Cookie:/ {print $2}')
|
||||
|
||||
# echo the login_cookie
|
||||
echo "$login_cookie"
|
||||
}
|
||||
|
||||
# 查询端口映射
|
||||
# Function to show the mapping action
|
||||
# Parameters:
|
||||
# - show_cookie: Cookie value for authentication
|
||||
# - show_comment: Comment to filter the results
|
||||
function show_mapping_action() {
|
||||
local show_cookie="$1"
|
||||
local show_comment="$2"
|
||||
# Construct the payload for the API request
|
||||
local show_payload='{
|
||||
"func_name": "dnat",
|
||||
"action": "show",
|
||||
"param": {
|
||||
"FINDS": "lan_addr,lan_port,wan_port,comment",
|
||||
"KEYWORDS": "'"$show_comment"'",
|
||||
"TYPE": "total,data",
|
||||
"limit": "0,20",
|
||||
"ORDER_BY": "",
|
||||
"ORDER": ""
|
||||
}
|
||||
}'
|
||||
|
||||
# Send the API request and store the response in show_result variable
|
||||
local show_result=$(curl -s -X POST -H "$headers" -b "$show_cookie" -d "$show_payload" "$call_url")
|
||||
# Extract the show_ids from the response using jq
|
||||
local show_ids=$(echo "$show_result" | jq -r '.Data.data[].id')
|
||||
|
||||
# echo the show_ids
|
||||
# echo "${show_ids[@]}"
|
||||
for id in "${show_ids[@]}"; do
|
||||
echo "$id"
|
||||
done
|
||||
}
|
||||
|
||||
# 删除端口映射
|
||||
# Deletes a mapping action
|
||||
# Arguments:
|
||||
# - del_cookie: The cookie used for authentication
|
||||
# - del_ids: An array of DNAT IDs to be deleted
|
||||
function del_mapping_action() {
|
||||
local del_cookie="$1"
|
||||
local del_id="$2"
|
||||
# Declare an empty array to store the delete response
|
||||
local del_result=""
|
||||
|
||||
# Construct the payload for the delete request.
|
||||
local del_payload='{
|
||||
"func_name": "dnat",
|
||||
"action": "del",
|
||||
"param": {
|
||||
"id": "'"$del_id"'"
|
||||
}
|
||||
}'
|
||||
|
||||
# Send the delete request using cURL and store the response.
|
||||
del_response=$(curl -s -X POST -H "$headers" -b "$del_cookie" -d "$del_payload" "$call_url")
|
||||
# echo "del_ids: $del_ids"
|
||||
# echo "del_response: $del_response"
|
||||
}
|
||||
|
||||
# 增加端口映射
|
||||
# Function to add a mapping action
|
||||
# Parameters:
|
||||
# - add_cookie - The cookie for authentication
|
||||
# - add_comment - The comment for the mapping action
|
||||
function add_mapping_action() {
|
||||
local add_cookie="$1"
|
||||
local add_comment="$2"
|
||||
local enabled="yes"
|
||||
|
||||
# Create the payload JSON object
|
||||
local add_payload='{
|
||||
"func_name": "dnat",
|
||||
"action": "add",
|
||||
"param": {
|
||||
"enabled": "'"$enabled"'",
|
||||
"comment": "'"$add_comment"'",
|
||||
"interface": "'"$FORWARD_IKUAI_MAPPING_WAN_INTERFACE"'",
|
||||
"lan_addr": "'"$FORWARD_TARGET_IP"'",
|
||||
"protocol": "'"$FORWARD_IKUAI_MAPPING_PROTOCOL"'",
|
||||
"wan_port": "'"$GENERAL_BIND_PORT"'",
|
||||
"lan_port": "'"$mapping_lan_port"'",
|
||||
"src_addr": ""
|
||||
}
|
||||
}'
|
||||
|
||||
# Send the POST request to the specified URL with the payload
|
||||
local add_result=$(curl -s -X POST -H "$headers" -b "$add_cookie" -d "$add_payload" "$call_url")
|
||||
|
||||
# Output the result
|
||||
echo "$add_result"
|
||||
}
|
||||
|
||||
# 初始化参数
|
||||
# cookie
|
||||
cookie=""
|
||||
# 端口映射id
|
||||
dnat_ids=()
|
||||
# 端口映射备注,区分不同的端口映射,查询时使用,唯一,不可重复
|
||||
comment="natmap-${GENERAL_NAT_NAME}"
|
||||
|
||||
# 默认重试次数为1,休眠时间为3s
|
||||
max_retries=1
|
||||
sleep_time=3
|
||||
retry_count=0
|
||||
|
||||
# 判断是否开启高级功能
|
||||
if [ "${FORWARD_ADVANCED_ENABLE}" == 1 ] && [ -n "$FORWARD_ADVANCED_MAX_RETRIES" ] && [ -n "$FORWARD_ADVANCED_SLEEP_TIME" ]; then
|
||||
# 获取最大重试次数
|
||||
max_retries=$((FORWARD_ADVANCED_MAX_RETRIES == "0" ? 1 : FORWARD_ADVANCED_MAX_RETRIES))
|
||||
# 获取休眠时间
|
||||
sleep_time=$((FORWARD_ADVANCED_SLEEP_TIME == "0" ? 3 : FORWARD_ADVANCED_SLEEP_TIME))
|
||||
fi
|
||||
|
||||
# 端口映射处理开始
|
||||
for ((retry_count = 0; retry_count <= max_retries; retry_count++)); do
|
||||
# 登录
|
||||
cookie=$(login_action "$FORWARD_IKUAI_USERNAME" "$FORWARD_IKUAI_PASSWORD")
|
||||
# echo "cookie: $cookie"
|
||||
|
||||
if [ -n "$cookie" ]; then
|
||||
# echo "$GENERAL_NAT_NAME - $FORWARD_MODE 登录成功"
|
||||
|
||||
# 查询端口映射id
|
||||
dnat_ids=($(show_mapping_action "$cookie" "$comment"))
|
||||
# echo "dnat_ids: ${dnat_ids[@]}"
|
||||
|
||||
# 删除端口映射
|
||||
for dnat_id in "${dnat_ids[@]}"; do
|
||||
del_mapping_action "$cookie" "$dnat_id"
|
||||
done
|
||||
|
||||
# 再次查询端口映射id
|
||||
dnat_ids=($(show_mapping_action "$cookie" "$comment"))
|
||||
|
||||
# 验证对应端口映射是否全部删除
|
||||
if [ ${#dnat_ids[@]} -eq 0 ]; then
|
||||
# echo "$GENERAL_NAT_NAME - $FORWARD_MODE Port mapping deleted successfully"
|
||||
|
||||
# 添加端口映射
|
||||
add_response=$(add_mapping_action "$cookie" "$comment")
|
||||
# Check if the modification was successful
|
||||
if [ "$(echo "$add_response" | jq -r '.ErrMsg')" = "Success" ]; then
|
||||
echo "$GENERAL_NAT_NAME - $FORWARD_MODE Port mapping modified successfully"
|
||||
break
|
||||
else
|
||||
echo "$GENERAL_NAT_NAME - $FORWARD_MODE Failed to modify the port mapping"
|
||||
fi
|
||||
else
|
||||
echo "$GENERAL_NAT_NAME - $FORWARD_MODE Failed to delete the port mapping"
|
||||
fi
|
||||
fi
|
||||
# echo "$FORWARD_MODE 修改失败,休眠$sleep_time秒"
|
||||
sleep $sleep_time
|
||||
done
|
||||
|
||||
# Check if maximum retries reached
|
||||
if [ $retry_count -eq $max_retries ]; then
|
||||
echo "$GENERAL_NAT_NAME - $FORWARD_MODE 达到最大重试次数,无法修改"
|
||||
exit 1
|
||||
fi
|
@ -0,0 +1,64 @@
|
||||
#!/bin/bash
|
||||
|
||||
# NATMap
|
||||
outter_ip=$1
|
||||
outter_port=$2
|
||||
|
||||
function get_current_rule() {
|
||||
# Function to get the current rule
|
||||
#
|
||||
# Returns:
|
||||
# string: The current rule
|
||||
curl --request GET \
|
||||
--url https://api.cloudflare.com/client/v4/zones/$LINK_CLOUDFLARE_ZONE_ID/rulesets/phases/http_request_origin/entrypoint \
|
||||
--header "Authorization: Bearer $LINK_CLOUDFLARE_TOKEN" \
|
||||
--header 'Content-Type: application/json'
|
||||
}
|
||||
|
||||
# 默认重试次数为1,休眠时间为3s
|
||||
max_retries=1
|
||||
sleep_time=3
|
||||
|
||||
# 判断是否开启高级功能
|
||||
if [ "$LINK_ADVANCED_ENABLE" == 1 ] && [ -n "$LINK_ADVANCED_MAX_RETRIES" ] && [ -n "$LINK_ADVANCED_SLEEP_TIME" ]; then
|
||||
# 获取最大重试次数
|
||||
max_retries=$((LINK_ADVANCED_MAX_RETRIES == "0" ? 1 : LINK_ADVANCED_MAX_RETRIES))
|
||||
# 获取休眠时间
|
||||
sleep_time=$((LINK_ADVANCED_SLEEP_TIME == "0" ? 3 : LINK_ADVANCED_SLEEP_TIME))
|
||||
fi
|
||||
|
||||
# 初始化参数
|
||||
# currrent_rule=""
|
||||
retry_count=0
|
||||
# cloudflare_ruleset_id=""
|
||||
|
||||
for ((retry_count = 0; retry_count < max_retries; retry_count++)); do
|
||||
local currrent_rule=$(get_current_rule)
|
||||
local cloudflare_ruleset_id=$(echo "$currrent_rule" | jq '.result.id' | sed 's/"//g')
|
||||
|
||||
if [ -z "$cloudflare_ruleset_id" ]; then
|
||||
# echo "$GENERAL_NAT_NAME - $LINK_MODE 登录失败,休眠$sleep_time秒"
|
||||
sleep $sleep_time
|
||||
else
|
||||
echo "$GENERAL_NAT_NAME - $LINK_MODE 登录成功"
|
||||
# 修改
|
||||
LINK_CLOUDFLARE_ORIGIN_RULE_NAME="\"$LINK_CLOUDFLARE_ORIGIN_RULE_NAME\""
|
||||
local new_rule=$(echo "$currrent_rule" | jq '.result.rules| to_entries | map(select(.value.description == '"$LINK_CLOUDFLARE_ORIGIN_RULE_NAME"')) | .[].key')
|
||||
new_rule=$(echo "$currrent_rule" | jq '.result.rules['"$new_rule"'].action_parameters.origin.port = '"$outter_port"'')
|
||||
|
||||
# delete last_updated
|
||||
local body=$(echo "$new_rule" | jq '.result | del(.last_updated)')
|
||||
curl --request PUT \
|
||||
--url https://api.cloudflare.com/client/v4/zones/$LINK_CLOUDFLARE_ZONE_ID/rulesets/$cloudflare_ruleset_id \
|
||||
--header "Authorization: Bearer $LINK_CLOUDFLARE_TOKEN" \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data "$body"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
# Check if maximum retries reached
|
||||
if [ $retry_count -eq $max_retries ]; then
|
||||
echo "$GENERAL_NAT_NAME - $LINK_MODE 达到最大重试次数,无法修改"
|
||||
exit 1
|
||||
fi
|
@ -0,0 +1,65 @@
|
||||
#!/bin/bash
|
||||
|
||||
# NATMap
|
||||
outter_ip=$1
|
||||
outter_port=$2
|
||||
|
||||
function get_current_rule() {
|
||||
curl --request GET \
|
||||
--url https://api.cloudflare.com/client/v4/zones/$LINK_CLOUDFLARE_ZONE_ID/rulesets/phases/http_request_dynamic_redirect/entrypoint \
|
||||
--header "Authorization: Bearer $LINK_CLOUDFLARE_TOKEN" \
|
||||
--header 'Content-Type: application/json'
|
||||
}
|
||||
|
||||
# 默认重试次数为1,休眠时间为3s
|
||||
max_retries=1
|
||||
sleep_time=3
|
||||
|
||||
# 判断是否开启高级功能
|
||||
if [ "$LINK_ADVANCED_ENABLE" == 1 ] && [ -n "$LINK_ADVANCED_MAX_RETRIES" ] && [ -n "$LINK_ADVANCED_SLEEP_TIME" ]; then
|
||||
# 获取最大重试次数
|
||||
max_retries=$((LINK_ADVANCED_MAX_RETRIES == "0" ? 1 : LINK_ADVANCED_MAX_RETRIES))
|
||||
# 获取休眠时间
|
||||
sleep_time=$((LINK_ADVANCED_SLEEP_TIME == "0" ? 3 : LINK_ADVANCED_SLEEP_TIME))
|
||||
fi
|
||||
|
||||
# 初始化参数
|
||||
# currrent_rule=""
|
||||
retry_count=0
|
||||
# cloudflare_ruleset_id=""
|
||||
|
||||
for ((retry_count = 0; retry_count < max_retries; retry_count++)); do
|
||||
local currrent_rule=$(get_current_rule)
|
||||
local cloudflare_ruleset_id=$(echo "$currrent_rule" | jq '.result.id' | sed 's/"//g')
|
||||
|
||||
if [ -z "$cloudflare_ruleset_id" ]; then
|
||||
# echo "$LINK_MODE 登录失败,休眠$sleep_time秒"
|
||||
sleep $sleep_time
|
||||
else
|
||||
echo "$GENERAL_NAT_NAME - $LINK_MODE 登录成功"
|
||||
|
||||
LINK_CLOUDFLARE_REDIRECT_RULE_NAME="\"$LINK_CLOUDFLARE_REDIRECT_RULE_NAME\""
|
||||
# replace NEW_PORT with outter_port
|
||||
LINK_CLOUDFLARE_REDIRECT_RULE_TARGET_URL=$(echo $LINK_CLOUDFLARE_REDIRECT_RULE_TARGET_URL | sed 's/NEW_PORT/'"$outter_port"'/g')
|
||||
local new_rule=$(echo "$currrent_rule" | jq '.result.rules| to_entries | map(select(.value.description == '"$LINK_CLOUDFLARE_REDIRECT_RULE_NAME"')) | .[].key')
|
||||
new_rule=$(echo "$currrent_rule" | jq '.result.rules['"$new_rule"'].action_parameters.from_value.target_url.value = "'"$LINK_CLOUDFLARE_REDIRECT_RULE_TARGET_URL"'"')
|
||||
|
||||
local body=$(echo "$new_rule" | jq '.result')
|
||||
|
||||
# delete last_updated
|
||||
body=$(echo "$body" | jq 'del(.last_updated)')
|
||||
curl --request PUT \
|
||||
--url https://api.cloudflare.com/client/v4/zones/$LINK_CLOUDFLARE_ZONE_ID/rulesets/$cloudflare_ruleset_id \
|
||||
--header "Authorization: Bearer $LINK_CLOUDFLARE_TOKEN" \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data "$body"
|
||||
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
# Check if maximum retries reached
|
||||
if [ $retry_count -eq $max_retries ]; then
|
||||
echo "$GENERAL_NAT_NAME - $LINK_MODE 达到最大重试次数,无法修改"
|
||||
exit 1
|
||||
fi
|
52
luci-app-natmap/luci-app-natmap/root/usr/share/natmap/plugin-link/emby.sh
Executable file
@ -0,0 +1,52 @@
|
||||
#!/bin/bash
|
||||
|
||||
# NATMap
|
||||
outter_ip=$1
|
||||
outter_port=$2
|
||||
LINK_EMBY_URL=$(echo $LINK_EMBY_URL | sed 's/\/$//')
|
||||
|
||||
# 默认重试次数为1,休眠时间为3s
|
||||
max_retries=1
|
||||
sleep_time=3
|
||||
|
||||
# 判断是否开启高级功能
|
||||
if [ "$LINK_ADVANCED_ENABLE" == 1 ] && [ -n "$LINK_ADVANCED_MAX_RETRIES" ] && [ -n "$LINK_ADVANCED_SLEEP_TIME" ]; then
|
||||
# 获取最大重试次数
|
||||
max_retries=$((LINK_ADVANCED_MAX_RETRIES == "0" ? 1 : LINK_ADVANCED_MAX_RETRIES))
|
||||
# 获取休眠时间
|
||||
sleep_time=$((LINK_ADVANCED_SLEEP_TIME == "0" ? 3 : LINK_ADVANCED_SLEEP_TIME))
|
||||
fi
|
||||
|
||||
# 初始化参数
|
||||
current_cfg=""
|
||||
retry_count=0
|
||||
|
||||
for ((retry_count = 0; retry_count < max_retries; retry_count++)); do
|
||||
current_cfg=$(curl -v $LINK_EMBY_URL/emby/System/Configuration?api_key=$LINK_EMBY_API_KEY)
|
||||
|
||||
if [ -z "$current_cfg" ]; then
|
||||
# echo "$LINK_MODE 登录失败,休眠$sleep_time秒"
|
||||
sleep $sleep_time
|
||||
else
|
||||
echo "$GENERAL_NAT_NAME - $LINK_MODE 登录成功"
|
||||
new_cfg=$current_cfg
|
||||
if [ ! -z $LINK_EMBY_USE_HTTPS ] && [ $LINK_EMBY_USE_HTTPS = '1' ]; then
|
||||
new_cfg=$(echo $current_cfg | jq ".PublicHttpsPort = $outter_port")
|
||||
else
|
||||
new_cfg=$(echo $current_cfg | jq ".PublicPort = $outter_port")
|
||||
fi
|
||||
|
||||
if [ ! -z $LINK_EMBY_UPDATE_HOST_WITH_IP ] && [ $LINK_EMBY_UPDATE_HOST_WITH_IP = '1' ]; then
|
||||
new_cfg=$(echo $new_cfg | jq ".WanDdns = \"$outter_ip\"")
|
||||
fi
|
||||
|
||||
curl -X POST "$LINK_EMBY_URL/emby/System/Configuration?api_key=$LINK_EMBY_API_KEY" -H "accept: */*" -H "Content-Type: application/json" -d "$new_cfg"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
# Check if maximum retries reached
|
||||
if [ $retry_count -eq $max_retries ]; then
|
||||
echo "$GENERAL_NAT_NAME - $LINK_MODE 达到最大重试次数,无法修改"
|
||||
exit 1
|
||||
fi
|
@ -0,0 +1,57 @@
|
||||
#!/bin/bash
|
||||
|
||||
# NATMap
|
||||
protocol=$5
|
||||
inner_port=$4
|
||||
outter_ip=$1
|
||||
outter_port=$2
|
||||
ip4p=$3
|
||||
|
||||
LINK_QB_WEB_URL=$(echo $LINK_QB_WEB_URL | sed 's/\/$//')
|
||||
|
||||
# 默认重试次数为1,休眠时间为3s
|
||||
max_retries=1
|
||||
sleep_time=3
|
||||
|
||||
# 判断是否开启高级功能
|
||||
if [ "$LINK_ADVANCED_ENABLE" == 1 ] && [ -n "$LINK_ADVANCED_MAX_RETRIES" ] && [ -n "$LINK_ADVANCED_SLEEP_TIME" ]; then
|
||||
# 获取最大重试次数
|
||||
max_retries=$((LINK_ADVANCED_MAX_RETRIES == "0" ? 1 : LINK_ADVANCED_MAX_RETRIES))
|
||||
# 获取休眠时间
|
||||
sleep_time=$((LINK_ADVANCED_SLEEP_TIME == "0" ? 3 : LINK_ADVANCED_SLEEP_TIME))
|
||||
fi
|
||||
|
||||
# 初始化参数
|
||||
# 获取qbcookie,直至重试次数用尽
|
||||
qbcookie=""
|
||||
retry_count=0
|
||||
|
||||
for ((retry_count = 0; retry_count < max_retries; retry_count++)); do
|
||||
# 获取qbcookie
|
||||
qbcookie=$(
|
||||
curl -Ssi -X POST \
|
||||
-d "username=$LINK_QB_USERNAME&password=$LINK_QB_PASSWORD" \
|
||||
"$LINK_QB_WEB_URL/api/v2/auth/login" |
|
||||
sed -n 's/.*\(SID=.\{32\}\);.*/\1/p'
|
||||
)
|
||||
|
||||
# 如果qbcookie为空,则重试
|
||||
if [ -z "$qbcookie" ]; then
|
||||
# echo "$GENERAL_NAT_NAME - $LINK_MODE 登录失败,正在重试..."
|
||||
sleep $sleep_time
|
||||
else
|
||||
echo "$GENERAL_NAT_NAME - $LINK_MODE 登录成功"
|
||||
# 修改端口
|
||||
curl -s -X POST \
|
||||
-b "$qbcookie" \
|
||||
-d 'json={"listen_port":"'$outter_port'"}' \
|
||||
"$LINK_QB_WEB_URL/api/v2/app/setPreferences"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
# Check if maximum retries reached
|
||||
if [ $retry_count -eq $max_retries ]; then
|
||||
echo "$GENERAL_NAT_NAME - $LINK_MODE 达到最大重试次数,无法修改"
|
||||
exit 1
|
||||
fi
|
@ -0,0 +1,65 @@
|
||||
#!/bin/bash
|
||||
|
||||
# NATMap
|
||||
outter_ip=$1
|
||||
outter_port=$2
|
||||
inner_port=$4
|
||||
protocol=$5
|
||||
|
||||
LINK_TR_RPC_URL=$(echo $LINK_TR_RPC_URL | sed 's/\/$//')
|
||||
url="$LINK_TR_RPC_URL/transmission/rpc"
|
||||
# update port
|
||||
trauth="-u $LINK_TR_USERNAME:$LINK_TR_PASSWORD"
|
||||
|
||||
# 默认重试次数为1,休眠时间为3s
|
||||
max_retries=1
|
||||
sleep_time=3
|
||||
|
||||
# 判断是否开启高级功能
|
||||
if [ "$LINK_ADVANCED_ENABLE" == 1 ] && [ -n "$LINK_ADVANCED_MAX_RETRIES" ] && [ -n "$LINK_ADVANCED_SLEEP_TIME" ]; then
|
||||
# 获取最大重试次数
|
||||
max_retries=$((LINK_ADVANCED_MAX_RETRIES == "0" ? 1 : LINK_ADVANCED_MAX_RETRIES))
|
||||
# 获取休眠时间
|
||||
sleep_time=$((LINK_ADVANCED_SLEEP_TIME == "0" ? 3 : LINK_ADVANCED_SLEEP_TIME))
|
||||
fi
|
||||
|
||||
# 初始化参数
|
||||
# # 获取trsid,直至重试次数用尽
|
||||
trsid=""
|
||||
retry_count=0
|
||||
|
||||
for ((retry_count = 0; retry_count <= max_retries; retry_count++)); do
|
||||
trsid=$(curl -s $trauth $url | sed 's/.*<code>//g;s/<\/code>.*//g')
|
||||
|
||||
# Check if the provided session ID contains the header "X-Transmission-Session-Id"
|
||||
if [[ $trsid == *"X-Transmission-Session-Id"* ]]; then
|
||||
echo "$GENERAL_NAT_NAME - $LINK_MODE 登录成功"
|
||||
|
||||
# Modify the port using the Transmission API
|
||||
tr_result=$(curl -s -X POST \
|
||||
-H "$trsid" $trauth \
|
||||
-d '{"method":"session-set","arguments":{"peer-port":'$outter_port'}}' \
|
||||
"$url")
|
||||
|
||||
# Check if the port modification was successful
|
||||
if [[ $(echo "$tr_result" | jq -r '.result') == "success" ]]; then
|
||||
echo "transmission port modified successfully"
|
||||
break
|
||||
else
|
||||
echo "transmission Failed to modify the port"
|
||||
# Sleep for a specified amount of time
|
||||
sleep $sleep_time
|
||||
fi
|
||||
else
|
||||
# Sleep for a specified amount of time
|
||||
sleep $sleep_time
|
||||
fi
|
||||
|
||||
# Sleep for a specified amount of time
|
||||
sleep $sleep_time
|
||||
done
|
||||
|
||||
if [ $retry_count -eq $max_retries ]; then
|
||||
echo "$GENERAL_NAT_NAME - $LINK_MODE 达到最大重试次数,无法修改"
|
||||
exit 1
|
||||
fi
|
@ -0,0 +1,39 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Define the Gotify URL, title, message, and priority
|
||||
title="natmap - ${GENERAL_NAT_NAME} 更新"
|
||||
message="$1"
|
||||
gotify_url="${NOTIFY_GOTIFY_URL}"
|
||||
priority="${NOTIFY_GOTIFY_PRIORITY:-5}"
|
||||
token="${NOTIFY_GOTIFY_TOKEN}"
|
||||
|
||||
# 默认重试次数为1,休眠时间为3s
|
||||
max_retries=1
|
||||
sleep_time=3
|
||||
|
||||
# 判断是否开启高级功能
|
||||
if [ "${NOTIFY_ADVANCED_ENABLE}" == 1 ] && [ -n "$NOTIFY_ADVANCED_MAX_RETRIES" ] && [ -n "$NOTIFY_ADVANCED_SLEEP_TIME" ]; then
|
||||
# 获取最大重试次数
|
||||
max_retries=$((NOTIFY_ADVANCED_MAX_RETRIES == "0" ? 1 : NOTIFY_ADVANCED_MAX_RETRIES))
|
||||
# 获取休眠时间
|
||||
sleep_time=$((NOTIFY_ADVANCED_SLEEP_TIME == "0" ? 3 : NOTIFY_ADVANCED_SLEEP_TIME))
|
||||
fi
|
||||
|
||||
for ((retry_count = 1; retry_count <= max_retries; retry_count++)); do
|
||||
# Send the message using curl
|
||||
curl -s -X POST -H "Content-Type: multipart/form-data" -F "token=$token" -F "title=$title" -F "message=$message" -F "priority=$priority" "$gotify_url/message"
|
||||
status=$?
|
||||
if [ $status -eq 0 ]; then
|
||||
echo "$GENERAL_NAT_NAME - $NOTIFY_MODE 发送成功"
|
||||
break
|
||||
else
|
||||
# echo "$NOTIFY_MODE 登录失败,休眠$sleep_time秒"
|
||||
sleep $sleep_time
|
||||
fi
|
||||
done
|
||||
|
||||
# Check if maximum retries reached
|
||||
if [ $retry_count -eq $max_retries ]; then
|
||||
echo "$GENERAL_NAT_NAME - $NOTIFY_MODE 达到最大重试次数,无法通知"
|
||||
break
|
||||
fi
|
@ -0,0 +1,38 @@
|
||||
#!/bin/bash
|
||||
|
||||
text="$1"
|
||||
title="natmap - ${GENERAL_NAT_NAME} 更新"
|
||||
token="${NOTIFY_PUSHPLUS_TOKEN}"
|
||||
|
||||
# 默认重试次数为1,休眠时间为3s
|
||||
max_retries=1
|
||||
sleep_time=3
|
||||
|
||||
# 判断是否开启高级功能
|
||||
if [ "${NOTIFY_ADVANCED_ENABLE}" == 1 ] && [ -n "$NOTIFY_ADVANCED_MAX_RETRIES" ] && [ -n "$NOTIFY_ADVANCED_SLEEP_TIME" ]; then
|
||||
# 获取最大重试次数
|
||||
max_retries=$((NOTIFY_ADVANCED_MAX_RETRIES == "0" ? 1 : NOTIFY_ADVANCED_MAX_RETRIES))
|
||||
# 获取休眠时间
|
||||
sleep_time=$((NOTIFY_ADVANCED_SLEEP_TIME == "0" ? 3 : NOTIFY_ADVANCED_SLEEP_TIME))
|
||||
fi
|
||||
|
||||
for ((retry_count = 1; retry_count <= max_retries; retry_count++)); do
|
||||
curl -4 -Ss -X POST \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"token": "'"${token}"'", "content": "'"${text}"'", "title": "'"${title}"'"}' \
|
||||
"http://www.pushplus.plus/send"
|
||||
status=$?
|
||||
if [ $status -eq 0 ]; then
|
||||
echo "$GENERAL_NAT_NAME - $NOTIFY_MODE 发送成功"
|
||||
break
|
||||
else
|
||||
# echo "$NOTIFY_MODE 登录失败,休眠$sleep_time秒"
|
||||
sleep $sleep_time
|
||||
fi
|
||||
done
|
||||
|
||||
# Check if maximum retries reached
|
||||
if [ $retry_count -eq $max_retries ]; then
|
||||
echo "$GENERAL_NAT_NAME - $NOTIFY_MODE 达到最大重试次数,无法通知"
|
||||
break
|
||||
fi
|
@ -0,0 +1,47 @@
|
||||
#!/bin/bash
|
||||
|
||||
title="natmap - ${GENERAL_NAT_NAME} 更新"
|
||||
desp="$1"
|
||||
|
||||
# 拼装post数据
|
||||
postdata="title=$title&desp=$desp"
|
||||
message=(
|
||||
"--header" "Content-type: application/x-www-form-urlencoded"
|
||||
"--data" "$postdata"
|
||||
)
|
||||
|
||||
# 获取url
|
||||
url=""
|
||||
if [ "${NOTIFY_SERVERCHAN_ADVANCED_ENABLE}" == 1 ] && [ -n "$NOTIFY_SERVERCHAN_ADVANCED_URL" ]; then
|
||||
url="$NOTIFY_SERVERCHAN_ADVANCED_URL/${NOTIFY_SERVERCHAN_SENDKEY}.send"
|
||||
else
|
||||
url="https://sctapi.ftqq.com/${NOTIFY_SERVERCHAN_SENDKEY}.send"
|
||||
fi
|
||||
# 默认重试次数为1,休眠时间为3s
|
||||
max_retries=1
|
||||
sleep_time=3
|
||||
|
||||
# 判断是否开启高级功能
|
||||
if [ "${NOTIFY_ADVANCED_ENABLE}" == 1 ] && [ -n "$NOTIFY_ADVANCED_MAX_RETRIES" ] && [ -n "$NOTIFY_ADVANCED_SLEEP_TIME" ]; then
|
||||
# 获取最大重试次数
|
||||
max_retries=$((NOTIFY_ADVANCED_MAX_RETRIES == "0" ? 1 : NOTIFY_ADVANCED_MAX_RETRIES))
|
||||
# 获取休眠时间
|
||||
sleep_time=$((NOTIFY_ADVANCED_SLEEP_TIME == "0" ? 3 : NOTIFY_ADVANCED_SLEEP_TIME))
|
||||
fi
|
||||
|
||||
for ((retry_count = 1; retry_count <= max_retries; retry_count++)); do
|
||||
result=$(curl -X POST -s -o /dev/null -w "%{http_code}" "$url" "${message[@]}")
|
||||
if [ $result -eq 200 ]; then
|
||||
echo "$GENERAL_NAT_NAME - $NOTIFY_MODE 发送成功"
|
||||
break
|
||||
else
|
||||
# echo "$NOTIFY_MODE 登录失败,休眠$sleep_time秒"
|
||||
sleep $sleep_time
|
||||
fi
|
||||
done
|
||||
|
||||
# Check if maximum retries reached
|
||||
if [ $retry_count -eq $max_retries ]; then
|
||||
echo "$GENERAL_NAT_NAME - $NOTIFY_MODE 达到最大重试次数,无法通知"
|
||||
break
|
||||
fi
|
@ -0,0 +1,55 @@
|
||||
#!/bin/bash
|
||||
|
||||
text="$1"
|
||||
chat_id="${NOTIFY_TELEGRAM_BOT_CHAT_ID}"
|
||||
token="${NOTIFY_TELEGRAM_BOT_TOKEN}"
|
||||
title="natmap - ${GENERAL_NAT_NAME} 更新"
|
||||
|
||||
function curl_proxy() {
|
||||
if [ -z "$NOTIFY_TELEGRAM_BOT_PROXY" ]; then
|
||||
curl "$@"
|
||||
else
|
||||
curl -x $NOTIFY_TELEGRAM_BOT_PROXY "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
# 默认重试次数为1,休眠时间为3s
|
||||
max_retries=1
|
||||
sleep_time=3
|
||||
|
||||
# 判断是否开启高级功能
|
||||
if [ "${NOTIFY_ADVANCED_ENABLE}" == 1 ] && [ -n "$NOTIFY_ADVANCED_MAX_RETRIES" ] && [ -n "$NOTIFY_ADVANCED_SLEEP_TIME" ]; then
|
||||
# 获取最大重试次数
|
||||
max_retries=$((NOTIFY_ADVANCED_MAX_RETRIES == "0" ? 1 : NOTIFY_ADVANCED_MAX_RETRIES))
|
||||
# 获取休眠时间
|
||||
sleep_time=$((NOTIFY_ADVANCED_SLEEP_TIME == "0" ? 3 : NOTIFY_ADVANCED_SLEEP_TIME))
|
||||
fi
|
||||
|
||||
# # 判断是否开启高级功能
|
||||
# if [ "$NOTIFY_ADVANCED_ENABLE" == 1 ]; then
|
||||
# # 获取最大重试次数
|
||||
# max_retries="${NOTIFY_ADVANCED_MAX_RETRIES%/:-$max_retries}"
|
||||
# # 获取休眠时间
|
||||
# sleep_time="${NOTIFY_ADVANCED_SLEEP_TIME%/:-$sleep_time}"
|
||||
# fi
|
||||
|
||||
for ((retry_count = 1; retry_count <= max_retries; retry_count++)); do
|
||||
curl_proxy -4 -Ss -o /dev/null -X POST \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"chat_id": "'"${chat_id}"'", "text": "'"${title}\n\n${text}"'", "parse_mode": "HTML", "disable_notification": "false"}' \
|
||||
"https://api.telegram.org/bot${token}/sendMessage"
|
||||
status=$?
|
||||
if [ $status -eq 0 ]; then
|
||||
echo "$GENERAL_NAT_NAME - $NOTIFY_MODE 发送成功"
|
||||
break
|
||||
else
|
||||
# echo "$NOTIFY_MODE 登录失败,休眠$sleep_time秒"
|
||||
sleep $sleep_time
|
||||
fi
|
||||
done
|
||||
|
||||
# Check if maximum retries reached
|
||||
if [ $retry_count -eq $max_retries ]; then
|
||||
echo "$GENERAL_NAT_NAME - $NOTIFY_MODE 达到最大重试次数,无法通知"
|
||||
exit 1
|
||||
fi
|
30
luci-app-natmap/luci-app-natmap/root/usr/share/natmap/update.sh
Executable file
@ -0,0 +1,30 @@
|
||||
#!/bin/sh
|
||||
|
||||
. /usr/share/libubox/jshn.sh
|
||||
|
||||
(
|
||||
json_init
|
||||
json_add_string ip "$1"
|
||||
json_add_int port "$2"
|
||||
json_add_int inner_port "$4"
|
||||
json_add_string protocol "$5"
|
||||
json_add_string name "$GENERAL_NAT_NAME"
|
||||
json_dump >/var/run/natmap/$PPID.json
|
||||
)
|
||||
|
||||
echo "natmap update json: $(cat /var/run/natmap/$PPID.json)"
|
||||
|
||||
# link setting
|
||||
[ "${LINK_ENABLE}" == 1 ] && source /usr/share/natmap/link.sh "$@"
|
||||
|
||||
# forward setting
|
||||
[ "${FORWARD_ENABLE}" == 1 ] && source /usr/share/natmap/forward.sh "$@"
|
||||
|
||||
# notify setting
|
||||
[ "${NOTIFY_ENABLE}" == 1 ] && source /usr/share/natmap/notify.sh "$@"
|
||||
|
||||
# custom setting
|
||||
[ "${CUSTOM_SCRIPT_ENABLE}" == 1 ] && [ -n "${CUSTOM_SCRIPT_PATH}" ] && {
|
||||
export -n CUSTOM_SCRIPT_PATH
|
||||
source "${CUSTOM_SCRIPT_PATH}" "$@"
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
{
|
||||
"luci-app-natmap": {
|
||||
"description": "Grant access to LuCI app natmap",
|
||||
"read": {
|
||||
"file": {
|
||||
"/var/run/natmap/*": ["read"]
|
||||
},
|
||||
"ubus": {
|
||||
"service": ["list"]
|
||||
},
|
||||
"uci": ["natmap"]
|
||||
},
|
||||
"write": {
|
||||
"uci": ["natmap"]
|
||||
}
|
||||
}
|
||||
}
|
35
luci-app-natmap/natmap/Makefile
Executable file
@ -0,0 +1,35 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=natmap
|
||||
PKG_VERSION:=20240126
|
||||
PKG_RELEASE:=2
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://github.com/heiher/natmap/releases/download/$(PKG_VERSION)
|
||||
PKG_HASH:=7d8b3fe5c29dfe30271197b8ea1f78af292830483aefb1ccc21a3ec05f74627b
|
||||
|
||||
PKG_MAINTAINER:=Richard Yu <yurichard3839@gmail.com>
|
||||
PKG_LICENSE:=MIT
|
||||
PKG_LICENSE_FILES:=License
|
||||
|
||||
PKG_BUILD_FLAGS:=no-mips16
|
||||
PKG_BUILD_PARALLEL:=1
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
define Package/natmap
|
||||
SECTION:=net
|
||||
CATEGORY:=Network
|
||||
TITLE:=TCP/UDP port mapping tool for full cone NAT
|
||||
URL:=https://github.com/heiher/natmap
|
||||
DEPENDS:=+jq +curl +openssl-util
|
||||
endef
|
||||
|
||||
MAKE_FLAGS += REV_ID="$(PKG_VERSION)"
|
||||
|
||||
define Package/${PKG_NAME}/install
|
||||
$(INSTALL_DIR) $(1)/usr/bin
|
||||
$(INSTALL_BIN) $(PKG_BUILD_DIR)/bin/natmap $(1)/usr/bin/
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,natmap))
|
69
luci-app-natmap/natmap/README.md
Normal file
@ -0,0 +1,69 @@
|
||||
### 新功能
|
||||
|
||||
1. 内置两种消息通知方式,Telegram Bot 和 PushPlus,可以为任何每一个打洞单独配置, PushPlus 支持推送到微信且不需要翻墙
|
||||
2. 内置多种配置模式,常用功能无需写脚本就能使用,详见内置模式
|
||||
|
||||
### 内置模式
|
||||
|
||||
#### qBittorrent
|
||||
|
||||
打洞成功后,自动修改 qBittorrent 的端口号,并配置转发(可选)
|
||||
|
||||
需要配置 qBittorrent 地址、账号、密码用于修改端口
|
||||
需要配置 qBittorrent 使用网卡的 IP 用于配置转发,端口填 0,会转发到修改后的端口
|
||||
|
||||
#### Transmission
|
||||
|
||||
打洞成功后,自动修改 Transmission 的端口号,并配置转发(可选)
|
||||
|
||||
需要配置 Transmission 地址、账号、密码用于修改端口
|
||||
需要配置 Transmission 使用网卡的 IP 用于配置转发,端口填 0,会转发到修改后的端口
|
||||
|
||||
#### Emby
|
||||
|
||||
配合 Emby Connect 使用时,用户登录账号后,会从服务器获取最新的连接地址信息,此模式就是用于配置这些信息的
|
||||
|
||||
需要配置 Emby 地址和 API Key 用于修改连接地址信息
|
||||
|
||||
此模式必须配置转发
|
||||
|
||||
默认不更新「外部域」,如果有配置 DDNS,将 DDNS 域名填入外部域后将不需要再次修改
|
||||
|
||||
若没有域名,需要将 IP 填入外部域,可以勾选 「Update host with IP」
|
||||
|
||||
若对外提供的是 HTTPS 服务,需要勾选 「Update HTTPS Port」
|
||||
|
||||
|
||||
#### Cloudflare Origin Rules
|
||||
|
||||
Cloudflare Origin Rules 可以设置回源端口,配合 DDNS 使用时,可以将 DDNS 域名指向 Cloudflare,然后将回源端口设置为打洞后的端口,这样就可以通过 Cloudflare 的 CDN 加速访问了
|
||||
|
||||
需要配置 Cloudflare 的 API Key,邮箱 和 Zone ID,Zone ID 可以在 Cloudflare 的域名首页找到
|
||||
|
||||
API Key 请访问 https://dash.cloudflare.com/profile/api-tokens 复制 Global API Key
|
||||
|
||||
需要先在 Cloudflare 后台的 Rules - Origin Rules 下添加一个 Origin Rules,然后将 Origin Rules 的 Name 填入配置中
|
||||
|
||||
Name 请保持唯一,否则会出现奇怪的问题
|
||||
|
||||
|
||||
### 使用
|
||||
|
||||
添加软件源
|
||||
|
||||
```
|
||||
curl -fsSL https://github.com/ekkog/openwrt-dist/raw/master/add-feed.sh | sh
|
||||
```
|
||||
|
||||
当前环境访问 GitHub 有问题时,可以使用 GitHub 镜像
|
||||
|
||||
```
|
||||
curl -fsSL https://ghproxy.com/https://github.com/EkkoG/openwrt-dist/blob/master/add-feed.sh | sh
|
||||
```
|
||||
|
||||
更新软件源并安装
|
||||
|
||||
```
|
||||
opkg update
|
||||
opkg install natmap
|
||||
```
|
20
luci-app-natmap/patch.sh
Normal file
@ -0,0 +1,20 @@
|
||||
#!/bin/bash -e
|
||||
function sed_wrapper() {
|
||||
# if run in linux, use sed -i
|
||||
# if run in macos, use sed -i ''
|
||||
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
|
||||
sed -i "$@"
|
||||
elif [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
/usr/local/opt/gnu-sed/libexec/gnubin/sed -i "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
sed_wrapper '/luci.mk/ c\include $(TOPDIR)/feeds/luci/luci.mk' packages/luci-app-natmap/Makefile
|
||||
|
||||
# remove the last \
|
||||
dep=${dep%\\}
|
||||
if [[ $1 =~ '21.02'* ]]; then
|
||||
dep="$dep +ip6tables-mod-nat +iptables-mod-extra +iptables-mod-tproxy"
|
||||
else
|
||||
dep="$dep +kmod-nft-tproxy"
|
||||
fi
|
674
luci-app-tinyfilemanager/LICENSE
Normal file
@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
45
luci-app-tinyfilemanager/Makefile
Normal file
@ -0,0 +1,45 @@
|
||||
# Tiny File Manager by prasathmani <https://tinyfilemanager.github.io>
|
||||
# Copyright (C) 2022-2023 muink <https://github.com/muink>
|
||||
#
|
||||
# This is free software, licensed under the GNU General Public License v3.
|
||||
# See /LICENSE for more information.
|
||||
#
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
LUCI_NAME:=luci-app-tinyfilemanager
|
||||
PKG_VERSION:=2.5.3-20231119
|
||||
#PKG_RELEASE:=1
|
||||
|
||||
LUCI_TITLE:=LuCI Tiny File Manager
|
||||
LUCI_DEPENDS:=+php8 +php8-cgi +php8-fastcgi +php8-fpm +php8-mod-session +php8-mod-ctype +php8-mod-fileinfo +php8-mod-zip +php8-mod-iconv +php8-mod-mbstring +coreutils-stat +zoneinfo-asia +bash +curl +tar
|
||||
|
||||
LUCI_DESCRIPTION:=A Web based File Manager in PHP
|
||||
|
||||
define Package/$(LUCI_NAME)/conffiles
|
||||
/etc/config/tinyfilemanager
|
||||
endef
|
||||
|
||||
define Package/$(LUCI_NAME)/postinst
|
||||
#!/bin/sh
|
||||
mkdir -p "$${IPKG_INSTROOT}/www/tinyfilemanager" 2>/dev/null
|
||||
[ ! -d "$${IPKG_INSTROOT}/www/tinyfilemanager/rootfs" ] && ln -s / "$${IPKG_INSTROOT}/www/tinyfilemanager/rootfs"
|
||||
total_size_limit=5G #post_max_size = 8M
|
||||
single_size_limit=2G #upload_max_filesize = 2M
|
||||
otime_uploads_limit=200 #max_file_uploads = 20
|
||||
sed -Ei "s|^(post_max_size) *=.*$$|\1 = $$total_size_limit|; \
|
||||
s|^(upload_max_filesize) *=.*$$|\1 = $$single_size_limit|; \
|
||||
s|^(max_file_uploads) *=.*$$|\1 = $$otime_uploads_limit|" \
|
||||
"$${IPKG_INSTROOT}/etc/php.ini"
|
||||
# unpack
|
||||
tar -C "$${IPKG_INSTROOT}/www/tinyfilemanager" -xzf "$${IPKG_INSTROOT}/www/tinyfilemanager/index.tgz"
|
||||
rm -f "$${IPKG_INSTROOT}/www/tinyfilemanager/index.tgz"
|
||||
endef
|
||||
|
||||
define Package/$(LUCI_NAME)/prerm
|
||||
#!/bin/sh
|
||||
if [ -d /www/tinyfilemanager ]; then rm -rf /www/tinyfilemanager; fi
|
||||
endef
|
||||
|
||||
include $(TOPDIR)/feeds/luci/luci.mk
|
||||
|
||||
# call BuildPackage - OpenWrt buildroot signature
|
85
luci-app-tinyfilemanager/README.md
Normal file
@ -0,0 +1,85 @@
|
||||
# LuCI Tiny File Manager
|
||||
> [TinyFileManager][] is web based file manager and it is a simple, fast and small file manager with a single file, multi-language ready web application for storing, uploading, editing and managing files and folders online via web browser. The Application runs on PHP 5.5+, It allows the creation of multiple users and each user can have its own directory and a build-in support for managing text files with cloud9 IDE and it supports syntax highlighting for over 150+ languages and over 35+ themes.
|
||||
|
||||
### Screenshots
|
||||
|
||||

|
||||
|
||||
<details><summary>Real installation</summary>
|
||||
<img src="example/root.png"/>
|
||||
<img src="example/localfeeds.png"/>
|
||||
<img src="example/pictute.png"/>
|
||||
<img src="example/video.png"/>
|
||||
</details>
|
||||
|
||||
### How to install
|
||||
|
||||
1. Goto ~~[releases](https://github.com/muink/luci-app-tinyfilemanager/tree/releases)~~ [here](https://fantastic-packages.github.io/packages/)
|
||||
2. Download the latest version of ipk
|
||||
3. Login router and goto **System --> Software**
|
||||
4. Upload and install ipk
|
||||
5. Reboot if the app is not automatically added in page
|
||||
6. Goto **NAS --> Tiny File Manager**
|
||||
7. Default username/password: admin/admin and user/12345.
|
||||
|
||||
### Uploading limit
|
||||
|
||||
**If you need to change the upload limit for Tiny File Manager**
|
||||
|
||||
Edit [config.js](htdocs/luci-static/resources/view/tinyfilemanager/config.js) before build
|
||||
Edit `/www/luci-static/resources/view/tinyfilemanager/config.js` in router
|
||||
```javascript
|
||||
o = s.option(form.Value, 'max_upload_size', _('Max upload size (MBytes)'));
|
||||
o.datatype = "and(uinteger,max(2048))"; //limit to 2048MB
|
||||
```
|
||||
And edit [Makefile](Makefile) before build
|
||||
```makefile
|
||||
total_size_limit=?? #Total size of multiple files
|
||||
single_size_limit=?? #Max single file size
|
||||
otime_uploads_limit=?? #Max count of simultaneous uploads
|
||||
```
|
||||
And edit `/etc/php.ini` in router
|
||||
```ini
|
||||
post_max_size = ?? ;Total size of multiple files
|
||||
upload_max_filesize = ?? ;Max single file size
|
||||
max_file_uploads = ?? ;Max count of simultaneous uploads
|
||||
```
|
||||
|
||||
### Build
|
||||
|
||||
- Compile from OpenWrt/LEDE SDK
|
||||
|
||||
```
|
||||
# Take the x86_64 platform as an example
|
||||
tar xjf openwrt-sdk-21.02.3-x86-64_gcc-8.4.0_musl.Linux-x86_64.tar.xz
|
||||
# Go to the SDK root dir
|
||||
cd OpenWrt-sdk-*-x86_64_*
|
||||
# First run to generate a .config file
|
||||
make menuconfig
|
||||
./scripts/feeds update -a
|
||||
./scripts/feeds install -a
|
||||
# Get Makefile
|
||||
git clone --depth 1 --branch master --single-branch --no-checkout https://github.com/muink/luci-app-tinyfilemanager.git package/luci-app-tinyfilemanager
|
||||
pushd package/luci-app-tinyfilemanager
|
||||
umask 022
|
||||
git checkout
|
||||
popd
|
||||
# Select the package LuCI -> Applications -> luci-app-tinyfilemanager
|
||||
make menuconfig
|
||||
# Upgrade to new version Tiny File Manager (optional)
|
||||
1. modify the tag VERSION='2.4.7' to new version in makenew.sh
|
||||
2. run makenew.sh to upgrade current version (if it worked)
|
||||
# Start compiling
|
||||
make package/luci-app-tinyfilemanager/compile V=99
|
||||
```
|
||||
|
||||
### Contributors
|
||||
|
||||
- [prasathmani](https://tinyfilemanager.github.io)
|
||||
- [muink](https://github.com/muink)
|
||||
|
||||
[TinyFileManager]: https://github.com/prasathmani/tinyfilemanager
|
||||
|
||||
### License
|
||||
|
||||
- This project is licensed under the [GPL-3.0](https://www.gnu.org/licenses/gpl-3.0.html)
|
BIN
luci-app-tinyfilemanager/example/demo.png
Normal file
After Width: | Height: | Size: 92 KiB |
BIN
luci-app-tinyfilemanager/example/localfeeds.png
Normal file
After Width: | Height: | Size: 71 KiB |
BIN
luci-app-tinyfilemanager/example/pictute.png
Normal file
After Width: | Height: | Size: 306 KiB |
BIN
luci-app-tinyfilemanager/example/root.png
Normal file
After Width: | Height: | Size: 85 KiB |
BIN
luci-app-tinyfilemanager/example/video.png
Normal file
After Width: | Height: | Size: 1.2 MiB |